2026-01-12 12:36:19 +01:00
|
|
|
#include "BitmapHelpers.h"
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
|
|
// Brightness/Contrast adjustments:
|
|
|
|
|
constexpr bool USE_BRIGHTNESS = false; // true: apply brightness/gamma adjustments
|
|
|
|
|
constexpr int BRIGHTNESS_BOOST = 10; // Brightness offset (0-50)
|
|
|
|
|
constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones)
|
|
|
|
|
constexpr float CONTRAST_FACTOR = 1.15f; // Contrast multiplier (1.0 = no change, >1 = more contrast)
|
|
|
|
|
constexpr bool USE_NOISE_DITHERING = false; // Hash-based noise dithering
|
|
|
|
|
|
|
|
|
|
// Integer approximation of gamma correction (brightens midtones)
|
|
|
|
|
// Uses a simple curve: out = 255 * sqrt(in/255) ≈ sqrt(in * 255)
|
|
|
|
|
static inline int applyGamma(int gray) {
|
|
|
|
|
if (!GAMMA_CORRECTION) return gray;
|
|
|
|
|
// Fast integer square root approximation for gamma ~0.5 (brightening)
|
|
|
|
|
// This brightens dark/mid tones while preserving highlights
|
|
|
|
|
const int product = gray * 255;
|
|
|
|
|
// Newton-Raphson integer sqrt (2 iterations for good accuracy)
|
|
|
|
|
int x = gray;
|
|
|
|
|
if (x > 0) {
|
|
|
|
|
x = (x + product / x) >> 1;
|
|
|
|
|
x = (x + product / x) >> 1;
|
|
|
|
|
}
|
|
|
|
|
return x > 255 ? 255 : x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply contrast adjustment around midpoint (128)
|
|
|
|
|
// factor > 1.0 increases contrast, < 1.0 decreases
|
|
|
|
|
static inline int applyContrast(int gray) {
|
|
|
|
|
// Integer-based contrast: (gray - 128) * factor + 128
|
|
|
|
|
// Using fixed-point: factor 1.15 ≈ 115/100
|
|
|
|
|
constexpr int factorNum = static_cast<int>(CONTRAST_FACTOR * 100);
|
|
|
|
|
int adjusted = ((gray - 128) * factorNum) / 100 + 128;
|
|
|
|
|
if (adjusted < 0) adjusted = 0;
|
|
|
|
|
if (adjusted > 255) adjusted = 255;
|
|
|
|
|
return adjusted;
|
|
|
|
|
}
|
|
|
|
|
// Combined brightness/contrast/gamma adjustment
|
|
|
|
|
int adjustPixel(int gray) {
|
|
|
|
|
if (!USE_BRIGHTNESS) return gray;
|
|
|
|
|
|
|
|
|
|
// Order: contrast first, then brightness, then gamma
|
|
|
|
|
gray = applyContrast(gray);
|
|
|
|
|
gray += BRIGHTNESS_BOOST;
|
|
|
|
|
if (gray > 255) gray = 255;
|
|
|
|
|
if (gray < 0) gray = 0;
|
|
|
|
|
gray = applyGamma(gray);
|
|
|
|
|
|
|
|
|
|
return gray;
|
|
|
|
|
}
|
|
|
|
|
// Simple quantization without dithering - divide into 4 levels
|
|
|
|
|
// The thresholds are fine-tuned to the X4 display
|
|
|
|
|
uint8_t quantizeSimple(int gray) {
|
|
|
|
|
if (gray < 45) {
|
|
|
|
|
return 0;
|
|
|
|
|
} else if (gray < 70) {
|
|
|
|
|
return 1;
|
|
|
|
|
} else if (gray < 140) {
|
|
|
|
|
return 2;
|
|
|
|
|
} else {
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hash-based noise dithering - survives downsampling without moiré artifacts
|
|
|
|
|
// Uses integer hash to generate pseudo-random threshold per pixel
|
|
|
|
|
static inline uint8_t quantizeNoise(int gray, int x, int y) {
|
|
|
|
|
uint32_t hash = static_cast<uint32_t>(x) * 374761393u + static_cast<uint32_t>(y) * 668265263u;
|
|
|
|
|
hash = (hash ^ (hash >> 13)) * 1274126177u;
|
|
|
|
|
const int threshold = static_cast<int>(hash >> 24);
|
|
|
|
|
|
|
|
|
|
const int scaled = gray * 3;
|
|
|
|
|
if (scaled < 255) {
|
|
|
|
|
return (scaled + threshold >= 255) ? 1 : 0;
|
|
|
|
|
} else if (scaled < 510) {
|
|
|
|
|
return ((scaled - 255) + threshold >= 255) ? 2 : 1;
|
|
|
|
|
} else {
|
|
|
|
|
return ((scaled - 510) + threshold >= 255) ? 3 : 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Main quantization function - selects between methods based on config
|
|
|
|
|
uint8_t quantize(int gray, int x, int y) {
|
|
|
|
|
if (USE_NOISE_DITHERING) {
|
|
|
|
|
return quantizeNoise(gray, x, y);
|
|
|
|
|
} else {
|
|
|
|
|
return quantizeSimple(gray);
|
|
|
|
|
}
|
|
|
|
|
}
|
Add cover image display in *Continue Reading* card with framebuffer caching (#200)
## Summary
* **What is the goal of this PR?** (e.g., Fixes a bug in the user
authentication module,
Display the book cover image in the **"Continue Reading"** card on the
home screen, with fast navigation using framebuffer caching.
* **What changes are included?**
- Display book cover image in the "Continue Reading" card on home screen
- Load cover from cached BMP (same as sleep screen cover)
- Add framebuffer store/restore functions (`copyStoredBwBuffer`,
`freeStoredBwBuffer`) for fast navigation after initial render
- Fix `drawBitmap` scaling bug: apply scale to offset only, not to base
coordinates
- Add white text boxes behind title/author/continue reading label for
readability on cover
- Support both EPUB and XTC file cover images
- Increase HomeActivity task stack size from 2048 to 4096 for cover
image rendering
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks, specific areas to
focus on).
- Performance: First render loads cover from SD card (~800ms),
subsequent navigation uses cached framebuffer (~instant)
- Memory: Framebuffer cache uses ~48KB (6 chunks × 8KB) while on home
screen, freed on exit
- Fallback: If cover image is not available, falls back to standard
text-only display
- The `drawBitmap` fix corrects a bug where screenY = (y + offset) scale
was incorrectly scaling the base coordinates. Now correctly uses screenY
= y + (offset scale)
2026-01-14 19:24:02 +09:00
|
|
|
|
|
|
|
|
// 1-bit noise dithering for fast home screen rendering
|
|
|
|
|
// Uses hash-based noise for consistent dithering that works well at small sizes
|
|
|
|
|
uint8_t quantize1bit(int gray, int x, int y) {
|
|
|
|
|
gray = adjustPixel(gray);
|
|
|
|
|
|
|
|
|
|
// Generate noise threshold using integer hash (no regular pattern to alias)
|
|
|
|
|
uint32_t hash = static_cast<uint32_t>(x) * 374761393u + static_cast<uint32_t>(y) * 668265263u;
|
|
|
|
|
hash = (hash ^ (hash >> 13)) * 1274126177u;
|
|
|
|
|
const int threshold = static_cast<int>(hash >> 24); // 0-255
|
|
|
|
|
|
|
|
|
|
// Simple threshold with noise: gray >= (128 + noise offset) -> white
|
|
|
|
|
// The noise adds variation around the 128 midpoint
|
|
|
|
|
const int adjustedThreshold = 128 + ((threshold - 128) / 2); // Range: 64-192
|
|
|
|
|
return (gray >= adjustedThreshold) ? 1 : 0;
|
|
|
|
|
}
|