You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* A comprehensive image utility function that performs various operations on an image.
*
* @param {Image} originalImg The original JavaScript Image object.
* @param {string} operation The operation to perform. Possible values:
* 'grayscale', 'sepia', 'invert', 'brightness', 'contrast', 'blur', 'sharpen',
* 'resize', 'crop', 'rotate', 'addText'.
* @param {number} filterValue A multi-purpose value for filters:
* - For 'brightness': Adjusts brightness (-255 to 255). Default is 20.
* - For 'contrast': Adjusts contrast (-100 to 100). Default is 20.
* - For 'sharpen': The sharpening amount (e.g., 0.5 to 3). Default is 1.
* @param {number} blurRadius The radius for the 'blur' or 'sharpen' effect. Default is 5.
* @param {number} newWidth The target width for 'resize'. Default (0) is half of original.
* @param {number} newHeight The target height for 'resize'. Default (0) is half of original.
* @param {number} cropX The starting X coordinate for 'crop'. Default logic centers the crop.
* @param {number} cropY The starting Y coordinate for 'crop'. Default logic centers the crop.
* @param {number} cropWidth The width of the 'crop' area. Default (0) is half of original.
* @param {number} cropHeight The height of the 'crop' area. Default (0) is half of original.
* @param {number} angle The rotation angle in degrees for 'rotate'. Default is 90.
* @param {string} text The text string to add for 'addText'.
* @param {number} textX The X coordinate for the text.
* @param {number} textY The Y coordinate for the text.
* @param {string} textFont The font family for the text. Can be a web-safe font or any Google Font.
* @param {number} textSize The font size in pixels for the text.
* @param {string} textColor The color of the text (e.g., '#FFFFFF', 'red').
* @returns {Promise<HTMLCanvasElement>} A promise that resolves to a canvas element with the processed image.
*/
async function processImage(
originalImg,
operation = 'grayscale',
filterValue = 20,
blurRadius = 5,
newWidth = 0,
newHeight = 0,
cropX = 0,
cropY = 0,
cropWidth = 0,
cropHeight = 0,
angle = 90,
text = 'Hello, World!',
textX = 20,
textY = 50,
textFont = 'Roboto',
textSize = 48,
textColor = '#FFFFFF'
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Helper function to clamp color values to the 0-255 range
const clamp = (value) => Math.max(0, Math.min(255, Math.round(value)));
switch (operation.toLowerCase()) {
// --- PIXEL-BASED FILTERS ---
case 'grayscale':
case 'sepia':
case 'invert':
case 'brightness':
case 'contrast':
{
canvas.width = originalImg.width;
canvas.height = originalImg.height;
ctx.drawImage(originalImg, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
if (operation === 'grayscale') {
const avg = (r + g + b) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
} else if (operation === 'sepia') {
data[i] = clamp(0.393 * r + 0.769 * g + 0.189 * b);
data[i + 1] = clamp(0.349 * r + 0.686 * g + 0.168 * b);
data[i + 2] = clamp(0.272 * r + 0.534 * g + 0.131 * b);
} else if (operation === 'invert') {
data[i] = 255 - r;
data[i + 1] = 255 - g;
data[i + 2] = 255 - b;
} else if (operation === 'brightness') {
data[i] = clamp(r + filterValue);
data[i + 1] = clamp(g + filterValue);
data[i + 2] = clamp(b + filterValue);
} else if (operation === 'contrast') {
const factor = (259 * (filterValue + 255)) / (255 * (259 - filterValue));
data[i] = clamp(factor * (r - 128) + 128);
data[i + 1] = clamp(factor * (g - 128) + 128);
data[i + 2] = clamp(factor * (b - 128) + 128);
}
}
ctx.putImageData(imageData, 0, 0);
break;
}
// --- CANVAS-NATIVE FILTERS ---
case 'blur':
{
canvas.width = originalImg.width;
canvas.height = originalImg.height;
ctx.filter = `blur(${blurRadius}px)`;
ctx.drawImage(originalImg, 0, 0);
ctx.filter = 'none';
break;
}
case 'sharpen':
{ // Unsharp Masking implementation
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.filter = `blur(${blurRadius}px)`;
tempCtx.drawImage(originalImg, 0, 0);
const blurredData = tempCtx.getImageData(0, 0, canvas.width, canvas.height).data;
ctx.drawImage(originalImg, 0, 0);
const originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const originalData = originalImageData.data;
// If filterValue is its default (20), use a sensible sharpen default of 1. Otherwise use provided value.
const sharpenAmount = (filterValue === 20) ? 1 : filterValue;
for (let i = 0; i < originalData.length; i += 4) {
originalData[i] = clamp(originalData[i] + (originalData[i] - blurredData[i]) * sharpenAmount);
originalData[i + 1] = clamp(originalData[i + 1] + (originalData[i + 1] - blurredData[i + 1]) * sharpenAmount);
originalData[i + 2] = clamp(originalData[i + 2] + (originalData[i + 2] - blurredData[i + 2]) * sharpenAmount);
}
ctx.putImageData(originalImageData, 0, 0);
break;
}
// --- TRANSFORMATIONS ---
case 'resize':
{
const w = newWidth > 0 ? newWidth : originalImg.width / 2;
const h = newHeight > 0 ? newHeight : originalImg.height / 2;
canvas.width = w;
canvas.height = h;
ctx.drawImage(originalImg, 0, 0, w, h);
break;
}
case 'crop':
{
const w = cropWidth > 0 ? cropWidth : originalImg.width / 2;
const h = cropHeight > 0 ? cropHeight : originalImg.height / 2;
const x = cropWidth > 0 ? cropX : (originalImg.width - w) / 2;
const y = cropHeight > 0 ? cropY : (originalImg.height - h) / 2;
canvas.width = w;
canvas.height = h;
ctx.drawImage(originalImg, x, y, w, h, 0, 0, w, h);
break;
}
case 'rotate':
{
const rad = angle * Math.PI / 180;
const w = originalImg.width;
const h = originalImg.height;
const absCos = Math.abs(Math.cos(rad));
const absSin = Math.abs(Math.sin(rad));
canvas.width = Math.ceil(w * absCos + h * absSin);
canvas.height = Math.ceil(w * absSin + h * absCos);
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(rad);
ctx.drawImage(originalImg, -w / 2, -h / 2);
break;
}
// --- OVERLAYS ---
case 'addtext':
{
canvas.width = originalImg.width;
canvas.height = originalImg.height;
ctx.drawImage(originalImg, 0, 0);
const webSafeFonts = ['Arial', 'Verdana', 'Helvetica', 'Tahoma', 'Times New Roman', 'Georgia', 'Courier New'];
let finalFont = textFont;
if (!webSafeFonts.find(f => finalFont.toLowerCase().includes(f.toLowerCase()))) {
try {
if (!document.fonts.check(`${textSize}px ${finalFont}`)) {
const fontNameForUrl = finalFont.replace(/ /g, '+');
const response = await fetch(`https://fonts.googleapis.com/css2?family=${fontNameForUrl}:wght@400;700&display=swap`, {
headers: { 'User-Agent': 'Mozilla/5.0' }
});
if (!response.ok) throw new Error('Font request failed');
const css = await response.text();
const fontUrlMatch = css.match(/url\((https:\/\/[^)]+\.woff2)\)/);
if (fontUrlMatch && fontUrlMatch[1]) {
const googleFont = new FontFace(finalFont, `url(${fontUrlMatch[1]})`);
await googleFont.load();
document.fonts.add(googleFont);
} else {
console.warn(`Could not load font '${finalFont}'. Falling back to Arial.`);
finalFont = 'Arial';
}
}
} catch (e) {
console.error(`Error loading font '${finalFont}':`, e, "Falling back to Arial.");
finalFont = 'Arial';
}
}
ctx.font = `${textSize}px "${finalFont}"`;
ctx.fillStyle = textColor;
ctx.textBaseline = 'top';
ctx.strokeStyle = '#000000';
ctx.lineWidth = Math.max(1, Math.ceil(textSize / 24));
ctx.strokeText(text, textX, textY);
ctx.fillText(text, textX, textY);
break;
}
// --- DEFAULT ---
default:
canvas.width = originalImg.width;
canvas.height = originalImg.height;
ctx.drawImage(originalImg, 0, 0);
console.warn(`Unknown operation: '${operation}'. Returning original image.`);
}
return canvas;
}
Apply Changes