refactor: clamp\storage dry

This commit is contained in:
2026-10-09 22:09:02 +05:00
parent d58f6c90be
commit 07c6475c21
15 changed files with 68 additions and 77 deletions
+2 -5
View File
@@ -2,6 +2,7 @@ import { formatStamp } from "./core/datefmt";
import type { OutputMime } from "./core/io";
import type { PixelImage } from "./core/types";
import { t } from "./i18n/t";
import { safeSetItem, storage } from "./storage";
import {
CHAIN_VERSION,
parseChain,
@@ -19,10 +20,6 @@ export interface NamedChain {
const STORE_KEY = "chains:named";
function storage(): Storage | null {
return typeof localStorage === "undefined" ? null : localStorage;
}
export function autoChainName(date = new Date()): string {
return t("savedChains.autoName", {
stamp: formatStamp(date, "YYYY-MM-DD hh:mm"),
@@ -92,7 +89,7 @@ function readAll(): NamedChain[] {
}
function writeAll(chains: NamedChain[]): void {
storage()?.setItem(STORE_KEY, JSON.stringify(serializeNamedChains(chains)));
safeSetItem(STORE_KEY, JSON.stringify(serializeNamedChains(chains)));
}
/** Newest first. */
+1 -4
View File
@@ -1,5 +1,6 @@
import { createPixelImage, type PixelImage } from "./types";
import { ToolError } from "./errors";
import { clamp } from "./math";
const MAX_COLOR_DISTANCE = Math.sqrt(3 * 255 * 255);
@@ -163,7 +164,3 @@ export function parseHex(hex: string): [number, number, number] {
parseInt(digits.slice(4, 6), 16),
];
}
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
+1 -4
View File
@@ -1,5 +1,6 @@
import { parseHex } from "./alpha";
import { createPixelImage, type PixelImage } from "./types";
import { clamp } from "./math";
export type BackgroundOptions = {
color: string;
@@ -132,7 +133,3 @@ export function backgroundMaskPreview(
}
return out;
}
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
+1 -4
View File
@@ -1,5 +1,6 @@
import { clonePixelImage, createPixelImage, type PixelImage } from "./types";
import { ToolError } from "./errors";
import { clamp } from "./math";
export type RgbChannel = "red" | "green" | "blue";
@@ -236,10 +237,6 @@ export function rgbToHex(r: number, g: number, b: number): string {
return `#${byte(r)}${byte(g)}${byte(b)}`;
}
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
export function gammaCorrection(img: PixelImage, value: number): PixelImage {
const g = clamp(value, 0.1, 5);
const lut = new Uint8ClampedArray(256);
+1 -8
View File
@@ -1,5 +1,6 @@
import { clonePixelImage, createPixelImage, type PixelImage } from "./types";
import { ToolError } from "./errors";
import { clamp, clampInt } from "./math";
type Plane = Float64Array;
@@ -177,11 +178,3 @@ function boxesForGauss(sigma: number, boxes: number): number[] {
}
return sizes;
}
function clamp(v: number, min: number, max: number): number {
return Math.min(max, Math.max(min, v));
}
function clampInt(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, Math.trunc(value)));
}
+1 -4
View File
@@ -1,6 +1,7 @@
import { parseHex } from "./alpha";
import { ToolError } from "./errors";
import { clonePixelImage, createPixelImage, type PixelImage } from "./types";
import { clampInt } from "./math";
export function expandCanvas(
img: PixelImage,
@@ -285,10 +286,6 @@ export function sampleBilinear(
return result;
}
function clampInt(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, Math.trunc(value)));
}
export type Anchor9 =
| "top-left"
| "top-center"
+15
View File
@@ -0,0 +1,15 @@
export function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
export function clampInt(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, Math.trunc(value)));
}
export function clamp01(value: number): number {
return clamp(value, 0, 1);
}
export function clampByte(value: number): number {
return value < 0 ? 0 : value > 255 ? 255 : Math.round(value);
}
+1 -4
View File
@@ -1,6 +1,7 @@
import { ToolError } from "./errors";
import type { PixelImage } from "./types";
import { createPixelImage } from "./types";
import { clamp01 } from "./math";
export type Rgb = { r: number; g: number; b: number };
export type Hsl = { h: number; s: number; l: number };
@@ -180,10 +181,6 @@ export function sortPalette(hexes: string[], key: SortKey): string[] {
.map((s) => s.h);
}
function clamp01(v: number): number {
return Math.min(1, Math.max(0, v));
}
/** Horizontal equal swatch columns (strip) or a grid of ~square cells (grid). */
export function renderSwatches(
colors: string[],
+1 -4
View File
@@ -2,6 +2,7 @@ import { hexToRgb } from "./palette";
import type { PixelImage } from "./types";
import { createPixelImage } from "./types";
import { gaussianBlur } from "./convolution";
import { clampByte } from "./math";
/** Deterministic PRNG (mulberry32): same seed gives the same result. */
export function mulberry32(seed: number): () => number {
@@ -15,10 +16,6 @@ export function mulberry32(seed: number): () => number {
};
}
function clampByte(v: number): number {
return v < 0 ? 0 : v > 255 ? 255 : Math.round(v);
}
/** Averages each blockSize x blockSize block into a single color. */
export function pixelate(img: PixelImage, blockSize: number): PixelImage {
const bs = Math.max(1, Math.round(blockSize));
+2 -5
View File
@@ -1,13 +1,10 @@
import { SvelteSet } from "svelte/reactivity";
import { safeSetItem, storage } from "./storage";
const KEY = "fav-tools";
const slugs = new SvelteSet<string>();
function storage(): Storage | null {
return typeof localStorage === "undefined" ? null : localStorage;
}
export function initFavorites(): void {
const raw = storage()?.getItem(KEY);
if (!raw) return;
@@ -31,7 +28,7 @@ export function toggleFav(slug: string): void {
} else {
slugs.add(slug);
}
storage()?.setItem(KEY, JSON.stringify([...slugs]));
safeSetItem(KEY, JSON.stringify([...slugs]));
}
/** Favorite pages from the list (order preserved). */
+2 -5
View File
@@ -1,6 +1,7 @@
import { BASE_LOCALE, isLocale, type Dict, type Locale } from "./dict";
import { ru } from "./ru";
import { en } from "./en";
import { safeSetItem, storage } from "../storage";
const STORAGE_KEY = "locale";
@@ -8,10 +9,6 @@ const DICTS: Record<Locale, Dict> = { ru, en };
let locale = $state<Locale>(BASE_LOCALE);
function storage(): Storage | null {
return typeof localStorage === "undefined" ? null : localStorage;
}
export function getLocale(): Locale {
return locale;
}
@@ -39,7 +36,7 @@ export function getMergedDict(): Dict {
export function setLocale(next: Locale): void {
locale = next;
storage()?.setItem(STORAGE_KEY, next);
safeSetItem(STORAGE_KEY, next);
syncLangAttr();
}
+9 -10
View File
@@ -6,6 +6,7 @@
import type { TextFont } from "./core/domText";
import type { Position9 } from "./core/textdraw";
import { clamp } from "./core/math";
/**
* Optional field of every spec: the field's label key in the dictionary.
@@ -405,12 +406,6 @@ export function resolveLayoutGroups<P>(
return named;
}
function clamp(value: number, min?: number, max?: number): number {
if (min !== undefined && value < min) return min;
if (max !== undefined && value > max) return max;
return value;
}
/** Field cap: from source dimensions under maxFromSource, else static. */
export function effectiveMax(
spec: NumberSpec,
@@ -444,7 +439,11 @@ export function clampSourceAwareMaxes<P>(
if (spec.kind === "number" && spec.maxFromSource) {
const v = out[key as string];
if (typeof v !== "number" || !Number.isFinite(v)) continue;
const clamped = clamp(v, spec.min, effectiveMax(spec, source));
const clamped = clamp(
v,
spec.min ?? -Infinity,
effectiveMax(spec, source) ?? Infinity,
);
if (clamped !== v) {
out[key as string] = clamped;
changed = true;
@@ -456,12 +455,12 @@ export function clampSourceAwareMaxes<P>(
}
const width = clamp(
v.width,
spec.min,
spec.min ?? -Infinity,
dimensionAxisMax(spec, "width", source),
);
const height = clamp(
v.height,
spec.min,
spec.min ?? -Infinity,
dimensionAxisMax(spec, "height", source),
);
if (width !== v.width || height !== v.height) {
@@ -583,7 +582,7 @@ export function sanitizeSchemaParams<P>(
spec.kind === "number"
? effectiveMax(spec, context?.source)
: spec.max;
out[key] = clamp(n, spec.min, max);
out[key] = clamp(n, spec.min ?? -Infinity, max ?? Infinity);
break;
}
case "select":
+12
View File
@@ -0,0 +1,12 @@
export function storage(): Storage | null {
return typeof localStorage === "undefined" ? null : localStorage;
}
// setItem throws in privacy modes; persistence is best-effort.
export function safeSetItem(key: string, value: string): void {
try {
storage()?.setItem(key, value);
} catch {
// ignore
}
}
+3 -5
View File
@@ -1,13 +1,11 @@
import { safeSetItem } from "./storage";
export type ThemeChoice = "light" | "dark";
const STORAGE_KEY = "theme";
let theme = $state<ThemeChoice>("light");
function storage(): Storage | null {
return typeof localStorage === "undefined" ? null : localStorage;
}
function systemTheme(): ThemeChoice {
if (typeof window === "undefined" || !window.matchMedia) return "light";
return window.matchMedia("(prefers-color-scheme: dark)").matches
@@ -26,7 +24,7 @@ export function getTheme(): ThemeChoice {
export function setTheme(next: ThemeChoice): void {
theme = next;
storage()?.setItem(STORAGE_KEY, next);
safeSetItem(STORAGE_KEY, next);
apply();
}