Search code examples
ccolorsrgbpixelbmp

How to get the most frequently appearing pixel in a bmp


As a function to determine "the base color of an image" I'm trying to implement the following code:

typedef unsigned long dword;
typedef unsigned short word;
typedef unsigned char BYTE;

typedef struct
{
    BYTE R;
    BYTE G;
    BYTE B;
} RGB;

RGB
bitfox_get_primecolor_direct
(char *FILE_NAME)
{
    RGB primecolor;
    BYTE rgb[3];

    dword *counts;
    dword max_count = 0;

    FILE* fp = fopen("sample.bmp", "rb");

    counts = calloc(pow(256, 3), sizeof(*counts));
    fseek(fp, 54, SEEK_SET);

    while (fread (rgb, sizeof(BYTE), 3, fp) == 1)
    {
        dword idx = (((dword)rgb[0]) << 16) | (((dword)rgb[1]) << 8) | (dword)rgb[2];
        if (++counts[idx] > max_count) max_count = idx;
    }

    primecolor.R = (rgb[max_count] >> 16) & 0xFF;
    primecolor.G = (rgb[max_count] >> 8) & 0xFF;
    primecolor.B = rgb[max_count] & 0xFF;

    free(counts);
    fclose(fp);
    return primecolor;
}

It is supposed to be a fast algorithm ( not really thrifty when it comes to RAM ) to return RGB struct, with the base color of an image. However.. it returns the incorrect color. What am i doing wrong?


Solution

  • As others have pointed out already, there are several issues, the most important one the indexing of max_count. You can make max_count an index and then derive the colour from that index with the reverse logic that you used when constructing the index. This is how Steffen's and user694733's answers work.

    You could also keep max_count as a count and assign primecolor when a new max count is found. That saves you the backward calculation.

    Thee's a potential other issue to do with padding. The BMP format stores its data line-wise, but the number of bytes in each line must be a multiple of 4. In your case, the image is 262 pixels wide. Each line is 786 bytes long, so it must be padded to 788. If you want to take padding into account, you must know the width of the image.

    Another source of confusion is that you pass a parameter FILE_NAME to your function, but always open "sample.bmp", so you might not really get what you want.

    Also, but this is a niggle, I think that the integer cube pow(256, 3) is better rendered as 256 * 256 * 256.

    Here's varaint that works for me (it needs more error checking):

    RGB bitfox_get_primecolor_direct(char *FILE_NAME)
    {
        RGB primecolor = {0, 0, 0};
        BYTE hdr[54];
    
        dword *counts;
        dword max_count = 0;
    
        word w, h;
        word i, j;
    
        FILE* fp = fopen(FILE_NAME, "rb");
    
        counts = calloc(256 * 256 * 256, sizeof(*counts));
    
        // Read header to get width and height
        fread(hdr, sizeof(hdr), 1, fp);        
        w = (hdr[19] << 8) | hdr[18];
        h = (hdr[23] << 8) | hdr[22];
    
        // Loop over pixels
        for (i = 0; i < h; i++) {
            for (j = 0; j < w; j++) {
                RGB rgb;
                dword idx;
    
                if (fread(&rgb, 3, 1, fp) < 1) {
                    fprintf(stderr, "Unexpected end of file.\n");
                    exit(1);
                }
                idx = (rgb.R << 16) | (rgb.G << 8) | rgb.B;
                if (++counts[idx] > max_count) {
                    max_count = counts[idx];
                    primecolor = rgb;
                }
            }
    
            // Treat padding
            j = 3 * w;
            while (j++ % 4) getc(fp);
        }
    
        free(counts);
        fclose(fp);
    
        return primecolor;
    }