Skip to content

Instantly share code, notes, and snippets.

@freddie-freeloader
Created August 20, 2025 14:40
Show Gist options
  • Save freddie-freeloader/9ed70ec67446daa9a59c36bc18c15ef8 to your computer and use it in GitHub Desktop.
Save freddie-freeloader/9ed70ec67446daa9a59c36bc18c15ef8 to your computer and use it in GitHub Desktop.
/**
* 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