diff --git a/src/pages/index.astro b/src/pages/index.astro index b51ab72..3eace07 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -206,6 +206,34 @@ import Tooltip from "../components/Tooltip.astro"; + + +
+ + +`;
+ const charSet = CHAR_SETS[settings.charSet] || CHAR_SETS.standard;
+ const charCount = charSet.length;
+
+ for (let y = 0; y < heightRows; y++) {
+ for (let x = 0; x < widthCols; x++) {
+ const i = (y * widthCols + x) * 4;
+ const r = pixels[i];
+ const g = pixels[i + 1];
+ const b = pixels[i + 2];
+
+ // 1. Calculate Luma
+ let luma = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
+
+ // 2. Apply Adjustments
+ // Note: For color mode, we might want to keep original color
+ // but apply luma to alpha or just use luma for char selection.
+ // The shader uses `color` argument.
+
+ // Adjust color for display
+ const adjColor = this.adjustColor(r, g, b, settings);
+
+ // Recalculate luma from adjusted color for char selection
+ let finalLuma = (0.2126 * adjColor.r + 0.7152 * adjColor.g + 0.0722 * adjColor.b) / 255;
+
+ // Adjust luma curve for char selection (gamma/contrast/exposure) explicitly?
+ // Actually `adjustColor` does that.
+
+ if (settings.invert) finalLuma = 1.0 - finalLuma;
+
+ const charIndex = Math.floor(finalLuma * (charCount - 1) + 0.5);
+ const char = charSet[Math.max(0, Math.min(charCount - 1, charIndex))];
+
+ // Escape HTML
+ const safeChar = char === '<' ? '<' : char === '>' ? '>' : char === '&' ? '&' : char;
+
+ const colorStyle = `color: rgb(${Math.round(adjColor.r)}, ${Math.round(adjColor.g)}, ${Math.round(adjColor.b)})`;
+ output += `${safeChar}`;
+ }
+ output += "
";
+ }
+ output += "";
+ return output;
+ }
+
+ private static getPixels(img: HTMLImageElement, width: number, height: number) {
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) throw new Error("Canvas 2D context not available");
+
+ // High quality downscaling
+ ctx.imageSmoothingEnabled = true;
+ ctx.imageSmoothingQuality = 'high';
+ ctx.drawImage(img, 0, 0, width, height);
+
+ return {
+ pixels: ctx.getImageData(0, 0, width, height).data,
+ ctx
+ };
+ }
+
+
+
+ private static adjustColor(r: number, g: number, b: number, settings: AsciiSettings): { r: number, g: number, b: number } {
+ let rNorm = r / 255;
+ let gNorm = g / 255;
+ let bNorm = b / 255;
+
+ // Exposure
+ rNorm *= settings.exposure;
+ gNorm *= settings.exposure;
+ bNorm *= settings.exposure;
+
+ // Contrast
+ rNorm = (rNorm - 0.5) * settings.contrast + 0.5;
+ gNorm = (gNorm - 0.5) * settings.contrast + 0.5;
+ bNorm = (bNorm - 0.5) * settings.contrast + 0.5;
+
+ // Saturation
+ const luma = 0.2126 * rNorm + 0.7152 * gNorm + 0.0722 * bNorm;
+ rNorm = luma + (rNorm - luma) * settings.saturation;
+ gNorm = luma + (gNorm - luma) * settings.saturation;
+ bNorm = luma + (bNorm - luma) * settings.saturation;
+
+ // Gamma
+ rNorm = Math.pow(Math.max(0, rNorm), settings.gamma);
+ gNorm = Math.pow(Math.max(0, gNorm), settings.gamma);
+ bNorm = Math.pow(Math.max(0, bNorm), settings.gamma);
+
+ return {
+ r: Math.max(0, Math.min(255, rNorm * 255)),
+ g: Math.max(0, Math.min(255, gNorm * 255)),
+ b: Math.max(0, Math.min(255, bNorm * 255))
+ };
+ }
+}
diff --git a/src/scripts/ascii-shared.ts b/src/scripts/ascii-shared.ts
index e9ca76a..866aecf 100644
--- a/src/scripts/ascii-shared.ts
+++ b/src/scripts/ascii-shared.ts
@@ -42,6 +42,21 @@ export interface ImageMetadata {
has_fine_detail?: boolean;
}
+export interface AsciiSettings {
+ exposure: number;
+ contrast: number;
+ saturation: number;
+ gamma: number;
+ invert: boolean;
+ color: boolean;
+ dither: number;
+ denoise: boolean;
+ edgeMode: number;
+ overlayStrength: number;
+ resolution: number;
+ charSet: CharSetKey;
+}
+
// ============= Constants =============
export const CHAR_SETS: Record