Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737b22a2df | ||
|
|
be5ce94c0f |
+46
-35
@@ -173,37 +173,53 @@ bool Xtc::generateCoverBmp() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write 1-bit BMP header (top-down row order)
|
||||
BmpHeader bmpHeader;
|
||||
createBmpHeader(&bmpHeader, pageInfo.width, pageInfo.height, BmpRowOrder::TopDown);
|
||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&bmpHeader), sizeof(bmpHeader));
|
||||
|
||||
const uint32_t rowSize = ((pageInfo.width + 31) / 32) * 4;
|
||||
|
||||
// Write bitmap data
|
||||
// BMP requires 4-byte row alignment
|
||||
const size_t dstRowSize = (pageInfo.width + 7) / 8; // 1-bit destination row size
|
||||
|
||||
if (bitDepth == 2) {
|
||||
// XTH 2-bit mode: Two bit planes, column-major order
|
||||
// XTH 2-bit mode: preserve all 4 gray levels in a 2-bit BMP so the sleep
|
||||
// screen's grayscale pass can render them (a 1-bit cover would silently
|
||||
// disable it). Source is two bit planes, column-major order:
|
||||
// - Columns scanned right to left (x = width-1 down to 0)
|
||||
// - 8 vertical pixels per byte (MSB = topmost pixel in group)
|
||||
// - First plane: Bit1, Second plane: Bit2
|
||||
// - Pixel value = (bit1 << 1) | bit2
|
||||
// - Pixel value = (bit1 << 1) | bit2: 0=white, 1=dark gray,
|
||||
// 2=light gray, 3=black
|
||||
// 70-byte 2-bit BMP header (14 file + 40 DIB + 4-entry gray palette),
|
||||
// top-down rows. Only the size and dimension fields vary; patch them in.
|
||||
// clang-format off
|
||||
uint8_t hdr[70] = {
|
||||
'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, // file header
|
||||
40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, // DIB: w/h patched
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0x13, 0x0B, 0, 0, 0x13, 0x0B, 0, 0, // 0, 2835 DPI
|
||||
4, 0, 0, 0, 4, 0, 0, 0, // 4 palette colors
|
||||
0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x00, // black, dark gray
|
||||
0xAA, 0xAA, 0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0x00}; // light gray, white
|
||||
// clang-format on
|
||||
const uint32_t rowSize2 = ((static_cast<uint32_t>(pageInfo.width) * 2 + 31) / 32) * 4;
|
||||
const uint32_t imageSize = rowSize2 * pageInfo.height;
|
||||
const uint32_t fileSize = sizeof(hdr) + imageSize;
|
||||
const int32_t topDownHeight = -static_cast<int32_t>(pageInfo.height);
|
||||
memcpy(hdr + 2, &fileSize, 4);
|
||||
memcpy(hdr + 18, &pageInfo.width, 2); // biWidth (upper bytes stay 0)
|
||||
memcpy(hdr + 22, &topDownHeight, 4); // negative biHeight = top-down
|
||||
memcpy(hdr + 34, &imageSize, 4);
|
||||
coverBmp.write(hdr, sizeof(hdr));
|
||||
|
||||
const size_t planeSize = (static_cast<size_t>(pageInfo.width) * pageInfo.height + 7) / 8;
|
||||
const uint8_t* plane1 = pageBuffer; // Bit1 plane
|
||||
const uint8_t* plane2 = pageBuffer + planeSize; // Bit2 plane
|
||||
const size_t colBytes = (pageInfo.height + 7) / 8; // Bytes per column
|
||||
|
||||
// Allocate a row buffer for 1-bit output
|
||||
uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(dstRowSize));
|
||||
// 2 bits per pixel, MSB first, rows padded to 4 bytes
|
||||
uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(rowSize2));
|
||||
if (!rowBuffer) {
|
||||
free(pageBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// XTH value -> BMP palette index (palette: 0=black, 1=dark, 2=light, 3=white)
|
||||
static constexpr uint8_t kXthToBmp[4] = {3, 1, 2, 0};
|
||||
|
||||
for (uint16_t y = 0; y < pageInfo.height; y++) {
|
||||
memset(rowBuffer, 0xFF, dstRowSize); // Start with all white
|
||||
memset(rowBuffer, 0x00, rowSize2);
|
||||
|
||||
for (uint16_t x = 0; x < pageInfo.width; x++) {
|
||||
// Column-major, right to left: column index = (width - 1 - x)
|
||||
@@ -216,28 +232,22 @@ bool Xtc::generateCoverBmp() const {
|
||||
const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1;
|
||||
const uint8_t pixelValue = (bit1 << 1) | bit2;
|
||||
|
||||
// Threshold: 0=white (1); 1,2,3=black (0)
|
||||
if (pixelValue >= 1) {
|
||||
// Set bit to 0 (black) in BMP format
|
||||
const size_t dstByte = x / 8;
|
||||
const size_t dstBit = 7 - (x % 8);
|
||||
rowBuffer[dstByte] &= ~(1 << dstBit);
|
||||
}
|
||||
const uint8_t bmpVal = kXthToBmp[pixelValue];
|
||||
rowBuffer[(x * 2) / 8] |= bmpVal << (6 - ((x * 2) % 8));
|
||||
}
|
||||
|
||||
// Write converted row
|
||||
coverBmp.write(rowBuffer, dstRowSize);
|
||||
|
||||
// Pad to 4-byte boundary
|
||||
uint8_t padding[4] = {0, 0, 0, 0};
|
||||
size_t paddingSize = rowSize - dstRowSize;
|
||||
if (paddingSize > 0) {
|
||||
coverBmp.write(padding, paddingSize);
|
||||
}
|
||||
// Row buffer is rowSize2 bytes and zero-padded, write it whole
|
||||
coverBmp.write(rowBuffer, rowSize2);
|
||||
}
|
||||
|
||||
free(rowBuffer);
|
||||
} else {
|
||||
// Write 1-bit BMP header (top-down row order)
|
||||
BmpHeader bmpHeader;
|
||||
createBmpHeader(&bmpHeader, pageInfo.width, pageInfo.height, BmpRowOrder::TopDown);
|
||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&bmpHeader), sizeof(bmpHeader));
|
||||
|
||||
const uint32_t rowSize = ((pageInfo.width + 31) / 32) * 4;
|
||||
// 1-bit source: write directly with proper padding
|
||||
const size_t srcRowSize = (pageInfo.width + 7) / 8;
|
||||
|
||||
@@ -422,9 +432,10 @@ bool Xtc::generateThumbBmp(int height) const {
|
||||
const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1;
|
||||
const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1;
|
||||
const uint8_t pixelValue = (bit1 << 1) | bit2;
|
||||
// Convert 2-bit (0-3) to grayscale: 0=black, 3=white
|
||||
// pixelValue: 0=white, 1=light gray, 2=dark gray, 3=black (XTC polarity)
|
||||
grayValue = (3 - pixelValue) * 85; // 0->255, 1->170, 2->85, 3->0
|
||||
// pixelValue: 0=white, 1=dark gray, 2=light gray, 3=black —
|
||||
// same semantics as the cover's kXthToBmp mapping above
|
||||
static constexpr uint8_t kXthToGray[4] = {255, 85, 170, 0};
|
||||
grayValue = kXthToGray[pixelValue];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user