简体   繁体   中英

Converting 1-bit bmp file to array in C/C++

I'm looking to turn a 1-bit bmp file of variable height/width into a simple two-dimensional array with values of either 0 or 1. I don't have any experience with image editing in code and most libraries that I've found involve higher bit-depth than what I need. Any help regarding this would be great.

Here's the code to read a monochrome .bmp file

(See dmb's answer below for a small fix for odd-sized .bmps)

#include <stdio.h>
#include <string.h>
#include <malloc.h>

unsigned char *read_bmp(char *fname,int* _w, int* _h)
{
    unsigned char head[54];
    FILE *f = fopen(fname,"rb");

    // BMP header is 54 bytes
    fread(head, 1, 54, f);

    int w = head[18] + ( ((int)head[19]) << 8) + ( ((int)head[20]) << 16) + ( ((int)head[21]) << 24);
    int h = head[22] + ( ((int)head[23]) << 8) + ( ((int)head[24]) << 16) + ( ((int)head[25]) << 24);

    // lines are aligned on 4-byte boundary
    int lineSize = (w / 8 + (w / 8) % 4);
    int fileSize = lineSize * h;

    unsigned char *img = malloc(w * h), *data = malloc(fileSize);

    // skip the header
    fseek(f,54,SEEK_SET);

    // skip palette - two rgb quads, 8 bytes
    fseek(f, 8, SEEK_CUR);

    // read data
    fread(data,1,fileSize,f);

    // decode bits
    int i, j, k, rev_j;
    for(j = 0, rev_j = h - 1; j < h ; j++, rev_j--) {
        for(i = 0 ; i < w / 8; i++) {
            int fpos = j * lineSize + i, pos = rev_j * w + i * 8;
            for(k = 0 ; k < 8 ; k++)
                img[pos + (7 - k)] = (data[fpos] >> k ) & 1;
        }
    }

    free(data);
    *_w = w; *_h = h;
    return img;
}

int main()
{
    int w, h, i, j;
    unsigned char* img = read_bmp("test1.bmp", &w, &h);

    for(j = 0 ; j < h ; j++)
    {
        for(i = 0 ; i < w ; i++)
            printf("%c ", img[j * w + i] ? '0' : '1' );

        printf("\n");
    }

    return 0;
}

It is plain C, so no pointer casting - beware while using it in C++.

The biggest problem is that the lines in .bmp files are 4-byte aligned which matters a lot with single-bit images. So we calculate the line size as "width / 8 + (width / 8) % 4". Each byte contains 8 pixels, not one, so we use the k-based loop.

I hope the other code is obvious - much has been told about .bmp header and pallete data (8 bytes which we skip).

样本图片

Expected output:

0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 
0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 
0 0 0 0 0 0 1 1 1 1 0 0 1 1 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 1 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 1 0 0 0 
0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 1 1 1 1 1 0 0 0 0 1 0 
0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 
0 0 0 1 0 1 1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 

I tried the solution of Viktor Lapyov on a 20x20 test image: 在此输入图像描述

But with his code, I get this output (slightly reformatted but you can see the problem):

在此输入图像描述

The last 4 pixels are not read. The problem is here. (The last partial byte in a row is ignored.)

// decode bits
int i, j, k, rev_j;
for(j = 0, rev_j = h - 1; j < h ; j++, rev_j--) {
    for(i = 0 ; i < w / 8; i++) {
        int fpos = j * lineSize + i, pos = rev_j * w + i * 8;
        for(k = 0 ; k < 8 ; k++)
            img[pos + (7 - k)] = (data[fpos] >> k ) & 1;
    }
}

I rewrote the inner loop like this:

// decode bits
int i, byte_ctr, j, rev_j;
for(j = 0, rev_j = h - 1; j < h ; j++, rev_j--) {
    for( i = 0; i < w; i++) {
        byte_ctr = i / 8;
        unsigned char data_byte = data[j * lineSize + byte_ctr];
        int pos = rev_j * w + i;
        unsigned char mask = 0x80 >> i % 8;
        img[pos] = (data_byte & mask ) ? 1 : 0;
    }
}

and all is well:

在此输入图像描述

The following c code works with monochrome bitmaps of any size. I'll assume you've got your bitmap in a buffer with heights and width initialized from file. So

// allocate mem for global buffer
if (!(img = malloc(h * w)) )
     return(0);

int i = 0, k, j, scanline;

// calc the scanline. Monochrome images are
// padded with 0 at every line end. This
// makes them divisible by 4.
scanline = ( w + (w % 8) ) >> 3;

// account for the paddings
if (scanline % 4)
    scanline += (4 - scanline % 4);

// loop and set the img values
for (i = 0, k = h - 1; i < h; i++)
    for (j = 0; j < w; j++) {
        img[j+i*w] = (buffer[(j>>3)+k*scanline])
           & (0x80 >> (j % 8));
    }

Hope this help's. To convert it to 2D is now a trivial matter: But if u get lost here is the math to convert 1D array to 2D suppose r & c are row and column and w is the width then: . c + r * w = r, c

If you got further remarks hit me back, am out!!!

Lets think of a1x7 monochrome bitmap ie This is a bitmap of a straight line with 7 pixels wide. To store this image on a Windows OS; since 7 is not evenly divisible by 4 it's going to pad in it an extra 3 bytes.

So the biSizeImage of the BITMAPINFOHEADER structure will show a total of 4 bytes. Nonetheless the biHeight and biWidth members will correctly state the true bitmap dimensions.

The above code will fail because 7 / 8 = 0 (by rounding off as with all c compilers do). Hence loop "i" will not execute so will "k".

That means the vector "img" now contains garbage values that do not correspond to the pixels contained in " data" ie the result is incorrect.

And by inductive reasoning if it does not satisfy the base case then chances are it wont do much good for general cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM