Skip to content

feat: 1614 lut pxc#13

Open
pablohc wants to merge 68 commits into
devfrom
feat/1614-lut-pxc
Open

feat: 1614 lut pxc#13
pablohc wants to merge 68 commits into
devfrom
feat/1614-lut-pxc

Conversation

@pablohc
Copy link
Copy Markdown
Owner

@pablohc pablohc commented May 18, 2026

Summary

  • What is the goal of this PR? (e.g., Implements the new feature for file uploading.)
  • What changes are included?

Additional Context

  • Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
    specific areas to focus on).

AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? < YES | PARTIALLY | NO >

Patryk Radtke and others added 30 commits April 23, 2026 22:44
Add GRAY2 absolute encoding for 4-shade e-ink rendering via factory LUTs.
Introduce FactoryFast and FactoryQuality GrayscaleMode variants with X3
fallback to Differential. Add direct-pixel BMP rendering path, 2-bit XTC
plane support in Xtc parser, and HALF_REFRESH display mode in HalDisplay.
displayXtchPlanes uses Differential on X3 (factory LUT not calibrated).
Skip fadingFix power-down on first BW render after factory LUT to avoid
redundant power cycle (display already off after 0xC7 sequence).
Add PxcViewerActivity for PXC image display using factory LUT grayscale.
Update BmpViewerActivity and SleepActivity with X3-aware grayscale mode.
EPUB reader uses FactoryQuality on X4 for image pages, Differential on X3.
XTC reader pre-flashes to white on entry; periodic FULL_REFRESH every 32 pages.
Add UITheme metrics for PXC viewer layout.
Add ScreenshotUtil to detect factory LUT display state and re-render
the current page via renderGrayscale for accurate screenshot output.
X3 falls back to Differential mode for all grayscale paths.
…m Home

When entering sleep from Home (not Reader), the display particle state
is in BW mode from FAST_REFRESH renders. Without pre-conditioning, the
subsequent factory LUT render produces a dirty appearance. Added a
clearScreen + HALF_REFRESH before displayXtchPlanes to reset particles
to a known state.
Replace the 2-bit BMP thumbnail generation for XTCH files with 1-bit BMP
using Atkinson dithering (matching EPUB thumbnail quality). Key changes:
- Load both XTCH bitplanes separately for area-averaged downsampling
- Apply Atkinson 1-bit dithering (3 error rows, 6 neighbors, 6/8 error)
- Apply 1.2x contrast boost for better tonal separation
- Inverted bitplane luminance to match display polarity
- Falls back to 2-bit BMP on memory allocation failure
Replace hash-based noise dithering with Atkinson 1-bit dithering to match
EPUB thumbnail quality. Key changes:
- Atkinson 1-bit dithering (3 error rows, 6 neighbors, 6/8 error)
- 1.2x contrast boost for better edge definition
- Same BMP convention as XTCH path (memset 0xFF + clear bit for dark)
Eliminate ~80 lines of duplicated code:
- Replace inline Atkinson dithering with Atkinson1BitDitherer class
  (same class used by EPUB thumbnail generation)
- Extract computeSrcRange() helper for downscale coordinate calc
  (was duplicated 4 times across XTCH/XTC paths)
After reader sessions, heap fragmentation reduces MaxAlloc to ~65KB,
preventing the primary path from allocating both planes simultaneously
(96KB). The fallback produced a raw 2-bit BMP with no dithering,
resulting in poor visual quality on the Home Screen.

Unify to a single sequential path: load plane1 (48KB) -> majority-vote
-> free, load plane2 (48KB) -> combine -> Atkinson dithering -> 1-bit BMP.
Peak memory ~52KB, always within MaxAlloc constraints.

This matches the same approach used by XTC 1-bit thumbnails, ensuring
consistent quality across all themes and memory states.
Standardize all grayscale rendering to use FactoryQuality mode instead of conditionally using Differential mode for X3 devices. Also inverts the useFactoryGray logic in EpubReaderActivity to enable factory grayscale for X3 devices on image pages.
Remove X3-specific factory gray rendering path and associated pre-flash logic. Factory gray mode now only applies to image pages when text anti-aliasing is enabled, regardless of device type.
Remove obsolete comments about factory gray mode and text-only antialiasing display handling that are no longer relevant to the current implementation.
The 1-bit XTC sleep path unconditionally cleared the screen and issued a
HALF_REFRESH before calling displayXtcBwPage, which itself runs
clearScreen + FAST_REFRESH. Match the 2-bit branch by skipping the
pre-flash when the previous activity was the reader, avoiding the
redundant refresh.

Also dedupes a few render callbacks while in the area:
- Extract the 'Entering sleep' overlay lambda into drawEnteringSleepOverlay.
- Promote PageRenderCtx + grayFn to a private nested struct and static
  renderPageCallback shared by renderContents and onScreenshotRequest.
