Improve EPUB cover image quality with pre-scaling and Atkinson dithering (#116)

## Summary

* **What is the goal of this PR?**

Replace simple threshold-based grayscale quantization with ordered
dithering using a 4x4 Bayer matrix. This eliminates color banding
artifacts and produces smoother gradients on e-ink display.

* **What changes are included?**

- Add 4x4 Bayer dithering matrix for 16-level threshold patterns
- Modify `grayscaleTo2Bit()` function to accept pixel coordinates and
apply position-based dithering
- Replace simple `grayscale >> 6` threshold with ordered dithering
algorithm that produces smoother gradients

## Additional Context

* Bayer matrix approach: The 4x4 Bayer matrix creates a repeating
pattern that distributes quantization error spatially, effectively
simulating 16 levels of gray using only 4 actual color levels (black,
dark gray, light gray, white).

* Cache invalidation: Existing cached `cover.bmp` files will need to be
deleted to see the improved rendering, as the converter only runs when
the cache is missing.
This commit is contained in:
Eunchurn Park
2025-12-28 08:38:14 +09:00
committed by GitHub
parent e3d0201365
commit f96b6ab29c
5 changed files with 727 additions and 68 deletions

View File

@@ -15,6 +15,7 @@ enum class BmpReaderError : uint8_t {
UnsupportedCompression,
BadDimensions,
ImageTooLarge,
PaletteTooLarge,
SeekPixelDataFailed,
@@ -28,8 +29,9 @@ class Bitmap {
static const char* errorToString(BmpReaderError err);
explicit Bitmap(File& file) : file(file) {}
~Bitmap();
BmpReaderError parseHeaders();
BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer) const;
BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const;
BmpReaderError rewindToData() const;
int getWidth() const { return width; }
int getHeight() const { return height; }
@@ -49,4 +51,9 @@ class Bitmap {
uint16_t bpp = 0;
int rowBytes = 0;
uint8_t paletteLum[256] = {};
// Floyd-Steinberg dithering state (mutable for const methods)
mutable int16_t* errorCurRow = nullptr;
mutable int16_t* errorNextRow = nullptr;
mutable int lastRowY = -1; // Track row progression for error propagation
};