refactor: Deduplicated BMP header writing in Xtc (#1439)
## Summary **What is the goal of this PR?** Replaced manual 1-bit BMP header logic in `Xtc::generateCoverBmp()` and `Xtc::generateThumbBmp()` with calls to the existing `createBmpHeader()` utility. Added a `BmpRowOrder` enum and param to support the top-down row order of XTC cover images. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_
This commit is contained in:
@@ -108,7 +108,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
|
||||||
@@ -126,15 +126,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>
|
||||||
|
|
||||||
@@ -172,52 +173,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
|
||||||
@@ -400,49 +361,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