【问题标题】:How to shrink an image size?如何缩小图像尺寸?
【发布时间】:2014-12-02 10:22:26
【问题描述】:

您好,我正在开发一个将 640x480 位图图像缩小为 320x240 图像的程序。我一直在研究这个问题一段时间,但我发现的所有好例子都是用于增加图像的大小。

(见这里:http://cboard.cprogramming.com/c-programming/154737-help-program-resize-image.html

我在将该程序中完成的内容转换为我需要完成的内容时遇到了困难。 到目前为止,这是我的代码:

include stdio.h
include stdlib.h
include string.h
include math.h


pragma pack(push, 1)
typedef struct tagBITMAPFILEHEADER
{
unsigned short bfType; //specifies the file type
unsigned int bfSize; //specifies the size in bytes of  the bitmap file
unsigned short bfReserved1; //reserved; must be 0
unsigned short bfReserved2; //reserved; must be 0
unsigned int bfOffBits; //species the offset in bytes from the bitmapfileheader to the bitmap bits
} BITMAPFILEHEADER;
pragma pack(pop)

pragma pack(push, 1)
typedef struct tagBITMAPDIBHEADER
{
unsigned int biSize; //specifies the number of bytes required by the struct
int biWidth; //specifies width in pixels
int biHeight; //species height in pixels
unsigned short biPlanes; //specifies the number of color planes, must be 1
unsigned short biBitCount; //specifies the number of bit per pixel
unsigned int biCompression;//spcifies the type of compression
unsigned int biSizeImage; //size of image in bytes
int biXPelsPerMeter; //number of pixels per meter in x axis
int biYPelsPerMeter; //number of pixels per meter in y axis
unsigned int biClrUsed; //number of colors used by th ebitmap
unsigned int biClrImportant; //number of colors that are important
} BITMAPDIBHEADER;
pragma pack(pop)

pragma pack(push, 1)
typedef struct
{
int  rgbtBlue;
int  rgbtGreen;
int  rgbtRed;
}
RGBTRIPLE;
pragma pack(pop)

int main()
{
FILE *input, *output;
BITMAPDIBHEADER inputdibHeader;
BITMAPFILEHEADER inputfileHeader;
BITMAPDIBHEADER outputdibHeader;
BITMAPFILEHEADER outputfileHeader;

int greenValue = 0;
int blueValue = 0;
int redValue = 0;
fopen_s(&output, "test.bmp", "wb");
if (output == NULL){
    return NULL;
}
fopen_s(&input, "lolcat.bmp", "rb");
if (input == NULL)
    return NULL;

rewind(input);  // rewind the file before reading it again
fread(&(inputfileHeader), sizeof(BITMAPFILEHEADER), 1, input);
fread(&(inputdibHeader), sizeof(BITMAPDIBHEADER), 1, input);
rewind(input);  // rewind the file before reading it again
fread(&(outputfileHeader), sizeof(BITMAPFILEHEADER), 1, input);
fread(&(outputdibHeader), sizeof(BITMAPDIBHEADER), 1, input);





outputdibHeader.biWidth = inputdibHeader.biWidth *.5;
outputdibHeader.biHeight = inputdibHeader.biHeight *.5;
outputfileHeader.bfSize = outputdibHeader.biWidth * outputdibHeader.biHeight;
outputdibHeader.biSizeImage = inputdibHeader.biSizeImage *.5;
fwrite(&(outputfileHeader), sizeof(BITMAPFILEHEADER), 1, output);
fwrite(&(outputdibHeader), sizeof(BITMAPDIBHEADER), 1, output);

rewind(input);
fseek(input, inputfileHeader.bfOffBits, SEEK_SET);
fseek(output, outputfileHeader.bfOffBits, SEEK_SET);
int oldheight = inputdibHeader.biHeight;
int oldwidth = inputdibHeader.biWidth;
int i;
int timeswriten = 0;
int oldPad = (4 - ((inputdibHeader.biWidth * sizeof(RGBTRIPLE)) % 4)) % 4;
int newPad = (4 - ((outputdibHeader.biWidth * sizeof(RGBTRIPLE)) % 4)) % 4;

// iterate over infile's scanlines
for (int i = 0; i < abs(oldheight); i++)
{
    if (i % 2){


        // iterate over pixels in scanline
        for (int j = 0; j < oldwidth; j++)
        {
            // temporary storage
            RGBTRIPLE triple;
            fread(&triple, sizeof(RGBTRIPLE), 1, input);
            if (j % 2){
                fwrite(&triple, sizeof(RGBTRIPLE), 1, output);
            }
            // skip over any input padding
            fseek(input, oldPad, SEEK_CUR);


        }
    }
    }

    fclose(input);
    fclose(output);

} 

目前,此代码生成有效的位图图像,但创建的图像是原始图像的严重失真版本。我相当确定这是由于我从新图像中省略像素的方式,但我不确定应该采用什么合适的方法。 总的来说,我的问题是谁能帮我解释我应该在哪里以及如何省略像素?

更新

我现在知道我的目标是将 2x2 像素平均为一个像素,但我找不到一个很好的例子来说明如何做到这一点。谁能解释一下这个过程?

更新 2 感谢 PeterT,我知道以下代码似乎是正确的,但我的输出却不是。

 RGBTRIPLE *line_a = (RGBTRIPLE*)malloc(inputdibHeader.biWidth * sizeof(RGBTRIPLE)); /* check malloc() */
RGBTRIPLE *line_b = (RGBTRIPLE*)malloc(inputdibHeader.biWidth *sizeof(RGBTRIPLE)); /* check malloc() */
RGBTRIPLE *dest_line = (RGBTRIPLE*)malloc(outputdibHeader.biWidth * sizeof(RGBTRIPLE));

    /* move through the target array line by line, consuming two lines from the source
    image at a time */
    /* also assuming you verified the source image is exactly 2x the size of the dest
    malloc() */
for (i = 0; i < outputdibHeader.biHeight; ++i)
{
    fread(&(line_a), sizeof(RGBTRIPLE), inputdibHeader.biWidth, input);  /* read scanline & advance file pointer, err check in func */
    fread(&(line_b), sizeof(RGBTRIPLE), inputdibHeader.biWidth, input);/* read scanline & advance file pointer, err check in func */
    for (j = 0; j < outputdibHeader.biWidth; ++j)
    {
        bilinear_filter(&(dest_line[j]), &(line_a[j * 2]), &(line_a[(j * 2) + 1]), &(line_b[j * 2]), &(line_b[(j * 2) + 1]));
    }
    fwrite(&(dest_line), sizeof(RGBTRIPLE), outputdibHeader.biWidth, output);
    /* or something... point is we're creeping through the files scaline by scanline,
    and letting another function handle it to keep this code more intelligble */
}

fclose(input);
fclose(output);

}
void bilinear_filter(RGBTRIPLE *dest, RGBTRIPLE *A, RGBTRIPLE *B, RGBTRIPLE *C, RGBTRIPLE *D)
{

/* assuming 0888 ARGB */
dest->Red = (A->Red + B->Red + C->Red + D->Red) / 4;
dest->Green = (A->Green + B->Green + C->Green + D->Green) / 4;
dest->Blue = (A->Blue + B->Blue + C->Blue + D->Blue) / 4;

}

