1

I still cannot access the color bits from a bitmap image. The problem is that, after saving the bitmap's content into a buffer, I do not know:

  1. Where to start the loop (If i start from 0 i think it will erase the headers)?

  2. How to access the bytes and make the changes (transform a color from the BMP into a desired one from the output)?

  3. And, how to insert the buffer into a new bitmap file?

All the images I want to modify have rows which are divisible by 4 (I have to insert 0 when a specific byte is padding) and 24 bits per pixel. Even few tips would be much appreciated.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "bmp_header.h"

int main(void)
{
    FILE *f;
    f = fopen("captcha.bmp","rb");
    if ((f = fopen("captcha.bmp", "rb")) == NULL)
    {
        printf("Error opening file %s.\n", "captcha.bmp");
        exit(1);
    }
    fread(&BMP_header,sizeof(BMP_header),1,f);
    fread(&BMP_info_header,sizeof(BMP_info_header),1,f);
    fseek(f,BMP_header.imageDataOffset,SEEK_SET);
    int rows = (BMP_info_header.bitPix * BMP_info_header.width + 31 ) /32 * 4 ;
    char *PixelArray =malloc( rows * abs(BMP_info_header.height)*sizeof(char));
    int i;
    for( i =sizeof(BMP_header)+sizeof(BMP_info_header); i<=(rows * abs(BMP_info_header.height))-2;i+=3)
    {
        PixelArray[i]=255; // just a random value to test if this makese any sense
        PixelArray[i+1]=255;
        PixelArray[i+2]=255;

    }

    return 0;
}

And, here is bmp_header.h's content:

#pragma pack(1)

struct bmp_fileheader
{
    unsigned char  fileMarker1; /* 'B' */
    unsigned char  fileMarker2; /* 'M' */
    unsigned int   bfSize; /* File's size */
    unsigned short unused1;
    unsigned short unused2;
    unsigned int   imageDataOffset; /* Offset to the start of image data */
}BMP_header,BMP_header_out;

struct bmp_infoheader
{
    unsigned int   biSize; /* Size of the info header - 40 bytes */
    signed int     width; /* Width of the image */
    signed int     height; /* Height of the image */
    unsigned short planes;
    unsigned short bitPix;
    unsigned int   biCompression;
    unsigned int   biSizeImage; /* Size of the image data */
    int            biXPelsPerMeter;
    int            biYPelsPerMeter;
    unsigned int   biClrUsed;
    unsigned int   biClrImportant;
}BMP_info_header,BMP_info_header_out;

