mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-09 00:19:31 -06:00
178 lines
5.5 KiB
TypeScript
178 lines
5.5 KiB
TypeScript
export function parseImage(file: File): Promise<{ imageData: ImageData; pngBlob: Blob }> {
|
|
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;
|
|
}
|
|
|
|
const img = new Image();
|
|
img.crossOrigin = 'anonymous';
|
|
img.onload = function () {
|
|
if (file.type === 'image/svg+xml') {
|
|
img.width = 1000;
|
|
img.height = 1000;
|
|
}
|
|
|
|
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;
|
|
} else if (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;
|
|
} else if (height < MIN_SIZE) {
|
|
width = Math.round((width * MIN_SIZE) / height);
|
|
height = MIN_SIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
function inside(x: number, y: number) {
|
|
if (x < 0 || x >= width || y < 0 || y >= height) return false;
|
|
return shapeMask[y * width + x];
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
if (isBoundary) {
|
|
boundaryMask[idx] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (
|
|
shapeMask[idx] &&
|
|
shapeMask[idx - 1] &&
|
|
shapeMask[idx + 1] &&
|
|
shapeMask[idx - width] &&
|
|
shapeMask[idx + width]
|
|
) {
|
|
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;
|
|
|
|
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];
|
|
}
|
|
|
|
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;
|
|
if (!shapeMask[idx] || boundaryMask[idx]) {
|
|
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;
|
|
}
|
|
}
|
|
u.set(newU);
|
|
}
|
|
|
|
let maxVal = 0;
|
|
for (let i = 0; i < width * height; i++) {
|
|
if (u[i] > maxVal) maxVal = u[i];
|
|
}
|
|
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;
|
|
if (!shapeMask[idx]) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
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');
|
|
};
|
|
|
|
img.onerror = () => reject(new Error('Failed to load image'));
|
|
img.src = URL.createObjectURL(file);
|
|
});
|
|
}
|