mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4970ef1f6b | ||
|
|
f449ce9e7d | ||
|
|
48e39eb0ee |
No files matched your search
+66
-49
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install clang-format-21
|
||||
run: |
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
@@ -51,15 +51,7 @@ jobs:
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: |
|
||||
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
# platform-espressif32's builder/main.py imports these unconditionally; its own
|
||||
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
|
||||
# but isn't picking them up reliably in this runner image, so pin them explicitly.
|
||||
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
|
||||
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
|
||||
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
|
||||
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
|
||||
- name: Cache PlatformIO packages
|
||||
uses: actions/cache@v4
|
||||
@@ -71,24 +63,9 @@ jobs:
|
||||
- name: Run cppcheck
|
||||
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.env }}
|
||||
build-default:
|
||||
name: Build default
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Artifact names match the release asset names OtaUpdater requests:
|
||||
# plain firmware.bin for the C3 X4/X3 binary, firmware-<board>.bin for
|
||||
# the S3 boards.
|
||||
include:
|
||||
- env: default
|
||||
asset: firmware.bin
|
||||
- env: sticky
|
||||
asset: firmware-sticky.bin
|
||||
- env: x4pro
|
||||
asset: firmware-x4pro.bin
|
||||
- env: papermono
|
||||
asset: firmware-papermono.bin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -96,7 +73,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
@@ -105,15 +82,7 @@ jobs:
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: |
|
||||
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
# platform-espressif32's builder/main.py imports these unconditionally; its own
|
||||
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
|
||||
# but isn't picking them up reliably in this runner image, so pin them explicitly.
|
||||
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
|
||||
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
|
||||
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
|
||||
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
|
||||
- name: Cache PlatformIO packages
|
||||
uses: actions/cache@v4
|
||||
@@ -122,10 +91,64 @@ jobs:
|
||||
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: pio-${{ runner.os }}-
|
||||
|
||||
- name: Build ${{ matrix.env }} firmware
|
||||
- name: Build default firmware
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pio run -e ${{ matrix.env }} | tee pio.log
|
||||
pio run -e default | tee pio.log
|
||||
|
||||
- name: Extract firmware stats
|
||||
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ram_line="$(grep -E "RAM:\\s" -m1 pio.log || true)"
|
||||
flash_line="$(grep -E "Flash:\\s" -m1 pio.log || true)"
|
||||
echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT"
|
||||
echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Firmware build stats"
|
||||
if [ -n "$ram_line" ]; then echo "- ${ram_line}"; else echo "- RAM: not found"; fi
|
||||
if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload firmware.bin artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: firmware.bin
|
||||
path: .pio/build/default/firmware.bin
|
||||
if-no-files-found: error
|
||||
|
||||
build-sticky:
|
||||
name: Build sticky
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
|
||||
- name: Cache PlatformIO packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: pio-${{ runner.os }}-
|
||||
|
||||
- name: Build sticky firmware
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pio run -e sticky | tee pio.log
|
||||
|
||||
- name: Extract firmware stats
|
||||
run: |
|
||||
@@ -135,18 +158,11 @@ jobs:
|
||||
echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT"
|
||||
echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## ${{ matrix.env }} firmware build stats"
|
||||
echo "## Sticky firmware build stats"
|
||||
if [ -n "$ram_line" ]; then echo "- ${ram_line}"; else echo "- RAM: not found"; fi
|
||||
if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload firmware artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.asset }}
|
||||
path: .pio/build/${{ matrix.env }}/firmware.bin
|
||||
if-no-files-found: error
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -179,7 +195,8 @@ jobs:
|
||||
test-status:
|
||||
name: Test Status
|
||||
needs:
|
||||
- build
|
||||
- build-default
|
||||
- build-sticky
|
||||
- clang-format
|
||||
- cppcheck
|
||||
- unit-tests
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
name: "PR Formatting"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
@@ -9,7 +8,7 @@ on:
|
||||
- edited
|
||||
|
||||
permissions:
|
||||
statuses: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
title-check:
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install font tools
|
||||
run: pip install freetype-py fonttools pyyaml
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
name: Compile Release
|
||||
|
||||
# Builds every board's release firmware on tag push, with the .bin named
|
||||
# exactly as OtaUpdater requests it from the GitHub release: plain
|
||||
# firmware.bin for the C3 X4/X3 binary, firmware-<board>.bin for every other
|
||||
# board. Attach the artifacts' .bin files to the release as-is.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- pio_env: gh_release
|
||||
asset: firmware.bin
|
||||
- pio_env: sticky-gh_release
|
||||
asset: firmware-sticky.bin
|
||||
- pio_env: x4pro-gh_release
|
||||
asset: firmware-x4pro.bin
|
||||
- pio_env: papermono-gh_release
|
||||
asset: firmware-papermono.bin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: |
|
||||
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
# platform-espressif32's builder/main.py imports these unconditionally; its own
|
||||
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
|
||||
# but isn't picking them up reliably in this runner image, so pin them explicitly.
|
||||
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
|
||||
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
|
||||
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
|
||||
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
|
||||
|
||||
- name: Cache PlatformIO packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: pio-${{ runner.os }}-
|
||||
|
||||
- name: Build CrossPoint
|
||||
run: pio run -e ${{ matrix.pio_env }} -j1
|
||||
|
||||
- name: Name release asset
|
||||
run: |
|
||||
if [ "${{ matrix.asset }}" != "firmware.bin" ]; then
|
||||
cp ".pio/build/${{ matrix.pio_env }}/firmware.bin" \
|
||||
".pio/build/${{ matrix.pio_env }}/${{ matrix.asset }}"
|
||||
fi
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.asset }}
|
||||
path: |
|
||||
.pio/build/${{ matrix.pio_env }}/bootloader.bin
|
||||
.pio/build/${{ matrix.pio_env }}/${{ matrix.asset }}
|
||||
.pio/build/${{ matrix.pio_env }}/firmware.elf
|
||||
.pio/build/${{ matrix.pio_env }}/firmware.map
|
||||
.pio/build/${{ matrix.pio_env }}/partitions.bin
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
@@ -23,15 +23,7 @@ jobs:
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: |
|
||||
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
# platform-espressif32's builder/main.py imports these unconditionally; its own
|
||||
# penv bootstrap (builder/penv_setup.py) is supposed to install them automatically
|
||||
# but isn't picking them up reliably in this runner image, so pin them explicitly.
|
||||
uv pip install --system -U "littlefs-python>=0.16.0" "fatfs-ng>=0.1.14" "pyyaml>=6.0.2" \
|
||||
"rich-click>=1.8.6" "zopfli>=0.2.2" "intelhex>=2.3.0" "rich>=14.0.0" "urllib3<2" \
|
||||
"cryptography>=45.0.3" "certifi>=2025.8.3" "ecdsa>=0.19.1" "bitstring>=4.3.1" \
|
||||
"reedsolo>=1.5.3,<1.8" "esp-idf-size>=2.0.0" "esp-coredump>=1.14.0"
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
|
||||
- name: Cache PlatformIO packages
|
||||
uses: actions/cache@v4
|
||||
|
||||
+6
-6
@@ -501,7 +501,7 @@ The **Sleep Screen** setting controls what is displayed when the device goes to
|
||||
| **Custom** | A custom image from the SD card (see below). Falls back to **Dark** if no custom image is found. |
|
||||
| **Cover** | The cover of the currently open book. Falls back to **Dark** if no book is open. |
|
||||
| **Cover + Custom** | The cover of the currently open book, shown only while actively reading. Falls back to **Custom** behavior when not reading. |
|
||||
| **Transparent** | A BMP overlay drawn over the current screen. Supports alpha transparency in 32-bit BGRA images and treats white as transparent in regular BMPs. Falls back to **Dark** if no valid overlay image is found. |
|
||||
| **Transparent** | A BMP or PNG overlay drawn over the current screen. Supports PNG and 32-bit BGRA alpha transparency, and treats white as transparent in regular BMPs. Falls back to **Dark** if no valid overlay image is found. |
|
||||
| **None** | A blank screen. |
|
||||
|
||||
#### Cover settings
|
||||
@@ -520,17 +520,17 @@ To use custom sleep images, set the sleep screen mode to **Custom** or **Cover +
|
||||
|
||||
#### Transparent overlay images
|
||||
|
||||
To use transparent sleep overlays, set the sleep screen mode to **Transparent**, then place BMP files on the SD card:
|
||||
To use transparent sleep overlays, set the sleep screen mode to **Transparent**, then place BMP or PNG files on the SD card:
|
||||
|
||||
- **Multiple Images (recommended):** Create a `.sleep-overlay` directory in the root of the SD card and place any number of valid overlay `.bmp` images inside. One will be randomly selected each time the device sleeps. A directory named `sleep-overlay` is also accepted as a fallback.
|
||||
- **Single Image:** Place a file named `sleep-overlay.bmp` in the root directory. This takes priority over the `.sleep-overlay`/`sleep-overlay` directories.
|
||||
- **Multiple Images (recommended):** Create a `.sleep-overlay` directory in the root of the SD card and place any number of valid overlay `.bmp` or `.png` images inside. One will be randomly selected each time the device sleeps. A directory named `sleep-overlay` is also accepted as a fallback.
|
||||
- **Single Image:** Place `sleep-overlay.bmp` or `sleep-overlay.png` in the root directory. A root BMP takes priority over a root PNG, and both take priority over the `.sleep-overlay`/`sleep-overlay` directories.
|
||||
|
||||
Transparent overlay files are intentionally separate from normal sleep images. Regular BMP formats supported by CrossPoint are accepted; white pixels leave the existing screen unchanged. For per-pixel alpha transparency, use a 32-bit BGRA BMP with both visible and non-opaque pixels. Opaque white pixels in these alpha images erase the content behind them.
|
||||
Transparent overlay files are intentionally separate from normal sleep images. Regular BMP formats supported by CrossPoint are accepted; white pixels leave the existing screen unchanged. For per-pixel alpha transparency, use a PNG with an alpha channel or a 32-bit BGRA BMP with both visible and non-opaque pixels. Opaque white pixels in alpha images erase the content behind them.
|
||||
|
||||
> [!TIP]
|
||||
> For best results:
|
||||
> - For non-transparent **Custom** mode, use uncompressed BMP files with 24-bit color depth.
|
||||
> - For **Transparent** mode, use a regular BMP for white-as-transparent artwork or an uncompressed 32-bit BGRA BMP for per-pixel alpha.
|
||||
> - For **Transparent** mode, use a PNG or uncompressed 32-bit BGRA BMP for per-pixel alpha, or a regular BMP for white-as-transparent artwork.
|
||||
> - X4: Use a resolution of 480x800 pixels to match the device's screen resolution.
|
||||
> - X3: Use a resolution of 528x792 pixels to match the device's screen resolution.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Touch and UI Development
|
||||
|
||||
CrossPoint runs on touch devices (Seeed Sticky, M5Paper, M5Stack PaperMono, LilyGo T5, Xteink X4 Pro) alongside the button-only Xteink X3/X4. Every screen must work with both input styles.
|
||||
CrossPoint runs on touch devices (Seeed Sticky, M5Paper, LilyGo T5) alongside the button-only Xteink X3/X4. Every screen must work with both input styles.
|
||||
|
||||
**There is one supported way to build a new screen: FreeInkUI, hosted through the firmware base classes below.** Touch hit-testing, tap highlighting, long-press, swipe scrolling, and button focus navigation all come from the shared stack; you never hand-roll coordinate math.
|
||||
|
||||
@@ -18,7 +18,7 @@ The old bridge helpers (`rowTouch`, `colTouch`, `wasTapInRect`, manual rect `con
|
||||
| A modal picker or confirm inside a legacy activity | `OptionPopup` (or push `ConfirmationActivity`) | [`OtaUpdateActivity`](../../src/activities/settings/OtaUpdateActivity.cpp) |
|
||||
| A Yes/No prompt inside a FUI screen | build `fui::optionDialog` into the screen | `WifiSelectionActivity::buildPromptDialog` |
|
||||
|
||||
All of these paths route input through the same SDK interaction table, so touch and physical buttons fire the same actions with no per-screen coordinate code.
|
||||
All four paths route input through the same SDK interaction table, so touch and physical buttons fire the same actions with no per-screen coordinate code.
|
||||
|
||||
## The hosting stack
|
||||
|
||||
|
||||
+1
-1
Submodule freeink-sdk updated: 58d34b3788...57f05e80c0.
@@ -144,14 +144,14 @@ struct DirectPixelWriter {
|
||||
// Write a single 2-bit dithered pixel value to the framebuffer.
|
||||
// Must be called after beginRow() for the current row.
|
||||
// No bounds checking — caller guarantees coordinates are valid.
|
||||
inline void writePixel(int logicalX, uint8_t pixelValue) const {
|
||||
inline void writePixel(int logicalX, uint8_t pixelValue, bool writeWhiteInBw = false) const {
|
||||
// Determine whether to draw based on render mode
|
||||
bool draw;
|
||||
bool state;
|
||||
switch (mode) {
|
||||
case GfxRenderer::BW:
|
||||
draw = (pixelValue < 3);
|
||||
state = true;
|
||||
draw = writeWhiteInBw || pixelValue < 3;
|
||||
state = pixelValue < 3;
|
||||
break;
|
||||
case GfxRenderer::GRAYSCALE_MSB:
|
||||
draw = (pixelValue == 1 || pixelValue == 2);
|
||||
|
||||
@@ -18,6 +18,9 @@ struct RenderConfig {
|
||||
bool useDithering = true;
|
||||
bool performanceMode = false;
|
||||
bool useExactDimensions = false; // If true, use maxWidth/maxHeight as exact output size (no recalculation)
|
||||
float sourceCropX = 0.0f; // Fraction cropped equally from the left and right edges
|
||||
float sourceCropY = 0.0f; // Fraction cropped equally from the top and bottom edges
|
||||
bool preserveAlpha = false; // Skip transparent pixels instead of compositing them against white
|
||||
std::string cachePath; // If non-empty, decoder will write pixel cache to this path
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <Memory.h>
|
||||
#include <PNGdec.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
@@ -21,6 +22,7 @@ namespace {
|
||||
// The draw callback receives this via pDraw->pUser (set by png.decode()).
|
||||
// The file I/O callbacks receive the HalFile* via pFile->fHandle (set by pngOpen()).
|
||||
struct PngContext {
|
||||
PNG* decoder{nullptr};
|
||||
GfxRenderer* renderer{nullptr};
|
||||
const RenderConfig* config{nullptr};
|
||||
int screenWidth{0};
|
||||
@@ -30,14 +32,18 @@ struct PngContext {
|
||||
float scale{1.f};
|
||||
int srcWidth{0};
|
||||
int srcHeight{0};
|
||||
int cropLeft{0};
|
||||
int cropTop{0};
|
||||
int visibleWidth{0};
|
||||
int visibleHeight{0};
|
||||
int dstWidth{0};
|
||||
int dstHeight{0};
|
||||
int lastDstY{-1}; // Track last rendered destination Y to avoid duplicates
|
||||
|
||||
PixelCache cache;
|
||||
bool caching{false};
|
||||
|
||||
uint8_t* grayLineBuffer{nullptr};
|
||||
uint8_t* alphaLineBuffer{nullptr};
|
||||
};
|
||||
|
||||
// File I/O callbacks use pFile->fHandle to access the HalFile*,
|
||||
@@ -131,19 +137,31 @@ uint8_t expandSampleToByte(uint8_t sample, int bitsPerSample) {
|
||||
return static_cast<uint8_t>((sample * 255U) / maxSample);
|
||||
}
|
||||
|
||||
// Convert entire source line to grayscale with alpha blending to white background.
|
||||
uint8_t alphaThreshold4x4(const int x, const int y) {
|
||||
static constexpr uint8_t BAYER_4X4[16] = {0, 128, 32, 160, 192, 64, 224, 96, 48, 176, 16, 144, 240, 112, 208, 80};
|
||||
return BAYER_4X4[((y & 0x03) << 2) | (x & 0x03)];
|
||||
}
|
||||
|
||||
// Convert an entire source line to grayscale and optionally retain alpha.
|
||||
// Low-bit-depth grayscale/indexed scanlines are packed most-significant sample first.
|
||||
// For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards.
|
||||
// Processing the whole line at once improves cache locality and reduces per-pixel overhead.
|
||||
void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int pixelType, int bitsPerSample,
|
||||
uint8_t* palette, int hasAlpha) {
|
||||
uint8_t* palette, int hasAlpha, uint32_t transparentColor, uint8_t* alphaLine) {
|
||||
const auto store = [grayLine, alphaLine](const int x, const uint8_t gray, const uint8_t alpha) {
|
||||
grayLine[x] = alphaLine ? gray : static_cast<uint8_t>((gray * alpha + 255u * (255u - alpha)) / 255u);
|
||||
if (alphaLine) alphaLine[x] = alpha;
|
||||
};
|
||||
|
||||
switch (pixelType) {
|
||||
case PNG_PIXEL_GRAYSCALE:
|
||||
if (bitsPerSample == 8) {
|
||||
if (bitsPerSample == 8 && !hasAlpha && !alphaLine) {
|
||||
memcpy(grayLine, pPixels, width);
|
||||
} else {
|
||||
for (int x = 0; x < width; x++) {
|
||||
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
|
||||
const uint8_t sample = readPackedSample(pPixels, x, bitsPerSample);
|
||||
const uint8_t gray = expandSampleToByte(sample, bitsPerSample);
|
||||
store(x, gray, hasAlpha && sample == static_cast<uint8_t>(transparentColor) ? 0 : 255);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -151,53 +169,44 @@ void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int
|
||||
case PNG_PIXEL_TRUECOLOR:
|
||||
for (int x = 0; x < width; x++) {
|
||||
const uint8_t* p = &pPixels[x * 3];
|
||||
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
const uint8_t gray = static_cast<uint8_t>((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
const uint32_t color = (static_cast<uint32_t>(p[0]) << 16) | (static_cast<uint32_t>(p[1]) << 8) | p[2];
|
||||
store(x, gray, hasAlpha && color == transparentColor ? 0 : 255);
|
||||
}
|
||||
break;
|
||||
|
||||
case PNG_PIXEL_INDEXED:
|
||||
if (palette) {
|
||||
if (hasAlpha) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
||||
uint8_t* p = &palette[idx * 3];
|
||||
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
uint8_t alpha = palette[768 + idx];
|
||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
||||
}
|
||||
} else {
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
||||
uint8_t* p = &palette[idx * 3];
|
||||
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
}
|
||||
for (int x = 0; x < width; x++) {
|
||||
const uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
||||
const uint8_t* p = &palette[idx * 3];
|
||||
const uint8_t gray = static_cast<uint8_t>((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
store(x, gray, hasAlpha ? palette[768 + idx] : 255);
|
||||
}
|
||||
} else {
|
||||
for (int x = 0; x < width; x++) {
|
||||
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
|
||||
store(x, expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample), 255);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PNG_PIXEL_GRAY_ALPHA:
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint8_t gray = pPixels[x * 2];
|
||||
uint8_t alpha = pPixels[x * 2 + 1];
|
||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
||||
store(x, pPixels[x * 2], pPixels[x * 2 + 1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case PNG_PIXEL_TRUECOLOR_ALPHA:
|
||||
for (int x = 0; x < width; x++) {
|
||||
const uint8_t* p = &pPixels[x * 4];
|
||||
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
uint8_t alpha = p[3];
|
||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
||||
const uint8_t gray = static_cast<uint8_t>((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||
store(x, gray, p[3]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
memset(grayLine, 128, width);
|
||||
if (alphaLine) memset(alphaLine, 255, width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -208,25 +217,29 @@ int pngDrawCallback(PNGDRAW* pDraw) {
|
||||
|
||||
int srcY = pDraw->y;
|
||||
int srcWidth = ctx->srcWidth;
|
||||
if (srcY < ctx->cropTop || srcY >= ctx->cropTop + ctx->visibleHeight) return 1;
|
||||
const int visibleSrcY = srcY - ctx->cropTop;
|
||||
|
||||
// Map source rows with the exact output-height ratio. During downscaling,
|
||||
// multiple source rows can select the same output row; during upscaling, one
|
||||
// source row must be repeated across every output row in its range. Emitting
|
||||
// only the first row of an upscale leaves zero-filled (black) gaps in the
|
||||
// streamed pixel cache.
|
||||
int firstDstY = (srcY * ctx->dstHeight) / ctx->srcHeight;
|
||||
int firstDstY = (visibleSrcY * ctx->dstHeight) / ctx->visibleHeight;
|
||||
int endDstY = firstDstY + 1;
|
||||
if (ctx->dstHeight > ctx->srcHeight) {
|
||||
endDstY = ((srcY + 1) * ctx->dstHeight) / ctx->srcHeight;
|
||||
if (ctx->dstHeight > ctx->visibleHeight) {
|
||||
endDstY = ((visibleSrcY + 1) * ctx->dstHeight) / ctx->visibleHeight;
|
||||
}
|
||||
|
||||
if (firstDstY <= ctx->lastDstY) firstDstY = ctx->lastDstY + 1;
|
||||
if (firstDstY >= endDstY || firstDstY >= ctx->dstHeight) return 1;
|
||||
if (endDstY > ctx->dstHeight) endDstY = ctx->dstHeight;
|
||||
|
||||
// Convert entire source line to grayscale (improves cache locality)
|
||||
// PNGdec parses tRNS while decoding, after open() returns, so query the
|
||||
// transparent color here rather than caching its pre-decode value.
|
||||
const uint32_t transparentColor = ctx->decoder ? ctx->decoder->getTransparentColor() : 0;
|
||||
convertLineToGray(pDraw->pPixels, ctx->grayLineBuffer, srcWidth, pDraw->iPixelType, pDraw->iBpp, pDraw->pPalette,
|
||||
pDraw->iHasAlpha);
|
||||
pDraw->iHasAlpha, transparentColor, ctx->alphaLineBuffer);
|
||||
|
||||
// Render scaled rows using Bresenham-style integer stepping (no floating-point division)
|
||||
int dstWidth = ctx->dstWidth;
|
||||
@@ -261,27 +274,30 @@ int pngDrawCallback(PNGDRAW* pDraw) {
|
||||
}
|
||||
}
|
||||
|
||||
int srcX = 0;
|
||||
int srcX = ctx->cropLeft;
|
||||
int error = 0;
|
||||
|
||||
for (int dstX = 0; dstX < dstWidth; dstX++) {
|
||||
int outX = outXBase + dstX;
|
||||
if (outX < screenWidth) {
|
||||
uint8_t gray = ctx->grayLineBuffer[srcX];
|
||||
const uint8_t alpha = ctx->alphaLineBuffer ? ctx->alphaLineBuffer[srcX] : 255;
|
||||
if (alpha >= 8 && alpha > alphaThreshold4x4(outX, outY)) {
|
||||
uint8_t gray = ctx->grayLineBuffer[srcX];
|
||||
|
||||
uint8_t ditheredGray;
|
||||
if (useDithering) {
|
||||
ditheredGray = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
ditheredGray = gray / 85;
|
||||
if (ditheredGray > 3) ditheredGray = 3;
|
||||
uint8_t ditheredGray;
|
||||
if (useDithering) {
|
||||
ditheredGray = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
ditheredGray = gray / 85;
|
||||
if (ditheredGray > 3) ditheredGray = 3;
|
||||
}
|
||||
pw.writePixel(outX, ditheredGray, ctx->alphaLineBuffer != nullptr);
|
||||
if (caching) cw.writePixel(outX, ditheredGray);
|
||||
}
|
||||
pw.writePixel(outX, ditheredGray);
|
||||
if (caching) cw.writePixel(outX, ditheredGray);
|
||||
}
|
||||
|
||||
// Bresenham-style stepping: advance srcX based on ratio srcWidth/dstWidth
|
||||
error += srcWidth;
|
||||
// Bresenham-style stepping: advance srcX based on ratio visibleWidth/dstWidth
|
||||
error += ctx->visibleWidth;
|
||||
while (error >= dstWidth) {
|
||||
error -= dstWidth;
|
||||
srcX++;
|
||||
@@ -340,6 +356,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
||||
}
|
||||
|
||||
PngContext ctx;
|
||||
ctx.decoder = png.get();
|
||||
ctx.renderer = &renderer;
|
||||
ctx.config = &config;
|
||||
ctx.screenWidth = renderer.getScreenWidth();
|
||||
@@ -360,28 +377,36 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
||||
// Calculate output dimensions
|
||||
ctx.srcWidth = png->getWidth();
|
||||
ctx.srcHeight = png->getHeight();
|
||||
ctx.cropLeft = static_cast<int>(ctx.srcWidth * std::clamp(config.sourceCropX, 0.0f, 0.99f) / 2.0f);
|
||||
ctx.cropTop = static_cast<int>(ctx.srcHeight * std::clamp(config.sourceCropY, 0.0f, 0.99f) / 2.0f);
|
||||
ctx.visibleWidth = ctx.srcWidth - 2 * ctx.cropLeft;
|
||||
ctx.visibleHeight = ctx.srcHeight - 2 * ctx.cropTop;
|
||||
if (ctx.visibleWidth <= 0 || ctx.visibleHeight <= 0) {
|
||||
LOG_ERR("PNG", "PNG crop leaves no visible pixels");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.useExactDimensions && config.maxWidth > 0 && config.maxHeight > 0) {
|
||||
// Use exact dimensions as specified (avoids rounding mismatches with pre-calculated sizes)
|
||||
ctx.dstWidth = config.maxWidth;
|
||||
ctx.dstHeight = config.maxHeight;
|
||||
ctx.scale = (float)ctx.dstWidth / ctx.srcWidth;
|
||||
ctx.scale = (float)ctx.dstWidth / ctx.visibleWidth;
|
||||
} else {
|
||||
// Calculate scale factor to fit within maxWidth/maxHeight
|
||||
float scaleX = (float)config.maxWidth / ctx.srcWidth;
|
||||
float scaleY = (float)config.maxHeight / ctx.srcHeight;
|
||||
float scaleX = (float)config.maxWidth / ctx.visibleWidth;
|
||||
float scaleY = (float)config.maxHeight / ctx.visibleHeight;
|
||||
ctx.scale = (scaleX < scaleY) ? scaleX : scaleY;
|
||||
if (ctx.scale > 1.0f) ctx.scale = 1.0f; // Don't upscale
|
||||
|
||||
ctx.dstWidth = (int)(ctx.srcWidth * ctx.scale);
|
||||
ctx.dstHeight = (int)(ctx.srcHeight * ctx.scale);
|
||||
ctx.dstWidth = (int)(ctx.visibleWidth * ctx.scale);
|
||||
ctx.dstHeight = (int)(ctx.visibleHeight * ctx.scale);
|
||||
}
|
||||
ctx.lastDstY = -1; // Reset row tracking
|
||||
|
||||
const int pixelType = png->getPixelType();
|
||||
const int bitsPerSample = png->getBpp();
|
||||
LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), type: %d, bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth,
|
||||
ctx.dstHeight, ctx.scale, pixelType, bitsPerSample);
|
||||
LOG_DBG("PNG", "PNG %dx%d (visible %dx%d) -> %dx%d (scale %.2f), type: %d, bpp: %d", ctx.srcWidth, ctx.srcHeight,
|
||||
ctx.visibleWidth, ctx.visibleHeight, ctx.dstWidth, ctx.dstHeight, ctx.scale, pixelType, bitsPerSample);
|
||||
|
||||
const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType, bitsPerSample);
|
||||
if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) {
|
||||
@@ -410,12 +435,18 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
||||
return false;
|
||||
}
|
||||
|
||||
auto grayLineBuffer = makeUniqueNoThrow<uint8_t[]>(grayBufSize);
|
||||
if (!grayLineBuffer) {
|
||||
LOG_ERR("PNG", "Failed to allocate gray line buffer");
|
||||
// tRNS chunks are parsed during decode(), so hasAlpha() is not reliable yet
|
||||
// for color-key and indexed transparency. Reserve the alpha line whenever
|
||||
// the caller requests preservation; opaque pixels simply store alpha 255.
|
||||
const bool retainAlpha = config.preserveAlpha;
|
||||
const size_t lineBufferBytes = grayBufSize * (retainAlpha ? 2u : 1u);
|
||||
auto lineBuffers = makeUniqueNoThrow<uint8_t[]>(lineBufferBytes);
|
||||
if (!lineBuffers) {
|
||||
LOG_ERR("PNG", "Failed to allocate PNG line buffers (%u bytes)", static_cast<unsigned>(lineBufferBytes));
|
||||
return false;
|
||||
}
|
||||
ctx.grayLineBuffer = grayLineBuffer.get();
|
||||
ctx.grayLineBuffer = lineBuffers.get();
|
||||
ctx.alphaLineBuffer = retainAlpha ? ctx.grayLineBuffer + grayBufSize : nullptr;
|
||||
|
||||
// Stream the pixel cache to disk. PNGdec delivers source scanlines top to
|
||||
// bottom and we emit at most one (downscaled) output row per callback, so the
|
||||
@@ -423,7 +454,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
||||
// unlike the old full-image buffer it neither competes with the ~44KB decoder
|
||||
// nor forces larger images to skip caching - which previously meant a full
|
||||
// re-decode on every one of an image page's ~14 render passes.
|
||||
ctx.caching = !config.cachePath.empty();
|
||||
ctx.caching = !config.preserveAlpha && !config.cachePath.empty();
|
||||
if (ctx.caching) {
|
||||
if (!ctx.cache.begin(config.cachePath, ctx.dstWidth, ctx.dstHeight, config.x, config.y, 1)) {
|
||||
LOG_ERR("PNG", "Failed to start cache stream, continuing without caching");
|
||||
@@ -436,6 +467,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
||||
unsigned long decodeTime = millis() - decodeStart;
|
||||
|
||||
ctx.grayLineBuffer = nullptr;
|
||||
ctx.alphaLineBuffer = nullptr;
|
||||
|
||||
if (rc != PNG_SUCCESS) {
|
||||
LOG_ERR("PNG", "Decode failed: %d", rc);
|
||||
|
||||
@@ -2138,8 +2138,6 @@ void GfxRenderer::writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* scratch
|
||||
|
||||
bool GfxRenderer::supportsStripGrayscale() const { return display.supportsStripGrayscale(); }
|
||||
|
||||
bool GfxRenderer::combinesGrayscaleBase() const { return display.combinesGrayscaleBase(); }
|
||||
|
||||
void GfxRenderer::freeBwBufferChunks() {
|
||||
for (auto& bwBufferChunk : bwBufferChunks) {
|
||||
if (bwBufferChunk) {
|
||||
|
||||
@@ -293,9 +293,6 @@ class GfxRenderer {
|
||||
// numRows)), bypassing the framebuffer. supportsStripGrayscale() gates use.
|
||||
void writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* scratch, int yStart, int numRows) const;
|
||||
bool supportsStripGrayscale() const;
|
||||
// Paper Mono: the base activation is deferred so base + gray planes go out
|
||||
// as one waveform. Route the base through displayGrayscaleBase() when true.
|
||||
bool combinesGrayscaleBase() const;
|
||||
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
||||
void restoreBwBuffer(); // Restore and free the stored buffer
|
||||
void cleanupGrayscaleWithFrameBuffer() const;
|
||||
|
||||
@@ -95,7 +95,6 @@ STR_SHORT_PWR_BTN: "Short Power Button Click"
|
||||
STR_ORIENTATION: "Reading Orientation"
|
||||
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
||||
STR_TOUCH_READER_CONTROLS: "Touch Reader Controls"
|
||||
STR_TAP_FOR_READER_MENU: "Tap For Reader Menu"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orient front buttons"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
@@ -185,7 +184,6 @@ STR_NEXT_PREV: "Next/Prev"
|
||||
STR_KOSYNC: "KOSync"
|
||||
STR_BOOKMARK_OPTION: "Bookmark"
|
||||
STR_DICTIONARY: "Dictionary"
|
||||
STR_READER_MENU: "Reader Menu"
|
||||
STR_DISABLED: "Disabled"
|
||||
STR_NOTO_SERIF: "Noto Serif"
|
||||
STR_NOTO_SANS: "Noto Sans"
|
||||
@@ -249,13 +247,6 @@ STR_SHOW: "Show"
|
||||
STR_HIDE: "Hide"
|
||||
STR_STATE_ON: "ON"
|
||||
STR_STATE_OFF: "OFF"
|
||||
STR_STATE_TAP: "Tap"
|
||||
STR_STATE_SWIPE: "Swipe"
|
||||
STR_STATE_INVERTED_TAP: "Inverted Tap"
|
||||
STR_FRONTLIGHT: "Frontlight"
|
||||
STR_BRIGHTNESS: "Brightness"
|
||||
STR_WARMTH: "Warmth"
|
||||
STR_RESTORE_LIGHT_ON_WAKE: "Restore Light on Wake"
|
||||
STR_NOT_SET: "Not Set"
|
||||
STR_DIR_LEFT: "Left"
|
||||
STR_DIR_RIGHT: "Right"
|
||||
|
||||
@@ -16,14 +16,9 @@ void safeCopy(char* dst, size_t dstSize, const char* src, size_t srcLen) {
|
||||
ReleaseJsonParser::ReleaseJsonParser()
|
||||
: parser(JsonCallbacks{this, sOnKey, sOnString, sOnNumber, sOnBool, sOnNull, sOnObjectStart, sOnObjectEnd,
|
||||
sOnArrayStart, sOnArrayEnd}) {
|
||||
safeCopy(firmwareAssetName, sizeof(firmwareAssetName), "firmware.bin", sizeof("firmware.bin") - 1);
|
||||
reset();
|
||||
}
|
||||
|
||||
void ReleaseJsonParser::setFirmwareAssetName(const char* name) {
|
||||
safeCopy(firmwareAssetName, sizeof(firmwareAssetName), name, strlen(name));
|
||||
}
|
||||
|
||||
void ReleaseJsonParser::reset() {
|
||||
parser.reset();
|
||||
position = Position::TOP_LEVEL;
|
||||
@@ -49,7 +44,7 @@ const char* ReleaseJsonParser::getFirmwareUrl() const { return firmwareUrl; }
|
||||
size_t ReleaseJsonParser::getFirmwareSize() const { return firmwareSize; }
|
||||
|
||||
void ReleaseJsonParser::commitAsset() {
|
||||
if (strcmp(currentAssetName, firmwareAssetName) == 0) {
|
||||
if (strcmp(currentAssetName, "firmware.bin") == 0) {
|
||||
memcpy(firmwareUrl, currentAssetUrl, sizeof(firmwareUrl));
|
||||
firmwareSize = currentAssetSize;
|
||||
firmwareFound = true;
|
||||
|
||||
@@ -15,11 +15,6 @@ class ReleaseJsonParser {
|
||||
void reset();
|
||||
void feed(const char* data, size_t len);
|
||||
|
||||
// Release-asset filename to match (default "firmware.bin"). Boards with
|
||||
// their own release binaries pass e.g. "firmware-papermono.bin". Survives
|
||||
// reset(); truncated silently if longer than the internal buffer.
|
||||
void setFirmwareAssetName(const char* name);
|
||||
|
||||
bool foundTag() const;
|
||||
bool foundFirmware() const;
|
||||
const char* getTagName() const;
|
||||
@@ -70,6 +65,4 @@ class ReleaseJsonParser {
|
||||
char currentAssetName[32];
|
||||
char currentAssetUrl[512];
|
||||
size_t currentAssetSize;
|
||||
|
||||
char firmwareAssetName[32];
|
||||
};
|
||||
@@ -143,8 +143,6 @@ void HalDisplay::writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* rows, ui
|
||||
|
||||
bool HalDisplay::supportsStripGrayscale() const { return einkDisplay.supportsStripGrayscale(); }
|
||||
|
||||
bool HalDisplay::combinesGrayscaleBase() const { return einkDisplay.combinesGrayscaleBase(); }
|
||||
|
||||
uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); }
|
||||
|
||||
uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); }
|
||||
|
||||
@@ -102,13 +102,6 @@ class HalDisplay {
|
||||
void writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* rows, uint16_t yStart, uint16_t numRows);
|
||||
bool supportsStripGrayscale() const;
|
||||
|
||||
// True when displayGrayscaleBase() defers the base activation so the gray
|
||||
// planes join it in a single waveform (Paper Mono). Callers should then route
|
||||
// the base of a grayscale page through displayGrayscaleBase() instead of
|
||||
// displayBuffer(): a separate B/W refresh first makes the gray pass re-drive
|
||||
// the whole text body (a visible flash).
|
||||
bool combinesGrayscaleBase() const;
|
||||
|
||||
// Runtime geometry passthrough
|
||||
uint16_t getDisplayWidth() const;
|
||||
uint16_t getDisplayHeight() const;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#include "HalFrontlight.h"
|
||||
|
||||
#include <Logging.h>
|
||||
|
||||
HalFrontlight HalFrontlight::instance;
|
||||
|
||||
void HalFrontlight::begin(const uint8_t brightness, const uint8_t warmth, const bool on) {
|
||||
if (!manager.present()) return;
|
||||
|
||||
manager.begin();
|
||||
lastBrightness = brightness > 100 ? 100 : brightness;
|
||||
manager.setColorTemperature(warmth > 100 ? 100 : warmth);
|
||||
lit = on;
|
||||
manager.setBrightness(lit ? lastBrightness : 0);
|
||||
LOG_INF("LIGHT", "Frontlight up: %u%% warm=%u%% %s", lastBrightness, manager.colorTemperature(), lit ? "on" : "off");
|
||||
}
|
||||
|
||||
void HalFrontlight::setBrightness(const uint8_t percent) {
|
||||
lastBrightness = percent > 100 ? 100 : percent;
|
||||
if (lit) manager.setBrightness(lastBrightness);
|
||||
}
|
||||
|
||||
void HalFrontlight::setWarmth(const uint8_t warmPercent) {
|
||||
manager.setColorTemperature(warmPercent > 100 ? 100 : warmPercent);
|
||||
}
|
||||
|
||||
void HalFrontlight::setOn(const bool on) {
|
||||
if (on == lit) return;
|
||||
lit = on;
|
||||
manager.setBrightness(lit ? lastBrightness : 0);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <FrontlightManager.h>
|
||||
|
||||
// Thin firmware HAL over the SDK frontlight manager. It is inert on boards
|
||||
// without a frontlight, so callers do not need board-specific conditionals.
|
||||
class HalFrontlight {
|
||||
public:
|
||||
static HalFrontlight& getInstance() { return instance; }
|
||||
|
||||
void begin(uint8_t brightness, uint8_t warmth, bool on);
|
||||
|
||||
bool present() const { return manager.present(); }
|
||||
bool hasColorTemperature() const { return manager.hasColorTemperature(); }
|
||||
|
||||
void setBrightness(uint8_t percent);
|
||||
void setWarmth(uint8_t warmPercent);
|
||||
void setOn(bool on);
|
||||
|
||||
uint8_t brightness() const { return lastBrightness; }
|
||||
uint8_t warmth() const { return manager.colorTemperature(); }
|
||||
bool isOn() const { return lit; }
|
||||
|
||||
private:
|
||||
HalFrontlight() = default;
|
||||
|
||||
FrontlightManager manager;
|
||||
// The SDK represents off as brightness 0. Keep the selected brightness so
|
||||
// toggling back on restores it.
|
||||
uint8_t lastBrightness = 60;
|
||||
bool lit = false;
|
||||
|
||||
static HalFrontlight instance;
|
||||
};
|
||||
|
||||
#define Frontlight HalFrontlight::getInstance()
|
||||
Loaded 20 of 66 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user