mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-18 15:17:42 +03:00
Merge 13fc5f7bbb into 57fdb1c0fb
This commit is contained in:
commit
7531af0ba0
@ -1,19 +1,258 @@
|
||||
#include "SleepActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <SD.h>
|
||||
// #include <Arduino.h>
|
||||
// #include <cstdint>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "config.h"
|
||||
#include "images/CrossLarge.h"
|
||||
|
||||
void convertBmpToBWdisplayData(uint8_t* bmpData,
|
||||
int width, int height, uint8_t* displayImage){
|
||||
// Convert BMP data to 1bpp format for e-ink display
|
||||
// Implementation details depend on the specific BMP format and display requirements
|
||||
|
||||
// Static lookup table for bit masks (better performance)
|
||||
static const uint8_t bitMasks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
// For 1bpp images where width is divisible by 8, we can use a highly optimized approach
|
||||
const int bytesPerSrcRow = width / 8;
|
||||
const int bytesPerRow = (height + 7) / 8; // use heigth due to 90deg rotation
|
||||
|
||||
int bmpRowSize = width / 8; // 1 bit per pixel, assuming width is divisible by 8
|
||||
|
||||
// Process each source row
|
||||
for (int y = 0; y < height; y++) {
|
||||
// Calculate source row (BMPs are normally stored bottom-to-top)
|
||||
int srcRow = height-1-y;
|
||||
|
||||
// In 90-degree rotation, source Y becomes destination X
|
||||
int destX = y;
|
||||
int destByteX = destX / 8;
|
||||
int destBitInByte = destX & 0x07; // Fast mod 8
|
||||
uint8_t destBitMask = bitMasks[destBitInByte];
|
||||
|
||||
// Get pointer to this row's data
|
||||
uint8_t* srcRowData = bmpData + (srcRow * bmpRowSize);
|
||||
|
||||
// Process all bytes in this row
|
||||
for (int xByte = 0; xByte < bytesPerSrcRow; xByte++) {
|
||||
uint8_t srcByte = srcRowData[xByte];
|
||||
|
||||
// Skip processing if byte is all white
|
||||
if (srcByte == 0xFF) continue;
|
||||
|
||||
// For bytes that are either all black or have a simple pattern, optimize
|
||||
if (srcByte == 0x00) {
|
||||
// All 8 pixels are black - use fast path
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
int srcX = (xByte * 8) + bit;
|
||||
int destY = width - 1 - srcX;
|
||||
int destByteIdx = (destY * bytesPerRow) + destByteX;
|
||||
displayImage[destByteIdx] &= ~destBitMask;
|
||||
}
|
||||
} else {
|
||||
// Process individual bits for mixed bytes
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
// Only process if this bit is black (0)
|
||||
if ((srcByte & bitMasks[bit]) == 0) {
|
||||
int srcX = (xByte * 8) + bit;
|
||||
int destY = width - 1 - srcX;
|
||||
int destByteIdx = (destY * bytesPerRow) + destByteX;
|
||||
displayImage[destByteIdx] &= ~destBitMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// BMP file header structure
|
||||
#pragma pack(push, 1)
|
||||
struct BMPHeader {
|
||||
uint16_t signature; // 'BM'
|
||||
uint32_t fileSize; // Size of the BMP file in bytes
|
||||
uint32_t reserved; // Reserved
|
||||
uint32_t dataOffset; // Offset to bitmap data
|
||||
uint32_t headerSize; // Size of the header
|
||||
int32_t width; // Width of the image
|
||||
int32_t height; // Height of the image
|
||||
uint16_t planes; // Number of color planes
|
||||
uint16_t bitsPerPixel; // Bits per pixel
|
||||
uint32_t compression; // Compression method
|
||||
uint32_t imageSize; // Size of the image data
|
||||
int32_t xPixelsPerMeter; // Horizontal resolution
|
||||
int32_t yPixelsPerMeter; // Vertical resolution
|
||||
uint32_t totalColors; // Number of colors in palette
|
||||
uint32_t importantColors;// Number of important colors
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Load BMP file from SD card and rotate 90 degrees clockwise
|
||||
// This rotation matches what we need for the e-ink display
|
||||
// Returns a buffer with data formatted for the e-ink display (1bpp, MSB first)
|
||||
uint8_t* loadBMP(const char* filename, int& width, int& height) {
|
||||
const unsigned long startTime = millis();
|
||||
Serial.printf("[%lu] [SleepScreen] Trying to load BMP: %s\n", millis(), filename);
|
||||
|
||||
if (!SD.exists(filename)) {
|
||||
Serial.printf("[%lu] [SleepScreen] File not found: %s\n", millis(), filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
File bmpFile = SD.open(filename);
|
||||
if (!bmpFile) {
|
||||
Serial.printf("[%lu] [SleepScreen] Failed to open file: %s\n", millis(), filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read BMP header
|
||||
BMPHeader header;
|
||||
bmpFile.read((uint8_t*)&header, sizeof(BMPHeader));
|
||||
|
||||
// Check if this is a valid BMP file
|
||||
if (header.signature != 0x4D42) { // "BM" in little-endian
|
||||
Serial.printf("[%lu] [SleepScreen] Invalid BMP signature\n", millis());
|
||||
bmpFile.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check for supported bit depths
|
||||
if (header.bitsPerPixel != 1){ //} && header.bitsPerPixel != 24) {
|
||||
Serial.printf("[%lu] [SleepScreen] Unsupported bit depth: %d\n", millis(), header.bitsPerPixel);
|
||||
bmpFile.close();
|
||||
return nullptr;
|
||||
}
|
||||
if (header.height < 0){ //} && header.bitsPerPixel != 24) {
|
||||
Serial.printf("[%lu] [SleepScreen] Unsupported negative height\n", millis());
|
||||
bmpFile.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get image dimensions
|
||||
width = header.width;
|
||||
height = header.height;
|
||||
|
||||
Serial.printf("[%lu] [SleepScreen] BMP dimensions: %dx%d, %d bits/pixel\n", millis(), width, height, header.bitsPerPixel);
|
||||
|
||||
// Calculate destination dimensions based on rotation type
|
||||
int destWidth, destHeight;
|
||||
|
||||
// 90 degree rotation to match display orientation
|
||||
destWidth = height;
|
||||
destHeight = width;
|
||||
|
||||
// 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;
|
||||
|
||||
// Allocate memory for the display image
|
||||
uint8_t* displayImage = (uint8_t*)malloc(bufferSize);
|
||||
if (!displayImage) {
|
||||
Serial.printf("[%lu] [SleepScreen] Failed to allocate memory for display image\n", millis());
|
||||
bmpFile.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize to all white (0xFF = all bits set to 1)
|
||||
memset(displayImage, 0xFF, bufferSize);
|
||||
|
||||
|
||||
// With 4-byte divisibility assertion, no padding calculations are needed
|
||||
|
||||
// Add assertion that dimensions are divisible by 4
|
||||
if (width % 4 != 0 || height % 4 != 0) {
|
||||
Serial.printf("[%lu] [SleepScreen] Image dimensions not divisible by 4: %dx%d\n", millis(), width, height);
|
||||
// Continue anyway - we're assuming divisibility
|
||||
}
|
||||
|
||||
// Verify BMP width is divisible by 8 for 1bpp images (for byte alignment)
|
||||
if (header.bitsPerPixel == 1 && width % 8 != 0) {
|
||||
Serial.printf("[%lu] [SleepScreen] Warning: 1bpp BMP width not divisible by 8: %d\n", millis(), width);
|
||||
}
|
||||
|
||||
// Optimized direct handling for 1bpp BMPs
|
||||
if (header.bitsPerPixel == 1) {
|
||||
// Calculate total file size needed for reading the whole bitmap at once
|
||||
const int totalBitmapSize = width * height / 8;
|
||||
|
||||
// Allocate a buffer for the entire bitmap
|
||||
uint8_t* bmpData = (uint8_t*)malloc(totalBitmapSize);
|
||||
if (!bmpData) {
|
||||
Serial.printf("[%lu] [SleepScreen] Failed to allocate bitmap buffer (%d bytes)\n",
|
||||
millis(), totalBitmapSize);
|
||||
free(displayImage);
|
||||
bmpFile.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read the entire bitmap data at once (efficient bulk loading)
|
||||
bmpFile.seek(header.dataOffset);
|
||||
bmpFile.read(bmpData, totalBitmapSize);
|
||||
|
||||
convertBmpToBWdisplayData(bmpData, width, height,displayImage);
|
||||
// Clean up
|
||||
free(bmpData);
|
||||
}
|
||||
bmpFile.close();
|
||||
|
||||
const unsigned long elapsedTime = millis() - startTime;
|
||||
Serial.printf("[%lu] [SleepScreen] Successfully loaded BMP: %dx%d in %lu ms\n",
|
||||
millis(), destWidth, destHeight, elapsedTime);
|
||||
return displayImage;
|
||||
}
|
||||
|
||||
void SleepActivity::onEnter() {
|
||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
||||
|
||||
// Try to load custom sleep image
|
||||
int imageWidth = 0;
|
||||
int imageHeight = 0;
|
||||
uint8_t* imageData = nullptr;
|
||||
|
||||
// Try different possible paths
|
||||
const char* bmpPaths[] = {"/sleep24bpp.bmp", "/sleep.bmp"};
|
||||
|
||||
// Try loading from different paths
|
||||
for (const char* path : bmpPaths) {
|
||||
imageData = loadBMP(path, imageWidth, imageHeight);
|
||||
if (imageData) {
|
||||
Serial.printf("[%lu] [SleepScreen] Successfully loaded: %s\n", millis(), path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageData) {
|
||||
// Image loaded successfully
|
||||
Serial.printf("[%lu] [SleepScreen] Drawing image: %dx%d\n", millis(), imageWidth, imageHeight);
|
||||
|
||||
// Calculate position to center the image
|
||||
int xPos = (pageWidth - imageWidth) / 2;
|
||||
int yPos = (pageHeight - imageHeight) / 2;
|
||||
if (xPos < 0) xPos = 0;
|
||||
if (yPos < 0) yPos = 0;
|
||||
|
||||
// Draw the image - this sends the bitmap data to the e-ink display
|
||||
// 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("[%lu] [SleepScreen] Drawing at position: %d,%d (dimensions: %dx%d)\n", millis(),
|
||||
xPos, yPos, imageWidth, imageHeight);
|
||||
renderer.drawImage(imageData, xPos, yPos, imageWidth, imageHeight);
|
||||
|
||||
// Free the image data
|
||||
free(imageData);
|
||||
} else {
|
||||
// Fall back to default image
|
||||
Serial.printf("[%lu] [SleepScreen] Failed to load sleep.bmp - using default image\n", millis());
|
||||
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
||||
}
|
||||
|
||||
// Apply white screen if enabled in settings
|
||||
if (!SETTINGS.whiteSleepScreen) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user