feat: replace 2× supersampling with 1× bitmap fonts and explicit dithering - #80
feat: replace 2× supersampling with 1× bitmap fonts and explicit dithering#80ghcpuman902 wants to merge 19 commits into
Conversation
Render pipeline — commit trailFor reviewers tracing the 2× removal vs new dithering:
What was removed (the old blanket pipeline): // lib/recipes/render/settings.ts (deleted)
export const SUPERSAMPLE_SCALE = 2;
// recipes: renderSettings: { supersample: true }
// rasterize: render 2× → sharp.resize(1×) → BMP with edge-snapWhat replaced it:
Release: merge to |
rbouteiller
left a comment
There was a problem hiding this comment.
It seems we have some scripts repeated in scripts/lib and lib/bitmap-font, maybe we want an unified surface
There was a problem hiding this comment.
This file seems not needed and not referenced in the code, should we delete it ?
There was a problem hiding this comment.
Yes — deleted. It was a one-off trace/debug output, not referenced anywhere.
There was a problem hiding this comment.
SHould we commit generated files ? Or have a generation step in readme ?
There was a problem hiding this comment.
Short answer: yes, commit the packs — but treat them as curated artifacts, not build output.
(I saw you removed generated schema artifacts in #78 in favor of validation at runtime — same instinct applies here, but font packs are different: the trace output is lossy and we hand-tune in the designer before commit.)
The trace pipeline (pnpm generate:fonts) is useful as an assistant to bootstrap a pack, but it is not reliable enough to run unattended or overwrite committed JSON on every build:
- Lossy tracing — many “pixel vibe” web fonts do not snap cleanly to a grid; gaps, x-height, and descenders often need manual fixes in the bitmap font designer.
- Missing glyphs — symbols and edge-case characters frequently need to be added or redrawn by hand after generation.
- Algorithm limits — grid discovery and metrics derivation work for some sources (e.g. Geist Pixel) but not universally; we keep hitting font-specific issues.
So the practical workflow today is: trace → human pass in the designer → commit the tuned JSON. Generation is a starting point, not the source of truth.
This PR updates docs (docs/pixel-font.md §17) and removes generate:fonts from prebuild / dev so a normal pnpm build does not clobber hand-edited packs. Regeneration stays available explicitly via pnpm generate:fonts when adding a new font or re-bootstrapping from source.
Longer term (not blocking this PR): either improve tracing until it is good enough to automate, or drop generation entirely and treat all packs as hand-authored in the designer. Happy to discuss which direction you prefer.
|
Agreed — the duplicated modules were a mistake from porting trace logic to TS for runtime/tests while leaving This commit removes the duplicates in
|
|
Sorry, I should have read #78 more carefully before pushing #80; I've now gone through it properly. I agree we should rethink the render path together. #78 reworks the palette/grayscale model my dithering sits on, so let's land #78 first; I'll then rebase #80 on top and reconcile dithering with palette-derived levels. For future reference:
Reference: TRMNL Framework v3.1 Image docs, Structure, Colors. Addressed review feedback:
Rendering alignment proposal:
Our default path stays Takumi-first (no browser head); palette quantization + dither levels follow the palette the same way Framework |
Agree
Yes
I thought it was already the case, will fix
Yes exactly |
|
Aligned on palette SSOT and Scope split: #78 — foundation
#80 — this PR (rebase after #78)
#80 — image dither (opt-in only, not global FS) For embedded photos only, authors choose:
|
|
@ghcpuman902 Merged the others PR, there might be some redundancy around image dithering |
- Introduced a new utility function `formatErrorMessage` to standardize error messages across the application. - Updated `executeSqlStatements` and database utility functions to utilize the new error formatting. - Revised README to clarify local development setup, including database configuration and initial setup instructions.
- Added new scripts for generating bitmap fonts and benchmarking font performance. - Updated package.json to include new font generation commands and dependencies. - Improved no-default-recipe error messaging and device add dialog UX. - Introduced new font styles in global CSS and updated components to utilize these styles. - Improved sign-in form with password visibility toggle for better user experience. Co-authored-by: Cursor <cursoragent@cursor.com>
TRMNL firmware calls /api/setup with MAC only (no Access-Token). Allow pre-registered devices imported from trmnl.com/devices to complete setup, add a MAC field when adding devices manually, and log incoming request headers to system logs for debugging. Co-authored-by: Cursor <cursoragent@cursor.com>
… designer - Introduced AGENTS.md to provide a comprehensive index for Next.js documentation, emphasizing the importance of consulting the docs before tasks. - Added a new script command for benchmarking font glyphs in package.json. - Enhanced bitmap font designer with new utility functions for handling font metrics and improved grid handling. - Updated bitmap font JSON files to support dynamic width and adjusted metrics for better rendering. - Introduced new generated bitmap font files for improved design consistency.
- Added new font variable `--font-pixelify-sans` to global CSS for improved typography options. - Updated `RootLayout` to suppress hydration warnings for better rendering consistency. - Enhanced `SimpleText` component to utilize `Pixelify Sans` font and added support for new bitmap font data. - Improved bitmap font designer with additional utility functions and updated editor UI for better glyph management. - Adjusted metrics and grid handling in bitmap font utilities for enhanced performance and accuracy.
- Removed supersampling settings from multiple recipe definitions to streamline rendering. - Updated global CSS to ensure proper formatting and consistency across components. - Enhanced styles in the ResponsiveExample component for better visual presentation. - Cleaned up unnecessary code and improved readability in bitmap font designer components.
- Added ditherImageToDataUrl function for converting images to dithered data URLs, improving visual quality for grayscale images. - Integrated dithering functionality into recipe rendering, allowing for dynamic image processing based on device context. - Updated recipe definitions to include prepareForDevice method for enhanced image handling. - Enhanced Wikipedia and Album components to utilize dithered images, improving overall presentation. - Introduced tests for dithering functionality to ensure reliability and performance.
- Added a new module for album city data, including city IDs, labels, timezones, and Wikipedia titles. - Updated the album component to utilize the new city data, allowing for dynamic city selection and improved presentation. - Enhanced data fetching logic to retrieve city-specific images and information from Wikipedia. - Refactored the album component to support optional custom image URLs and improved layout for better user experience. - Introduced a new utility function for resolving image URLs based on city data and custom inputs.
Apply Biome auto-fixes, align convert-legacy-font tests with defaultCharGap metrics, regenerate font packs, and remove the PR draft notes file. Co-authored-by: Cursor <cursoragent@cursor.com>
Delete one-off trace debug output; nothing in the repo references it. Co-authored-by: Cursor <cursoragent@cursor.com>
Remove duplicated scripts/lib/*.mjs modules and import shared logic from lib/bitmap-font/*.ts via a Node strip-types resolve hook. Extend trace-core and metrics-derive with script-only helpers previously duplicated in .mjs. Co-authored-by: Cursor <cursoragent@cursor.com>
Document trace → designer → commit workflow in docs/pixel-font.md. Remove generate:fonts from prebuild and dev so pnpm build cannot overwrite curated packs; keep pnpm generate:fonts as an explicit manual assistant step. Co-authored-by: Cursor <cursoragent@cursor.com>
Rebase onto main (#78 palette SSOT, snap quantizer) and merge the overlapping image paths into one surface: - prepareDeviceImage / prepareDeviceImageFromContext (default Bayer) - Author-selectable methods: bayer, white-noise, floyd-steinberg, etc. - Color e-ink: bayerRgbToPalette in palette-reduction - Gray paths use Lab L* luminance (lib/render/luminance.ts) - RecipeDeviceContext carries levels + colorPalette via buildRecipeDeviceContext - DeviceImage component; album/wikipedia use unified prep - responsive-example: dither-* on 1-bit, solid colors on color screens - Remove frame-wide dither flag; keep 1× rasterize (no supersample) - Document e-ink palette mapping and update image dither docs Co-authored-by: Cursor <cursoragent@cursor.com>
e95ec83 to
c7b3ffb
Compare
|
Rebased onto main (#78 merged). Redundancy resolved by merging into one device-aware image surface:
Full-frame quantize stays hard snap from #78; dithering is image-scoped only. |
Remove leftover unused resolveReactRecipe call, drop unused palette param from prepareDeviceImageFromContext, prefix intentionally unused weather/designer destructuring vars, fix redundant JSX fragment, and organize imports per Biome. Co-authored-by: Cursor <cursoragent@cursor.com>
When local schema is behind code after a rebase, the setup panel now surfaces only pending migration SQL and an apply action instead of the full initialize flow, and the dashboard badge reads setup required. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Have you tested bayer dithering on your trmnl ? |
… page - Refactored the handling of device API server URLs to use a new structure, enhancing clarity and maintainability. - Updated the NotFound component to replace the Next.js Image component with a standard img element, optimizing performance for a static 404 page. - Adjusted image dimensions for better display on the NotFound page. - Improved error messaging for device API server URL configuration, ensuring clearer guidance for users. Co-authored-by: Cursor <cursoragent@cursor.com>
- Updated image preparation to ensure dithering occurs at the physical screen pixel size, preventing resampling artifacts. - Introduced a new utility function, `toPhysicalImageDimensions`, to convert logical dimensions to physical dimensions based on pixel ratio. - Adjusted image dither policy to enforce a single dither method, improving clarity and consistency in image rendering. - Enhanced recipe rendering logic to utilize the new physical dimension calculations, ensuring accurate image display across devices. - Added tests for the new image dimension utility to validate functionality. Co-authored-by: Cursor <cursoragent@cursor.com>
- Updated the AppSidebar component to enhance the clarity of LAN IP instructions. - Refactored HealthChip component to use a new HealthChipProps type for better type management. - Simplified the getLocalLanIPv4 function by consolidating return statements. - Adjusted the getConfiguredBaseUrl function for improved readability. - Enhanced regex in parseDataUrl function to handle whitespace in data URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
- Introduced a new function to resolve bitmap font pack disk paths, ensuring proper validation and clarity. - Updated the GET request handler to utilize the new path resolution function for improved maintainability. - Added a check for the BETTER_AUTH_SECRET in the authentication process to enhance security. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Ok, my bad, it's working on my TRMNL X and not inverted |

Summary
Rebased onto
mainafter #78 (palette SSOT, hard-snap device quantizer, image intercept). This PR keeps bitmap fonts + 1× rendering and replaces the overlapping image-dither paths with one device-aware surface.Render pipeline
Removed / dropped (now on
mainor redundant):getRenderScale,settings.ts, per-recipesupersample)renderSettings.dither)device-imagequantizer andgetPaletteGrayLevelscopyKept from this PR:
BitmapText, designer, curated packs)Unified image dithering (new):
prepareDeviceImage/prepareDeviceImageFromContextbayer(default),white-noise,floyd-steinberg,atkinson,threshold,snap,none<img>auto-prep +prepareForDeviceshare the same implementationbayerRgbToPalette)<DeviceImage>component documents author intent; album/wikipedia updatedAuthoring:
responsive-example:dither-*patterns on 1-bit BW; solid Tailwind colors on color screensdocs/trmnl/eink-palette-mapping.mdmaps TRMNL device families ↔palettes.jsonFollow-up fixes (post-rebase):
0021frommain), show Apply pending migrations + pending SQL only — not full “Initialize database”Expected behaviour
Test plan
pnpm lint/pnpm typecheck/pnpm test(136) /pnpm build/recipes/responsive-exampleon BW vscolor-6a— dither panels vs solid colors/recipes/album,/recipes/wikipedia— photo prep on gray + color palettes/recipes/simple-text,/recipes/bitmap-patterns— BitmapText preview/api/bitmap-fonts/[packId]0021) — apply + refresh restores devicesNotes for reviewers
Resolves overlap with #78’s image intercept: same wiring, default algorithm is now Bayer (not FS). Floyd–Steinberg remains available via
renderSettings.imageDither: "floyd-steinberg"orprepareForDevicemethod.