Created
August 20, 2025 14:40
-
-
Save freddie-freeloader/9ed70ec67446daa9a59c36bc18c15ef8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Applies a box blur convolution filter to image data | |
* @param imageData - The image data to blur | |
* @param radius - The blur radius | |
* @returns Blurred image data | |
*/ | |
function applyBoxBlur(imageData: ImageData, radius: number): ImageData { | |
const { width, height, data } = imageData; | |
const output = new ImageData(width, height); | |
const iterations = Math.max(1, Math.floor(radius / 2)); // Multiple iterations for smoother blur | |
// Copy original data for processing | |
let currentData = new Uint8ClampedArray(data); | |
let tempData = new Uint8ClampedArray(data.length); | |
for (let iter = 0; iter < iterations; iter++) { | |
// Horizontal pass | |
for (let y = 0; y < height; y++) { | |
for (let x = 0; x < width; x++) { | |
let r = 0, g = 0, b = 0, a = 0; | |
let count = 0; | |
for (let dx = -radius; dx <= radius; dx++) { | |
const nx = Math.max(0, Math.min(width - 1, x + dx)); | |
const srcOffset = (y * width + nx) * 4; | |
r += currentData[srcOffset]; | |
g += currentData[srcOffset + 1]; | |
b += currentData[srcOffset + 2]; | |
a += currentData[srcOffset + 3]; | |
count++; | |
} | |
const dstOffset = (y * width + x) * 4; | |
tempData[dstOffset] = r / count; | |
tempData[dstOffset + 1] = g / count; | |
tempData[dstOffset + 2] = b / count; | |
tempData[dstOffset + 3] = a / count; | |
} | |
} | |
// Vertical pass | |
for (let x = 0; x < width; x++) { | |
for (let y = 0; y < height; y++) { | |
let r = 0, g = 0, b = 0, a = 0; | |
let count = 0; | |
for (let dy = -radius; dy <= radius; dy++) { | |
const ny = Math.max(0, Math.min(height - 1, y + dy)); | |
const srcOffset = (ny * width + x) * 4; | |
r += tempData[srcOffset]; | |
g += tempData[srcOffset + 1]; | |
b += tempData[srcOffset + 2]; | |
a += tempData[srcOffset + 3]; | |
count++; | |
} | |
const dstOffset = (y * width + x) * 4; | |
currentData[dstOffset] = r / count; | |
currentData[dstOffset + 1] = g / count; | |
currentData[dstOffset + 2] = b / count; | |
currentData[dstOffset + 3] = a / count; | |
} | |
} | |
} | |
// Copy final result to output | |
for (let i = 0; i < currentData.length; i++) { | |
output.data[i] = currentData[i]; | |
} | |
return output; | |
} | |
/** | |
* Blurs an image file using custom convolution filter | |
* @param file - The image file to blur | |
* @param blurAmount - The blur amount in pixels (default: 10) | |
* @returns Promise<File> - The blurred image as a new File object | |
*/ | |
export async function blurImage(file: File, blurAmount: number = 10): Promise<File> { | |
return new Promise((resolve, reject) => { | |
const img = new Image(); | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
if (!ctx) { | |
reject(new Error('Could not get canvas context')); | |
return; | |
} | |
img.onload = () => { | |
// Set canvas dimensions to match image | |
canvas.width = img.width; | |
canvas.height = img.height; | |
// Draw the original image | |
ctx.drawImage(img, 0, 0); | |
// Get image data and apply custom blur | |
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
const blurredData = applyBoxBlur(imageData, Math.max(1, Math.floor(blurAmount / 2))); | |
// Put the blurred data back | |
ctx.putImageData(blurredData, 0, 0); | |
// Convert canvas to blob and then to File | |
canvas.toBlob((blob) => { | |
if (!blob) { | |
reject(new Error('Failed to create blob from canvas')); | |
return; | |
} | |
// Create a new File with the same name but add '_blurred' suffix | |
const fileName = file.name; | |
// const fileName = file.name.replace(/(\.[^.]+)$/, '_blurred$1'); | |
const blurredFile = new File([blob], fileName, { | |
type: file.type, | |
lastModified: Date.now(), | |
}); | |
resolve(blurredFile); | |
}, file.type, 0.9); // 0.9 quality for JPEG compression | |
}; | |
img.onerror = () => { | |
reject(new Error('Failed to load image')); | |
}; | |
// Load the image file | |
const reader = new FileReader(); | |
reader.onload = (e) => { | |
if (e.target?.result) { | |
img.src = e.target.result as string; | |
} else { | |
reject(new Error('Failed to read file')); | |
} | |
}; | |
reader.onerror = () => { | |
reject(new Error('Failed to read file')); | |
}; | |
reader.readAsDataURL(file); | |
}); | |
} | |
/** | |
* Blurs multiple image files | |
* @param files - Array of image files to blur | |
* @param blurAmount - The blur amount in pixels (default: 10) | |
* @returns Promise<File[]> - Array of blurred image files | |
*/ | |
export async function blurImages(files: File[], blurAmount: number = 10): Promise<File[]> { | |
const blurPromises = files.map(file => blurImage(file, blurAmount)); | |
return Promise.all(blurPromises); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment