我正在尝试使用 C 读取 BMP 文件并生成该图像的灰度版本。但是,我不知道如何处理行的填充。 (回想一下,每行的字节大小必须是 4 的倍数。)当我在每行中每步移动 3 个字节时,我如何才能在填充字节之前停止?我的代码的关键部分是:
byte pixel[bytes_per_pixel];
for (int i = height - 1; i > 0; i = i - 1)
for (int j = 0; j < width; j = j + 1)
{
fread(pixel, 3, 1, image); // What to do with the padding?
int gray_scale_value = grayScaleConversion(pixel);
converted_image[i][j] = gray_scale_value;
}
为了完整起见,这是我目前所掌握的,您可以在下面找到参考图片。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define HEADER_LOCATION 0x0000
#define WIDTH_LOCATION 0x0012
#define HEIGHT_LOCATION 0x0016
#define BITS_PER_PIXEL_LOCATION 0x001C
typedef short i16; // integers with 16 bits
typedef unsigned int i32; // integers with 32 bits
typedef unsigned char byte;
void printMatrix(int rows, int columns, int matrix[rows][columns])
{
for (int i = 0; i < rows; i = i + 1)
{
for (int j = 0; j < columns; j = j + 1)
printf("%d ", matrix[i][j]);
printf("\n");
}
printf("\n");
}
int grayScaleConversion(byte pixel[3])
{
unsigned char gray_scale_value = (int) (pixel[0] * 0.0722 + pixel[1] * 0.7152 + pixel[2] * 0.2126);
return gray_scale_value;
}
int read_bmp_header(const char* file_name, int* image_header, int* image_height, int* image_width)
{
// Here we open the given file for reading in binary mode.
FILE* image = fopen(file_name, "rb");
// Here we check whether opening the given file was successful.
if (!image)
{
printf("Invalid File\n");
return -1;
}
else
{
i32 header; // should be "BM" (or "4D 42" in hex)
i32 height;
i32 width;
// Here we read in the header of the given BM image.
fseek(image, HEADER_LOCATION, SEEK_SET);
fread(&header, 2, 1, image);
*image_header = header;
// Here we read in the height of the given BM image.
fseek(image, HEIGHT_LOCATION, SEEK_SET);
fread(&height, 4, 1, image);
*image_height = height;
// Here we read in the width of the given BM image.
fseek(image, WIDTH_LOCATION, SEEK_SET);
fread(&width, 4, 1, image);
*image_width = width;
return 0;
}
}
void read_bmp_data(const char* file_name, int height, int width, int converted_image[height][width])
{
FILE* image = fopen(file_name, "rb");
// Here we read in the number of bits per pixel in the given BM image.
i16 bits_per_pixel;
fseek(image, BITS_PER_PIXEL_LOCATION, SEEK_SET);
fread(&bits_per_pixel, 2, 1, image);
// Here we compute the number of bytes per pixel in the given BM image.
i32 bytes_per_pixel = ((i32) bits_per_pixel) / 8;
// Here we initialise the converted image.
byte pixel[bytes_per_pixel];
for (int i = height - 1; i > 0; i = i - 1)
for (int j = 0; j < width; j = j + 1)
{
fread(pixel, 3, 1, image); // What to do with the padding?
int gray_scale_value = grayScaleConversion(pixel);
converted_image[i][j] = gray_scale_value;
}
fclose(image);
}
int main()
{
int image_header;
int image_width;
int image_height;
read_bmp_header("img.bmp", &image_header, &image_height, &image_width);
// Some prints to see the collected data.
printf("Size of int16 in byte = %llu \n", sizeof(i16));
printf("Size of int32 in byte = %llu \n", sizeof(i32));
printf("Size of byte in byte = %llu \n", sizeof(byte));
printf("header (in hex) = %x \n", image_header);
printf("width (in bytes) = %u \n", image_header);
printf("height (in bytes) = %u \n", image_header);
int grayed_image[image_height][image_width];
read_bmp_data("img.bmp", image_height, image_width, grayed_image);
printMatrix(image_height, image_width, grayed_image);
return 0;
}
对于
.bmp
文件,一行的像素数据必须[向上]对齐到4的倍数。
这是 not pixels 之间的填充。它是行之间的对齐。
行之间的bytes数称为stride:
int bytes_per_pixel = 3; // number of bytes for 8 bit RGB pixels
int alignment = 4; // required byte alignment for BMP image rows
int width = ...; // number of pixels / row
// number of bytes in a row (round _up_ by alignment)
int stride = (width * bytes_per_pixel) + (alignment - 1);
stride /= alignment;
stride *= alignment;
在
read_bmp_data
中,在您的第一个for
循环(对于i
)之后,添加一个fseek
。这将寻找行 i
的磁盘数据(即它处理行填充/对齐):
for (int i = height - 1; i > 0; i = i - 1) {
fseek(image, i * stride, SEEK_SET);
for (int j = 0; j < width; j = j + 1) {
fread(pixel, 3, 1, image);
int gray_scale_value = grayScaleConversion(pixel);
converted_image[i][j] = gray_scale_value;
}
}
填充不是每个像素;它是每行像素。
这意味着每行像素的长度必须是 4 的倍数。
没有在填充字节之前停止的事情。
你想要做的是读取整行像素,就像你已经在做的那样,然后根据需要前进尽可能多的字节以跳过填充,然后再开始读取下一行。
所以,首先,你需要计算有多少字节的填充。那将是
int padding = (width * 3) % 4
.
由于您使用的是
fread()
,那么在j
循环结束之后和i
循环结束之前,您将需要使用fseek()
来跳过padding
字节。
稍后您会发现,使用
fread()
逐像素读取图像是荒谬的慢,因此您应该先将整个图像读入内存,然后使用指针从内存中读取像素,但是那是一个不同的故事。