You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
cropStr = "", // "x,y,w,h" (empty string for full original image)
resizeStr = "", // "w,h" (empty string or "0,0" to bypass, specify one to keep aspect ratio)
rotateDeg = 0, // Rotation angle in standard degrees
flipStr = "0,0", // "flipHorizontal,flipVertical" (use 1 for true, 0 for false)
filter = "none", // CSS filter strings e.g., "grayscale(100%)", "blur(5px)"
censorStr = "", // "x,y,w,h,type" (type is "black" or "pixelate", absolute to final canvas)
text = "", // Overlay text, use \n for multiline
overlayImgData = "", // URL or Base64 string for an overlay/watermark image
format = "image/webp", // Target output format: "canvas", "webp", "jpeg", "png", "gif"
quality = 0.92 // Output quality for JPEG/WebP compression (0.0 to 1.0)
) {
// 1. Process Dimensions & Crop
let sx = 0, sy = 0;
let sw = originalImg.naturalWidth || originalImg.width;
let sh = originalImg.naturalHeight || originalImg.height;
if (cropStr && cropStr.trim() !== "") {
const cParts = cropStr.split(',').map(Number);
if (cParts.length >= 4) {
sx = cParts[0];
sy = cParts[1];
sw = cParts[2];
sh = cParts[3];
}
}
// Bounds check to avoid invalid extract regions
const nw = originalImg.naturalWidth || originalImg.width;
const nh = originalImg.naturalHeight || originalImg.height;
sw = Math.min(sw, nw - sx);
sh = Math.min(sh, nh - sy);
// 2. Process Resize
let rw = sw, rh = sh;
if (resizeStr && resizeStr.trim() !== "") {
const rParts = resizeStr.split(',').map(Number);
if (rParts.length >= 2) {
const tw = rParts[0];
const th = rParts[1];
if (tw > 0 && th > 0) {
rw = tw; rh = th;
} else if (tw > 0) {
rw = tw; rh = (tw / sw) * sh;
} else if (th > 0) {
rh = th; rw = (th / sh) * sw;
}
}
}
// 3. Process Rotation Canvas Bounds
const rad = (Number(rotateDeg) || 0) * (Math.PI / 180);
const absCos = Math.abs(Math.cos(rad));
const absSin = Math.abs(Math.sin(rad));
const finalW = (rw * absCos) + (rh * absSin);
const finalH = (rw * absSin) + (rh * absCos);
// Setup Canvas
const canvas = document.createElement('canvas');
canvas.width = finalW;
canvas.height = finalH;
const ctx = canvas.getContext('2d');
// Parse Output format
let outMime = format.toLowerCase().trim();
if (outMime === "jpg") outMime = "image/jpeg";
else if (!outMime.startsWith("image/") && outMime !== "canvas") {
outMime = "image/" + outMime;
}
// Fill white background for JPEG exports to prevent transparent areas becoming black
if (outMime === "image/jpeg") {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, finalW, finalH);
}
// 4. Process Flips / Reverse
let flipH = 1, flipV = 1;
if (flipStr && flipStr.trim() !== "") {
const fParts = flipStr.split(',').map(Number);
if (fParts.length >= 2) {
if (fParts[0] === 1) flipH = -1;
if (fParts[1] === 1) flipV = -1;
}
}
// Apply Transformation Matrix
ctx.translate(finalW / 2, finalH / 2);
ctx.rotate(rad);
ctx.scale(flipH, flipV);
// 5. Apply Optical Filters / Effects
if (filter && filter !== "none") {
ctx.filter = filter;
}
// Draw Source Image
ctx.drawImage(originalImg, sx, sy, sw, sh, -rw / 2, -rh / 2, rw, rh);
// Reset transformations for Censor, Overlay Image, and Text logic
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.filter = "none";
// 6. Censor Coordinates (Pixelate or Black block)
if (censorStr && censorStr.trim() !== "") {
const p = censorStr.split(',');
if (p.length >= 4) {
const cx = Number(p[0]), cy = Number(p[1]), cw = Number(p[2]), ch = Number(p[3]);
const cType = p[4] ? p[4].trim().toLowerCase() : "black";
if (cType === "pixelate") {
// Extract Censor Area
const tCanv = document.createElement('canvas');
tCanv.width = cw; tCanv.height = ch;
const tCtx = tCanv.getContext('2d');
tCtx.drawImage(canvas, cx, cy, cw, ch, 0, 0, cw, ch);
// Down-scale
const shrinkRatio = 0.1; // 10% blocks
const smW = Math.max(1, Math.floor(cw * shrinkRatio));
const smH = Math.max(1, Math.floor(ch * shrinkRatio));
const sCanv = document.createElement('canvas');
sCanv.width = smW; sCanv.height = smH;
sCanv.getContext('2d').drawImage(tCanv, 0, 0, cw, ch, 0, 0, smW, smH);
// Up-scale sharply without anti-aliasing interpolation
tCtx.imageSmoothingEnabled = false;
tCtx.clearRect(0, 0, cw, ch);
tCtx.drawImage(sCanv, 0, 0, smW, smH, 0, 0, cw, ch);
// Overlay back onto main canvas
ctx.drawImage(tCanv, 0, 0, cw, ch, cx, cy, cw, ch);
} else {
ctx.fillStyle = "black";
ctx.fillRect(cx, cy, cw, ch);
}
}
}
// 7. Embed Add-on Image
if (overlayImgData && overlayImgData.length > 5) {
try {
const oImg = new Image();
oImg.crossOrigin = "anonymous";
await new Promise((resolve, reject) => {
oImg.onload = resolve;
oImg.onerror = reject;
oImg.src = overlayImgData;
});
// Defaulting placement to exact center
ctx.drawImage(oImg, (finalW - oImg.width) / 2, (finalH - oImg.height) / 2);
} catch (err) {
console.error("Could not load overlay image.", err);
}
}
// 8. Add Meme/Captions Overlay Text
if (text && text.trim() !== "") {
ctx.font = "bold 40px sans-serif";
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.lineWidth = 4;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// Multi-line spacing alignment
const parsedText = text.replace(/\\n/g, '\n');
const lines = parsedText.split('\n');
const lineHeight = 48;
// Position chunk at the bottom relative to line count
const startY = finalH - (lines.length * lineHeight) + (lineHeight / 2) - 20;
lines.forEach((line, index) => {
const y = startY + (index * lineHeight);
ctx.strokeText(line, finalW / 2, y);
ctx.fillText(line, finalW / 2, y);
});
}
// 9. Output Processing / Converter Engine (to WebP, GIF, etc.)
if (outMime === "canvas") {
return canvas;
}
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (!blob) return reject(new Error("Image conversion format processing failed."));
const resImg = new Image();
resImg.onload = () => resolve(resImg);
resImg.onerror = reject;
resImg.src = URL.createObjectURL(blob);
}, outMime, Number(quality));
});
}
Apply Changes