我认为这个问题可能在于我的标题创建,所以这里是

fread(&(inputHeader), sizeof(TwoHeader), 1, input);
inputfileHeader = inputHeader.fileHeader;
inputdibHeader = inputHeader.dibHeader;
rewind(input);  // rewind the file before reading it again
fread(&(outputHeader), sizeof(TwoHeader), 1, input);
outputfileHeader = outputHeader.fileHeader;
outputdibHeader = outputHeader.dibHeader;


outputdibHeader.biWidth = inputdibHeader.biWidth *.5;
outputdibHeader.biHeight = inputdibHeader.biHeight *.5;
//outputfileHeader.bfSize = inputfileHeader.bfSize - (inputdibHeader.biWidth*inputdibHeader.biHeight) + outputdibHeader.biWidth*outputdibHeader.biHeight;
outputfileHeader.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPDIBHEADER)+outputdibHeader.biSizeImage;
//outputdibHeader.biSizeImage = inputdibHeader.biSizeImage * .25;
//outputdibHeader.biXPelsPerMeter = inputdibHeader.biXPelsPerMeter * .5;
//outputdibHeader.biYPelsPerMeter = inputdibHeader.biYPelsPerMeter * .5;
//fwrite(&(outputfileHeader), sizeof(BITMAPFILEHEADER), 1, output);
//fwrite(&(outputdibHeader), sizeof(BITMAPDIBHEADER), 1, output);
fwrite(&(outputHeader), sizeof(TwoHeader), 1, output);
rewind(input);
fseek(input, inputfileHeader.bfOffBits, SEEK_SET);
fseek(output, outputfileHeader.bfOffBits, SEEK_SET);

请原谅所有的 cmets 大部分是旧代码或我不确定的代码。