- Consolidate PxcCtx and the rendering/overlay callbacks into file-scope
  statics behind a renderPxcToFramebuffer helper used by both onEnter
  and renderGrayscaleImage.
- Reject PXC files whose dimensions don't exactly match the runtime
  screen size. The previous abs(w-sw) > 1 tolerance allowed a one-pixel
  oversize, which would cause out-of-bounds writes in DirectPixelWriter
  (no bounds checking on writePixel). Strict equality is correct per
  device since renderer.getScreenWidth/Height are device-aware
  (X4 800x480 vs X3 792x528).
- Make renderPxcSleepScreen return bool so the /sleep.pxc path can fall
  through to /sleep.bmp when the PXC is missing/invalid, instead of
  short-circuiting straight to the default sleep screen. Other callers
  (random wallpaper picker, screenshot replay) explicitly fall back to
  renderDefaultSleepScreen on failure to preserve prior behaviour.
- In onScreenshotRequest, store the BW page before the factory-gray
  render and restore it before cleanupGrayscaleWithFrameBuffer, so the
  controller's BW state is synced to the actual page rather than a
  cleared framebuffer. Mirrors the pattern in the normal render path.
The clearScreen + cleanupGrayscaleWithFrameBuffer in onScreenshotRequest
intentionally puts the controller into a 'BW = white' state. This is the
correct precondition for the HALF_REFRESH pre-flash done by the next
SleepActivity render (triggered by goToSleep on the way into deep sleep)
and the panel resets across deep sleep, so no preservation of the
displayed image is needed here.
zgredex and others added 28 commits May 6, 2026 22:26
# Conflicts:
#	src/activities/reader/XtcReaderActivity.cpp
- Remove BW/Inverted filter path for PXC (bypassed factory LUT)
- PXC now always uses FactoryQuality grayscale mode
- DirectPixelWriter handles orientation transforms automatically
- Filter settings (BLACK_AND_WHITE/INVERTED) ignored for PXC
- Consistent with PxcViewerActivity behavior
- Maintains PR 1614 intent (factory LUT + PXC support)
# Conflicts:
#	src/activities/reader/XtcReaderActivity.cpp
#	src/activities/reader/XtcReaderActivity.h
- GfxRenderer.h: merge factory LUT grayscale (PR) + SD card fonts (master)
- EpubReaderActivity.cpp: keep PR's refactored renderGrayscale() approach
Two coupled changes for the new SDK contract:

- After displayGrayBuffer in GfxRenderer::renderGrayscale and
  renderGrayscaleSinglePass (both the malloc'd path and the two-pass
  fallback), call display.clearGrayscaleModeFlag(). Differential mode
  rendering was leaving inGrayscaleMode=true, which (post-SDK fix)
  triggered an automatic ~700ms revert refresh on the next BW page
  turn. The renderer already manages cleanup via cleanupGrayscaleBuffers
  inside restoreBwBuffer, so the SDK auto-revert is unwanted overhead.

- Drop the redundant cleanupGrayscaleWithFrameBuffer call after
  restoreBwBuffer in EpubReaderActivity (page render path and
  onScreenshotRequest). restoreBwBuffer already calls
  cleanupGrayscaleBuffers(frameBuffer), so the explicit second call
  wrote RED RAM with identical data twice per page. RED RAM is now
  rebased exactly once per gray-to-BW transition.

