refactor: Deduplicated BMP header writing in Xtc (#1439) by znelson
This commit is contained in:
@@ -107,7 +107,7 @@ uint8_t quantize1bit(int gray, int x, int y) {
|
|||||||
return (gray >= adjustedThreshold) ? 1 : 0;
|
return (gray >= adjustedThreshold) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void createBmpHeader(BmpHeader* bmpHeader, int width, int height) {
|
void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder rowOrder) {
|
||||||
if (!bmpHeader) return;
|
if (!bmpHeader) return;
|
||||||
|
|
||||||
// Zero out the memory to ensure no garbage data if called on uninitialized stack memory
|
// Zero out the memory to ensure no garbage data if called on uninitialized stack memory
|
||||||
@@ -125,15 +125,15 @@ void createBmpHeader(BmpHeader* bmpHeader, int width, int height) {
|
|||||||
|
|
||||||
bmpHeader->infoHeader.biSize = sizeof(bmpHeader->infoHeader);
|
bmpHeader->infoHeader.biSize = sizeof(bmpHeader->infoHeader);
|
||||||
bmpHeader->infoHeader.biWidth = width;
|
bmpHeader->infoHeader.biWidth = width;
|
||||||
bmpHeader->infoHeader.biHeight = height;
|
bmpHeader->infoHeader.biHeight = (rowOrder == BmpRowOrder::TopDown) ? -height : height;
|
||||||
bmpHeader->infoHeader.biPlanes = 1;
|
bmpHeader->infoHeader.biPlanes = 1;
|
||||||
bmpHeader->infoHeader.biBitCount = 1;
|
bmpHeader->infoHeader.biBitCount = 1;
|
||||||
bmpHeader->infoHeader.biCompression = 0;
|
bmpHeader->infoHeader.biCompression = 0;
|
||||||
bmpHeader->infoHeader.biSizeImage = imageSize;
|
bmpHeader->infoHeader.biSizeImage = imageSize;
|
||||||
bmpHeader->infoHeader.biXPelsPerMeter = 0;
|
bmpHeader->infoHeader.biXPelsPerMeter = 2835; // 72 DPI
|
||||||
bmpHeader->infoHeader.biYPelsPerMeter = 0;
|
bmpHeader->infoHeader.biYPelsPerMeter = 2835; // 72 DPI
|
||||||
bmpHeader->infoHeader.biClrUsed = 0;
|
bmpHeader->infoHeader.biClrUsed = 2;
|
||||||
bmpHeader->infoHeader.biClrImportant = 0;
|
bmpHeader->infoHeader.biClrImportant = 2;
|
||||||
|
|
||||||
// Color 0 (black)
|
// Color 0 (black)
|
||||||
bmpHeader->colors[0].rgbBlue = 0;
|
bmpHeader->colors[0].rgbBlue = 0;
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ uint8_t quantizeSimple(int gray);
|
|||||||
uint8_t quantize1bit(int gray, int x, int y);
|
uint8_t quantize1bit(int gray, int x, int y);
|
||||||
int adjustPixel(int gray);
|
int adjustPixel(int gray);
|
||||||
|
|
||||||
|
enum class BmpRowOrder { BottomUp, TopDown };
|
||||||
|
|
||||||
// Populates a 1-bit BMP header in the provided memory.
|
// Populates a 1-bit BMP header in the provided memory.
|
||||||
void createBmpHeader(BmpHeader* bmpHeader, int width, int height);
|
void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder rowOrder);
|
||||||
|
|
||||||
// 1-bit Atkinson dithering - better quality than noise dithering for thumbnails
|
// 1-bit Atkinson dithering - better quality than noise dithering for thumbnails
|
||||||
// Error distribution pattern (same as 2-bit but quantizes to 2 levels):
|
// Error distribution pattern (same as 2-bit but quantizes to 2 levels):
|
||||||
|
|||||||
+11
-87
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Xtc.h"
|
#include "Xtc.h"
|
||||||
|
|
||||||
|
#include <Bitmap.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
@@ -175,52 +176,12 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write BMP header
|
// Write 1-bit BMP header (top-down row order)
|
||||||
// BMP file header (14 bytes)
|
BmpHeader bmpHeader;
|
||||||
const uint32_t rowSize = ((pageInfo.width + 31) / 32) * 4; // Row size aligned to 4 bytes
|
createBmpHeader(&bmpHeader, pageInfo.width, pageInfo.height, BmpRowOrder::TopDown);
|
||||||
const uint32_t imageSize = rowSize * pageInfo.height;
|
coverBmp.write(reinterpret_cast<const uint8_t*>(&bmpHeader), sizeof(bmpHeader));
|
||||||
const uint32_t fileSize = 14 + 40 + 8 + imageSize; // Header + DIB + palette + data
|
|
||||||
|
|
||||||
// File header
|
const uint32_t rowSize = ((pageInfo.width + 31) / 32) * 4;
|
||||||
coverBmp.write('B');
|
|
||||||
coverBmp.write('M');
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&fileSize), 4);
|
|
||||||
uint32_t reserved = 0;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&reserved), 4);
|
|
||||||
uint32_t dataOffset = 14 + 40 + 8; // 1-bit palette has 2 colors (8 bytes)
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&dataOffset), 4);
|
|
||||||
|
|
||||||
// DIB header (BITMAPINFOHEADER - 40 bytes)
|
|
||||||
uint32_t dibHeaderSize = 40;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&dibHeaderSize), 4);
|
|
||||||
int32_t width = pageInfo.width;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&width), 4);
|
|
||||||
int32_t height = -static_cast<int32_t>(pageInfo.height); // Negative for top-down
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&height), 4);
|
|
||||||
uint16_t planes = 1;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&planes), 2);
|
|
||||||
uint16_t bitsPerPixel = 1; // 1-bit monochrome
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&bitsPerPixel), 2);
|
|
||||||
uint32_t compression = 0; // BI_RGB (no compression)
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&compression), 4);
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&imageSize), 4);
|
|
||||||
int32_t ppmX = 2835; // 72 DPI
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&ppmX), 4);
|
|
||||||
int32_t ppmY = 2835;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&ppmY), 4);
|
|
||||||
uint32_t colorsUsed = 2;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&colorsUsed), 4);
|
|
||||||
uint32_t colorsImportant = 2;
|
|
||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&colorsImportant), 4);
|
|
||||||
|
|
||||||
// Color palette (2 colors for 1-bit)
|
|
||||||
// XTC 1-bit polarity: 0 = black, 1 = white (standard BMP palette order)
|
|
||||||
// Color 0: Black (text/foreground in XTC)
|
|
||||||
uint8_t black[4] = {0x00, 0x00, 0x00, 0x00};
|
|
||||||
coverBmp.write(black, 4);
|
|
||||||
// Color 1: White (background in XTC)
|
|
||||||
uint8_t white[4] = {0xFF, 0xFF, 0xFF, 0x00};
|
|
||||||
coverBmp.write(white, 4);
|
|
||||||
|
|
||||||
// Write bitmap data
|
// Write bitmap data
|
||||||
// BMP requires 4-byte row alignment
|
// BMP requires 4-byte row alignment
|
||||||
@@ -403,49 +364,12 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write 1-bit BMP header for fast home screen rendering
|
// Write 1-bit BMP header (top-down row order)
|
||||||
const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; // 1 bit per pixel, aligned to 4 bytes
|
BmpHeader bmpHeader;
|
||||||
const uint32_t imageSize = rowSize * thumbHeight;
|
createBmpHeader(&bmpHeader, thumbWidth, thumbHeight, BmpRowOrder::TopDown);
|
||||||
const uint32_t fileSize = 14 + 40 + 8 + imageSize; // 8 bytes for 2-color palette
|
thumbBmp.write(reinterpret_cast<const uint8_t*>(&bmpHeader), sizeof(bmpHeader));
|
||||||
|
|
||||||
// File header
|
const uint32_t rowSize = (thumbWidth + 31) / 32 * 4;
|
||||||
thumbBmp.write('B');
|
|
||||||
thumbBmp.write('M');
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&fileSize), 4);
|
|
||||||
uint32_t reserved = 0;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&reserved), 4);
|
|
||||||
uint32_t dataOffset = 14 + 40 + 8; // 1-bit palette has 2 colors (8 bytes)
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&dataOffset), 4);
|
|
||||||
|
|
||||||
// DIB header
|
|
||||||
uint32_t dibHeaderSize = 40;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&dibHeaderSize), 4);
|
|
||||||
int32_t widthVal = thumbWidth;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&widthVal), 4);
|
|
||||||
int32_t heightVal = -static_cast<int32_t>(thumbHeight); // Negative for top-down
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&heightVal), 4);
|
|
||||||
uint16_t planes = 1;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&planes), 2);
|
|
||||||
uint16_t bitsPerPixel = 1; // 1-bit for black and white
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&bitsPerPixel), 2);
|
|
||||||
uint32_t compression = 0;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&compression), 4);
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&imageSize), 4);
|
|
||||||
int32_t ppmX = 2835;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&ppmX), 4);
|
|
||||||
int32_t ppmY = 2835;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&ppmY), 4);
|
|
||||||
uint32_t colorsUsed = 2;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&colorsUsed), 4);
|
|
||||||
uint32_t colorsImportant = 2;
|
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&colorsImportant), 4);
|
|
||||||
|
|
||||||
// Color palette (2 colors for 1-bit: black and white)
|
|
||||||
uint8_t palette[8] = {
|
|
||||||
0x00, 0x00, 0x00, 0x00, // Color 0: Black
|
|
||||||
0xFF, 0xFF, 0xFF, 0x00 // Color 1: White
|
|
||||||
};
|
|
||||||
thumbBmp.write(palette, 8);
|
|
||||||
|
|
||||||
// Allocate row buffer for 1-bit output
|
// Allocate row buffer for 1-bit output
|
||||||
uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(rowSize));
|
uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(rowSize));
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ bool ScreenshotUtil::saveFramebufferAsBmp(const char* filename, const uint8_t* f
|
|||||||
|
|
||||||
BmpHeader header;
|
BmpHeader header;
|
||||||
|
|
||||||
createBmpHeader(&header, phyWidth, phyHeight);
|
createBmpHeader(&header, phyWidth, phyHeight, BmpRowOrder::BottomUp);
|
||||||
|
|
||||||
bool write_error = false;
|
bool write_error = false;
|
||||||
if (file.write(reinterpret_cast<uint8_t*>(&header), sizeof(header)) != sizeof(header)) {
|
if (file.write(reinterpret_cast<uint8_t*>(&header), sizeof(header)) != sizeof(header)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user