Skip to content

Render pictures in colour on Kaleido panels (Clara Colour, Libra Colour) - #99

Open
abhishek-anand wants to merge 2 commits into
mainfrom
feature/colour-content
Open

Render pictures in colour on Kaleido panels (Clara Colour, Libra Colour)#99
abhishek-anand wants to merge 2 commits into
mainfrom
feature/colour-content

Conversation

@abhishek-anand

@abhishek-anand abhishek-anand commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Why

Clara Colour and Libra Colour have a Kaleido 3 colour filter over the same greyscale panel every other Kobo uses. Cobalt has only ever written luminance to them, so pictures on those devices came out grey. This PR adds a colour path for pictures end to end while keeping every greyscale device byte-for-byte on its current behaviour.

Not tested on hardware yet — I don't have a Kaleido device. It needs a run on a Clara Colour or Libra Colour before this can be released (see checklist below). Nothing changes in what gets written on the BW/Libra 2/Clara HD/Elipsa devices, so those are unaffected.

What changes, by crate

Crate Change
kobo-profile colour_panel: bool on DeviceProfile, true only for CLARA_COLOUR_393 and LIBRA_COLOUR_390. A test pins colour_panel == id.contains("colour") for every supported profile, because applications only ever see the name (see protocol note).
kobo-abi hwtcon waveform ids GCK16 8 / GLKW16 9 / GCC16 10 / GLRC16 11 and update flags (dithering 0x1, monochrome-only 0x8000, colour-filter mode mask 0x7f00, standard mode 0x100). Added as _Static_asserts in tools/abi/hwtcon_conformance.c; check-hwtcon.sh passes against Kobo's published kernel header (linux/v4.9/include/linux/hwtcon_ioctl_cmd.h from Kobo's GPL source drop).
kobo-hal RefreshIntent::ColourContent — GC16 with the standard colour-filter flag on hwtcon, plain GC16 on mxcfb. DisplaySession::issue() downgrades it to QualityContent on any panel without colour_panel, so nothing new is ever sent to a greyscale driver. ChannelOrder is read from the framebuffer bitfields (i.MX is BGRA, MTK is RGBA) and RegionSnapshot::from_rgb writes accordingly.
kobo-ui Surface gets an optional chroma plane, allocated lazily on the first colour blend, kept in step by every write method, dropped on clear. Screens with no colour cost nothing extra. PanelWaveform::Colour; FramePlanner emits it when the region it is writing carries colour and tracks previous chroma for change detection. PictureFormat { Grey, Rgb }, PictureCache::put_report_with / begin_upload_with; existing grey methods are unchanged wrappers. draw_picture scales RGB per channel. luma() is Rec.601.
kobo-protocol RGB pictures travel on new tags 28 (inline) and 29 (begin upload). Grey stays on 18/20 with an identical wire layout, so no VERSION bump. PictureChunk.grey renamed to pixels (wire unchanged). DeviceIdentity::colour_panel() is derived from the profile name because Welcome is a fixed 7-byte frame and kobo-profile is publish = false so the published crates cannot depend on it.
kobo-sdk put_colour_picture(handle, w, h, rgb); Command::PutPicture gains format and greypixels. Re-exports PictureFormat.
kobo-image Picture keeps an optional RGB plane. decode_colour (grey output byte-identical to decode), from_rgb, encode_png_rgb; scaled_to / cover scale both planes. dither is grey-only and documented as such.
kobod Painter::paint writes the RGB region when the transition carries colour and the display reports a channel order; grey plane otherwise.
kobo-sim Accepts the new messages. The preview panel is a Clara BW, so colour lands as luminance — which is exactly what that device would show. No RGB preview yet.
apps/morse Test pattern updated for the field rename.

