feat: update geometry tools to new types

This commit is contained in:
2026-09-10 12:01:54 +05:00
parent 1e85fe36a6
commit 2937974fe7
+45 -23
View File
@@ -23,7 +23,7 @@ import {
type FlipAxis, type FlipAxis,
} from "../core/geometry"; } from "../core/geometry";
import { field, toolSchema, type Dimension } from "../registry-schema"; import { field, toolSchema, type Dimension } from "../registry-schema";
import type { ToolEntry } from "./types"; import { imgTool, type ToolEntry } from "./types";
interface AddBorderParams { interface AddBorderParams {
thickness: number; thickness: number;
@@ -42,7 +42,8 @@ const addBorder: ToolEntry<AddBorderParams> = {
"Draws a colored frame of the chosen thickness around the image.", "Draws a colored frame of the chosen thickness around the image.",
category: "geometry", category: "geometry",
schema: addBorderSchema, schema: addBorderSchema,
run: (img, p) => input: "image",
run: imgTool((img, p) =>
expandCanvas( expandCanvas(
img, img,
p.thickness, p.thickness,
@@ -51,6 +52,7 @@ const addBorder: ToolEntry<AddBorderParams> = {
p.thickness, p.thickness,
p.color, p.color,
), ),
),
}; };
interface FitOnBackgroundParams { interface FitOnBackgroundParams {
@@ -82,7 +84,8 @@ const fitOnBackground: ToolEntry<FitOnBackgroundParams> = {
"Places the image centered on a canvas of the given size with a transparent or colored background.", "Places the image centered on a canvas of the given size with a transparent or colored background.",
category: "geometry", category: "geometry",
schema: fitOnBackgroundSchema, schema: fitOnBackgroundSchema,
run: (img, p) => { input: "image",
run: imgTool((img, p) => {
const width = Math.trunc(p.size.width); const width = Math.trunc(p.size.width);
const height = Math.trunc(p.size.height); const height = Math.trunc(p.size.height);
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
@@ -98,7 +101,7 @@ const fitOnBackground: ToolEntry<FitOnBackgroundParams> = {
Math.max(0, height - img.height - top), Math.max(0, height - img.height - top),
p.transparent ? undefined : p.color, p.transparent ? undefined : p.color,
); );
}, }),
}; };
interface ChangeCanvasSizeParams { interface ChangeCanvasSizeParams {
@@ -141,13 +144,15 @@ const changeCanvasSizeTool: ToolEntry<ChangeCanvasSizeParams> = {
"Sets the exact canvas size: overflow is cropped, missing space is filled with transparency. Anchor picks which part of the image stays.", "Sets the exact canvas size: overflow is cropped, missing space is filled with transparency. Anchor picks which part of the image stays.",
category: "geometry", category: "geometry",
schema: changeCanvasSizeSchema, schema: changeCanvasSizeSchema,
run: (img, p) => input: "image",
run: imgTool((img, p) =>
changeCanvasSize( changeCanvasSize(
img, img,
Math.trunc(p.size.width), Math.trunc(p.size.width),
Math.trunc(p.size.height), Math.trunc(p.size.height),
p.anchor, p.anchor,
), ),
),
}; };
interface ResizeParams { interface ResizeParams {
@@ -177,7 +182,8 @@ const resizeTool: ToolEntry<ResizeParams> = {
"Scales the image with bilinear interpolation. With aspect kept, one side defines the scale; if both are set, the image fits inside them.", "Scales the image with bilinear interpolation. With aspect kept, one side defines the scale; if both are set, the image fits inside them.",
category: "geometry", category: "geometry",
schema: resizeSchema, schema: resizeSchema,
run: (img, p) => { input: "image",
run: imgTool((img, p) => {
const keepAspect = p.keepAspect; const keepAspect = p.keepAspect;
let w = Math.trunc(p.size.width); let w = Math.trunc(p.size.width);
let h = Math.trunc(p.size.height); let h = Math.trunc(p.size.height);
@@ -196,7 +202,7 @@ const resizeTool: ToolEntry<ResizeParams> = {
throw new ToolError("errors.resizeSize"); throw new ToolError("errors.resizeSize");
} }
return resize(img, w, h); return resize(img, w, h);
}, }),
}; };
interface CropParams { interface CropParams {
@@ -228,14 +234,15 @@ const cropTool: ToolEntry<CropParams> = {
"Cuts out a rectangular area. Coordinates and sizes may go beyond the image — the area is clipped to the intersection.", "Cuts out a rectangular area. Coordinates and sizes may go beyond the image — the area is clipped to the intersection.",
category: "geometry", category: "geometry",
schema: cropSchema, schema: cropSchema,
run: (img, p) => { input: "image",
run: imgTool((img, p) => {
const w = Math.trunc(p.size.width); const w = Math.trunc(p.size.width);
const h = Math.trunc(p.size.height); const h = Math.trunc(p.size.height);
if (w <= 0 || h <= 0) { if (w <= 0 || h <= 0) {
throw new ToolError("errors.cropSize"); throw new ToolError("errors.cropSize");
} }
return crop(img, Math.trunc(p.x), Math.trunc(p.y), w, h); return crop(img, Math.trunc(p.x), Math.trunc(p.y), w, h);
}, }),
}; };
interface RotateParams { interface RotateParams {
@@ -259,7 +266,8 @@ const rotateTool: ToolEntry<RotateParams> = {
description: "Rotates by 90°, 180° or 270° clockwise without quality loss.", description: "Rotates by 90°, 180° or 270° clockwise without quality loss.",
category: "geometry", category: "geometry",
schema: rotateSchema, schema: rotateSchema,
run: (img, p) => rotate90(img, Number(p.angle) / 90), input: "image",
run: imgTool((img, p) => rotate90(img, Number(p.angle) / 90)),
}; };
interface FlipParams { interface FlipParams {
@@ -282,7 +290,8 @@ const flipTool: ToolEntry<FlipParams> = {
description: "Mirrors horizontally or vertically without quality loss.", description: "Mirrors horizontally or vertically without quality loss.",
category: "geometry", category: "geometry",
schema: flipSchema, schema: flipSchema,
run: (img, p) => flip(img, p.axis), input: "image",
run: imgTool((img, p) => flip(img, p.axis)),
}; };
interface AddPaddingParams { interface AddPaddingParams {
@@ -314,7 +323,8 @@ const addPaddingTool: ToolEntry<AddPaddingParams> = {
"Expands the canvas on all sides by the chosen number of pixels.", "Expands the canvas on all sides by the chosen number of pixels.",
category: "geometry", category: "geometry",
schema: addPaddingSchema, schema: addPaddingSchema,
run: (img, p) => input: "image",
run: imgTool((img, p) =>
expandCanvas( expandCanvas(
img, img,
p.padding, p.padding,
@@ -323,6 +333,7 @@ const addPaddingTool: ToolEntry<AddPaddingParams> = {
p.padding, p.padding,
p.transparent ? undefined : p.color, p.transparent ? undefined : p.color,
), ),
),
}; };
interface TileParams { interface TileParams {
@@ -341,7 +352,8 @@ const tileTool: ToolEntry<TileParams> = {
description: "Repeats the image in a grid of the chosen columns and rows.", description: "Repeats the image in a grid of the chosen columns and rows.",
category: "geometry", category: "geometry",
schema: tileSchema, schema: tileSchema,
run: (img, p) => tile(img, p.columns, p.rows), input: "image",
run: imgTool((img, p) => tile(img, p.columns, p.rows)),
}; };
interface EmptyParams {} interface EmptyParams {}
@@ -355,7 +367,8 @@ const centerByAlphaTool: ToolEntry<EmptyParams> = {
"Finds the opaque part of the image and centers it on the original canvas.", "Finds the opaque part of the image and centers it on the original canvas.",
category: "geometry", category: "geometry",
schema: centerByAlphaSchema, schema: centerByAlphaSchema,
run: (img) => centerByAlpha(img), input: "image",
run: imgTool((img) => centerByAlpha(img)),
}; };
interface SkewParams { interface SkewParams {
@@ -375,7 +388,8 @@ const skewTool: ToolEntry<SkewParams> = {
"Shifts content horizontally and vertically — a perspective effect.", "Shifts content horizontally and vertically — a perspective effect.",
category: "geometry", category: "geometry",
schema: skewSchema, schema: skewSchema,
run: (img, p) => skewImage(img, p.degX, p.degY), input: "image",
run: imgTool((img, p) => skewImage(img, p.degX, p.degY)),
}; };
interface RotateFreeParams { interface RotateFreeParams {
@@ -393,7 +407,8 @@ const rotateFreeTool: ToolEntry<RotateFreeParams> = {
"Rotation by any angle. The canvas grows to fit the new bounds; corners stay transparent.", "Rotation by any angle. The canvas grows to fit the new bounds; corners stay transparent.",
category: "geometry", category: "geometry",
schema: rotateFreeSchema, schema: rotateFreeSchema,
run: (img, p) => rotateFreeImage(img, p.angle), input: "image",
run: imgTool((img, p) => rotateFreeImage(img, p.angle)),
}; };
interface ZoomParams { interface ZoomParams {
@@ -411,7 +426,8 @@ const zoomTool: ToolEntry<ZoomParams> = {
"Magnifies content toward the center. The canvas keeps its size — edges are cropped.", "Magnifies content toward the center. The canvas keeps its size — edges are cropped.",
category: "geometry", category: "geometry",
schema: zoomSchema, schema: zoomSchema,
run: (img, p) => zoomImage(img, p.scale), input: "image",
run: imgTool((img, p) => zoomImage(img, p.scale)),
}; };
interface TrimEmptySpaceParams { interface TrimEmptySpaceParams {
@@ -429,7 +445,8 @@ const trimEmptySpaceTool: ToolEntry<TrimEmptySpaceParams> = {
"Crops transparent borders around the content. Pixels with alpha above the threshold count as content.", "Crops transparent borders around the content. Pixels with alpha above the threshold count as content.",
category: "geometry", category: "geometry",
schema: trimEmptySpaceSchema, schema: trimEmptySpaceSchema,
run: (img, p) => trimToContent(img, p.threshold), input: "image",
run: imgTool((img, p) => trimToContent(img, p.threshold)),
}; };
type AspectRatio = "1:1" | "4:3" | "3:4" | "3:2" | "2:3" | "16:9" | "9:16"; type AspectRatio = "1:1" | "4:3" | "3:4" | "3:2" | "2:3" | "16:9" | "9:16";
@@ -468,11 +485,12 @@ const changeAspectRatioTool: ToolEntry<ChangeAspectRatioParams> = {
"Fits the image into a target aspect ratio: crop the center to fill, or pad with transparency.", "Fits the image into a target aspect ratio: crop the center to fill, or pad with transparency.",
category: "geometry", category: "geometry",
schema: changeAspectRatioSchema, schema: changeAspectRatioSchema,
run: (img, p) => { input: "image",
run: imgTool((img, p) => {
const [rw, rh] = p.ratio.split(":").map(Number); const [rw, rh] = p.ratio.split(":").map(Number);
const ratio = rw / rh; const ratio = rw / rh;
return p.mode === "pad" ? padToRatio(img, ratio) : cropToRatio(img, ratio); return p.mode === "pad" ? padToRatio(img, ratio) : cropToRatio(img, ratio);
}, }),
}; };
interface SwapOrientationParams { interface SwapOrientationParams {
@@ -496,7 +514,8 @@ const swapOrientationTool: ToolEntry<SwapOrientationParams> = {
"Rotates the image by 90° when its orientation differs from the target — landscape becomes portrait and back. Square images are untouched.", "Rotates the image by 90° when its orientation differs from the target — landscape becomes portrait and back. Square images are untouched.",
category: "geometry", category: "geometry",
schema: swapOrientationSchema, schema: swapOrientationSchema,
run: (img, p) => forceOrientation(img, p.target), input: "image",
run: imgTool((img, p) => forceOrientation(img, p.target)),
}; };
type SymmetricAxis = "horizontal" | "vertical"; type SymmetricAxis = "horizontal" | "vertical";
@@ -533,7 +552,8 @@ const symmetricCopyTool: ToolEntry<SymmetricCopyParams> = {
"Doubles the canvas by mirroring the kept side onto the empty half — instant symmetric pattern.", "Doubles the canvas by mirroring the kept side onto the empty half — instant symmetric pattern.",
category: "geometry", category: "geometry",
schema: symmetricCopySchema, schema: symmetricCopySchema,
run: (img, p) => symmetricCopy(img, p.axis, p.keepSide), input: "image",
run: imgTool((img, p) => symmetricCopy(img, p.axis, p.keepSide)),
}; };
interface ShiftParams { interface ShiftParams {
@@ -554,7 +574,8 @@ const shiftTool: ToolEntry<ShiftParams> = {
description: "Moves content by the given X and Y offset.", description: "Moves content by the given X and Y offset.",
category: "geometry", category: "geometry",
schema: shiftSchema, schema: shiftSchema,
run: (img, p) => input: "image",
run: imgTool((img, p) =>
transformImage( transformImage(
img, img,
[1, 0, 0, 1, -Math.trunc(p.offsetX), -Math.trunc(p.offsetY)], [1, 0, 0, 1, -Math.trunc(p.offsetX), -Math.trunc(p.offsetY)],
@@ -562,6 +583,7 @@ const shiftTool: ToolEntry<ShiftParams> = {
img.height, img.height,
p.color, p.color,
), ),
),
}; };
export const geometryEntries = [ export const geometryEntries = [