diff --git a/assets/app.js b/assets/app.js
index 27b9867..2ebfc4c 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -1005,6 +1005,23 @@ self.onmessage = function(e) {
showHowtoIfNeeded();
// --- Exporters ---
+ const getPathBoundingBox = (paths, sx, sy) => {
+ if (!paths || paths.length === 0) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+ for (const path of paths) {
+ for (const pt of path) {
+ const x = pt.x * sx;
+ const y = pt.y * sy;
+ if (x < minX) minX = x;
+ if (y < minY) minY = y;
+ if (x > maxX) maxX = x;
+ if (y > maxY) maxY = y;
+ }
+ }
+ if (minX === Infinity) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
+ return { minX, minY, maxX, maxY };
+ };
+
const formatSvgNumber = (value, precision = 2) => Number.isFinite(value) ? Number(value.toFixed(precision)).toString() : '0';
const getSvgPathString = (pts, closed, precision = 2) => {
@@ -1069,12 +1086,14 @@ self.onmessage = function(e) {
els.btnExportSvg.addEventListener('click', () => {
if (state.paths.length === 0) return;
- const outputW = state.originalWidth || state.imageObj.width;
- const outputH = state.originalHeight || state.imageObj.height;
const sx = state.traceScaleX || 1;
const sy = state.traceScaleY || 1;
- const scalePts = (pts, stride = 1) => thinSvgPoints(pts, stride).map(pt => ({ x: pt.x * sx, y: pt.y * sy }));
+ const bbox = getPathBoundingBox(state.paths, sx, sy);
+ const trimmedW = Math.max(1, bbox.maxX - bbox.minX);
+ const trimmedH = Math.max(1, bbox.maxY - bbox.minY);
+
+ const scalePts = (pts, stride = 1) => thinSvgPoints(pts, stride).map(pt => ({ x: pt.x * sx - bbox.minX, y: pt.y * sy - bbox.minY }));
const makeSvgPath = (precision = 2, stride = 1) => state.paths.map(p => getSvgPathString(scalePts(p, stride), true, precision)).join(' ');
const fullSvgPath = makeSvgPath();
@@ -1083,15 +1102,15 @@ self.onmessage = function(e) {
if (textureDataUrl) {
if (state.fillMode === 'pattern') {
const pWidth = state.fillImageObj.width * state.fillScale * sx, pHeight = state.fillImageObj.height * state.fillScale * sy;
- svgContent = ``;
+ svgContent = ``;
} else {
const imgWidth = state.fillImageObj.width * state.fillScale * sx, imgHeight = state.fillImageObj.height * state.fillScale * sy;
- svgContent = ``;
+ svgContent = ``;
}
} else {
svgContent = ` `;
}
- return ``;
+ return ``;
};
let blob = svgBlobFromString(buildSvg());
@@ -1137,16 +1156,19 @@ self.onmessage = function(e) {
els.btnExportPng.addEventListener('click', () => {
if (state.paths.length === 0) return;
- const outputW = state.originalWidth || state.imageObj.width;
- const outputH = state.originalHeight || state.imageObj.height;
const sx = state.traceScaleX || 1;
const sy = state.traceScaleY || 1;
- const pngScale = getPngExportScale(outputW, outputH);
- const exportW = Math.max(1, Math.round(outputW * pngScale));
- const exportH = Math.max(1, Math.round(outputH * pngScale));
- const ex = exportW / outputW;
- const ey = exportH / outputH;
- const scalePts = (pts) => pts.map(pt => ({ x: pt.x * sx * ex, y: pt.y * sy * ey }));
+
+ const bbox = getPathBoundingBox(state.paths, sx, sy);
+ const trimmedW = Math.max(1, bbox.maxX - bbox.minX);
+ const trimmedH = Math.max(1, bbox.maxY - bbox.minY);
+
+ const pngScale = getPngExportScale(trimmedW, trimmedH);
+ const exportW = Math.max(1, Math.round(trimmedW * pngScale));
+ const exportH = Math.max(1, Math.round(trimmedH * pngScale));
+ const ex = exportW / trimmedW;
+ const ey = exportH / trimmedH;
+ const scalePts = (pts) => pts.map(pt => ({ x: (pt.x * sx - bbox.minX) * ex, y: (pt.y * sy - bbox.minY) * ey }));
const expCanvas = document.createElement('canvas');
expCanvas.width = exportW;
@@ -1166,7 +1188,7 @@ self.onmessage = function(e) {
enableHighQualitySmoothing(pCtx);
pCtx.drawImage(state.fillImageObj, 0, 0, pCanvas.width, pCanvas.height);
const pattern = eCtx.createPattern(pCanvas, 'repeat');
- pattern.setTransform(new DOMMatrix().translate(state.fillOffsetX * sx * ex, state.fillOffsetY * sy * ey));
+ pattern.setTransform(new DOMMatrix().translate((state.fillOffsetX * sx - bbox.minX) * ex, (state.fillOffsetY * sy - bbox.minY) * ey));
eCtx.fillStyle = pattern;
eCtx.fill('evenodd');
} else {
@@ -1174,8 +1196,8 @@ self.onmessage = function(e) {
eCtx.clip('evenodd');
eCtx.drawImage(
state.fillImageObj,
- state.fillOffsetX * sx * ex,
- state.fillOffsetY * sy * ey,
+ (state.fillOffsetX * sx - bbox.minX) * ex,
+ (state.fillOffsetY * sy - bbox.minY) * ey,
Math.max(1, Math.round(state.fillImageObj.width * state.fillScale * sx * ex)),
Math.max(1, Math.round(state.fillImageObj.height * state.fillScale * sy * ey))
);