Skip to content

feat: replace 2× supersampling with 1× bitmap fonts and explicit dithering#80

Open
ghcpuman902 wants to merge 9 commits into
mainfrom
feat/pixel-font-rendering
Open

feat: replace 2× supersampling with 1× bitmap fonts and explicit dithering#80
ghcpuman902 wants to merge 9 commits into
mainfrom
feat/pixel-font-rendering

Conversation

@ghcpuman902

@ghcpuman902 ghcpuman902 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR replaces the old 2× supersampling + blanket dither/heuristic pipeline with 1× rendering and targeted crispness tools: traced bitmap fonts for text, palette-aware photo dithering at data-fetch time, and opt-in Floyd-Steinberg only where a recipe explicitly asks for it.

Render pipeline change (important)

Before (on main):

  • Recipes like hacker-news, calendar, simple-text, and wikipedia set renderSettings: { supersample: true }.
  • getRenderScale() (lib/recipes/render/settings.ts) rendered Takumi/Satori output at device resolution, then downscaled with sharp back to physical pixels.
  • The legacy BMP path defaulted to Floyd-Steinberg + edge-snap (applyEdgeSnap ?? true) as a global sharpness hack.
  • This was never wired per-recipe in a meaningful way — it was a blanket workaround for soft vector text on e-ink.

After (this PR):

  • Always render at 1× physical resolution. supersample and lib/recipes/render/settings.ts are removed.
  • BMP edge-snap defaults to off (applyEdgeSnap ?? false).
  • Crispness is handled explicitly instead of via supersampling:
    1. Bitmap fonts — baseline-relative v2 format, tracing pipeline, BitmapText, and packs (Block Kie, Geist Pixel Grid/Square, Geneva, Pixelify Sans) for pixel-aligned text at native resolution.
    2. Recipe prepareForDevice — album and wikipedia pre-dither embedded photos with palette-aware white-noise dithering (ditherImageToDataUrl) before JSX render, using the device’s gray levels.
    3. Opt-in device ditherrenderSettings.dither: true enables Floyd-Steinberg during palette quantization in renderDeviceImage (default off). No recipe sets this yet; photos use prepareForDevice instead.

Expected visual change: Photo recipes (album, wikipedia thumbnails) should improve. Text-heavy recipes still on Takumi/Satori vector fonts (hacker-news, calendar, local-news, wikipedia body) may look softer until migrated to BitmapText — that is intentional; 2× downscale was masking the problem rather than fixing it.

Also includes: bitmap font designer overhaul, /api/bitmap-fonts/[packId], album city presets, TRMNL MAC setup fix, shared formatErrorMessage, README local-setup notes, and docs/pixel-font.md spec.

Test plan

  • pnpm lint
  • pnpm typecheck
  • pnpm test (105 tests)
  • pnpm build
  • Compare device preview before/after on a recipe that previously used supersample: true (e.g. hacker-news, simple-text) — expect softer vector text until bitmap font migration
  • Album / wikipedia — verify dithered photos on 1-bit and multi-gray palettes
  • Bitmap font designer: edit glyph, preview paragraph layout, export pack
  • BitmapText on simple-text / device preview — baseline alignment
  • pnpm run generate:fonts
  • Device setup MAC recognition on real TRMNL hardware

Follow-up

  • Migrate remaining vector-font recipes (wikipedia, hacker-news, calendar, local-news) to traced BitmapText packs for fully crisp 1× text.
  • Coordinate with open PR Fix/remove grayscale parameter #78 (fix/remove-grayscale-parameter) if both touch the render/device path.

ghcpuman902 and others added 9 commits June 24, 2026 13:23
- 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>
@ghcpuman902 ghcpuman902 self-assigned this Jun 25, 2026
@ghcpuman902

Copy link
Copy Markdown
Collaborator Author

Render pipeline — commit trail

For reviewers tracing the 2× removal vs new dithering:

Commit What changed
6794844 Removes 2× supersampling: deletes lib/recipes/render/settings.ts, drops supersample: true from recipe meta, renders at 1× in rasterize.ts (no sharp downscale), flips BMP applyEdgeSnap default to false, wires opt-in renderSettings.dither into renderDeviceImage.
7fbadfb Adds explicit photo dithering: ditherImageToDataUrl + prepareForDevice hook on album/wikipedia; white-noise dither tuned to palette gray levels (getPaletteGrayLevels). This replaces the old “dither everything at BMP export” approach for embedded images.
37e98f0+ Bitmap font path for crisp 1× text (designer, packs, BitmapText, weather BitmapMarker).

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-snap

What replaced it:

  • Text → pixel font packs at 1× (not downscaled vector)
  • Photos → prepareForDevice dithers source images before they enter JSX
  • Full-frame palette quantization → Floyd-Steinberg only when renderSettings.dither: true (currently unused; default off)

Release: merge to main → release workflow auto-bumps patch, tags v*.*.*, publishes GitHub Release + Docker. No version change in this branch; do not [skip release] unless intentionally holding back a user-visible release.

@ghcpuman902 ghcpuman902 changed the title feat: pixel font rendering, dithering, and recipe text crispness feat: replace 2× supersampling with 1× bitmap fonts and explicit dithering Jun 25, 2026
@ghcpuman902 ghcpuman902 requested a review from rbouteiller June 25, 2026 23:25

@rbouteiller rbouteiller left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems we have some scripts repeated in scripts/lib and lib/bitmap-font, maybe we want an unified surface

Comment thread tmp/geist.v2.json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This file seems not needed and not referenced in the code, should we delete it ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SHould we commit generated files ? Or have a generation step in readme ?

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