cimagebmp

How to handle padding when reading in a BMP?


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 > 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;
      }

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

#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;
}

enter image description here


Solution

  • 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 > 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;
        }
    }