Bumps SDK submodule to ccfd37b (community-sdk PR #4 head): the
clearGrayscaleModeFlag() accessor and idempotent grayscaleRevert
contract this code depends on.
Mirrors the BMP viewer feature set for .pxc files: Up/Down navigates
siblings in the current folder, Confirm copies the current image to
/sleep.pxc and switches sleepScreen to CUSTOM.
Exposes the SSD1677 factory LUT trade-off for XTC reading: Speed uses
lut_factory_fast (default), Quality uses lut_factory_quality. Selectable
under Settings -> Reader as "2-bit XTC". Stored as xtcRenderQuality in
the JSON settings file.

displayXtchPlanes() gains an optional GrayscaleMode parameter
(default FactoryFast) and selects the LUT at the displayGrayBuffer
call. XtcReaderActivity reads SETTINGS.xtcRenderQuality at the call
site to pick FactoryFast or FactoryQuality.
Ports upstream crosspoint-reader#1852 prev/next arrow labels onto BmpViewer and mirrors
the same behavior on PxcViewer for parity with the recently-added
sibling navigation. Both viewers now show '<' / '>' next to BACK and
SET_SLEEP_COVER when adjacent siblings exist, and accept Left/Right
alongside Up/Down for sibling navigation.

PxcViewer's renderPxcToFramebuffer() gains hasPrevious/hasNext bools
so the interactive onEnter path receives arrow labels while the
screenshot path passes false/false (matches BMP screenshot behavior).
# Conflicts:
#	open-x4-sdk
#	src/activities/util/BmpViewerActivity.cpp
Refactor:
- Rename GfxRenderer::GrayscaleMode -> GrayscaleDriveMode to separate caller
  intent (which LUT, fast vs quality vs differential) from low-level panel
  plumbing (RenderMode values, lut pointer, factoryMode flag,
  g_differentialQuantize).
- Add file-private GrayscaleDriveSpec + resolveGrayscaleDrive() in
  GfxRenderer.cpp. Replaces four duplicated ternary clusters across
  renderGrayscale, renderGrayscaleSinglePass (main + malloc-failed fallback),
  and displayXtchPlanes.
- Tidy SleepActivity displayXtchPlanes call: drop two inert args (preFlash
  is false, so passes count and refresh mode were unreachable). Add a
  one-line comment pointing to the external precondition.

Stock-firmware experiments (PXC sleep ghost investigation, see
docs/v559-disassembly-findings.md):
- renderGrayscaleSinglePass factory path now uses the split SDK API
  (displayGrayBufferFactorySetup + RAM writes + displayGrayBufferFactoryActivate)
  to match stock's SPI order: LUT load -> Border Waveform -> RAM writes ->
  CTRL1/CTRL2/MASTER_ACTIVATION.
- Precondition (runFactorySleepPrecondition) commented out in all three
  SleepActivity render paths (PXC, BMP grayscale, XTCH planes) to test
  whether the precondition's post-FULL_REFRESH RED RAM sync was contributing.
- Bump open-x4-sdk pointer to pick up the matching SDK experiments
  (FACTORY_GRAY CTRL2 = 0xCC, Border Waveform per render, BOOSTER and BORDER
  init bytes matching stock, deepSleep factory branch early-return, factory
  LUT voltages set to stock's byte-exact 0x00/0x00/0x01/0x22/0x22 sequence).
Picks up the fix for displayGrayBufferFactoryActivate not restoring VCOM
after a non-default-VCOM LUT load. See SDK commit 4e949dc.
Picks up the revert of the VCOM restore in displayGrayBufferFactoryActivate.
Preserves byte-exact stock V5.5.9 SPI sequence for the PXC sleep ghost
experiment. See SDK commit 7c8afd0.
Re-enable runFactorySleepPrecondition in all three SleepActivity render
paths (PXC, BMP grayscale, XTCH planes). The disable was an experiment
that didn't move the ghost; restoring it to test against stock's actual
pre-LUT sequence.

Switch the precondition itself from renderer.clearScreen +
renderer.displayBuffer(FULL_REFRESH) to renderer.displayBufferPrecondition()
— a new HAL/SDK method that fires CTRL2 = 0xF7 (full power cycle, matches
stock V5.5.9) and skips the SINGLE_BUFFER_MODE post-RED-sync. Wrappers
added to HalDisplay and GfxRenderer.

Bump open-x4-sdk pointer to 4982527 which contains the new
EInkDisplay::displayBufferPrecondition implementation.

See docs/v559-disassembly-findings.md for the full byte-match rationale.
The stock byte-exact lut_factory_quality swap caused all-white renders on
device. Reverted in SDK; bumping pointer to match. The "OTP-preserve for
0x00 voltages" hypothesis from Difference #8 is falsified by this empirical
result. See docs/v559-disassembly-findings.md.
Previously the split SDK API (displayGrayBufferFactorySetup +
displayGrayBufferFactoryActivate, matching stock V5.5.9's
LUT-load-before-RAM-writes SPI order — Difference #4) was applied only to
renderGrayscaleSinglePass. PxcViewer and BmpViewer use that singlepass
path so they got the patch implicitly, but the other factory render
callers did not:

- renderGrayscale (two-pass) → used by EpubReaderActivity for inline
  grayscale images
- displayXtchPlanes → used by XtcReaderActivity and SleepActivity's XTCH
  cover path

Refactor both to use the split SDK API for factory mode:
- renderGrayscale: insert displayGrayBufferFactorySetup before the first
  copyGrayscaleLsbBuffers, replace the trailing displayGrayBuffer with
  displayGrayBufferFactoryActivate. Differential mode keeps the combined
  displayGrayBuffer (no change).
- displayXtchPlanes: resolve drive spec early, call Setup before the
  plane1 RAM write, call Activate after the plane2 RAM write.

Net result: all 6 factory render call sites (PXC viewer, BMP viewer, EPUB
inline images, XTC reader, sleep XTCH path, sleep PXC/BMP path) now follow
the stock SPI order. See docs/v559-disassembly-findings.md Difference #4.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ace45b3c-308b-4c84-b2b5-be4a557c83f7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1614-lut-pxc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pablohc pablohc changed the title Feat/1614 lut pxc feat: 1614 lut pxc May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants