57 lines
2.2 KiB
TypeScript
57 lines
2.2 KiB
TypeScript
import { FILTER_PRESETS, ZERO_ADJUSTMENTS } from '../constants';
|
|
import type { EditAdjustments, EditState } from '../types';
|
|
|
|
const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));
|
|
|
|
function resolveAdjustments(edits: EditState): EditAdjustments {
|
|
const preset = edits.filter ? FILTER_PRESETS[edits.filter]?.adjustments ?? {} : {};
|
|
return { ...ZERO_ADJUSTMENTS, ...preset, ...edits.adjustments };
|
|
}
|
|
|
|
/** Build a CSS `filter` string approximating the adjustment stack. */
|
|
export function editFilterCss(edits?: EditState): string | undefined {
|
|
if (!edits) return undefined;
|
|
const a = resolveAdjustments(edits);
|
|
|
|
const brightness = clamp(
|
|
1 + a.exposure * 0.4 + a.brightness * 0.4 + a.shadows * 0.12 - a.highlights * 0.06,
|
|
0.2,
|
|
2.2,
|
|
);
|
|
const contrast = clamp(
|
|
1 + a.contrast * 0.5 + a.definition * 0.2 + a.sharpness * 0.15 + a.blackPoint * 0.25,
|
|
0.2,
|
|
2.6,
|
|
);
|
|
const saturate = clamp(1 + a.saturation * 0.85 + a.vibrance * 0.35, 0, 3);
|
|
const sepia = clamp(Math.max(0, a.warmth) * 0.5, 0, 1);
|
|
const hue = (a.warmth < 0 ? a.warmth * 14 : 0) + a.tint * 14;
|
|
|
|
const parts = [
|
|
`brightness(${brightness.toFixed(3)})`,
|
|
`contrast(${contrast.toFixed(3)})`,
|
|
];
|
|
// Heavily desaturated presets read as true monochrome.
|
|
if (saturate <= 0.2) parts.push('grayscale(1)');
|
|
else parts.push(`saturate(${saturate.toFixed(3)})`);
|
|
if (sepia > 0.01) parts.push(`sepia(${sepia.toFixed(3)})`);
|
|
if (Math.abs(hue) > 0.5) parts.push(`hue-rotate(${hue.toFixed(1)}deg)`);
|
|
|
|
return parts.join(' ');
|
|
}
|
|
|
|
/** Build a CSS `transform` for rotation + straighten + flips. */
|
|
export function editTransformCss(edits?: EditState): string | undefined {
|
|
if (!edits) return undefined;
|
|
const straighten = edits.straighten ?? 0;
|
|
const rot = (edits.rotation ?? 0) + straighten;
|
|
// Scale up slightly when straightening so the rotated image still covers the frame.
|
|
const cover = straighten ? 1 + Math.min(0.5, Math.abs(straighten) / 90) : 1;
|
|
const sx = (edits.flipH ? -1 : 1) * cover;
|
|
const sy = (edits.flipV ? -1 : 1) * cover;
|
|
if (!rot && sx === 1 && sy === 1) return undefined;
|
|
return `rotate(${rot}deg) scale(${sx}, ${sy})`;
|
|
}
|
|
|
|
export { ZERO_ADJUSTMENTS };
|