mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
138 lines
3.5 KiB
TypeScript
138 lines
3.5 KiB
TypeScript
export interface TransparencyOptions {
|
|
lowerBound?: { r: number; g: number; b: number };
|
|
upperBound?: { r: number; g: number; b: number };
|
|
autoDetectCorner?: boolean;
|
|
tolerance?: number;
|
|
}
|
|
|
|
const DEFAULT_LOWER_BOUND = { r: 200, g: 200, b: 200 };
|
|
const DEFAULT_UPPER_BOUND = { r: 255, g: 255, b: 255 }; // #FFFFFF
|
|
|
|
export async function removeWhiteBackground(
|
|
imageFile: File | string,
|
|
options: TransparencyOptions = {}
|
|
): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
|
|
img.onload = () => {
|
|
try {
|
|
const result = processImageTransparency(img, options);
|
|
resolve(result);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
};
|
|
|
|
img.onerror = () => {
|
|
reject(new Error('Failed to load image'));
|
|
};
|
|
|
|
if (typeof imageFile === 'string') {
|
|
img.src = imageFile;
|
|
} else {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
img.src = e.target?.result as string;
|
|
};
|
|
reader.onerror = () => {
|
|
reject(new Error('Failed to read image file'));
|
|
};
|
|
reader.readAsDataURL(imageFile);
|
|
}
|
|
});
|
|
}
|
|
|
|
function processImageTransparency(
|
|
img: HTMLImageElement,
|
|
options: TransparencyOptions
|
|
): string {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
if (!ctx) {
|
|
throw new Error('Failed to get canvas context');
|
|
}
|
|
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const data = imageData.data;
|
|
|
|
let lowerBound = options.lowerBound || DEFAULT_LOWER_BOUND;
|
|
let upperBound = options.upperBound || DEFAULT_UPPER_BOUND;
|
|
|
|
if (options.autoDetectCorner) {
|
|
const cornerColor = detectCornerColor(imageData);
|
|
const tolerance = options.tolerance || 10;
|
|
lowerBound = {
|
|
r: Math.max(0, cornerColor.r - tolerance),
|
|
g: Math.max(0, cornerColor.g - tolerance),
|
|
b: Math.max(0, cornerColor.b - tolerance)
|
|
};
|
|
upperBound = {
|
|
r: Math.min(255, cornerColor.r + tolerance),
|
|
g: Math.min(255, cornerColor.g + tolerance),
|
|
b: Math.min(255, cornerColor.b + tolerance)
|
|
};
|
|
}
|
|
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
|
|
if (
|
|
r >= lowerBound.r && r <= upperBound.r &&
|
|
g >= lowerBound.g && g <= upperBound.g &&
|
|
b >= lowerBound.b && b <= upperBound.b
|
|
) {
|
|
data[i + 3] = 0;
|
|
}
|
|
}
|
|
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
return canvas.toDataURL('image/png');
|
|
}
|
|
|
|
|
|
function detectCornerColor(imageData: ImageData): { r: number; g: number; b: number } {
|
|
const { width, height, data } = imageData;
|
|
|
|
const sampleSize = 5;
|
|
const corners = [
|
|
{ x: 0, y: 0 }, // top-left
|
|
{ x: width - sampleSize, y: 0 }, // top-right
|
|
{ x: 0, y: height - sampleSize }, // bottom-left
|
|
{ x: width - sampleSize, y: height - sampleSize } // bottom-right
|
|
];
|
|
|
|
let totalR = 0, totalG = 0, totalB = 0;
|
|
let samples = 0;
|
|
|
|
corners.forEach(corner => {
|
|
for (let dy = 0; dy < sampleSize; dy++) {
|
|
for (let dx = 0; dx < sampleSize; dx++) {
|
|
const x = Math.min(width - 1, Math.max(0, corner.x + dx));
|
|
const y = Math.min(height - 1, Math.max(0, corner.y + dy));
|
|
const i = (y * width + x) * 4;
|
|
|
|
totalR += data[i];
|
|
totalG += data[i + 1];
|
|
totalB += data[i + 2];
|
|
samples++;
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
r: Math.round(totalR / samples),
|
|
g: Math.round(totalG / samples),
|
|
b: Math.round(totalB / samples)
|
|
};
|
|
}
|