mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
feat: add masks core tools
This commit is contained in:
+186
-5
@@ -77,6 +77,13 @@ import {
|
||||
triadicSet,
|
||||
type SortKey
|
||||
} from './core/palette';
|
||||
import {
|
||||
extractByColor,
|
||||
isGrayscaleish,
|
||||
luma01,
|
||||
rarityPredicate,
|
||||
renderPredicateMask
|
||||
} from './core/masks';
|
||||
import { renderSpace, SPACES, type SpaceId } from './core/channels';
|
||||
import { hexToPixels, pixelsToHex } from './core/text';
|
||||
import { clonePixelImage, type PixelImage } from './core/types';
|
||||
@@ -174,6 +181,167 @@ function bool(params: Record<string, unknown>, id: string): boolean {
|
||||
return v;
|
||||
}
|
||||
|
||||
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')
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
type SpaceEntry = {
|
||||
id: SpaceId;
|
||||
suffix: string;
|
||||
@@ -312,7 +480,8 @@ function hexToRgba(hex: string, alpha = 255): [number, number, number, number] {
|
||||
}
|
||||
|
||||
export const TOOLS: ToolEntry[] = [
|
||||
...channelEntries(),
|
||||
...channelEntries(),
|
||||
...maskEntries(),
|
||||
decodeToPng(
|
||||
'jpg-to-png',
|
||||
'Convert JPG to PNG',
|
||||
@@ -910,10 +1079,22 @@ export const TOOLS: ToolEntry[] = [
|
||||
{ id: 'targetColor', label: 'Color to remove', type: 'color', default: '#00ff00' },
|
||||
{ id: 'tolerance', label: 'Similarity threshold, %', type: 'slider', min: 0, max: 100, step: 1, default: 10 }
|
||||
],
|
||||
run: (img, p) => removeColorToAlpha(img, str(p, 'targetColor'), num(p, 'tolerance')),
|
||||
preview: (img, p) => colorMask(img, str(p, 'targetColor'), num(p, 'tolerance'))
|
||||
},
|
||||
{
|
||||
run: (img, p) => removeColorToAlpha(img, str(p, 'targetColor'), num(p, 'tolerance')),
|
||||
preview: (img, p) => colorMask(img, str(p, 'targetColor'), num(p, 'tolerance'))
|
||||
},
|
||||
{
|
||||
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: 'png-info',
|
||||
title: 'PNG info',
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user