Design decisions worth a look

  • GC16 + standard colour-filter flag rather than GCC16. GCC16 is not guaranteed to be present in every device's waveform table; GC16 is, and the colour-filter mode field is what actually tells the driver to render through the CFA. If a Kaleido test shows GCC16 is better, it's a one-line change in refresh.rs.
  • Dithering off. Kobo's own UI dithers for colour, but I'd rather see the raw result first and add dithering as a separate, measured change.
  • Grey intents keep flags = 0. hwtcon_flags() returns the colour-filter flag only for ColourContent. Validated devices see exactly what they saw before.
  • Only pictures. Text and chrome stay in the grey plane. There is no colour API for widgets in this PR.
  • No protocol version bump. Follows the existing convention of adding tags rather than bumping; an old kobod will reject tag 28/29 with the existing unknown-tag error.

Hardware validation needed (Clara Colour / Libra Colour)

  • Gallery example shows a colour picture in colour, not grey
  • Channel order is correct (red is red — MTK is RGBA; a swapped R/B would show as cyan/orange)
  • Colour region refresh doesn't flash or leave residue worse than a normal GC16
  • Grey screens (home, text) look and refresh exactly as before
  • kobod trace shows ColourContent only on colour transitions
  • Try GCC16 in place of GC16 in RefreshIntent::ColourContent and compare

Conflicts

Touches Painter::paint in kobod/src/device.rs, which #79 also edits. The overlap is small (the intent match and the frame-building block); whichever lands second needs a short rebase.

Checks

  • cargo fmt --all -- --check
  • cargo test --workspace --all-targets --all-features ✓ (0 failures)
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • sh tools/abi/check-hwtcon.sh <kobo kernel header>

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Clara Colour and Libra Colour carry a colour filter array over the
same greyscale panel, but Cobalt has only ever written luminance to
them. This adds a colour path for pictures end to end while keeping
every greyscale device byte-for-byte on its current behaviour.

kobo-profile: a `colour_panel` flag on each device profile, true only
for the two Kaleido models. A test pins the flag to the profile name so
applications, which only ever see the name, can rely on it.

kobo-abi: the hwtcon waveform ids and update flags the MTK driver uses
for colour (GCK16/GLKW16/GCC16/GLRC16, dithering, monochrome-only and
the colour-filter mode field), checked against Kobo's published kernel
header by the existing conformance build.

kobo-hal: a `ColourContent` refresh intent that asks for the standard
colour-filter mode on hwtcon and degrades to `QualityContent` on any
other panel; `ChannelOrder` derived from the framebuffer bitfields so
RGB lands in the right bytes on both i.MX and MTK layouts; and
`RegionSnapshot::from_rgb`.

kobo-ui: `Surface` gains an optional chroma plane that is allocated
lazily, kept in step by every write, and dropped on clear, so screens
without colour cost nothing extra. The frame planner emits a `Colour`
waveform when the region it is about to write carries colour, and the
picture cache accepts `PictureFormat::Rgb` uploads alongside grey.

kobo-protocol: RGB pictures travel on new tags (28 inline, 29 begin
upload) so the grey tags are unchanged on the wire and no version bump
is needed. `DeviceIdentity::colour_panel()` reads the profile name.

kobo-sdk: `put_colour_picture` and a `format` on `Command::PutPicture`.

kobo-image: `Picture` keeps an RGB plane when decoded with
`decode_colour`, scales it with the grey, and can encode it as PNG.

kobod: the painter writes the RGB region through the display session
when the transition carries colour and the panel can show it, and the
grey plane otherwise.

kobo-sim: accepts the new messages; its preview panel is a Clara BW so
colour updates land as luminance, which is what the device would do.

Untested on hardware. The colour path needs a run on a Clara Colour or
Libra Colour before release; on every other device the written bytes
and flags are unchanged.
Copilot AI lite review requested due to automatic review settings September 3, 2026 17:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes the on-device rendering/refresh pipeline for a new hardware class and is explicitly unvalidated on Kaleido hardware, so it needs careful human + device testing before approval.

Pull request overview

Adds end-to-end support for rendering RGB pictures in colour on Kaleido (CFA) Kobo panels while preserving byte-for-byte behaviour for existing greyscale devices by downgrading colour-specific intents/flags and keeping legacy picture tags unchanged.

