mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
feat: update convert and analyze tools to new types
This commit is contained in:
@@ -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<ExtractColorParams> = {
|
||||
"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<MaskParams> = {
|
||||
"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<GrayscalePixelsParams> = {
|
||||
"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<ColorPixelsParams> = {
|
||||
"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<LightPixelParams> = {
|
||||
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<DarkPixelParams> = {
|
||||
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<UniqueColorParams> = {
|
||||
"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<NoParams> = {
|
||||
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<NoParams> = {
|
||||
@@ -204,9 +216,11 @@ const pngIsGrayscale: ToolEntry<NoParams> = {
|
||||
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<NoParams> = {
|
||||
@@ -216,13 +230,14 @@ const pngFileSize: ToolEntry<NoParams> = {
|
||||
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<NoParams> = {
|
||||
@@ -232,9 +247,11 @@ const pngIsTransparent: ToolEntry<NoParams> = {
|
||||
"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<NoParams> = {
|
||||
@@ -243,8 +260,9 @@ const pngOrientation: ToolEntry<NoParams> = {
|
||||
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<NoParams> = {
|
||||
default:
|
||||
return "Square";
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export const analyzeEntries = [
|
||||
|
||||
@@ -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<ConvertToJpgParams> = {
|
||||
"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<ConvertToWebpParams> = {
|
||||
"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<ConvertToBmpParams> = {
|
||||
"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<NoParams> = {
|
||||
"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<NoParams> = {
|
||||
@@ -93,8 +97,9 @@ const pngToDataUri: ToolEntry<NoParams> = {
|
||||
"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<NoParams> = {
|
||||
@@ -104,8 +109,9 @@ const pngToHex: ToolEntry<NoParams> = {
|
||||
"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<NoParams> = {
|
||||
@@ -115,8 +121,9 @@ const pngToBytes: ToolEntry<NoParams> = {
|
||||
"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<NoParams> = {
|
||||
@@ -125,8 +132,9 @@ const pngToRgbValues: ToolEntry<NoParams> = {
|
||||
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<NoParams> = {
|
||||
@@ -137,7 +145,7 @@ const base64ToPng: ToolEntry<NoParams> = {
|
||||
category: "convert",
|
||||
schema: emptySchema,
|
||||
input: "text",
|
||||
runFromText: async (text) => decodeBytes(base64ToBytes(stripDataUri(text))),
|
||||
run: textGen((text) => decodeBytes(base64ToBytes(stripDataUri(text)))),
|
||||
};
|
||||
|
||||
const dataUriToPng: ToolEntry<NoParams> = {
|
||||
@@ -147,7 +155,7 @@ const dataUriToPng: ToolEntry<NoParams> = {
|
||||
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<HexToPngParams> = {
|
||||
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<BytesToPngParams> = {
|
||||
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<RgbValuesToPngParams> = {
|
||||
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<SvgToPngParams> = {
|
||||
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 = [
|
||||
|
||||
@@ -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<P>(ctx: ToolContext<P>): string {
|
||||
* из контекста. Позволяет оставить тело инструмента в виде `(img, p) => …`.
|
||||
*/
|
||||
export function imgTool<P>(
|
||||
fn: (img: PixelImage, params: P) => ToolResult,
|
||||
): (ctx: ToolContext<P>) => ToolResult {
|
||||
fn: (img: PixelImage, params: P) => ToolResult | Promise<ToolResult>,
|
||||
): (ctx: ToolContext<P>) => ToolResult | Promise<ToolResult> {
|
||||
return (ctx) => fn(requireSource(ctx), ctx.params);
|
||||
}
|
||||
|
||||
/** Обёртка для генераторов: инструменту нужны только параметры. */
|
||||
export function genTool<P>(
|
||||
fn: (params: P) => ToolResult,
|
||||
): (ctx: ToolContext<P>) => ToolResult {
|
||||
fn: (params: P) => ToolResult | Promise<ToolResult>,
|
||||
): (ctx: ToolContext<P>) => ToolResult | Promise<ToolResult> {
|
||||
return (ctx) => fn(ctx.params);
|
||||
}
|
||||
|
||||
/** Обёртка для text-to-image инструментов: подставляет `text` и `params`. */
|
||||
export function textGen<P>(
|
||||
fn: (text: string, params: P) => ToolResult,
|
||||
): (ctx: ToolContext<P>) => ToolResult {
|
||||
fn: (text: string, params: P) => ToolResult | Promise<ToolResult>,
|
||||
): (ctx: ToolContext<P>) => ToolResult | Promise<ToolResult> {
|
||||
return (ctx) => fn(requireText(ctx), ctx.params);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user