From c5d8d8bef438f7214636fd5cca59985f66db84bf Mon Sep 17 00:00:00 2001 From: chodaict Date: Wed, 3 Jun 2026 00:56:33 +0900 Subject: [PATCH 1/2] feat(ascii): add braille, half-block, and edge fill modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The renderer only did square density-glyph fills. Add a `mode` option with three smoother methods, all returning the same Cell[][] so the existing plain/HTML/ANSI serializers keep working: - braille (⣿): 2×4 dot matrix per cell — 8× resolution, curves read smooth - halfblock (▀): split each cell top/bottom; mono uses ▀▄█, coloured carries the top colour as foreground and the bottom as background (two colours + double vertical detail). Serializers gain background-colour support (HTML `background:`, ANSI `48;2`), with spaces/line-ends resetting cleanly. - edge (╱): Sobel edge detect, then stroke the contour with direction glyphs (─ │ ╱ ╲) and leave flats blank — a hand-drawn outline. Crisp on vector input since the raster is clean, so gradient angles are accurate. mode defaults to 'ramp': the original path is byte-identical (14 existing tests unchanged). Studio's charset picker becomes a Style picker grouped into Density ramp / Smooth; seam-fill applies only to the solid-block tilers. 12 new unit tests pin the sub-cell resolution, two-colour carriers, and edge direction mapping. 204/204 tests pass. Co-Authored-By: Claude Opus 4.8 --- src/components/Studio.svelte | 44 +++-- src/lib/ascii.test.ts | 103 ++++++++++ src/lib/ascii.ts | 370 ++++++++++++++++++++++++++++++----- 3 files changed, 456 insertions(+), 61 deletions(-) diff --git a/src/components/Studio.svelte b/src/components/Studio.svelte index 8e0ca82..01da50e 100644 --- a/src/components/Studio.svelte +++ b/src/components/Studio.svelte @@ -73,7 +73,7 @@ import { History } from '~/lib/history'; import { detectBackgroundColor, type PathBox } from '~/lib/background'; import { applyEffects, type EffectOptions } from '~/lib/effects'; - import { imageToAscii, type AsciiColor, TERMINAL_CELL_ASPECT } from '~/lib/ascii'; + import { imageToAscii, type AsciiColor, type AsciiMode, TERMINAL_CELL_ASPECT } from '~/lib/ascii'; import { previewView, hasImage } from '~/lib/view-store'; import { buildSizeSet, DEFAULT_SCALES } from '~/lib/export-set'; import { @@ -1136,6 +1136,7 @@ return imageToAscii(id.data, id.width, id.height, { cols: asciiCols, charAspect: TERMINAL_CELL_ASPECT, + mode: asciiMode, ramp: asciiRamp, invert: asciiInvert, color, @@ -1211,7 +1212,21 @@ let asciiBusy = $state(false); let asciiSeq = 0; let lastAsciiSig = ''; // inputs that produced the current asciiArt - let asciiRamp = $state<'standard' | 'blocks' | 'detailed'>('standard'); + // ASCII style = fill method. The three ramp styles drive the density-glyph + // renderer (`ramp` mode); braille/halfblock/edge are the smoother sub-cell and + // contour modes. One picker, mapped to imageToAscii's {mode, ramp}. + type AsciiStyle = 'standard' | 'blocks' | 'detailed' | 'braille' | 'halfblock' | 'edge'; + let asciiStyle = $state('standard'); + const asciiMode = $derived( + asciiStyle === 'braille' || asciiStyle === 'halfblock' || asciiStyle === 'edge' + ? asciiStyle + : 'ramp', + ); + // Ramp name only matters in 'ramp' mode; harmless default otherwise. + const asciiRamp = $derived(asciiMode === 'ramp' ? (asciiStyle as string) : 'standard'); + // Seam-fill only the solid-block tilers (█ and ▀▄█); braille is discrete dots + // that read best crisp, and edge strokes are sparse — dilating them muddies. + const asciiTight = $derived(asciiStyle === 'blocks' || asciiStyle === 'halfblock'); let asciiInvert = $state(false); let asciiColor = $state(false); // keep each glyph's source colour (web + ANSI) // Measured advance ratio (glyph width ÷ font-size) of the preview's monospace @@ -1244,7 +1259,7 @@ const sig = [ displaySvg, asciiCols, - asciiRamp, + asciiStyle, asciiInvert, asciiColor, backdrop.color, @@ -2408,11 +2423,18 @@ {asciiCols} cols - + {#if isVector} + + {:else} + + {/if}