diff --git a/frontend/src/lib/components/PhotoEditPanel.svelte b/frontend/src/lib/components/PhotoEditPanel.svelte index 13370cc..df98d2c 100644 --- a/frontend/src/lib/components/PhotoEditPanel.svelte +++ b/frontend/src/lib/components/PhotoEditPanel.svelte @@ -2,6 +2,7 @@ import { photosApi } from '$lib/api'; import Icon from './Icon.svelte'; import { edit } from '$lib/stores/edit.svelte'; + import { applyLight } from '$lib/light-wasm'; import { CHANNEL_COLORS, CHANNEL_LABELS, @@ -53,21 +54,22 @@ }; }); - // Гистограмма каналов считается по базовому превью фото. + // Гистограмма каналов считается по базовому превью фото и пересчитывается + // с учётом текущих правок, поэтому реагирует на движение ползунков и кривой. type Histogram = { red: number[]; green: number[]; blue: number[] }; - let histogram = $state(null); + let sample = $state(null); $effect(() => { const url = photo.preview_url; if (!url) { - histogram = null; + sample = null; return; } let cancelled = false; const img = new Image(); img.onload = () => { if (cancelled) return; - histogram = computeHistogram(img); + sample = sampleImage(img); }; img.src = url; return () => { @@ -75,8 +77,8 @@ }; }); - /** Считает гистограмму каналов по уменьшенной копии фото. */ - function computeHistogram(img: HTMLImageElement): Histogram | null { + /** Уменьшенная копия фото: пиксели для расчёта гистограммы. */ + function sampleImage(img: HTMLImageElement): Uint8ClampedArray | null { const width = 256; const height = Math.max(1, Math.round((img.naturalHeight / img.naturalWidth) * width)); const canvas = document.createElement('canvas'); @@ -85,7 +87,15 @@ const ctx = canvas.getContext('2d'); if (!ctx) return null; ctx.drawImage(img, 0, 0, width, height); - const data = ctx.getImageData(0, 0, width, height).data; + return new Uint8ClampedArray(ctx.getImageData(0, 0, width, height).data); + } + + /** Гистограмма с учётом текущих правок. */ + const histogram = $derived.by(() => { + const pixels = sample; + if (!pixels) return null; + const data = new Uint8ClampedArray(pixels); + applyLight(data, params.light); const r = new Array(256).fill(0); const g = new Array(256).fill(0); const b = new Array(256).fill(0); @@ -101,7 +111,7 @@ if (!max) return null; const norm = (values: number[]) => values.map((v) => v / max); return { red: norm(r), green: norm(g), blue: norm(b) }; - } + }); /** Каналы, которые показываем на графике: у мастер-канала — все три. */ const visibleChannels = $derived(