【问题讨论】:

  • 可能与您的问题无关(您可能需要更详细地解释),但如果您将图像的宽度和高度都减半,则新的biSizeImage 不会是 1 /2,实际上是 1/2*1/2 = 1/4 大小。主要是,我猜你的 RGB Triplets 应该是 unsigned char,而不是 int
  • 有相当多的算法具有不同的效率和质量水平。这是一个相当广泛的主题。这可能会让你思考一些事情:stackoverflow.com/questions/6133957/…
  • @RetiredNinja 谢谢这有很大帮助,至少现在我知道要寻找什么了。不幸的是,该页面并没有真正展示任何关于算法在使用中的样子的好例子。我会找一个。如果你碰巧知道一个,你也可以链接。
  • code.google.com/p/nvidia-texture-tools/wiki/MipmapGeneration 有一些关于一些下采样算法/过滤器背后的理论的文章,​​如果您选择不重新发明轮子,它也是一个很好的库。

标签: c image-resizing bitmapimage


【解决方案1】:

“我研究这个问题已经有一段时间了”……真的吗? /挑眉/ ;)

听起来您正在寻找一个双线性过滤器:新像素实际上是四个像素的平均值,并且位于前四个像素的中心。

您真的需要重新发明轮子吗?我只是为此使用一个可靠的库,然后将精力集中在解决其他问题上:

https://github.com/nothings/stb

关于插值的基本方法有一篇很棒的维基百科文章:

http://en.wikipedia.org/wiki/Image_scaling

对 2D 图像进行下采样是计算机科学中一个 40 多年前的问题。 Foley 在他的开创性著作“计算机图形学”中对此进行了介绍,这是一本很棒的书。

关于您的代码:

对于简单的双线性下采样,您必须存储至少两条扫描线。我建议将您的代码分解成更模块化的东西,例如这是一个非常简单的实现:

triple *line_a = (triple*)malloc(...) /* check malloc() */
triple *line_b = (triple*)malloc(...) /* check malloc() */
triple *dest_line = etc...

/* move through the target array line by line, consuming two lines from the source 
   image at a time */
/* also assuming you verified the source image is exactly 2x the size of the dest 
   malloc() */
for (i = 0; i < dest_height; ++i) 
{
    read_line(line_a); /* read scanline & advance file pointer, err check in func */
    read_line(line_b); /* read scanline & advance file pointer, err check in func */
    for (j = 0; j < dest_width; ++j)
    {
        bilinear_filter(&(dest_line[j]), &(line_a[j*2]), &(line_a[(j*2)+1]), &(line_b[j*2]), &(line_b[(j*2)+1]));
    }
    write_line_to_file(dest_line, fp); /* or something... point is we're creeping through the files scaline by scanline, and letting another function handle it to keep this code more intelligble */
}
:
:
void bilinear_filter(triple *dest, triple *A, triple *B, triple *C, triple *D) 
{

    /* assuming 0888 ARGB */
    dest->r = (A->r + B->r + C->r + D->r) / 4;
    dest->g = (A->g + B->g + C->g + D->g) / 4;
    dest->b = (A->b + B->b + C->b + D->b) / 4;

}

现在,有很多方法可以插入颜色通道。有些理论考虑了眼睛可见光谱的能量发射,或印刷/胶片的色域曲线等。大多数都不像我那样独立缩放 r/g/b,因为它不保留三个值对眼睛的敏感度。我上面展示的方法只是指出双线性滤波器需要读取四个像素的数据才能产生一个新像素。

我希望这会有所帮助。

【讨论】:

  • 感谢您的回复。很抱歉没有早点对此发表评论。我还不能真正看你的帖子,但乍一看我看起来很有帮助。一旦我可以多看一点,我会再发第二个帖子。再次感谢您的帖子。
  • 我刚刚在我的项目中实现了您的代码。经过一番思考,我让它运行创建一个功能性的 bmp 文件。但是,生成的图片不正确,甚至尺寸也不正确。如果您发现任何明显错误,我已经用当前代码更新了我的问题,请告诉我。即使到目前为止还没有感谢您的帮助。
  • 我已经很久没有使用位图了,但我确实记得标题很挑剔。您注释掉了许多字段,这意味着它们仍然具有输入文件的属性。这是问题的一部分。如果我是你,我会编写一个 debugPrintHeader() 函数来打印所有位图字段,这样你就可以验证你是否正确地写出它。您甚至可能想要使用十六进制编辑器来确保您写入文件的内容与内存中的内容匹配,直到您确信标题是正确的。祝你好运!
  • 谢谢。如果我有时间,我会以不同的方式进行更多研究。只是为了确保您发布的代码的实现对您来说似乎是正确的,对吗?只是试图隔离错误。
猜你喜欢
  • 1970-01-01
  • 2012-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-02
  • 2013-02-10
  • 2018-07-17
  • 1970-01-01
相关资源
最近更新 更多