Changes:

  • Introduces a colour-aware refresh intent/flags path (HWTCON) and RGB region snapshot writing with per-device channel order.
  • Extends UI surfaces, picture caching, and drawing to optionally carry and render a chroma plane alongside the existing grey plane.
  • Extends protocol/SDK to transport RGB pictures via new tags (28/29) while keeping the legacy grey picture tags (18/20) unchanged.
File summaries
File Description
tools/abi/hwtcon_conformance.c Adds ABI conformance asserts for new waveform IDs and colour-related update flags.
crates/kobo-abi/src/lib.rs Defines colour-era waveform IDs and HWTCON flag constants used by the new colour refresh path.
crates/kobo-profile/src/lib.rs Adds colour_panel to device profiles and marks Kaleido profiles accordingly, with tests pinning naming/flag consistency.
crates/kobo-hal/src/surface.rs Adds ChannelOrder and RegionSnapshot::from_rgb to write RGB into the framebuffer byte layout safely.
crates/kobo-hal/src/refresh.rs Adds RefreshIntent::ColourContent and emits HWTCON colour-filter flags only for colour updates.
crates/kobo-hal/src/display.rs Offers colour capability (DisplaySession::colour) and downgrades colour intents on non-colour panels.
crates/kobo-ui/src/lib.rs Adds chroma plane to Surface, colour-aware planning (PanelWaveform::Colour), picture formats, caching, scaling and tests.
crates/kobo-protocol/src/lib.rs Adds PictureFormat to picture messages; introduces new tags for RGB pictures without bumping protocol VERSION.
crates/kobo-sdk/src/lib.rs Adds put_colour_picture and threads PictureFormat/pixels through the command/message pipeline.
crates/kobo-sim/src/lib.rs Accepts new picture messages and treats colour waveform as luminance on the BW preview panel.
crates/kobo-image/src/lib.rs Adds colour-preserving decode/encode and picture scaling that keeps grey+colour planes in step.
crates/kobod/src/main.rs Updates picture message handling to use format + pixels APIs.
crates/kobod/src/device.rs Writes RGB regions when a transition carries colour and the display reports a channel order; otherwise uses grey.
apps/morse/src/main.rs Updates tests for the greypixels field rename in Command::PutPicture.
Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/kobo-protocol/src/lib.rs
Comment thread crates/kobod/src/device.rs Outdated
Comment thread crates/kobo-ui/src/lib.rs
The painter copied the colour bytes of a region out of the frame and
then converted that copy into framebuffer bytes, holding two buffers
for one write. `Surface::colour_rows` now lends the rows straight out
of the chroma plane and `RegionSnapshot::from_rgb_rows` builds the
snapshot from them, so only the snapshot is allocated. A source with
more or fewer rows than the region, or a row of the wrong length, is
refused rather than cropped.

`DeviceIdentity::colour_panel` compares case-insensitively over the
name's bytes instead of lowering a copy of it, and the picture scaling
loop names the three bytes of a pixel explicitly rather than relying on
`zip` to stop reading.
@abhishek-anand

Copy link
Copy Markdown
Contributor Author

Blocked on a port rather than a conflict.

#79 (now on beta) extracted the frame planner into crates/kobod/src/frame.rs
with its own PanelWaveform. This PR added Colour and writes_colour() to the
older enum in crates/kobo-ui/src/lib.rs, which is no longer the planner that
paints. Merging beta in succeeds and then fails to build, because the two
enums are different files and git has no reason to object.

To land on beta the colour work needs moving onto the new planner:

  • Colour variant and writes_colour() on frame::PanelWaveform
  • per-region colour detection in plan and plan_damage (frame.rs:127-151)
  • colour in the waveform severity ranking and the full flag (frame.rs:267-278)
  • the planner colour tests moved across from kobo-ui

The runtime side is already reconciled locally: apply_region now decides colour
per region rather than per frame, so one transition can carry a colour picture and
a grey caption and each is written the way its own panel area wants.

Also failing independently of the merge: the host job's
reviewed compatible-change entries name the exact current files test, on a
content-hash mismatch (expected f34528d7, actual 6234e973) — a review ledger
entry naming a file that has since changed.

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.

2 participants