From bea010dd907e1c5ffe0b3b2f43c5db6efac9d348 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Thu, 10 Sep 2026 10:27:51 +0500 Subject: [PATCH] feat: update alpha and colors tools to new types --- docs/backlog.md | 15 ++++++ web/src/lib/registry-new/alpha.ts | 78 +++++++++++++++++++------------ web/src/lib/registry-new/color.ts | 64 ++++++++++++++++--------- 3 files changed, 105 insertions(+), 52 deletions(-) diff --git a/docs/backlog.md b/docs/backlog.md index 76048f0..126ea38 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -203,3 +203,18 @@ тона (например поднять tone кнопок до ~55) и вернуть порог 4.5. Также проверить `--color-danger` (dark, тон 55): текст на нём 4.93 — ок, но light-danger тон 48 = 4.25 на границе. + +17. **Отображение маски для инструментов (runMask/preview-mask)** — придумать и + реализовать процесс показа маски результата. **Контекст:** в старом + контракте поле `preview` у `remove-color`/`remove-background` показывало + быстрый ч/б превью-маску (`colorMask`/`backgroundMaskPreview`) того, что + будет вырезано. При миграции на единый `run(ctx)` поле убрано (шаг 8, + `registry-new/alpha.ts`) — новый executor и schema-превью зовут `run()` + напрямую, маска пока не нужна. **Идея 1:** дать каждому `run` возможность + вернуть (или инструмент — объявить) `runMask` — ч/б изображение маски + изменений либо иной подход (отдельное поле `mask` + функция маски, второй + результат, overlay). Оценка — на усмотрение при проработке (S/M). Пока живём + без неё. **Идея 2:** каждый `run` возвращает два изображения - для + отображения (показывает маску или сам результат), и для передачи в следующий + инструмент (всегда результат. Оценка — на усмотрение при проработке (S/M). + Пока живём без неё. diff --git a/web/src/lib/registry-new/alpha.ts b/web/src/lib/registry-new/alpha.ts index 9a7a63e..5b15fa2 100644 --- a/web/src/lib/registry-new/alpha.ts +++ b/web/src/lib/registry-new/alpha.ts @@ -1,8 +1,7 @@ -import type { ToolEntry } from "./types"; +import { imgTool, type ToolEntry } from "./types"; import { field, toolSchema } from "../registry-schema"; import type { Offset } from "../registry-schema"; import { - colorMask, extractAlphaMask, flattenOntoColor, hardenAlpha, @@ -11,7 +10,7 @@ import { roundCorners, setAlphaChannel, } from "../core/alpha"; -import { backgroundMaskPreview, removeBackground } from "../core/background"; +import { removeBackground } from "../core/background"; import { closingImage, contourImage, @@ -46,7 +45,8 @@ const addStroke: ToolEntry = { "Adds a colored ring outline around the opaque content with the chosen thickness.", category: "alpha", schema: addStrokeSchema, - run: (img, p) => strokeImage(img, p.thickness, p.color), + input: "image", + run: imgTool((img, p) => strokeImage(img, p.thickness, p.color)), }; interface FindContourParams { @@ -66,7 +66,8 @@ const findContour: ToolEntry = { "Leaves only a line along the boundary of opaque regions in the chosen color and thickness.", category: "alpha", schema: findContourSchema, - run: (img, p) => contourImage(img, p.thickness, p.color), + input: "image", + run: imgTool((img, p) => contourImage(img, p.thickness, p.color)), }; interface RemoveColorParams { @@ -86,8 +87,8 @@ const removeColor: ToolEntry = { "Makes all pixels close to the chosen color transparent. The tolerance sets the allowed deviation as a percentage of the maximum color distance.", category: "alpha", schema: removeColorSchema, - run: (img, p) => removeColorToAlpha(img, p.targetColor, p.tolerance), - preview: (img, p) => colorMask(img, p.targetColor, p.tolerance), + input: "image", + run: imgTool((img, p) => removeColorToAlpha(img, p.targetColor, p.tolerance)), }; interface CircleMaskParams { @@ -117,13 +118,15 @@ const circleMask: ToolEntry = { "Cuts the image into a circle. Diameter is set as a share of the smaller side.", category: "alpha", schema: circleMaskSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderShape( img, circleTest(p.size / 200), p.offset.x / 100, p.offset.y / 100, ), + ), }; interface SquareMaskParams { @@ -159,13 +162,15 @@ const squareMask: ToolEntry = { "Cuts the image into a rectangle with sides as a share of the smaller side.", category: "alpha", schema: squareMaskSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderShape( img, boxTest(p.widthPct / 200, p.heightPct / 200), p.offset.x / 100, p.offset.y / 100, ), + ), }; interface StarMaskParams { @@ -205,13 +210,15 @@ const starMask: ToolEntry = { "Cuts the image into an n-pointed star with adjustable inner radius and rotation.", category: "alpha", schema: starMaskSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderShape( img, starTest(p.points, p.innerRadius / 100, p.size / 200, p.rotation), p.offset.x / 100, p.offset.y / 100, ), + ), }; interface WavyMaskParams { @@ -251,13 +258,15 @@ const wavyMask: ToolEntry = { "Cuts the image into a wavy-edged circle: radius is modulated by a sine with chosen amplitude and frequency.", category: "alpha", schema: wavyMaskSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderShape( img, wavyTest(p.size / 200, p.amplitude / 200, p.waves, p.phase), p.offset.x / 100, p.offset.y / 100, ), + ), }; interface EmptyParams {} @@ -271,7 +280,8 @@ const removeAlphaChannel: ToolEntry = { "Composites the image over a white background and saves without transparency.", category: "alpha", schema: removeAlphaChannelSchema, - run: (img) => flattenOntoColor(img, "#ffffff"), + input: "image", + run: imgTool((img) => flattenOntoColor(img, "#ffffff")), }; interface SetAlphaChannelParams { @@ -288,7 +298,8 @@ const setAlphaChannelTool: ToolEntry = { description: "Assigns the same opacity to all pixels; colors stay unchanged.", category: "alpha", schema: setAlphaChannelSchema, - run: (img, p) => setAlphaChannel(img, p.percent), + input: "image", + run: imgTool((img, p) => setAlphaChannel(img, p.percent)), }; export const extractAlphaMaskSchema = toolSchema({}); @@ -299,7 +310,8 @@ const extractAlphaMaskTool: ToolEntry = { description: "Turns transparency into a black-and-white opaque mask.", category: "alpha", schema: extractAlphaMaskSchema, - run: (img) => extractAlphaMask(img), + input: "image", + run: imgTool((img) => extractAlphaMask(img)), }; interface RoundCornersParams { @@ -317,7 +329,8 @@ const roundCornersTool: ToolEntry = { "Clips corners by a radius set as a percentage of half the smaller side.", category: "alpha", schema: roundCornersSchema, - run: (img, p) => roundCorners(img, p.radius), + input: "image", + run: imgTool((img, p) => roundCorners(img, p.radius)), }; export const invertAlphaSchema = toolSchema({}); @@ -328,7 +341,8 @@ const invertAlphaTool: ToolEntry = { description: "Opaque areas become transparent and vice versa.", category: "alpha", schema: invertAlphaSchema, - run: (img) => invertAlpha(img), + input: "image", + run: imgTool((img) => invertAlpha(img)), }; interface RemoveBackgroundParams { @@ -366,20 +380,15 @@ const removeBackgroundTool: ToolEntry = { "Removes a solid background: by color with tolerance, outer regions from the edges only, or every matching pixel. Can smooth the boundary.", category: "alpha", schema: removeBackgroundSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => removeBackground(img, { color: p.color, tolerancePercent: p.tolerance, outerOnly: p.outerOnly, smoothPasses: p.smooth, }), - preview: (img, p) => - backgroundMaskPreview(img, { - color: p.color, - tolerancePercent: p.tolerance, - outerOnly: p.outerOnly, - smoothPasses: p.smooth, - }), + ), }; interface MakeThickerParams { @@ -396,7 +405,8 @@ const makeThickerTool: ToolEntry = { description: "Expands opaque areas by the given number of pixels.", category: "alpha", schema: makeThickerSchema, - run: (img, p) => dilateImage(img, p.radius), + input: "image", + run: imgTool((img, p) => dilateImage(img, p.radius)), }; interface MakeThinnerParams { @@ -413,7 +423,8 @@ const makeThinnerTool: ToolEntry = { description: "Shrinks opaque areas — thins the strokes of text and details.", category: "alpha", schema: makeThinnerSchema, - run: (img, p) => erodeImage(img, p.radius), + input: "image", + run: imgTool((img, p) => erodeImage(img, p.radius)), }; interface FeatherEdgesParams { @@ -431,7 +442,8 @@ const featherEdgesTool: ToolEntry = { "Blurs only the alpha channel: hard cutout edges become soft and gradual, colors stay untouched.", category: "alpha", schema: featherEdgesSchema, - run: (img, p) => featherAlpha(img, p.radius), + input: "image", + run: imgTool((img, p) => featherAlpha(img, p.radius)), }; interface CleanEdgesParams { @@ -449,7 +461,8 @@ const cleanEdgesTool: ToolEntry = { "Replaces edge-halo colors of semi-transparent pixels with the nearest fully opaque color. Alpha stays as is.", category: "alpha", schema: cleanEdgesSchema, - run: (img, p) => defringe(img, p.radius), + input: "image", + run: imgTool((img, p) => defringe(img, p.radius)), }; interface HardenAlphaParams { @@ -467,7 +480,8 @@ const hardenAlphaTool: ToolEntry = { "Binarizes the alpha channel by threshold: semi-transparent pixels become either fully transparent or fully opaque.", category: "alpha", schema: hardenAlphaSchema, - run: (img, p) => hardenAlpha(img, p.threshold), + input: "image", + run: imgTool((img, p) => hardenAlpha(img, p.threshold)), }; interface DespeckleAlphaParams { @@ -485,7 +499,8 @@ const despeckleAlphaTool: ToolEntry = { "Opening: removes lone semi-transparent pixels and small specks.", category: "alpha", schema: despeckleAlphaSchema, - run: (img, p) => openingImage(img, p.radius), + input: "image", + run: imgTool((img, p) => openingImage(img, p.radius)), }; interface CloseHolesParams { @@ -502,7 +517,8 @@ const closeHolesTool: ToolEntry = { description: "Closing: fills lone transparent dots inside the object.", category: "alpha", schema: closeHolesSchema, - run: (img, p) => closingImage(img, p.radius), + input: "image", + run: imgTool((img, p) => closingImage(img, p.radius)), }; export const alphaEntries = [ diff --git a/web/src/lib/registry-new/color.ts b/web/src/lib/registry-new/color.ts index 0d59481..abf2eca 100644 --- a/web/src/lib/registry-new/color.ts +++ b/web/src/lib/registry-new/color.ts @@ -21,7 +21,7 @@ import { renderSpace, SPACES, type SpaceId } from "../core/channels"; import { parseHexList } from "../core/palette"; import { ditherImage, mapToNearest, quantizeImage } from "../core/quantize"; import { field, toolSchema, type ColorPair } from "../registry-schema"; -import type { ToolEntry } from "./types"; +import { imgTool, type ToolEntry } from "./types"; interface TwoColorsParams { pair: ColorPair; @@ -40,7 +40,8 @@ const twoColorsTool: ToolEntry = { "Recolors the image into two chosen colors by luminance threshold.", category: "color", schema: twoColorsSchema, - run: (img, p) => twoColors(img, p.pair.from, p.pair.to, p.threshold), + input: "image", + run: imgTool((img, p) => twoColors(img, p.pair.from, p.pair.to, p.threshold)), }; interface GammaParams { @@ -58,7 +59,8 @@ const gammaTool: ToolEntry = { "Corrects midtone brightness. <1 darker, >1 lighter, 1 — unchanged.", category: "color", schema: gammaSchema, - run: (img, p) => gammaCorrection(img, p.value), + input: "image", + run: imgTool((img, p) => gammaCorrection(img, p.value)), }; interface TemperatureParams { @@ -76,7 +78,8 @@ const temperatureTool: ToolEntry = { "Positive values make the image warmer (more orange), negative ones cooler (more blue).", category: "color", schema: temperatureSchema, - run: (img, p) => temperature(img, p.percent), + input: "image", + run: imgTool((img, p) => temperature(img, p.percent)), }; interface TintParams { @@ -96,7 +99,8 @@ const tintTool: ToolEntry = { "Multiplies color channels by the chosen tint with the given strength.", category: "color", schema: tintSchema, - run: (img, p) => tint(img, p.color, p.strength), + input: "image", + run: imgTool((img, p) => tint(img, p.color, p.strength)), }; interface QuantizeParams { @@ -114,7 +118,8 @@ const quantizeTool: ToolEntry = { "Reduces the image to k colors via median-cut palette. Transparent pixels are preserved.", category: "color", schema: quantizeSchema, - run: (img, p) => quantizeImage(img, p.colors).image, + input: "image", + run: imgTool((img, p) => quantizeImage(img, p.colors).image), }; interface CustomPaletteParams { @@ -132,7 +137,8 @@ const customPalette: ToolEntry = { "Maps every pixel to the nearest color from your comma-separated hex list.", category: "color", schema: customPaletteSchema, - run: (img, p) => mapToNearest(img, parseHexList(p.colors)), + input: "image", + run: imgTool((img, p) => mapToNearest(img, parseHexList(p.colors))), }; interface DitheringParams { @@ -158,7 +164,8 @@ const ditheringTool: ToolEntry = { "Applies Floyd–Steinberg error diffusion or ordered Bayer dithering while reducing to k colors.", category: "color", schema: ditheringSchema, - run: (img, p) => ditherImage(img, p.colors, p.pattern), + input: "image", + run: imgTool((img, p) => ditherImage(img, p.colors, p.pattern)), }; interface EmptyParams {} @@ -172,7 +179,8 @@ const grayscaleTool: ToolEntry = { "Converts the image to shades of gray using the BT.601 luminance formula. Alpha is preserved.", category: "color", schema: grayscaleSchema, - run: (img) => grayscale(img), + input: "image", + run: imgTool((img) => grayscale(img)), }; export const invertColorsSchema = toolSchema({}); @@ -183,7 +191,8 @@ const invertColorsTool: ToolEntry = { description: "Inverts each color channel (255 − value). Alpha is unchanged.", category: "color", schema: invertColorsSchema, - run: (img) => invert(img), + input: "image", + run: imgTool((img) => invert(img)), }; interface BrightnessContrastParams { @@ -212,7 +221,8 @@ const brightnessContrastTool: ToolEntry = { "Adjusts brightness and contrast in the range from −100 to +100. Zero means no change.", category: "color", schema: brightnessContrastSchema, - run: (img, p) => brightnessContrast(img, p.brightness, p.contrast), + input: "image", + run: imgTool((img, p) => brightnessContrast(img, p.brightness, p.contrast)), }; interface OpacityParams { @@ -230,7 +240,8 @@ const opacityTool: ToolEntry = { "Multiplies the alpha channel by a percentage: 0% — fully transparent, 100% — unchanged.", category: "color", schema: opacitySchema, - run: (img, p) => setOpacity(img, p.percent), + input: "image", + run: imgTool((img, p) => setOpacity(img, p.percent)), }; export const sepiaSchema = toolSchema({}); @@ -241,7 +252,8 @@ const sepiaTool: ToolEntry = { description: "Tints the image into the warm brown tones of classic sepia.", category: "color", schema: sepiaSchema, - run: (img) => sepia(img), + input: "image", + run: imgTool((img) => sepia(img)), }; interface HueShiftParams { @@ -259,7 +271,8 @@ const hueShiftTool: ToolEntry = { "Shifts the hue around the circle. Saturation and lightness are preserved.", category: "color", schema: hueShiftSchema, - run: (img, p) => changeHue(img, p.degrees), + input: "image", + run: imgTool((img, p) => changeHue(img, p.degrees)), }; interface ExtractChannelParams { @@ -284,7 +297,8 @@ const extractChannelTool: ToolEntry = { "Keeps only the chosen channel — red, green or blue — as shades of gray.", category: "color", schema: extractChannelSchema, - run: (img, p) => extractChannel(img, p.channel), + input: "image", + run: imgTool((img, p) => extractChannel(img, p.channel)), }; interface SwapChannelsParams { @@ -309,7 +323,8 @@ const swapChannelsTool: ToolEntry = { "Swaps two color channels — a quick way to get unusual coloring.", category: "color", schema: swapChannelsSchema, - run: (img, p) => swapChannels(img, p.pair), + input: "image", + run: imgTool((img, p) => swapChannels(img, p.pair)), }; interface BlackAndWhiteParams { @@ -327,7 +342,8 @@ const blackAndWhiteTool: ToolEntry = { "Hard binarization by luminance: every pixel becomes black or white.", category: "color", schema: blackAndWhiteSchema, - run: (img, p) => thresholdBlackWhite(img, p.threshold), + input: "image", + run: imgTool((img, p) => thresholdBlackWhite(img, p.threshold)), }; interface PosterizeParams { @@ -344,7 +360,8 @@ const posterizeTool: ToolEntry = { description: "Reduces the number of levels per channel — a poster effect.", category: "color", schema: posterizeSchema, - run: (img, p) => posterize(img, p.levels), + input: "image", + run: imgTool((img, p) => posterize(img, p.levels)), }; export const autoContrastSchema = toolSchema({}); @@ -356,7 +373,8 @@ const autoContrastTool: ToolEntry = { "Stretches each channel's range across the full available brightness range.", category: "color", schema: autoContrastSchema, - run: (img) => autoContrast(img), + input: "image", + run: imgTool((img) => autoContrast(img)), }; interface DecreaseColorCountParams { @@ -390,7 +408,8 @@ const decreaseColorCountTool: ToolEntry = { "Median-cut engine as a quick way to drop to 2–256 colors. Presets marked (extreme/strong/balanced/light) match the classic compression levels.", category: "color", schema: decreaseColorCountSchema, - run: (img, p) => quantizeImage(img, Number(p.maxColors)).image, + input: "image", + run: imgTool((img, p) => quantizeImage(img, Number(p.maxColors)).image), }; interface ChannelParams { @@ -474,7 +493,10 @@ function channelEntries(): ToolEntry[] { ], }), }), - run: (img, p) => renderSpace(img, space.id, p.component, p.display), + input: "image", + run: imgTool((img, p) => + renderSpace(img, space.id, p.component, p.display), + ), } satisfies ToolEntry; }); }