From 60c7d61daba60f656522e3ab45ffb87149910e69 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Thu, 10 Sep 2026 10:04:16 +0500 Subject: [PATCH] feat: update convert and analyze tools to new types --- web/src/lib/registry-new/analyze.ts | 48 ++++++++++++++++++++--------- web/src/lib/registry-new/convert.ts | 38 ++++++++++++++--------- web/src/lib/registry-new/types.ts | 20 ++++++------ 3 files changed, 66 insertions(+), 40 deletions(-) diff --git a/web/src/lib/registry-new/analyze.ts b/web/src/lib/registry-new/analyze.ts index 66b0cda..7fccab1 100644 --- a/web/src/lib/registry-new/analyze.ts +++ b/web/src/lib/registry-new/analyze.ts @@ -1,4 +1,4 @@ -import type { ToolEntry } from "./types"; +import { imgTool, textGen, type ToolEntry } from "./types"; import { field, toolSchema } from "../registry-schema"; import { extractByColor, @@ -28,7 +28,8 @@ const extractColor: ToolEntry = { "Keeps only pixels close to the chosen color and makes everything else transparent — the inverse of Remove Color.", category: "analyze", schema: extractColorSchema, - run: (img, p) => extractByColor(img, p.color, p.tolerance), + input: "image", + run: imgTool((img, p) => extractByColor(img, p.color, p.tolerance)), }; interface MaskParams { @@ -79,7 +80,8 @@ const showTransparent: ToolEntry = { "Highlights every transparent or semi-transparent pixel with the chosen color so gaps become obvious.", category: "analyze", schema: showTransparentSchema, - run: (img, p) => renderMask(img, p, (_r, _g, _b, a) => a < 255), + input: "image", + run: imgTool((img, p) => renderMask(img, p, (_r, _g, _b, a) => a < 255)), }; interface GrayscalePixelsParams extends MaskParams { @@ -98,8 +100,10 @@ const showGrayscalePixels: ToolEntry = { "Finds pixels whose channels are nearly equal and renders them as a mask. Tolerance is in channel units.", category: "analyze", schema: showGrayscalePixelsSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderMask(img, p, (r, g, b) => isGrayscaleish(r, g, b, p.tolerance)), + ), }; interface ColorPixelsParams extends MaskParams { @@ -118,8 +122,10 @@ const showColorPixels: ToolEntry = { "Finds colored (non-gray) pixels beyond the channel tolerance and renders them as a mask.", category: "analyze", schema: showColorPixelsSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderMask(img, p, (r, g, b) => !isGrayscaleish(r, g, b, p.tolerance)), + ), }; interface LightPixelParams extends MaskParams { @@ -137,8 +143,10 @@ const lightPixelMask: ToolEntry = { description: "Selects pixels brighter than the luminance threshold.", category: "analyze", schema: lightPixelMaskSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderMask(img, p, (r, g, b) => luma01(r, g, b) >= p.threshold / 100), + ), }; interface DarkPixelParams extends MaskParams { @@ -156,8 +164,10 @@ const darkPixelMask: ToolEntry = { description: "Selects pixels darker than the luminance threshold.", category: "analyze", schema: darkPixelMaskSchema, - run: (img, p) => + input: "image", + run: imgTool((img, p) => renderMask(img, p, (r, g, b) => luma01(r, g, b) <= p.threshold / 100), + ), }; interface UniqueColorParams extends MaskParams { @@ -176,7 +186,8 @@ const uniqueColorMask: ToolEntry = { "Selects colors that occur no more than the given number of times — rare and one-off pixels.", category: "analyze", schema: uniqueColorMaskSchema, - run: (img, p) => renderMask(img, p, rarityPredicate(img, p.rarity)), + input: "image", + run: imgTool((img, p) => renderMask(img, p, rarityPredicate(img, p.rarity))), }; interface NoParams {} @@ -192,10 +203,11 @@ const verifyIsPng: ToolEntry = { schema: emptySchema, input: "text", result: "verdict", - textToText: (text) => + run: textGen((text) => looksLikePng(base64ToBytes(stripDataUri(text))) ? "Yes — valid PNG signature." : "No — the content is not a PNG.", + ), }; const pngIsGrayscale: ToolEntry = { @@ -204,9 +216,11 @@ const pngIsGrayscale: ToolEntry = { description: "Reports whether the image consists only of shades of gray.", category: "analyze", schema: emptySchema, + input: "image", result: "verdict", - toText: (img) => + run: imgTool((img) => isGrayscale(img) ? "Yes — grayscale." : "No — contains colors.", + ), }; const pngFileSize: ToolEntry = { @@ -216,13 +230,14 @@ const pngFileSize: ToolEntry = { category: "analyze", schema: emptySchema, domOnly: true, + input: "image", result: "verdict", - toText: async (img) => { + run: imgTool(async (img) => { const blob = await encode(img, "image/png"); const kb = blob.size / 1024; const kbText = kb >= 100 ? Math.round(kb).toString() : kb.toFixed(1); return `${kbText} KB`; - }, + }), }; const pngIsTransparent: ToolEntry = { @@ -232,9 +247,11 @@ const pngIsTransparent: ToolEntry = { "Reports whether the image contains transparent or semi-transparent pixels.", category: "analyze", schema: emptySchema, + input: "image", result: "verdict", - toText: (img) => + run: imgTool((img) => hasTransparency(img) ? "Yes — has transparency." : "No — fully opaque.", + ), }; const pngOrientation: ToolEntry = { @@ -243,8 +260,9 @@ const pngOrientation: ToolEntry = { description: "Reports whether it is portrait, landscape or square.", category: "analyze", schema: emptySchema, + input: "image", result: "verdict", - toText: (img) => { + run: imgTool((img) => { switch (orientationOf(img)) { case "portrait": return "Portrait"; @@ -253,7 +271,7 @@ const pngOrientation: ToolEntry = { default: return "Square"; } - }, + }), }; export const analyzeEntries = [ diff --git a/web/src/lib/registry-new/convert.ts b/web/src/lib/registry-new/convert.ts index 3b1a8a7..e989b90 100644 --- a/web/src/lib/registry-new/convert.ts +++ b/web/src/lib/registry-new/convert.ts @@ -1,4 +1,4 @@ -import type { ToolEntry } from "./types"; +import { imgTool, textGen, type ToolEntry } from "./types"; import { field, toolSchema } from "../registry-schema"; import { flattenOntoColor } from "../core/alpha"; import { @@ -33,7 +33,8 @@ const convertToJpg: ToolEntry = { "Transparency is composited over the chosen backdrop color (white by default) and saved as JPEG.", category: "convert", schema: convertToJpgSchema, - run: (img, p) => flattenOntoColor(img, p.background), + input: "image", + run: imgTool((img, p) => flattenOntoColor(img, p.background)), output: { mime: "image/jpeg", ext: "jpg", qualityParamId: "quality" }, }; @@ -52,7 +53,8 @@ const convertToWebp: ToolEntry = { "Re-encodes the image into WebP with adjustable quality. Transparency is preserved.", category: "convert", schema: convertToWebpSchema, - run: (img) => clonePixelImage(img), + input: "image", + run: imgTool((img) => clonePixelImage(img)), output: { mime: "image/webp", ext: "webp", qualityParamId: "quality" }, }; @@ -67,7 +69,8 @@ const convertToBmp: ToolEntry = { "Saves the image as 24-bit BMP without an alpha channel: transparency is replaced with a black background.", category: "convert", schema: convertToBmpSchema, - run: (img) => flattenOntoColor(img, "#000000"), + input: "image", + run: imgTool((img) => flattenOntoColor(img, "#000000")), output: { mime: "image/bmp", ext: "bmp" }, }; @@ -82,8 +85,9 @@ const pngToBase64: ToolEntry = { "Encodes the image into a base64 string for embedding in code or styles.", category: "convert", schema: emptySchema, + input: "image", result: "text", - toText: (img) => toBase64(img), + run: imgTool((img) => toBase64(img)), }; const pngToDataUri: ToolEntry = { @@ -93,8 +97,9 @@ const pngToDataUri: ToolEntry = { "Builds a full data-uri (data:image/png;base64,…) for embedding in HTML/CSS.", category: "convert", schema: emptySchema, + input: "image", result: "text", - toText: (img) => toDataUrl(img), + run: imgTool((img) => toDataUrl(img)), }; const pngToHex: ToolEntry = { @@ -104,8 +109,9 @@ const pngToHex: ToolEntry = { "Shows all pixels as rrggbbaa hex values — row by row, space separated.", category: "convert", schema: emptySchema, + input: "image", result: "text", - toText: (img) => pixelsToHex(img), + run: imgTool((img) => pixelsToHex(img)), }; const pngToBytes: ToolEntry = { @@ -115,8 +121,9 @@ const pngToBytes: ToolEntry = { "Lists every pixel as four decimal bytes (R G B A), one image row per line.", category: "convert", schema: emptySchema, + input: "image", result: "text", - toText: (img) => imageToByteRows(img), + run: imgTool((img) => imageToByteRows(img)), }; const pngToRgbValues: ToolEntry = { @@ -125,8 +132,9 @@ const pngToRgbValues: ToolEntry = { description: "Lists every pixel as rgba(r, g, b, a), one image row per line.", category: "convert", schema: emptySchema, + input: "image", result: "text", - toText: (img) => imageToRgbValues(img), + run: imgTool((img) => imageToRgbValues(img)), }; const base64ToPng: ToolEntry = { @@ -137,7 +145,7 @@ const base64ToPng: ToolEntry = { category: "convert", schema: emptySchema, input: "text", - runFromText: async (text) => decodeBytes(base64ToBytes(stripDataUri(text))), + run: textGen((text) => decodeBytes(base64ToBytes(stripDataUri(text)))), }; const dataUriToPng: ToolEntry = { @@ -147,7 +155,7 @@ const dataUriToPng: ToolEntry = { category: "convert", schema: emptySchema, input: "text", - runFromText: async (text) => decodeBytes(base64ToBytes(stripDataUri(text))), + run: textGen((text) => decodeBytes(base64ToBytes(stripDataUri(text)))), }; interface HexToPngParams { @@ -166,7 +174,7 @@ const hexToPng: ToolEntry = { category: "convert", schema: hexToPngSchema, input: "text", - runFromText: (text, p) => hexToPixels(text, Math.trunc(p.width)), + run: textGen((text, p) => hexToPixels(text, Math.trunc(p.width))), }; interface BytesToPngParams { @@ -185,7 +193,7 @@ const bytesToPng: ToolEntry = { category: "convert", schema: bytesToPngSchema, input: "text", - runFromText: (text, p) => bytesToImage(text, Math.trunc(p.width)), + run: textGen((text, p) => bytesToImage(text, Math.trunc(p.width))), }; interface RgbValuesToPngParams { @@ -204,7 +212,7 @@ const rgbValuesToPng: ToolEntry = { category: "convert", schema: rgbValuesToPngSchema, input: "text", - runFromText: (text, p) => rgbValuesToImage(text, Math.trunc(p.width)), + run: textGen((text, p) => rgbValuesToImage(text, Math.trunc(p.width))), }; interface SvgToPngParams { @@ -224,7 +232,7 @@ const svgToPng: ToolEntry = { schema: svgToPngSchema, input: "text", domOnly: true, - runFromText: (text, p) => decodeSvgText(text, Math.trunc(p.width)), + run: textGen((text, p) => decodeSvgText(text, Math.trunc(p.width))), }; export const convertEntries = [ diff --git a/web/src/lib/registry-new/types.ts b/web/src/lib/registry-new/types.ts index c041568..3a2d147 100644 --- a/web/src/lib/registry-new/types.ts +++ b/web/src/lib/registry-new/types.ts @@ -1,8 +1,8 @@ +import { ToolError } from "../core/errors"; +import type { OutputMime } from "../core/io"; +import type { PixelImage } from "../core/types"; import type { CategoryId } from "../preview/categories"; import type { ToolSchema } from "../registry-schema"; -import type { PixelImage } from "../core/types"; -import type { OutputMime } from "../core/io"; -import { ToolError } from "../core/errors"; /** Формат скачивания, отличный от PNG (bmp/jpeg/webp). */ export interface OutputFormat { @@ -83,21 +83,21 @@ export function requireText

(ctx: ToolContext

): string { * из контекста. Позволяет оставить тело инструмента в виде `(img, p) => …`. */ export function imgTool

( - fn: (img: PixelImage, params: P) => ToolResult, -): (ctx: ToolContext

) => ToolResult { + fn: (img: PixelImage, params: P) => ToolResult | Promise, +): (ctx: ToolContext

) => ToolResult | Promise { return (ctx) => fn(requireSource(ctx), ctx.params); } /** Обёртка для генераторов: инструменту нужны только параметры. */ export function genTool

( - fn: (params: P) => ToolResult, -): (ctx: ToolContext

) => ToolResult { + fn: (params: P) => ToolResult | Promise, +): (ctx: ToolContext

) => ToolResult | Promise { return (ctx) => fn(ctx.params); } /** Обёртка для text-to-image инструментов: подставляет `text` и `params`. */ export function textGen

( - fn: (text: string, params: P) => ToolResult, -): (ctx: ToolContext

) => ToolResult { + fn: (text: string, params: P) => ToolResult | Promise, +): (ctx: ToolContext

) => ToolResult | Promise { return (ctx) => fn(requireText(ctx), ctx.params); -} \ No newline at end of file +}