Working version to load 24bit and display BW.

This commit is contained in:
Jonas Diemer 2025-12-17 12:07:39 +01:00
parent e7785003af
commit 9dcdcb02ba

View File

@ -29,52 +29,73 @@ struct BMPHeader {
}; };
#pragma pack(pop) #pragma pack(pop)
// Load BMP file from SD card // Load BMP file from SD card and rotate 90 degrees clockwise
// This rotation matches what we need for the e-ink display
uint8_t* loadBMP(const char* filename, int& width, int& height) { uint8_t* loadBMP(const char* filename, int& width, int& height) {
// Using rotation type 1: 90 degrees clockwise
const int rotationType = 1;
Serial.printf("[SleepScreen] Trying to load BMP: %s\n", filename); Serial.printf("[SleepScreen] Trying to load BMP: %s\n", filename);
if (!SD.exists(filename)) { if (!SD.exists(filename)) {
Serial.printf("[SleepScreen] File not found: %s\n", filename); Serial.printf("[SleepScreen] File not found: %s\n", filename);
return nullptr; return nullptr;
} }
File bmpFile = SD.open(filename); File bmpFile = SD.open(filename);
if (!bmpFile) { if (!bmpFile) {
Serial.printf("[SleepScreen] Failed to open file: %s\n", filename); Serial.printf("[SleepScreen] Failed to open file: %s\n", filename);
return nullptr; return nullptr;
} }
// Read BMP header // Read BMP header
BMPHeader header; BMPHeader header;
bmpFile.read((uint8_t*)&header, sizeof(BMPHeader)); bmpFile.read((uint8_t*)&header, sizeof(BMPHeader));
// Check if this is a valid BMP file // Check if this is a valid BMP file
if (header.signature != 0x4D42) { // "BM" in little-endian if (header.signature != 0x4D42) { // "BM" in little-endian
Serial.println("[SleepScreen] Invalid BMP signature"); Serial.println("[SleepScreen] Invalid BMP signature");
bmpFile.close(); bmpFile.close();
return nullptr; return nullptr;
} }
// Check for supported bit depths // Check for supported bit depths
if (header.bitsPerPixel != 1 && header.bitsPerPixel != 24) { if (header.bitsPerPixel != 1 && header.bitsPerPixel != 24) {
Serial.printf("[SleepScreen] Unsupported bit depth: %d\n", header.bitsPerPixel); Serial.printf("[SleepScreen] Unsupported bit depth: %d\n", header.bitsPerPixel);
bmpFile.close(); bmpFile.close();
return nullptr; return nullptr;
} }
// Get image dimensions // Get image dimensions
width = header.width; width = header.width;
height = (header.height < 0) ? -header.height : header.height; // Handle top-down BMPs height = (header.height < 0) ? -header.height : header.height; // Handle top-down BMPs
bool topDown = (header.height < 0); bool topDown = (header.height < 0);
Serial.printf("[SleepScreen] BMP dimensions: %dx%d, %d bits/pixel\n", width, height, header.bitsPerPixel); Serial.printf("[SleepScreen] BMP dimensions: %dx%d, %d bits/pixel\n", width, height, header.bitsPerPixel);
// E-ink display uses 1 bit per pixel (8 pixels per byte) // Calculate destination dimensions based on rotation type
int bytesPerRow = (width + 7) / 8; // Calculate bytes needed, rounding up int destWidth, destHeight;
int bufferSize = bytesPerRow * height;
if (rotationType == 0 || rotationType == 2) {
// No rotation or 180 degree rotation: keep same dimensions
destWidth = width;
destHeight = height;
} else {
// 90 degree rotations (clockwise or counterclockwise): swap width and height
destWidth = height;
destHeight = width;
}
Serial.printf("[SleepScreen] Output dimensions: %dx%d (rotation type: %d)\n",
destWidth, destHeight, rotationType);
// E-ink display: 1 bit per pixel (8 pixels per byte), MSB first format
int bytesPerRow = (destWidth + 7) / 8; // Round up to nearest byte
int bufferSize = bytesPerRow * destHeight;
Serial.printf("[SleepScreen] Buffer dimensions: %d bytes per row, %d total bytes\n", bytesPerRow, bufferSize);
Serial.printf("[SleepScreen] Buffer size: %d bytes (%d bytes per row)\n", bufferSize, bytesPerRow); Serial.printf("[SleepScreen] Buffer size: %d bytes (%d bytes per row)\n", bufferSize, bytesPerRow);
// Allocate memory for the display image // Allocate memory for the display image
uint8_t* displayImage = (uint8_t*)malloc(bufferSize); uint8_t* displayImage = (uint8_t*)malloc(bufferSize);
if (!displayImage) { if (!displayImage) {
@ -82,10 +103,10 @@ uint8_t* loadBMP(const char* filename, int& width, int& height) {
bmpFile.close(); bmpFile.close();
return nullptr; return nullptr;
} }
// Initialize to all white (0xFF = all bits set to 1) // Initialize to all white (0xFF = all bits set to 1)
memset(displayImage, 0xFF, bufferSize); memset(displayImage, 0xFF, bufferSize);
// Calculate BMP file row size (padded to 4-byte boundaries) // Calculate BMP file row size (padded to 4-byte boundaries)
int bmpRowSize; int bmpRowSize;
if (header.bitsPerPixel == 1) { if (header.bitsPerPixel == 1) {
@ -93,7 +114,7 @@ uint8_t* loadBMP(const char* filename, int& width, int& height) {
} else { // 24-bit } else { // 24-bit
bmpRowSize = ((width * 3 + 3) / 4) * 4; // 3 bytes per pixel (RGB), 4-byte alignment bmpRowSize = ((width * 3 + 3) / 4) * 4; // 3 bytes per pixel (RGB), 4-byte alignment
} }
// Allocate buffer for reading BMP rows // Allocate buffer for reading BMP rows
uint8_t* rowBuffer = (uint8_t*)malloc(bmpRowSize); uint8_t* rowBuffer = (uint8_t*)malloc(bmpRowSize);
if (!rowBuffer) { if (!rowBuffer) {
@ -102,59 +123,66 @@ uint8_t* loadBMP(const char* filename, int& width, int& height) {
bmpFile.close(); bmpFile.close();
return nullptr; return nullptr;
} }
// Process each row // Process each row
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
// Calculate source row (BMPs are normally stored bottom-to-top) // Calculate source row (BMPs are normally stored bottom-to-top)
int bmpRow = topDown ? y : (height - 1 - y); int bmpRow = topDown ? y : (height - 1 - y);
// Read one row from the BMP file // Read one row from the BMP file
bmpFile.seek(header.dataOffset + (bmpRow * bmpRowSize)); bmpFile.seek(header.dataOffset + (bmpRow * bmpRowSize));
bmpFile.read(rowBuffer, bmpRowSize); bmpFile.read(rowBuffer, bmpRowSize);
// Process each pixel in the row // Process each pixel in the row
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
// Determine if this pixel should be black based on bit depth // Determine if this pixel should be black based on bit depth
bool isBlack; bool isBlack;
if (header.bitsPerPixel == 1) { if (header.bitsPerPixel == 1) {
// For 1-bit BMPs, read the bit directly // For 1-bit BMPs, read the bit directly
int byteIndex = x / 8; int byteIndex = x / 8;
int bitIndex = 7 - (x % 8); // MSB first int bitIndex = 7 - (x % 8); // MSB first in BMP file format
// In BMPs, 1 typically means black and 0 means white // In 1-bit BMPs, bit value 1 typically means black and 0 means white
// Check if the bit is set (1) at the specified position
isBlack = (rowBuffer[byteIndex] & (1 << bitIndex)) != 0; isBlack = (rowBuffer[byteIndex] & (1 << bitIndex)) != 0;
} else { // 24-bit } else { // 24-bit
// For 24-bit BMPs, convert RGB to grayscale // For 24-bit BMPs, convert RGB to grayscale
// BMP stores colors as BGR (Blue, Green, Red)
int byteIndex = x * 3; int byteIndex = x * 3;
uint8_t blue = rowBuffer[byteIndex]; uint8_t blue = rowBuffer[byteIndex];
uint8_t green = rowBuffer[byteIndex + 1]; uint8_t green = rowBuffer[byteIndex + 1];
uint8_t red = rowBuffer[byteIndex + 2]; uint8_t red = rowBuffer[byteIndex + 2];
// Convert to grayscale using standard weights // Convert to grayscale using standard luminance formula
uint8_t gray = (red * 30 + green * 59 + blue * 11) / 100; uint8_t gray = (red * 30 + green * 59 + blue * 11) / 100;
// If below threshold, consider it black // If below threshold (128), consider it black
isBlack = (gray < 128); isBlack = (gray < 128);
} }
// Calculate destination position in our display buffer (8 pixels per byte) // Apply 90 degree clockwise rotation
int destByteIndex = y * bytesPerRow + (x / 8); // For rotation type 1: destX = y, destY = width - 1 - x
int destBitIndex = 7 - (x % 8); // MSB first (leftmost pixel is highest bit) int destX = y;
int destY = width - 1 - x;
// Set pixel in the display buffer (for e-ink: 0=black, 1=white)
// Calculate byte and bit position (1 bit per pixel)
int destByteIndex = destY * bytesPerRow + (destX / 8);
int destBitIndex = 7 - (destX % 8); // MSB first (leftmost pixel in highest bit)
// For e-ink display: 0=black, 1=white
if (isBlack) { if (isBlack) {
// Set to black (0) - clear bit // Set to black (0) by clearing the corresponding bit
displayImage[destByteIndex] &= ~(1 << destBitIndex); displayImage[destByteIndex] &= ~(1 << destBitIndex);
} }
// White pixels (1) are already set by the memset(0xFF) above // White pixels (1) are already set to 1 by the memset(0xFF) initialization
} }
} }
// Clean up // Clean up
free(rowBuffer); free(rowBuffer);
bmpFile.close(); bmpFile.close();
Serial.printf("[SleepScreen] Successfully loaded BMP: %dx%d\n", width, height); Serial.printf("[SleepScreen] Successfully loaded BMP: %dx%d\n", width, height);
return displayImage; return displayImage;
} }
@ -162,40 +190,54 @@ uint8_t* loadBMP(const char* filename, int& width, int& height) {
void SleepScreen::onEnter() { void SleepScreen::onEnter() {
const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageWidth = GfxRenderer::getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight(); const auto pageHeight = GfxRenderer::getScreenHeight();
Serial.printf("[SleepScreen] Screen dimensions: %dx%d\n", pageWidth, pageHeight); Serial.printf("[SleepScreen] Screen dimensions: %dx%d\n", pageWidth, pageHeight);
renderer.clearScreen(); renderer.clearScreen();
// Try to load custom sleep image // Try to load custom sleep image
int imageWidth = 0; int imageWidth = 0;
int imageHeight = 0; int imageHeight = 0;
uint8_t* imageData = nullptr; uint8_t* imageData = nullptr;
// Try different possible paths // Try different possible paths
const char* bmpPaths[] = {"sleep.bmp", "/sleep.bmp"}; const char* bmpPaths[] = {"sleep.bmp", "/sleep.bmp", "/SD/sleep.bmp"};
// Try loading from different paths
for (const char* path : bmpPaths) { for (const char* path : bmpPaths) {
imageData = loadBMP(path, imageWidth, imageHeight); imageData = loadBMP(path, imageWidth, imageHeight);
if (imageData) { if (imageData) {
Serial.printf("[SleepScreen] Successfully loaded: %s\n", path); Serial.printf("[SleepScreen] Successfully loaded: %s\n", path);
break; break;
} }
} }
if (imageData) { if (imageData) {
// Image loaded successfully // Image loaded successfully
Serial.printf("[SleepScreen] Drawing image: %dx%d\n", imageWidth, imageHeight); Serial.printf("[SleepScreen] Drawing image: %dx%d\n", imageWidth, imageHeight);
Serial.printf("[SleepScreen] Screen dimensions: %dx%d\n", pageWidth, pageHeight);
// Print the first 16 bytes of image data (for debugging)
Serial.print("[SleepScreen] Image data sample: ");
for (int i = 0; i < 16 && i < (imageWidth+7)/8 * imageHeight; i++) {
Serial.printf("%02X ", imageData[i]);
}
Serial.println();
// Calculate position to center the image // Calculate position to center the image
int xPos = (pageWidth - imageWidth) / 2; int xPos = (pageWidth - imageWidth) / 2;
int yPos = (pageHeight - imageHeight) / 2; int yPos = (pageHeight - imageHeight) / 2;
if (xPos < 0) xPos = 0; if (xPos < 0) xPos = 0;
if (yPos < 0) yPos = 0; if (yPos < 0) yPos = 0;
// Draw the image - this sends the 1-bit bitmap data to the e-ink display // Draw the image - this sends the bitmap data to the e-ink display
Serial.printf("[SleepScreen] Drawing image at position: %d, %d\n", xPos, yPos); // Note: We've applied 90-degree clockwise rotation to compensate for
// the renderer's behavior and ensure the image appears correctly
// on the e-ink display.
Serial.printf("[SleepScreen] Drawing at position: %d,%d (dimensions: %dx%d)\n",
xPos, yPos, imageWidth, imageHeight);
renderer.drawImage(imageData, xPos, yPos, imageWidth, imageHeight); renderer.drawImage(imageData, xPos, yPos, imageWidth, imageHeight);
// Free the image data // Free the image data
free(imageData); free(imageData);
} else { } else {
@ -205,11 +247,11 @@ void SleepScreen::onEnter() {
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD); renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING"); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
} }
// Apply white screen if enabled in settings // Apply white screen if enabled in settings
if (!SETTINGS.whiteSleepScreen) { if (!SETTINGS.whiteSleepScreen) {
renderer.invertScreen(); renderer.invertScreen();
} }
renderer.displayBuffer(EInkDisplay::HALF_REFRESH); renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
} }