feat: add compression tools

This commit is contained in:
2026-08-26 18:06:03 +05:00
parent 501d2dda4e
commit 83d3ce1198
8 changed files with 189 additions and 8 deletions
+66
View File
@@ -71,6 +71,9 @@ import {
stripDataUri
} from './core/textio';
import { colorSpectrum, drawGrid, randomColorBlocks } from './core/gen-tools';
import { COMPRESSION_LEVELS, findMaxColorsWithin, type CompressionLevel } from './core/compress';
import { encode } from './core/io';
import { t } from './i18n/t';
import { formatStamp } from './core/datefmt';
import type { Position9 } from './core/textdraw';
import {
@@ -2370,6 +2373,69 @@ export const TOOLS: ToolEntry[] = [
str(p, 'pattern') === 'bayer' ? 'bayer' : 'floyd-steinberg'
)
},
{
id: 'compress-png',
title: 'Compress PNG',
description:
'Shrinks the PNG by reducing its palette to a preset level. Honest trade-off: fewer colors = smaller file.',
category: 'convert',
params: [
{
id: 'level',
label: 'Compression level',
type: 'select',
default: 'balanced',
options: [
{ value: 'light', label: 'Light (192 colors)' },
{ value: 'balanced', label: 'Balanced (96 colors)' },
{ value: 'strong', label: 'Strong (44 colors)' },
{ value: 'extreme', label: 'Extreme (16 colors)' }
]
}
],
run: (img, p) => {
const level = str(p, 'level') as CompressionLevel;
const k = COMPRESSION_LEVELS[level] ?? COMPRESSION_LEVELS.balanced;
return quantizeImage(img, k).image;
}
},
{
id: 'reduce-to-size-png',
title: 'Reduce PNG to Size',
description:
'Binary-searches the palette size until the encoded PNG fits the target KB. Best effort: if even 2 colors exceed the target, returns the 2-color version.',
category: 'convert',
domOnly: true,
params: [
{ id: 'targetKB', label: 'Target size, KB', type: 'slider', min: 5, max: 2000, step: 5, default: 100 },
{ id: 'maxColors', label: 'Max colors to try', type: 'slider', min: 2, max: 256, step: 1, default: 256 }
],
run: async (img, p) => {
const targetBytes = num(p, 'targetKB') * 1024;
const maxK = num(p, 'maxColors');
const encodeSize = async (k: number): Promise<number | null> => {
const blob = await encode(quantizeImage(img, k).image, 'image/png');
return blob.size;
};
const k = await findMaxColorsWithin(targetBytes, maxK, encodeSize);
return quantizeImage(img, k).image;
}
},
{
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: 'svg-to-png',
title: 'SVG to PNG',