如何处理读取BMP文件时的填充?

huangapple go评论59阅读模式
英文:

How to handle padding when reading in a BMP?

问题

我尝试读取BMP文件,使用C生成该图像的灰度版本。然而,我不知道如何处理行的填充。 (回想一下,每行的字节数必须是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);           // 如何处理填充?
      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;                            // 16位整数
typedef unsigned int i32;                     // 32位整数
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)
{
      // 在这里,我们以二进制模式打开给定的文件。
      FILE* image = fopen(file_name, "rb");

      // 在这里,我们检查是否成功打开了给定的文件。
      if (!image)
        {
          printf("Invalid File\n");
          return -1;
        }
      else
        {
          i32 header;  // 应该是“BM”(或16进制中的“4D 42”)
          i32 height;
          i32 width;

          // 在这里,我们读取给定BM图像的头部。
          fseek(image, HEADER_LOCATION, SEEK_SET);
          fread(&header, 2, 1, image);
          *image_header = header;
          // 在这里,我们读取给定BM图像的高度。
          fseek(image, HEIGHT_LOCATION, SEEK_SET);
          fread(&height, 4, 1, image);
          *image_height = height;
          // 在这里,我们读取给定BM图像的宽度。
          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");
      // 在这里,我们读取给定BM图像中每像素的位数。
      i16 bits_per_pixel;
      fseek(image, BITS_PER_PIXEL_LOCATION, SEEK_SET);
      fread(&bits_per_pixel, 2, 1, image);
      // 在这里,我们计算给定BM图像中每像素的字节数。
      i32 bytes_per_pixel = ((i32) bits_per_pixel) / 8;
      // 在这里,我们初始化转换后的图像。
      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);                      // 如何处理填充?
            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);

  // 一些打印以查看收集到的数据。
  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;
}
英文:

I am trying to read in a BMP file using C and to generate a grayscale version of that image. However, I do not know how to handle the padding of rows. (Recall that the size of each row in bytes has to be a multiple of 4.) When I move 3 bytes per step in each row, how can I then stop before the padded bytes? The critical part of my code is:

  byte pixel[bytes_per_pixel];
for (int i = height - 1; i &gt; 0; i = i - 1)
for (int j = 0; j &lt; 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;
}

For the sake of completeness, here is what I've got so far and you can find a reference image below.

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;math.h&gt;
#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 &lt; rows; i = i + 1)
{
for (int j = 0; j &lt; columns; j = j + 1)
printf(&quot;%d &quot;, matrix[i][j]);
printf(&quot;\n&quot;);
}
printf(&quot;\n&quot;);
}
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, &quot;rb&quot;);
// Here we check whether opening the given file was successful.
if (!image)
{
printf(&quot;Invalid File\n&quot;);
return -1;
}
else
{
i32 header;  // should be &quot;BM&quot; (or &quot;4D 42&quot; in hex)
i32 height;
i32 width;
// Here we read in the header of the given BM image.
fseek(image, HEADER_LOCATION, SEEK_SET);
fread(&amp;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(&amp;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(&amp;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, &quot;rb&quot;);
// 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(&amp;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 &gt; 0; i = i - 1)
for (int j = 0; j &lt; 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(&quot;img.bmp&quot;, &amp;image_header, &amp;image_height, &amp;image_width);
// Some prints to see the collected data.
printf(&quot;Size of int16 in byte = %llu \n&quot;, sizeof(i16));
printf(&quot;Size of int32 in byte = %llu \n&quot;, sizeof(i32));
printf(&quot;Size of byte in byte = %llu \n&quot;, sizeof(byte));
printf(&quot;header (in hex) = %x \n&quot;, image_header);
printf(&quot;width (in bytes) = %u \n&quot;, image_header);
printf(&quot;height (in bytes) = %u \n&quot;, image_header);
int grayed_image[image_height][image_width];
read_bmp_data(&quot;img.bmp&quot;, image_height, image_width, grayed_image);
printMatrix(image_height, image_width, grayed_image);
return 0;
}

如何处理读取BMP文件时的填充?

答案1

得分: 5

对于.bmp文件,一行的像素数据必须对齐到4的倍数。

这不是像素之间的填充,而是行之间的对齐。

行之间的字节数被称为步幅(stride):

int bytes_per_pixel = 3;  // 8位RGB像素的字节数
int alignment = 4;  // BMP图像行所需的字节对齐
int width = ...;  // 每行像素数

// 一行中的字节数(通过对齐向上取整)
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;
	}
}
英文:

For .bmp files, the pixel data for a row must be aligned [upward] to a multiple of 4.

This is not padding between pixels. It is alignment between rows.

The number of bytes between rows is known as the 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;

In read_bmp_data, after your first for loop (for i), add an fseek. This will seek to the disk data for row i (i.e. it handles row padding/alignment):

for (int i = height - 1; i &gt; 0; i = i - 1) {
fseek(image, i * stride, SEEK_SET);
for (int j = 0; j &lt; width; j = j + 1) {
fread(pixel, 3, 1, image);
int gray_scale_value = grayScaleConversion(pixel);
converted_image[i][j] = gray_scale_value;
}
}

答案2

得分: 2

Padding不是按像素计算的;而是按照像素行计算的。

这意味着每一行像素的长度必须是4的倍数。

不存在在填充字节之前停止的情况。

你需要做的是读取整行像素,然后在开始读取下一行之前,根据需要跳过填充的字节数。

所以,首先,你需要计算有多少字节的填充。计算方法是 int padding = (width * 3) % 4

因为你正在使用 fread(),所以在j循环结束后、i循环结束前,你需要使用 fseek() 跳过 padding 字节。

随着你后来会发现,使用 fread() 逐像素读取图像是非常慢的,所以你应该首先将整个图像读入内存,然后使用指针从内存中读取像素,但这是另外一个故事。

英文:

Padding is not per pixel; it is per row of pixels.

This means that each row of pixels must have a length which is a multiple of 4.

There is no such thing as stopping before the padded bytes.

What you want to do is to read an entire row of pixels, as you are already doing, and then advance by as many bytes as necessary in order to skip the padding, before starting to read the next row.

So, first, you need to calculate how many bytes of padding there are. That would be int padding = (width * 3) % 4.

Since you are using fread(), then after the end of the j loop and before the end of the i loop you will need to use fseek() to skip padding
bytes.

As you will find out later on, reading an image pixel by pixel using fread() is preposterously slow, so you should first read the entire image into memory, and then use a pointer to read pixels from memory, but that's a different story.

huangapple
  • 本文由 发表于 2023年5月18日 08:54:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76277082.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定