This commit is contained in:
David Haz
2025-07-12 14:52:40 +03:00
parent d4ff1787eb
commit 62cdbc2dba
7 changed files with 256 additions and 249 deletions

View File

@@ -1,97 +1,97 @@
export function parseImage(file: File): Promise<{ imageData: ImageData; pngBlob: Blob }> {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
return new Promise((resolve, reject) => {
if (!file || !ctx) {
reject(new Error('Invalid file or context'))
return
reject(new Error('Invalid file or context'));
return;
}
const img = new Image()
img.crossOrigin = 'anonymous'
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function () {
if (file.type === 'image/svg+xml') {
img.width = 1000
img.height = 1000
img.width = 1000;
img.height = 1000;
}
const MAX_SIZE = 1000
const MIN_SIZE = 500
let width = img.naturalWidth
let height = img.naturalHeight
const MAX_SIZE = 1000;
const MIN_SIZE = 500;
let width = img.naturalWidth;
let height = img.naturalHeight;
if (width > MAX_SIZE || height > MAX_SIZE || width < MIN_SIZE || height < MIN_SIZE) {
if (width > height) {
if (width > MAX_SIZE) {
height = Math.round((height * MAX_SIZE) / width)
width = MAX_SIZE
height = Math.round((height * MAX_SIZE) / width);
width = MAX_SIZE;
} else if (width < MIN_SIZE) {
height = Math.round((height * MIN_SIZE) / width)
width = MIN_SIZE
height = Math.round((height * MIN_SIZE) / width);
width = MIN_SIZE;
}
} else {
if (height > MAX_SIZE) {
width = Math.round((width * MAX_SIZE) / height)
height = MAX_SIZE
width = Math.round((width * MAX_SIZE) / height);
height = MAX_SIZE;
} else if (height < MIN_SIZE) {
width = Math.round((width * MIN_SIZE) / height)
height = MIN_SIZE
width = Math.round((width * MIN_SIZE) / height);
height = MIN_SIZE;
}
}
}
canvas.width = width
canvas.height = height
canvas.width = width;
canvas.height = height;
const shapeCanvas = document.createElement('canvas')
shapeCanvas.width = width
shapeCanvas.height = height
const shapeCtx = shapeCanvas.getContext('2d')!
shapeCtx.drawImage(img, 0, 0, width, height)
const shapeCanvas = document.createElement('canvas');
shapeCanvas.width = width;
shapeCanvas.height = height;
const shapeCtx = shapeCanvas.getContext('2d')!;
shapeCtx.drawImage(img, 0, 0, width, height);
const shapeImageData = shapeCtx.getImageData(0, 0, width, height)
const data = shapeImageData.data
const shapeMask = new Array(width * height).fill(false)
const shapeImageData = shapeCtx.getImageData(0, 0, width, height);
const data = shapeImageData.data;
const shapeMask = new Array(width * height).fill(false);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx4 = (y * width + x) * 4
const r = data[idx4]
const g = data[idx4 + 1]
const b = data[idx4 + 2]
const a = data[idx4 + 3]
shapeMask[y * width + x] = !((r === 255 && g === 255 && b === 255 && a === 255) || a === 0)
const idx4 = (y * width + x) * 4;
const r = data[idx4];
const g = data[idx4 + 1];
const b = data[idx4 + 2];
const a = data[idx4 + 3];
shapeMask[y * width + x] = !((r === 255 && g === 255 && b === 255 && a === 255) || a === 0);
}
}
function inside(x: number, y: number) {
if (x < 0 || x >= width || y < 0 || y >= height) return false
return shapeMask[y * width + x]
if (x < 0 || x >= width || y < 0 || y >= height) return false;
return shapeMask[y * width + x];
}
const boundaryMask = new Array(width * height).fill(false)
const boundaryMask = new Array(width * height).fill(false);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x
if (!shapeMask[idx]) continue
let isBoundary = false
const idx = y * width + x;
if (!shapeMask[idx]) continue;
let isBoundary = false;
for (let ny = y - 1; ny <= y + 1 && !isBoundary; ny++) {
for (let nx = x - 1; nx <= x + 1 && !isBoundary; nx++) {
if (!inside(nx, ny)) {
isBoundary = true
isBoundary = true;
}
}
}
if (isBoundary) {
boundaryMask[idx] = true
boundaryMask[idx] = true;
}
}
}
const interiorMask = new Array(width * height).fill(false)
const interiorMask = new Array(width * height).fill(false);
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const idx = y * width + x
const idx = y * width + x;
if (
shapeMask[idx] &&
shapeMask[idx - 1] &&
@@ -99,82 +99,79 @@ export function parseImage(file: File): Promise<{ imageData: ImageData; pngBlob:
shapeMask[idx - width] &&
shapeMask[idx + width]
) {
interiorMask[idx] = true
interiorMask[idx] = true;
}
}
}
const u = new Float32Array(width * height).fill(0)
const newU = new Float32Array(width * height).fill(0)
const C = 0.01
const ITERATIONS = 300
const u = new Float32Array(width * height).fill(0);
const newU = new Float32Array(width * height).fill(0);
const C = 0.01;
const ITERATIONS = 300;
function getU(x: number, y: number, arr: Float32Array) {
if (x < 0 || x >= width || y < 0 || y >= height) return 0
if (!shapeMask[y * width + x]) return 0
return arr[y * width + x]
if (x < 0 || x >= width || y < 0 || y >= height) return 0;
if (!shapeMask[y * width + x]) return 0;
return arr[y * width + x];
}
for (let iter = 0; iter < ITERATIONS; iter++) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x
const idx = y * width + x;
if (!shapeMask[idx] || boundaryMask[idx]) {
newU[idx] = 0
continue
newU[idx] = 0;
continue;
}
const sumN = getU(x + 1, y, u) + getU(x - 1, y, u) + getU(x, y + 1, u) + getU(x, y - 1, u)
newU[idx] = (C + sumN) / 4
const sumN = getU(x + 1, y, u) + getU(x - 1, y, u) + getU(x, y + 1, u) + getU(x, y - 1, u);
newU[idx] = (C + sumN) / 4;
}
}
u.set(newU)
u.set(newU);
}
let maxVal = 0
let maxVal = 0;
for (let i = 0; i < width * height; i++) {
if (u[i] > maxVal) maxVal = u[i]
if (u[i] > maxVal) maxVal = u[i];
}
const alpha = 2.0
const outImg = ctx.createImageData(width, height)
const alpha = 2.0;
const outImg = ctx.createImageData(width, height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x
const px = idx * 4
const idx = y * width + x;
const px = idx * 4;
if (!shapeMask[idx]) {
outImg.data[px] = 255
outImg.data[px + 1] = 255
outImg.data[px + 2] = 255
outImg.data[px + 3] = 255
outImg.data[px] = 255;
outImg.data[px + 1] = 255;
outImg.data[px + 2] = 255;
outImg.data[px + 3] = 255;
} else {
const raw = u[idx] / maxVal
const remapped = Math.pow(raw, alpha)
const gray = 255 * (1 - remapped)
outImg.data[px] = gray
outImg.data[px + 1] = gray
outImg.data[px + 2] = gray
outImg.data[px + 3] = 255
const raw = u[idx] / maxVal;
const remapped = Math.pow(raw, alpha);
const gray = 255 * (1 - remapped);
outImg.data[px] = gray;
outImg.data[px + 1] = gray;
outImg.data[px + 2] = gray;
outImg.data[px + 3] = 255;
}
}
}
ctx.putImageData(outImg, 0, 0)
ctx.putImageData(outImg, 0, 0);
canvas.toBlob(
blob => {
if (!blob) {
reject(new Error('Failed to create PNG blob'))
return
}
resolve({
imageData: outImg,
pngBlob: blob
})
},
'image/png'
)
}
canvas.toBlob(blob => {
if (!blob) {
reject(new Error('Failed to create PNG blob'));
return;
}
resolve({
imageData: outImg,
pngBlob: blob
});
}, 'image/png');
};
img.onerror = () => reject(new Error('Failed to load image'))
img.src = URL.createObjectURL(file)
})
img.onerror = () => reject(new Error('Failed to load image'));
img.src = URL.createObjectURL(file);
});
}