feat: Add background color setting and apply it to HTML and WebGL output.
This commit is contained in:
@@ -194,6 +194,19 @@ import { ChevronDown } from "@lucide/astro";
|
|||||||
<span class="label">TINT</span>
|
<span class="label">TINT</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Background Color Picker -->
|
||||||
|
<div class="tui-color-btn" title="Background Color">
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="input-bg-color"
|
||||||
|
value="#000000"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
id="bg-color-swatch-display"
|
||||||
|
class="color-swatch"></span>
|
||||||
|
<span class="label">BG</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TuiToggle
|
<TuiToggle
|
||||||
id="toggle-denoise"
|
id="toggle-denoise"
|
||||||
label="Denoise"
|
label="Denoise"
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ export class AsciiController {
|
|||||||
highlights: 0,
|
highlights: 0,
|
||||||
scanlines: 0,
|
scanlines: 0,
|
||||||
vignette: 0,
|
vignette: 0,
|
||||||
monoColor: '#ffffff'
|
monoColor: '#ffffff',
|
||||||
|
backgroundColor: '#000000'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +261,8 @@ export class AsciiController {
|
|||||||
highlights: suggestions.highlights ?? this.settings.highlights,
|
highlights: suggestions.highlights ?? this.settings.highlights,
|
||||||
scanlines: suggestions.scanlines ?? this.settings.scanlines,
|
scanlines: suggestions.scanlines ?? this.settings.scanlines,
|
||||||
vignette: suggestions.vignette ?? this.settings.vignette,
|
vignette: suggestions.vignette ?? this.settings.vignette,
|
||||||
monoColor: suggestions.monoColor ?? this.settings.monoColor
|
monoColor: suggestions.monoColor ?? this.settings.monoColor,
|
||||||
|
backgroundColor: suggestions.backgroundColor ?? this.settings.backgroundColor
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onSettingsChange?.();
|
this.onSettingsChange?.();
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export class AsciiExporter {
|
|||||||
heightRows: number
|
heightRows: number
|
||||||
): string {
|
): string {
|
||||||
const { pixels } = this.getPixels(img, widthCols, heightRows);
|
const { pixels } = this.getPixels(img, widthCols, heightRows);
|
||||||
let output = `<pre style="font-family: monospace; line-height: 1; letter-spacing: 0; background-color: #000; color: #fff; font-size: 8px;">`;
|
let output = `<pre style="font-family: monospace; line-height: 1; letter-spacing: 0; background-color: ${settings.backgroundColor}; color: #fff; font-size: 8px;">`;
|
||||||
const charSet = CHAR_SETS[settings.charSet] || CHAR_SETS.standard;
|
const charSet = CHAR_SETS[settings.charSet] || CHAR_SETS.standard;
|
||||||
const charCount = charSet.length;
|
const charCount = charSet.length;
|
||||||
|
|
||||||
@@ -101,13 +101,11 @@ export class AsciiExporter {
|
|||||||
|
|
||||||
// Adjust color for display
|
// Adjust color for display
|
||||||
const adjColor = this.adjustColor(r, g, b, settings);
|
const adjColor = this.adjustColor(r, g, b, settings);
|
||||||
|
const finalColor = settings.color ? adjColor : this.hexToRgb(settings.monoColor);
|
||||||
|
|
||||||
// Recalculate luma from adjusted color for char selection
|
// Recalculate luma from adjusted color for char selection
|
||||||
let finalLuma = (0.2126 * adjColor.r + 0.7152 * adjColor.g + 0.0722 * adjColor.b) / 255;
|
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;
|
if (settings.invert) finalLuma = 1.0 - finalLuma;
|
||||||
|
|
||||||
const charIndex = Math.floor(finalLuma * (charCount - 1) + 0.5);
|
const charIndex = Math.floor(finalLuma * (charCount - 1) + 0.5);
|
||||||
@@ -116,7 +114,7 @@ export class AsciiExporter {
|
|||||||
// Escape HTML
|
// Escape HTML
|
||||||
const safeChar = char === '<' ? '<' : char === '>' ? '>' : char === '&' ? '&' : char;
|
const safeChar = char === '<' ? '<' : char === '>' ? '>' : char === '&' ? '&' : char;
|
||||||
|
|
||||||
const colorStyle = `color: rgb(${Math.round(adjColor.r)}, ${Math.round(adjColor.g)}, ${Math.round(adjColor.b)})`;
|
const colorStyle = `color: rgb(${Math.round(finalColor.r)}, ${Math.round(finalColor.g)}, ${Math.round(finalColor.b)})`;
|
||||||
output += `<span style="${colorStyle}">${safeChar}</span>`;
|
output += `<span style="${colorStyle}">${safeChar}</span>`;
|
||||||
}
|
}
|
||||||
output += "<br>";
|
output += "<br>";
|
||||||
@@ -177,4 +175,11 @@ export class AsciiExporter {
|
|||||||
b: Math.max(0, Math.min(255, bNorm * 255))
|
b: Math.max(0, Math.min(255, bNorm * 255))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static hexToRgb(hex: string): { r: number, g: number, b: number } {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16);
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16);
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16);
|
||||||
|
return { r, g, b };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface AsciiOptions {
|
|||||||
scanlines?: number;
|
scanlines?: number;
|
||||||
vignette?: number;
|
vignette?: number;
|
||||||
monoColor?: string;
|
monoColor?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AsciiResult {
|
export interface AsciiResult {
|
||||||
@@ -69,6 +70,7 @@ export interface AsciiSettings {
|
|||||||
scanlines: number;
|
scanlines: number;
|
||||||
vignette: number;
|
vignette: number;
|
||||||
monoColor: string;
|
monoColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= Constants =============
|
// ============= Constants =============
|
||||||
|
|||||||
@@ -243,6 +243,19 @@ export class UIBindings {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bgColorInput = document.getElementById('input-bg-color') as HTMLInputElement;
|
||||||
|
const bgColorSwatch = document.getElementById('bg-color-swatch-display');
|
||||||
|
|
||||||
|
if (bgColorInput) {
|
||||||
|
bgColorInput.addEventListener('input', () => {
|
||||||
|
if (this.isUpdatingUI) return;
|
||||||
|
this.controller.setSetting('backgroundColor', bgColorInput.value);
|
||||||
|
if (bgColorSwatch) {
|
||||||
|
bgColorSwatch.style.backgroundColor = bgColorInput.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= Segments =============
|
// ============= Segments =============
|
||||||
@@ -552,6 +565,18 @@ export class UIBindings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bgColorInput = document.getElementById('input-bg-color') as HTMLInputElement;
|
||||||
|
const bgColorSwatch = document.getElementById('bg-color-swatch-display');
|
||||||
|
|
||||||
|
if (bgColorInput && settings.backgroundColor) {
|
||||||
|
if (bgColorInput.value !== settings.backgroundColor) {
|
||||||
|
bgColorInput.value = settings.backgroundColor;
|
||||||
|
}
|
||||||
|
if (bgColorSwatch) {
|
||||||
|
bgColorSwatch.style.backgroundColor = settings.backgroundColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.updateQueueDisplay();
|
this.updateQueueDisplay();
|
||||||
|
|
||||||
this.isUpdatingUI = false;
|
this.isUpdatingUI = false;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface RenderOptions {
|
|||||||
scanlines?: number;
|
scanlines?: number;
|
||||||
vignette?: number;
|
vignette?: number;
|
||||||
monoColor?: string;
|
monoColor?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
zoomCenter?: { x: number; y: number };
|
zoomCenter?: { x: number; y: number };
|
||||||
mousePos?: { x: number; y: number };
|
mousePos?: { x: number; y: number };
|
||||||
@@ -120,6 +121,7 @@ export class WebGLAsciiRenderer {
|
|||||||
uniform float u_scanlines;
|
uniform float u_scanlines;
|
||||||
uniform float u_vignette;
|
uniform float u_vignette;
|
||||||
uniform vec3 u_monoColor;
|
uniform vec3 u_monoColor;
|
||||||
|
uniform vec3 u_backgroundColor;
|
||||||
|
|
||||||
uniform float u_zoom;
|
uniform float u_zoom;
|
||||||
uniform vec2 u_zoomCenter;
|
uniform vec2 u_zoomCenter;
|
||||||
@@ -323,7 +325,7 @@ export class WebGLAsciiRenderer {
|
|||||||
finalColor = u_monoColor;
|
finalColor = u_monoColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
gl_FragColor = vec4(finalColor * charAlpha, charAlpha);
|
gl_FragColor = vec4(mix(u_backgroundColor, finalColor, charAlpha), 1.0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -358,7 +360,7 @@ export class WebGLAsciiRenderer {
|
|||||||
'u_invert', 'u_color', 'u_overlayStrength', 'u_edgeMode',
|
'u_invert', 'u_color', 'u_overlayStrength', 'u_edgeMode',
|
||||||
'u_dither', 'u_denoise',
|
'u_dither', 'u_denoise',
|
||||||
'u_sharpen', 'u_edgeThreshold', 'u_shadows', 'u_highlights',
|
'u_sharpen', 'u_edgeThreshold', 'u_shadows', 'u_highlights',
|
||||||
'u_scanlines', 'u_vignette', 'u_monoColor',
|
'u_scanlines', 'u_vignette', 'u_monoColor', 'u_backgroundColor',
|
||||||
'u_zoom', 'u_zoomCenter', 'u_mousePos',
|
'u_zoom', 'u_zoomCenter', 'u_mousePos',
|
||||||
'u_magnifierRadius', 'u_magnifierZoom', 'u_showMagnifier', 'u_aspect'
|
'u_magnifierRadius', 'u_magnifierZoom', 'u_showMagnifier', 'u_aspect'
|
||||||
];
|
];
|
||||||
@@ -515,6 +517,12 @@ export class WebGLAsciiRenderer {
|
|||||||
const b = parseInt(hex.slice(5, 7), 16) / 255.0;
|
const b = parseInt(hex.slice(5, 7), 16) / 255.0;
|
||||||
gl.uniform3f(u['u_monoColor'], r, g, b);
|
gl.uniform3f(u['u_monoColor'], r, g, b);
|
||||||
|
|
||||||
|
const bgHex = options.backgroundColor || '#000000';
|
||||||
|
const br = parseInt(bgHex.slice(1, 3), 16) / 255.0;
|
||||||
|
const bg = parseInt(bgHex.slice(3, 5), 16) / 255.0;
|
||||||
|
const bb = parseInt(bgHex.slice(5, 7), 16) / 255.0;
|
||||||
|
gl.uniform3f(u['u_backgroundColor'], br, bg, bb);
|
||||||
|
|
||||||
gl.uniform1f(u['u_zoom'], options.zoom || 1.0);
|
gl.uniform1f(u['u_zoom'], options.zoom || 1.0);
|
||||||
gl.uniform2f(u['u_zoomCenter'], options.zoomCenter?.x ?? 0.5, options.zoomCenter?.y ?? 0.5);
|
gl.uniform2f(u['u_zoomCenter'], options.zoomCenter?.x ?? 0.5, options.zoomCenter?.y ?? 0.5);
|
||||||
gl.uniform2f(u['u_mousePos'], options.mousePos?.x ?? -1.0, options.mousePos?.y ?? -1.0);
|
gl.uniform2f(u['u_mousePos'], options.mousePos?.x ?? -1.0, options.mousePos?.y ?? -1.0);
|
||||||
|
|||||||
Reference in New Issue
Block a user