#pragma pack()
perror
  • 7,071
  • 16
  • 58
  • 85
  • 3
    Try changing one pixel instead of all of them. Merry Christmas btw. – Millie Smith Dec 25 '15 at 18:37
  • @MillieSmith the problem is that the value of one colour is stored in 3 bytes.So i can't do that.. –  Dec 25 '15 at 18:41
  • 1
    You don't have to put it all in a loop though. I guess I don't understand this statement: `int rows = (BMP_info_header.bitPix * BMP_info_header.width + 31 ) /32 * 4 ;`. Can you explain what the significance of 31, 32, and 4 are here? – Millie Smith Dec 25 '15 at 18:43
  • @MillieSmith https://upload.wikimedia.org/math/a/2/a/a2ae03037f8bf48ffe736e9d5b331c88.png from wikipedia's article about Bitmaps –  Dec 25 '15 at 18:45
  • @MillieSmith How can i make the changes without a loop ? –  Dec 25 '15 at 18:46
  • 1
    Ah okay. I'll read that section then. I only meant to remove the loop so that you could edit 1 pixel and see if it's working. It's a lot harder to find your problem when you edit all of the pixels. I have to step away for 20 minutes or so. I'll be back soon. – Millie Smith Dec 25 '15 at 18:49
  • 1
    OK, I see a couple issues here. You do not need `* sizeof(char)` in the malloc for the PixelArray. The malloc size is not based on the size of a character - only on the size in bytes of the pixel image data. The big mistake though is doing `i =sizeof(BMP_header)+sizeof(BMP_info_header);` and then using i (which isn't starting at 0) to index into your malloced PixelArray (which does start at 0). Another mistake is that `sizeof(BMP_header)+sizeof(BMP_info_header)` is the offset of the color table, not the offset of the pixel data. – Millie Smith Dec 25 '15 at 19:18
  • 1
    `i<=(rows * abs(BMP_info_header.height))-2` implies you don't want to edit the last couple rows? Also, are you sure your pixels are only 3 bytes? (as opposed to something like 4). – Millie Smith Dec 25 '15 at 19:18
  • 1
    `int rows` should be named `int rowSize`. And you can't simply do `for( i =sizeof(BMP_header)+sizeof(BMP_info_header); i<=(rows * abs(BMP_info_header.height))-2; i += 3)` because you start at some pre-defined offset, you bound yourself by the total size of the pixel storage in bytes, and then you add 3 all the time, completely forgetting the padding on the end of the rows. – Millie Smith Dec 25 '15 at 19:22
  • 1
    Try `for (i = 0; i < BMP_info_header.height; i++) { for (j = 0; j < rowSize - 4; j += 3) { int offset = i * rowSize + j; pixelArray[offset] = 255; pixelArray[offset + 1] = 255; pixelArray[offset + 2] = 255; } }`. Obviously this could be optimized, but it's Christmas and I'm being rushed into a car :(. – Millie Smith Dec 25 '15 at 19:26
  • this question and answers might be helpful: http://stackoverflow.com/questions/9296059/read-pixel-value-in-bmp-file/9296467#9296467 – Sufian Latif Dec 25 '15 at 19:54
  • @0605002 i've seen this one already . thanks .. –  Dec 25 '15 at 20:18
  • @MillieSmith i see now , thanks a lot for the amazing tips ! I have one more question : what about the Array ? How can i convert it into a new bmp file ? I've tried this: `f = fopen("result.bmp","wb"); fwrite(&BMP_header,sizeof(BMP_header),1,f); fwrite(&BMP_info_header,sizeof(BMP_info_header),sizeof(BMP_header),f); fwrite(&PixelArray,BMP_info_header.biSizeImage,sizeof(BMP_header)+sizeof(BMP_info_header),f);`but it's not working properly.. the new file has 40*the dimension of the first bmp .. –  Dec 25 '15 at 20:37
  • You're welcome. Those aren't the only three things in the bmp file, are they? I really don't know hah. – Millie Smith Dec 25 '15 at 21:16
  • is there a color table and an icc color profile? – Millie Smith Dec 25 '15 at 21:55
  • 1
    I screwed some things up. The padding is not always 4. Do `for (j=0; j < BMP_info_header.width; j++)` and adjust the offset assignment accordingly – Millie Smith Dec 25 '15 at 22:03
  • @MillieSmith i've been strugling with this for some time now..Can you give me a hint ? –  Dec 26 '15 at 00:28
  • Well, the problem is that I don't have any hints to give :/. Is bmp_header a standard header? I'm at a Christmas celebration at the moment. When I get home tonight I'll try to write some code and see if I can get it to work. I can't promise anything cause my grandma doesn't have internet and I don't think I have c installed. Are you comfortable uploading your bmp file? – Millie Smith Dec 26 '15 at 01:16
  • Yes , sure . Here we go : http://postimg.org/image/qb8zgdrob/afe6fbee/ –  Dec 26 '15 at 01:26
  • Now it's a png though haha. I wanted the exact bytes of the bmp so I could know I'm working on the same image as you. – Millie Smith Dec 26 '15 at 01:30
  • @MillieSmith i see.. http://image.online-convert.com/convert-to-bmp isn't doing the job ? –  Dec 26 '15 at 08:43
  • I finally have some internet. Where did you get the bmp_header.h code? – Millie Smith Dec 27 '15 at 03:35

1 Answers1

1

Alright, I wrote some code to write out a completely black bitmap file with the original dimensions of the old bitmap file.

This will not work with all types of bitmaps. 16-color bitmaps, for instance, have a color palette after the info header that my program does not account for. The bits per pixel also needs to be divisible by 8. If those preconditions are met, as I believe they are in 24-bit bitmaps, then this program should work.

The main code here is in getNewImageData. We calculate the row size for the image with the same formula that Wikipedia uses - it calculates the required bits and then pads that to a multiple of four bytes and then converts bits to bytes. Then we set all of the pixel array memory to zero (mostly me being paranoid about leaving values in the pad bytes). Then we run along each row and edit each pixel. The innermost for loop corresponds to each pixel. One iteration through the middle for loop writes to one pixel.

You can obviously modify this code to read the pixel data into a malloced section of memory and then edit the pixel data in place before writing it back out. This sample code does not read the input pixel data and just writes out a black bitmap of the same dimensions as the input file.

Edit: I guess I should mention what you were doing wrong.

  • You called fopen twice, leaving an open file pointer hanging somewhere in memory.
  • rows should probably be renamed to rowSize since it's the size of a row of pixels in bytes
  • PixelArray is not being freed.
  • Your for loop starts from an offset (the size of the two headers) and then is bounded by the size of the pixel data. The for loop should start from 0 and go to the size of the pixel data (unless you are reading pixel data in as you do this... which you probably shouldn't do).
  • Your for loop tries to write to all of the pixel data at once and doesn't account for the fact that there is anywhere between 0 and 31 bits of padding on the end of each row of pixels (my program assumes the padding is only 0, 8, 16, or 24 bits).

Here is the code:

#include <stdio.h>
#include "bmp_header.h"
#include <stdlib.h>

int readBitmapHeaders(char* fileLocation, bmp_fileheader* fileheader, bmp_infoheader* infoheader)
{
    FILE* f;
    f = fopen(fileLocation, "rb");

    if (!f)
    {
        printf("Error opening file %s.\n", fileLocation);
        return 1;
    }

    fread(fileheader, sizeof(bmp_fileheader), 1, f);
    fread(infoheader, sizeof(bmp_infoheader), 1, f);

    fclose(f);
    return 0;
}

int writeBitmap(char* fileName, bmp_fileheader* fileheader, bmp_infoheader* infoheader, char* pixelArray, size_t pixelArraySize)
{
    FILE* out;
    out = fopen(fileName, "wb");
    if (!out)
    {
        printf("Error opening file %s.\n", fileName);
        return 1;
    }

    fwrite(fileheader, sizeof(bmp_fileheader), 1, out);
    fwrite(infoheader, sizeof(bmp_infoheader), 1, out);
    fwrite(pixelArray, pixelArraySize, 1, out);

    fclose(out);
    return 0;
}

char* getNewImageData(bmp_infoheader* infoheader, size_t* imageSize)
{
    //rowsize is padded to 4 bytes
    size_t rowSize = (infoheader->bitPix * infoheader->width + 31) / 32 * 4;

    size_t pixelArraySize = rowSize * abs(infoheader->height);

    char* pixelArray = (char*)malloc(pixelArraySize);
    if (!pixelArray)
    {
        return NULL;
    }

    memset(pixelArray, 0, pixelArraySize);

    size_t bytesPerPixel = infoheader->bitPix / 8;
    for (int i = 0; i < infoheader->height; i++)
    {
        for (int j = 0; j < infoheader->width; j++)
        {
            size_t offset = rowSize * i + bytesPerPixel * j;
            for (size_t k = 0; k < bytesPerPixel; k++)
            {
                pixelArray[offset + k] = 0;
            }
        }
    }

    if (imageSize)
    {
        *imageSize = pixelArraySize;
    }
    return pixelArray;
}

int main()
{
    char* fileLocation = "test.bmp";
    bmp_fileheader header;
    bmp_infoheader infoheader;

    int readResult = readBitmapHeaders(fileLocation, &header, &infoheader);
    if (readResult)
    {
        return readResult;
    }

    size_t pixelArraySize;
    char* pixelArray = getNewImageData(&infoheader, &pixelArraySize);
    if (!pixelArray)
    {
        printf("%s", "Failed to create the new image data. Exiting with fatal error.\n");
        return 1;
    }

    char* outFile = "out.bmp";
    int writeResult = writeBitmap(outFile, &header, &infoheader, pixelArray, pixelArraySize);

    free(pixelArray);
    return writeResult;
}

I changed the bitmap header file a little to typedef the structs and make life easier (at least for me):

#pragma once
#pragma pack(1)

typedef struct _bmp_fileheader
{
    unsigned char  fileMarker1; /* 'B' */
    unsigned char  fileMarker2; /* 'M' */
    unsigned int   bfSize; /* File's size */
    unsigned short unused1;
    unsigned short unused2;
    unsigned int   imageDataOffset; /* Offset to the start of image data */
} bmp_fileheader;

typedef struct _bmp_infoheader
{
    unsigned int   biSize; /* Size of the info header - 40 bytes */
    signed int     width; /* Width of the image */
    signed int     height; /* Height of the image */
    unsigned short planes;
    unsigned short bitPix;
    unsigned int   biCompression;
    unsigned int   biSizeImage; /* Size of the image data */
    int            biXPelsPerMeter;
    int            biYPelsPerMeter;
    unsigned int   biClrUsed;
    unsigned int   biClrImportant;
} bmp_infoheader;

#pragma pack()
Millie Smith
  • 4,536
  • 2
  • 24
  • 60
  • why do you assign offset that value ? What does rowSize has to do with height and width ? –  Dec 27 '15 at 09:18
  • Width is used to calculate the rowsize and can be used to index into a row. The pixel data is a bunch of rows in contiguous memory. Specifically, a total of height rows. The calculation finds the correct row and offset into that row for the specific pixel to edit. – Millie Smith Dec 27 '15 at 09:20
  • I throught the padding has to be added at the end of the row..that's what i don't understand why you put it in the second for . I have one more unclarity : where can i edit the pixel (modify white ones into a specific color)? in the same second for ? –  Dec 27 '15 at 09:28
  • The first for loops over the height. We are going through all of the rows one by one. The second for loops over the width of the row. That width is in pixels. This avoids the padding. When we are on the third row, the part of the offset calculation that does `rowSize * i` *jumps over* the padding for both the first and second rows. The format in memory for three rows is ROW1,PAD1,ROW2,PAD2,ROW3,PAD3. The third for is setting the value for one pixel. In your case, k will go from 0 to 2. When k is 0, you are setting the R value. When k is 1, you are setting the G value. etc... – Millie Smith Dec 27 '15 at 17:06
  • I understand now. Thanks a lot for you effort and time Millie , you enlightened me . –  Dec 27 '15 at 17:39