feat: add crop and canvas resize workflows

This commit is contained in:
syntaxbullet
2026-07-11 12:16:33 +02:00
parent 4d7358af4b
commit 9fb3a19912
20 changed files with 312 additions and 35 deletions

View File

@@ -0,0 +1,17 @@
import { describe, expect, test } from "bun:test";
import { resolveLayerDrawImage } from "./exportArtboardPng";
describe("artboard PNG export", () => {
test("draws only cropped source pixels at their retained document position", () => {
const draw = resolveLayerDrawImage({
id: "layer", type: "raster", name: "Layer", visible: true, locked: false, opacity: 1, assetId: "asset",
sourceRect: { x: 10, y: 5, w: 40, h: 20 },
transform: { position: { x: 100, y: 50 }, scale: { x: 2, y: 3 }, rotation: 0 },
}, { w: 200, h: 100 });
expect(draw).toEqual({
source: { x: 10, y: 5, w: 40, h: 20 },
destination: { x: 120, y: 65, w: 80, h: 60 },
});
});
});

View File

@@ -2,6 +2,7 @@ import type { Artboard } from "@core/artboard";
import type { Asset } from "@core/asset";
import type { Rect } from "@core/geometry";
import type { Layer } from "@core/layer";
import type { Size } from "@core/geometry";
import { getLayerMask } from "@core/layer-mask-utils";
export async function downloadArtboardPng(artboard: Artboard, assets: readonly Asset[]) {
@@ -66,16 +67,25 @@ async function drawLayer(
}
const image = await loadImage(asset.source);
context.drawImage(
image,
layer.transform.position.x,
layer.transform.position.y,
asset.intrinsicSize.w * layer.transform.scale.x,
asset.intrinsicSize.h * layer.transform.scale.y,
);
const draw = resolveLayerDrawImage(layer, asset.intrinsicSize);
context.drawImage(image, draw.source.x, draw.source.y, draw.source.w, draw.source.h,
draw.destination.x, draw.destination.y, draw.destination.w, draw.destination.h);
context.restore();
}
export function resolveLayerDrawImage(layer: Exclude<Layer, { type: "group" }>, intrinsicSize: Size) {
const source = layer.sourceRect ?? { x: 0, y: 0, ...intrinsicSize };
return {
source,
destination: {
x: layer.transform.position.x + source.x * layer.transform.scale.x,
y: layer.transform.position.y + source.y * layer.transform.scale.y,
w: source.w * layer.transform.scale.x,
h: source.h * layer.transform.scale.y,
},
};
}
async function drawMaskedLayer(
context: CanvasRenderingContext2D,
layer: Layer,