mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
refactor: separate registry file
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
import type { ToolEntry } from "../registry";
|
||||
import {
|
||||
extractByColor,
|
||||
isGrayscaleish,
|
||||
luma01,
|
||||
rarityPredicate,
|
||||
renderPredicateMask,
|
||||
} from "../core/masks";
|
||||
import { hasTransparency, isGrayscale, orientationOf } from "../core/analyze";
|
||||
import { looksLikePng, base64ToBytes, stripDataUri } from "../core/textio";
|
||||
import { clonePixelImage, type PixelImage } from "../core/types";
|
||||
import { encode } from "../core/io";
|
||||
import { t } from "../i18n/t";
|
||||
import { num, str } from "../registry-helpers";
|
||||
|
||||
type MaskToolSpec = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
defaultMode: "binary" | "highlight";
|
||||
predicate: (
|
||||
img: PixelImage,
|
||||
p: Record<string, unknown>,
|
||||
) => (r: number, g: number, b: number, a: number) => boolean;
|
||||
extraParams?: ToolEntry["params"];
|
||||
};
|
||||
|
||||
const MASK_TOOLS: MaskToolSpec[] = [
|
||||
{
|
||||
id: "show-transparent-png",
|
||||
title: "Show Transparent Areas PNG",
|
||||
description:
|
||||
"Highlights every transparent or semi-transparent pixel with the chosen color so gaps become obvious.",
|
||||
defaultMode: "highlight",
|
||||
predicate: (_img, _p) => (_r, _g, _b, a) => a < 255,
|
||||
},
|
||||
{
|
||||
id: "show-grayscale-pixels-png",
|
||||
title: "Show Grayscale Pixels PNG",
|
||||
description:
|
||||
"Finds pixels whose channels are nearly equal and renders them as a mask. Tolerance is in channel units.",
|
||||
defaultMode: "binary",
|
||||
predicate: (_i, p) => (r, g, b) =>
|
||||
isGrayscaleish(r, g, b, num(p, "tolerance")),
|
||||
extraParams: [
|
||||
{
|
||||
id: "tolerance",
|
||||
label: "Channel tolerance",
|
||||
type: "slider",
|
||||
min: 0,
|
||||
max: 64,
|
||||
step: 1,
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "show-color-pixels-png",
|
||||
title: "Show Color Pixels PNG",
|
||||
description:
|
||||
"Finds colored (non-gray) pixels beyond the channel tolerance and renders them as a mask.",
|
||||
defaultMode: "binary",
|
||||
predicate: (_i, p) => (r, g, b) =>
|
||||
!isGrayscaleish(r, g, b, num(p, "tolerance")),
|
||||
extraParams: [
|
||||
{
|
||||
id: "tolerance",
|
||||
label: "Channel tolerance",
|
||||
type: "slider",
|
||||
min: 0,
|
||||
max: 64,
|
||||
step: 1,
|
||||
default: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "light-pixel-mask-png",
|
||||
title: "Light Pixel Mask PNG",
|
||||
description: "Selects pixels brighter than the luminance threshold.",
|
||||
defaultMode: "binary",
|
||||
predicate: (_i, p) => (r, g, b) =>
|
||||
luma01(r, g, b) >= num(p, "threshold") / 100,
|
||||
extraParams: [
|
||||
{
|
||||
id: "threshold",
|
||||
label: "Luminance threshold, %",
|
||||
type: "slider",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
default: 70,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "dark-pixel-mask-png",
|
||||
title: "Dark Pixel Mask PNG",
|
||||
description: "Selects pixels darker than the luminance threshold.",
|
||||
defaultMode: "binary",
|
||||
predicate: (_i, p) => (r, g, b) =>
|
||||
luma01(r, g, b) <= num(p, "threshold") / 100,
|
||||
extraParams: [
|
||||
{
|
||||
id: "threshold",
|
||||
label: "Luminance threshold, %",
|
||||
type: "slider",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
default: 30,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "unique-color-mask-png",
|
||||
title: "Unique Color Mask PNG",
|
||||
description:
|
||||
"Selects colors that occur no more than the given number of times — rare and one-off pixels.",
|
||||
defaultMode: "binary",
|
||||
predicate: (img, p) => rarityPredicate(img, num(p, "rarity")),
|
||||
extraParams: [
|
||||
{
|
||||
id: "rarity",
|
||||
label: "Max occurrences",
|
||||
type: "slider",
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
default: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function maskEntries(): ToolEntry[] {
|
||||
return MASK_TOOLS.map((spec) => ({
|
||||
id: spec.id,
|
||||
title: spec.title,
|
||||
description: spec.description,
|
||||
category: "analyze" as const,
|
||||
params: [
|
||||
...(spec.extraParams ?? []),
|
||||
{
|
||||
id: "mode",
|
||||
label: "Mask mode",
|
||||
type: "select" as const,
|
||||
default: spec.defaultMode,
|
||||
options: [
|
||||
{ value: "binary", label: "Black & white mask" },
|
||||
{ value: "highlight", label: "Color highlight" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "color",
|
||||
label: "Highlight color",
|
||||
type: "color",
|
||||
default: "#ff00aa",
|
||||
},
|
||||
{
|
||||
id: "opacity",
|
||||
label: "Highlight opacity, %",
|
||||
type: "slider",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 5,
|
||||
default: 70,
|
||||
},
|
||||
],
|
||||
run: (img, p) =>
|
||||
renderPredicateMask(img, spec.predicate(img, p), {
|
||||
mode: str(p, "mode") === "highlight" ? "highlight" : "binary",
|
||||
color: str(p, "color"),
|
||||
opacityPercent: num(p, "opacity"),
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
export function analyzeEntries(): ToolEntry[] {
|
||||
return [
|
||||
...maskEntries(),
|
||||
{
|
||||
id: "extract-color-from-png",
|
||||
title: "Extract Color from PNG",
|
||||
description:
|
||||
"Keeps only pixels close to the chosen color and makes everything else transparent — the inverse of Remove Color.",
|
||||
category: "analyze",
|
||||
params: [
|
||||
{
|
||||
id: "color",
|
||||
label: "Color to keep",
|
||||
type: "color",
|
||||
default: "#00ff88",
|
||||
},
|
||||
{
|
||||
id: "tolerance",
|
||||
label: "Similarity tolerance, %",
|
||||
type: "slider",
|
||||
min: 0,
|
||||
max: 50,
|
||||
step: 1,
|
||||
default: 10,
|
||||
},
|
||||
],
|
||||
run: (img, p) =>
|
||||
extractByColor(img, str(p, "color"), num(p, "tolerance")),
|
||||
},
|
||||
{
|
||||
id: "verify-is-png",
|
||||
title: "Verify If Image Is a PNG",
|
||||
description:
|
||||
"Checks the signature of pasted base64 / data-uri content and reports whether it is a real PNG.",
|
||||
category: "analyze",
|
||||
sourceMode: "text",
|
||||
params: [],
|
||||
resultType: "text",
|
||||
textToText: (text) =>
|
||||
looksLikePng(base64ToBytes(stripDataUri(text)))
|
||||
? "verifyYes"
|
||||
: "verifyNo",
|
||||
},
|
||||
{
|
||||
id: "png-info",
|
||||
title: "PNG info",
|
||||
description:
|
||||
"Shows dimensions, alpha presence and the number of unique colors of the uploaded image.",
|
||||
category: "analyze",
|
||||
params: [],
|
||||
resultType: "info",
|
||||
run: (img) => clonePixelImage(img),
|
||||
},
|
||||
{
|
||||
id: "png-is-grayscale",
|
||||
title: "Check: is PNG grayscale?",
|
||||
description: "Reports whether the image consists only of shades of gray.",
|
||||
category: "analyze",
|
||||
params: [],
|
||||
resultType: "text",
|
||||
toText: (img) => (isGrayscale(img) ? "grayscaleYes" : "grayscaleNo"),
|
||||
},
|
||||
{
|
||||
id: "png-file-size",
|
||||
title: "PNG File Size",
|
||||
description:
|
||||
"Encodes the image as PNG and reports the resulting file size.",
|
||||
category: "analyze",
|
||||
domOnly: true,
|
||||
params: [],
|
||||
resultType: "text",
|
||||
toText: 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 t("tools.png-file-size.results.line", {
|
||||
kb: kbText,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "png-is-transparent",
|
||||
title: "Check: is PNG transparent?",
|
||||
description:
|
||||
"Reports whether the image contains transparent or semi-transparent pixels.",
|
||||
category: "analyze",
|
||||
params: [],
|
||||
resultType: "text",
|
||||
toText: (img) =>
|
||||
hasTransparency(img) ? "transparentYes" : "transparentNo",
|
||||
},
|
||||
{
|
||||
id: "png-orientation",
|
||||
title: "PNG orientation",
|
||||
description: "Reports whether it is portrait, landscape or square.",
|
||||
category: "analyze",
|
||||
params: [],
|
||||
resultType: "text",
|
||||
toText: (img) => {
|
||||
switch (orientationOf(img)) {
|
||||
case "portrait":
|
||||
return "orientationPortrait";
|
||||
case "landscape":
|
||||
return "orientationLandscape";
|
||||
default:
|
||||
return "orientationSquare";
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user