Создание растрового изображения 8bpp с GDI и сохранение его в виде файла

У меня есть отлично работающий код, который создает растровое изображение 32bpp, и мне нужно изменить его, чтобы создать растровое изображение 8bpp.

Вот фрагмент кода, который создает растровое изображение 32bpp, рисует его, затем создает файл растрового изображения и сохраняет его в векторе байтов:

// prepare bitmap:
BYTE* bitmap_data = NULL;
HDC hDC = GetDC(NULL);
HDC memHDC = CreateCompatibleDC(hDC);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = desiredWidth;                 // desiredWidth is 800
bmi.bmiHeader.biHeight = desiredHeight;               // desiredHeight is 202
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = (((desiredWidth * bmi.bmiHeader.biBitCount + 31) & ~31) >> 3) * desiredHeight;
HBITMAP bitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, (void**)&bitmap_data, NULL, NULL);
ReleaseDC(NULL, hDC);
DeleteDC(hDC);

... // drawing into bitmap

// prepare bitmap file header:
BITMAPFILEHEADER bf;
memset(&bf, 0, sizeof(BITMAPFILEHEADER));
bf.bfType = MAKEWORD('B', 'M');
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;
bf.bfSize = bf.bfOffBits + bmi.bmiHeader.biSizeImage;

// write bitmap file into the vector:
std::vector<BYTE> bitmapData;
bitmapData.insert(bitmapData.end(), (BYTE*)&bf, ((BYTE*)&bf) + sizeof(BITMAPFILEHEADER));
bitmapData.insert(bitmapData.end(), (BYTE*)&bmi.bmiHeader, ((BYTE*)&bmi.bmiHeader) + sizeof(BITMAPINFOHEADER));
bitmapData.insert(bitmapData.end(), bitmap_data, bitmap_data + bmi.bmiHeader.biSizeImage);

И позже вектор сохраняется в файле:

std::ofstream of("picture.bmp", std::ofstream::out | std::ofstream::binary);
of.write((char*)&bitmapData[0], bitmapData.size());
of.close();

и вот выходное изображение:

Изображение 32bpp

Что я пробовал:

Первым шагом была естественная замена 32 с 8 в этой строке: bmi.bmiHeader.biBitCount = 32; что привело к изображению, заполненному сплошным серым цветом. Затем на основе этот ответ Я сделал следующие изменения:

BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));

изменился на:

struct BITMAPINFO256 {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[256];
} bmi;
memset(&bmi, 0, sizeof(BITMAPINFO256));

добавил этот цикл прямо перед CreateDIBSection называется:

for (UINT i = 0; i < 256; i++) {
bmi.bmiColors[i].rgbRed   = i;
bmi.bmiColors[i].rgbGreen = i;
bmi.bmiColors[i].rgbBlue  = i;
}

и когда bmi.bmiHeader записывается в вектор, RGBQUAD массив включен: так sizeof(BITMAPINFO256) выражает размер заголовка.

Новый код (полный код здесь) производит этот вывод:

Изображение 8bpp

Почему новый образ выглядит именно так? Что там происходит? Что мне не хватает?

Любая помощь будет оценена.

2

Решение

Это похоже на проблему выравнивания. Убедитесь, что вы обновили bfOffBits в BITMAPFILEHEADER, чтобы он указывал на первый байт данных пикселей. (Если вы не измените его, то, вероятно, он указывает на начало палитры.)

Другими словами, sizeof(RGBQUAD)*256 также следует добавить сюда:

bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;

Также убедитесь, что первая линия сканирования начинается на границе DWORD. Таким образом, его смещение от начала файла должно быть кратным четырем байтам. Аналогично, каждая строка развертки должна быть дополнена до четырех байтов. (Вы можете не увидеть эти проблемы, если ваши ширины хорошие четные числа. Хорошо иметь изображение нечетной ширины среди ваших тестовых случаев.)

3

Другие решения

Вам необходимо указать размер палитры, которую вы прикрепили. Прямо сейчас это ноль, поэтому палитра отображается как первая группа пикселей на вашем изображении.

bmi.bmiHeader.biClrUsed = 256;
0

Вам нужно создать палитру для вашего изображения.
Каждый пиксель в 32-битном изображении сохраняется как 8-битный для альфа, красного, зеленого и синего.
Где, как в 8-битном изображении, значение в каждом пикселе 8-битного индекса в палитре.

Ваша форма для (i = 0..255) {bmi.bmiColors [i] .rgbRed = i; ….} код генерируется серой палитрой.

Если все изображение становится серым, то это звучит как ошибка выравнивания, из памяти ширина палитризованного изображения должна быть кратна 4.

Попробуйте сохранить цвет SMALL 256 (он же 8-битное изображение) из Paint и сравнить в шестнадцатеричном редакторе.

0