improved sleep screen performance and cover art
This commit is contained in:
@@ -82,6 +82,10 @@ const char* Bitmap::errorToString(BmpReaderError err) {
|
||||
|
||||
BmpReaderError Bitmap::parseHeaders() {
|
||||
if (!file) return BmpReaderError::FileInvalid;
|
||||
|
||||
// Store file size for cache validation
|
||||
fileSize = file.size();
|
||||
|
||||
if (!file.seek(0)) return BmpReaderError::SeekStartFailed;
|
||||
|
||||
// --- BMP FILE HEADER ---
|
||||
@@ -263,17 +267,24 @@ BmpReaderError Bitmap::rewindToData() const {
|
||||
return BmpReaderError::Ok;
|
||||
}
|
||||
|
||||
bool Bitmap::detectPerimeterIsBlack() const {
|
||||
// Detect if the 1-pixel perimeter of the image is mostly black or white.
|
||||
// Returns true if mostly black (luminance < 128), false if mostly white.
|
||||
EdgeLuminance Bitmap::detectEdgeLuminance(int depth) const {
|
||||
// Detect average luminance for each edge of the image.
|
||||
// Samples 'depth' pixels from each edge for more stable averages.
|
||||
// Returns per-edge luminance values (0-255).
|
||||
|
||||
if (width <= 0 || height <= 0) return false;
|
||||
EdgeLuminance result = {128, 128, 128, 128}; // Default to neutral gray
|
||||
|
||||
if (width <= 0 || height <= 0) return result;
|
||||
if (depth < 1) depth = 1;
|
||||
if (depth > width / 2) depth = width / 2;
|
||||
if (depth > height / 2) depth = height / 2;
|
||||
|
||||
auto* rowBuffer = static_cast<uint8_t*>(malloc(rowBytes));
|
||||
if (!rowBuffer) return false;
|
||||
if (!rowBuffer) return result;
|
||||
|
||||
int blackCount = 0;
|
||||
int whiteCount = 0;
|
||||
// Accumulators for each edge
|
||||
uint32_t topSum = 0, bottomSum = 0, leftSum = 0, rightSum = 0;
|
||||
int topCount = 0, bottomCount = 0, leftCount = 0, rightCount = 0;
|
||||
|
||||
// Helper lambda to get luminance from a pixel at position x in rowBuffer
|
||||
auto getLuminance = [&](int x) -> uint8_t {
|
||||
@@ -299,16 +310,6 @@ bool Bitmap::detectPerimeterIsBlack() const {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to classify and count a pixel
|
||||
auto countPixel = [&](int x) {
|
||||
const uint8_t lum = getLuminance(x);
|
||||
if (lum < 128) {
|
||||
blackCount++;
|
||||
} else {
|
||||
whiteCount++;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to seek to a specific image row (accounting for top-down vs bottom-up)
|
||||
auto seekToRow = [&](int imageRow) -> bool {
|
||||
// In bottom-up BMP (topDown=false), row 0 in file is the bottom row of image
|
||||
@@ -317,35 +318,56 @@ bool Bitmap::detectPerimeterIsBlack() const {
|
||||
return file.seek(bfOffBits + static_cast<uint32_t>(fileRow) * rowBytes);
|
||||
};
|
||||
|
||||
// Sample top row (image row 0) - all pixels
|
||||
if (seekToRow(0) && file.read(rowBuffer, rowBytes) == rowBytes) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
countPixel(x);
|
||||
}
|
||||
}
|
||||
|
||||
// Sample bottom row (image row height-1) - all pixels
|
||||
if (height > 1) {
|
||||
if (seekToRow(height - 1) && file.read(rowBuffer, rowBytes) == rowBytes) {
|
||||
// Sample top rows (image rows 0 to depth-1) - all pixels
|
||||
for (int row = 0; row < depth && row < height; row++) {
|
||||
if (seekToRow(row) && file.read(rowBuffer, rowBytes) == rowBytes) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
countPixel(x);
|
||||
topSum += getLuminance(x);
|
||||
topCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sample left and right edges from intermediate rows
|
||||
for (int y = 1; y < height - 1; y++) {
|
||||
// Sample bottom rows (image rows height-depth to height-1) - all pixels
|
||||
for (int row = height - depth; row < height; row++) {
|
||||
if (row >= depth && row >= 0) { // Avoid overlap with top rows
|
||||
if (seekToRow(row) && file.read(rowBuffer, rowBytes) == rowBytes) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
bottomSum += getLuminance(x);
|
||||
bottomCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sample left and right edges from all rows
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (seekToRow(y) && file.read(rowBuffer, rowBytes) == rowBytes) {
|
||||
countPixel(0); // Left edge
|
||||
countPixel(width - 1); // Right edge
|
||||
// Left edge (first 'depth' pixels)
|
||||
for (int x = 0; x < depth && x < width; x++) {
|
||||
leftSum += getLuminance(x);
|
||||
leftCount++;
|
||||
}
|
||||
// Right edge (last 'depth' pixels)
|
||||
for (int x = width - depth; x < width; x++) {
|
||||
if (x >= depth) { // Avoid overlap with left edge
|
||||
rightSum += getLuminance(x);
|
||||
rightCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(rowBuffer);
|
||||
|
||||
// Calculate averages
|
||||
if (topCount > 0) result.top = static_cast<uint8_t>(topSum / topCount);
|
||||
if (bottomCount > 0) result.bottom = static_cast<uint8_t>(bottomSum / bottomCount);
|
||||
if (leftCount > 0) result.left = static_cast<uint8_t>(leftSum / leftCount);
|
||||
if (rightCount > 0) result.right = static_cast<uint8_t>(rightSum / rightCount);
|
||||
|
||||
// Rewind file position for subsequent drawing
|
||||
rewindToData();
|
||||
|
||||
// Return true if perimeter is mostly black
|
||||
return blackCount > whiteCount;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
|
||||
#include "BitmapHelpers.h"
|
||||
|
||||
// Per-edge average luminance values (0-255)
|
||||
struct EdgeLuminance {
|
||||
uint8_t top;
|
||||
uint8_t bottom;
|
||||
uint8_t left;
|
||||
uint8_t right;
|
||||
};
|
||||
|
||||
enum class BmpReaderError : uint8_t {
|
||||
Ok = 0,
|
||||
FileInvalid,
|
||||
@@ -37,7 +45,7 @@ class Bitmap {
|
||||
BmpReaderError parseHeaders();
|
||||
BmpReaderError readNextRow(uint8_t* data, uint8_t* rowBuffer) const;
|
||||
BmpReaderError rewindToData() const;
|
||||
bool detectPerimeterIsBlack() const;
|
||||
EdgeLuminance detectEdgeLuminance(int depth = 2) const;
|
||||
int getWidth() const { return width; }
|
||||
int getHeight() const { return height; }
|
||||
bool isTopDown() const { return topDown; }
|
||||
@@ -45,6 +53,7 @@ class Bitmap {
|
||||
int getRowBytes() const { return rowBytes; }
|
||||
bool is1Bit() const { return bpp == 1; }
|
||||
uint16_t getBpp() const { return bpp; }
|
||||
uint32_t getFileSize() const { return fileSize; }
|
||||
|
||||
private:
|
||||
static uint16_t readLE16(FsFile& f);
|
||||
@@ -58,6 +67,7 @@ class Bitmap {
|
||||
uint32_t bfOffBits = 0;
|
||||
uint16_t bpp = 0;
|
||||
int rowBytes = 0;
|
||||
uint32_t fileSize = 0;
|
||||
uint8_t paletteLum[256] = {};
|
||||
|
||||
// Floyd-Steinberg dithering state (mutable for const methods)
|
||||
|
||||
@@ -144,6 +144,44 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::fillRectGray(const int x, const int y, const int width, const int height,
|
||||
const uint8_t grayLevel) const {
|
||||
// Fill rectangle with 4-level grayscale value.
|
||||
// The grayscale encoding for 4 levels uses 3 passes:
|
||||
// - Level 0 (black): BW=black, MSB=skip, LSB=skip
|
||||
// - Level 1 (dark gray): BW=black, MSB=white, LSB=white
|
||||
// - Level 2 (light gray): BW=black, MSB=white, LSB=skip
|
||||
// - Level 3 (white): BW=skip, MSB=skip, LSB=skip
|
||||
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
switch (renderMode) {
|
||||
case BW:
|
||||
// In BW mode, fill with black for levels 0-2, skip for level 3
|
||||
if (grayLevel < 3) {
|
||||
fillRect(x, y, width, height, true); // true = black
|
||||
}
|
||||
// Level 3 = white, which is the default clear color, so skip
|
||||
break;
|
||||
|
||||
case GRAYSCALE_MSB:
|
||||
// In MSB mode (buffer cleared to black), fill with white for levels 1-2
|
||||
if (grayLevel == 1 || grayLevel == 2) {
|
||||
fillRect(x, y, width, height, false); // false = white
|
||||
}
|
||||
// Levels 0 and 3 stay black (which is correct for 0, will combine correctly for 3)
|
||||
break;
|
||||
|
||||
case GRAYSCALE_LSB:
|
||||
// In LSB mode (buffer cleared to black), fill with white for level 1 only
|
||||
if (grayLevel == 1) {
|
||||
fillRect(x, y, width, height, false); // false = white
|
||||
}
|
||||
// Levels 0, 2, 3 stay black
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||
// TODO: Rotate bits
|
||||
int rotatedX = 0;
|
||||
|
||||
@@ -65,6 +65,9 @@ class GfxRenderer {
|
||||
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||
// Fill rectangle with 4-level grayscale (0=black, 1=dark gray, 2=light gray, 3=white)
|
||||
// Handles current render mode (BW, GRAYSCALE_MSB, GRAYSCALE_LSB)
|
||||
void fillRectGray(int x, int y, int width, int height, uint8_t grayLevel) const;
|
||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
|
||||
float cropY = 0, bool invert = false) const;
|
||||
|
||||
Reference in New Issue
Block a user