diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 1142c763..8052c88e 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -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(&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(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(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(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(malloc(dstRowSize)); + // 2 bits per pixel, MSB first, rows padded to 4 bytes + uint8_t* rowBuffer = static_cast(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(&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;