You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies a Japanese Anime-style filter to an image.
* This effect is achieved through a combination of posterization (cel shading),
* edge detection for outlines, and color enhancements.
*
* @param {HTMLImageElement} originalImg The original image element.
* @param {number} [posterizationLevels=5] The number of color levels for the cel shading effect (2-255). Lower values create a more stylized, cartoonish look.
* @param {number} [outlineThickness=2] The thickness of the black outlines in pixels. Set to 0 to disable outlines.
* @param {number} [outlineThreshold=50] The sensitivity for edge detection (0-255). A lower value detects more subtle edges.
* @param {number} [saturation=1.2] A multiplier for color saturation. 1.0 is original, >1.0 increases saturation.
* @param {number} [brightness=1.1] A multiplier for image brightness. 1.0 is original, >1.0 increases brightness.
* @returns {HTMLCanvasElement} A new canvas element with the anime-style filter applied.
*/
function processImage(originalImg, posterizationLevels = 5, outlineThickness = 2, outlineThreshold = 50, saturation = 1.2, brightness = 1.1) {
// 1. SETUP: Create canvases and get dimensions
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', {
willReadFrequently: true
});
// Ensure parameters are valid numbers
const pLevels = Number(posterizationLevels) || 5;
const oThick = Number(outlineThickness) || 0;
const oThresh = Number(outlineThreshold) || 50;
const sat = Number(saturation) || 1.0;
const brt = Number(brightness) || 1.0;
// 2. CEL SHADING: Apply color adjustments and posterization
// Apply saturation and brightness filters
tempCtx.filter = `saturate(${sat}) brightness(${brt})`;
tempCtx.drawImage(originalImg, 0, 0, width, height);
tempCtx.filter = 'none'; // Reset filter
// Get pixel data from the adjusted image
const adjustedImageData = tempCtx.getImageData(0, 0, width, height);
const posterizedData = tempCtx.createImageData(width, height);
const data = adjustedImageData.data;
const pData = posterizedData.data;
// Posterization logic to reduce color palette
const levels = Math.max(2, Math.min(255, parseInt(pLevels)));
const step = 255 / (levels - 1);
for (let i = 0; i < data.length; i += 4) {
pData[i] = Math.round(data[i] / step) * step;
pData[i + 1] = Math.round(data[i + 1] / step) * step;
pData[i + 2] = Math.round(data[i + 2] / step) * step;
pData[i + 3] = data[i + 3]; // Preserve alpha
}
// Draw the posterized base layer onto the final canvas
ctx.putImageData(posterizedData, 0, 0);
// 3. OUTLINES: Detect edges, thicken them, and composite
if (oThick > 0) {
// Step A: Create a grayscale version of the original image for edge detection
tempCtx.clearRect(0, 0, width, height);
tempCtx.filter = 'grayscale(1)';
tempCtx.drawImage(originalImg, 0, 0, width, height);
tempCtx.filter = 'none';
const grayscale = tempCtx.getImageData(0, 0, width, height);
const grayData = grayscale.data;
// Step B: Perform edge detection by checking brightness difference of neighboring pixels
const outlineData = tempCtx.createImageData(width, height);
const oData = outlineData.data;
const threshold = parseInt(oThresh);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
const current = grayData[i];
let isEdge = false;
if (x < width - 1) {
const right = grayData[i + 4];
if (Math.abs(current - right) > threshold) {
isEdge = true;
}
}
if (y < height - 1) {
const bottom = grayData[i + width * 4];
if (Math.abs(current - bottom) > threshold) {
isEdge = true;
}
}
if (isEdge) {
oData[i] = oData[i + 1] = oData[i + 2] = 0; // Black
oData[i + 3] = 255; // Opaque
}
}
}
// Step C: Thicken the outlines using a blur and threshold technique
// Use a second temporary canvas because drawing a canvas with a filter onto itself is problematic
const tempCanvas2 = document.createElement('canvas');
tempCanvas2.width = width;
tempCanvas2.height = height;
const tempCtx2 = tempCanvas2.getContext('2d');
tempCtx2.putImageData(outlineData, 0, 0);
tempCtx.clearRect(0, 0, width, height);
tempCtx.filter = `blur(${oThick}px)`;
tempCtx.drawImage(tempCanvas2, 0, 0, width, height);
tempCtx.filter = 'none';
const blurredOutline = tempCtx.getImageData(0, 0, width, height);
const bData = blurredOutline.data;
// Threshold the alpha channel of the blurred outline to make it a hard edge
for (let i = 0; i < bData.length; i += 4) {
if (bData[i + 3] > 128) {
bData[i] = bData[i + 1] = bData[i + 2] = 0;
bData[i + 3] = 255;
} else {
bData[i + 3] = 0;
}
}
tempCtx.putImageData(blurredOutline, 0, 0);
// Step D: Composite the final outlines onto the main canvas
ctx.drawImage(tempCanvas, 0, 0, width, height);
}
// 4. RETURN FINAL CANVAS
return canvas;
}
Apply Changes