Программа для записи растровых изображений не создает читабельное изображение, несмотря на следующие спецификации формата - PullRequest
0 голосов
/ 30 сентября 2019

Я делаю растровый редактор на C как часть более крупного проекта. Я следовал спецификациям формата заголовка Windows .bmp и проверил сгенерированный файл в шестнадцатеричном редакторе, чтобы сравнить его с функциональными изображениями .bmp, однако все программы изображений, которые есть на моем компьютере, не могут открыть его. Вот код:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#pragma pack(push, 1) /* remove padding from sructs */

void generateBitmap(int width, int height, float dpi, const char* filename, pixel* imgData) {
    FILE* bitmap;

    struct fHeader {
        uint16_t type; 
        uint32_t size;
        uint16_t reserved1;
        uint16_t reserved2;
        uint32_t offset;
    }bmpFileHeader;

    struct iHeader {
        uint32_t headerSize;
        int32_t  width;
        int32_t  height;
        uint16_t planes;
        uint16_t bitCount;
        uint32_t compression;
        uint32_t imageSize; /* may be 0 if uncompressed */
        int32_t  xPPM;
        int32_t  yPPM;
        uint32_t colorEntriesUsed;
        uint32_t importantColors;
    }bmpImageHeader;

    int bytesPerPixel = 3; /* 24 bit color */
    uint32_t imgSize = width * height;
    uint32_t fileSize = sizeof(bmpFileHeader) + sizeof(bmpImageHeader) + (bytesPerPixel * width * height);
    int32_t ppm = dpi * 39;

    bmpFileHeader.type = 0x4D42;
    bmpFileHeader.size = fileSize;
    bmpFileHeader.reserved1 = 0;
    bmpFileHeader.reserved2 = 0;
    bmpFileHeader.offset = sizeof(bmpFileHeader) + sizeof(bmpImageHeader);

    bmpImageHeader.headerSize = sizeof(bmpImageHeader);
    bmpImageHeader.width = width;
    bmpImageHeader.height = height;
    bmpImageHeader.planes = 1;
    bmpImageHeader.bitCount = 8 * bytesPerPixel;
    bmpImageHeader.compression = 0;
    bmpImageHeader.imageSize = bytesPerPixel * height * width;
    bmpImageHeader.xPPM = ppm; /* typically set these to zero */
    bmpImageHeader.yPPM = ppm;
    bmpImageHeader.colorEntriesUsed = 0;
    bmpImageHeader.importantColors = 0;

    bitmap = fopen(filename, "wb");
    fwrite(&bmpFileHeader, 1, sizeof(bmpFileHeader), bitmap);
    fwrite(&bmpImageHeader, 1, sizeof(bmpImageHeader), bitmap);

    int i;
    for (i = 0; i < (width * height); i++) {
        fwrite(&imgData[i], 3, sizeof(char), bitmap);
    }

    fclose(bitmap);
}

int main(void) {
    pixel imData[4];

    int i;

    for(i = 0; i < 4; i++) {
        imData[i].r = 32;
        imData[i].g = 64;
        imData[i].b = 32;
    }

    generateBitmap(2, 2, 0, "bmptest.bmp", imData);

    return 0;
}

Конечным результатом должно быть только монотонное изображение 2x2. Некоторые примеры, которые я нашел, установили еще несколько значений заголовков, таких как imageSize на ноль, но другие примеры рассматривали их так же, как здесь.

Ответы [ 2 ]

1 голос
/ 30 сентября 2019

В растровом формате пиксели дополняются таким образом, чтобы «ширина в байтах» для каждой строки была кратна 4 байтам. См. ссылка для получения более подробной информации.

В этом случае у вас есть 2 пикселя на строку, каждый пиксель составляет 3 байта. Таким образом, у вас есть 6 байтов на строку. Его необходимо заполнить, чтобы каждая строка составляла 8 байтов (последние 2 байта игнорируются).

Это также влияет на размер файла растрового изображения и imageSize структуры iHeader.

int bytesPerPixel = 3; 
int bits_per_pixel = bytesPerPixel * 8;
int width_in_bytes = ((width * bits_per_pixel + 31) / 32) * 4;

...
bmpFileHeader.size = sizeof(bmpFileHeader)+sizeof(bmpImageHeader) + width_in_bytes*height;

...
bmpImageHeader.imageSize = width_in_bytes*height; 

Вы хотите переписать цикл так, чтобы он шел сверху вниз, слева направо

for(int y = height - 1; y >= 0; y--)
{
    for(int x = 0; x < width; x++)
    {
        int i = y * width + x;
        fwrite(&imgData[i], 3, sizeof(char), bitmap);
    }

    for(int x = width * bits_per_pixel / 8; x < width_in_bytes; x++)
        fputc('\0', bitmap);
}

.xPPM, а .yPPM может быть нулем. Наконец, вы можете использовать #pragma pack(pop) для восстановления пакета компилятора по умолчанию

#pragma pack(push, 1) /* remove padding from sructs */
    struct fHeader {
    ...
    };

    struct iHeader {
    ...
    };
#pragma pack(pop)
1 голос
/ 30 сентября 2019

Хорошо, я наконец-то получил его на работу. Проблема была со смещением битов между рядами пикселей. Лично я не нашел, чтобы это было невероятно хорошо задокументировано, но формат файла .bmp предполагает нулевые байты заполнения в конце каждой строки, в зависимости от размеров изображения. Все, что мне нужно было сделать, это добавить эту строку в код записи пикселей:

int i;
int p;
for (i = 0; i < (width * height); i++) {
    fwrite(&imgData[i], 3, sizeof(char), bitmap);
    if ((i + 1) % width == 0) { /* if at end of scanline */
        for(p = 0; p < (width % 4); p++) {
            fputc('\0', bitmap); /* append appropriate # of padding bits */
        }
    }
}
...