You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, posterizeLevels = 5, edgeThreshold = 50, edgeColor = '#000000', dotSize = 4, dotIntensity = 0.3) {
/**
* Converts an image to a comic book art style using posterization, edge detection, and a halftone overlay.
*
* @param {Image} originalImg - The source javascript Image object.
* @param {number} posterizeLevels - The number of color levels for posterization (e.g., 2-10).
* @param {number} edgeThreshold - The sensitivity for edge detection (e.g., 20-80). Higher means less sensitive.
* @param {string} edgeColor - The hex color code for the outlines (e.g., '#000000').
* @param {number} dotSize - The size of the halftone dots in pixels.
* @param {number} dotIntensity - The opacity of the halftone overlay (0.0 to 1.0).
* @returns {HTMLCanvasElement} A canvas element with the comic-styled image.
*/
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0);
const originalImageData = ctx.getImageData(0, 0, width, height);
const processedImageData = ctx.createImageData(width, height);
const originalData = originalImageData.data;
const processedData = processedImageData.data;
// Helper to convert a hex color string to an RGB object
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 }; // Default to black if invalid
};
const edgeRgb = hexToRgb(edgeColor);
// Helper to calculate the luminance of a color
const getLuminance = (r, g, b) => 0.299 * r + 0.587 * g + 0.114 * b;
// Clamp posterizeLevels to a safe range
const safePosterizeLevels = Math.max(2, Math.min(255, posterizeLevels));
const factor = 255 / (safePosterizeLevels - 1);
// --- Pass 1: Posterization and Edge Detection ---
for (let i = 0; i < originalData.length; i += 4) {
const x = (i / 4) % width;
const y = Math.floor((i / 4) / width);
// Edge detection by comparing luminance with neighboring pixels
let isEdge = false;
if (x < width - 1 && y < height - 1) {
const currentLuminance = getLuminance(originalData[i], originalData[i + 1], originalData[i + 2]);
const rightIndex = i + 4;
const rightLuminance = getLuminance(originalData[rightIndex], originalData[rightIndex + 1], originalData[rightIndex + 2]);
const bottomIndex = i + width * 4;
const bottomLuminance = getLuminance(originalData[bottomIndex], originalData[bottomIndex + 1], originalData[bottomIndex + 2]);
const diff = Math.abs(currentLuminance - rightLuminance) + Math.abs(currentLuminance - bottomLuminance);
if (diff > edgeThreshold) {
isEdge = true;
}
}
// Set pixel color based on whether it's an edge or not
if (isEdge) {
processedData[i] = edgeRgb.r;
processedData[i + 1] = edgeRgb.g;
processedData[i + 2] = edgeRgb.b;
processedData[i + 3] = 255;
} else {
// Posterize the color
processedData[i] = Math.round(Math.round(originalData[i] / factor) * factor);
processedData[i + 1] = Math.round(Math.round(originalData[i + 1] / factor) * factor);
processedData[i + 2] = Math.round(Math.round(originalData[i + 2] / factor) * factor);
processedData[i + 3] = 255;
}
}
// Draw the posterized and inked image to the main canvas
ctx.putImageData(processedImageData, 0, 0);
// --- Pass 2: Halftone Overlay ---
if (dotIntensity > 0 && dotSize > 0) {
const halftoneCanvas = document.createElement('canvas');
const halftoneCtx = halftoneCanvas.getContext('2d');
halftoneCanvas.width = width;
halftoneCanvas.height = height;
// Fill with neutral gray for the 'overlay' blend mode to work correctly
halftoneCtx.fillStyle = '#808080';
halftoneCtx.fillRect(0, 0, width, height);
halftoneCtx.fillStyle = '#000000';
// Create the dot pattern based on the luminance of the processed image
for (let y = 0; y < height; y += dotSize) {
for (let x = 0; x < width; x += dotSize) {
const index = (y * width + x) * 4;
const luminance = getLuminance(processedData[index], processedData[index + 1], processedData[index + 2]) / 255; // Normalize to 0-1
// Darker areas get bigger dots
const radius = (dotSize / 2.2) * (1 - luminance);
if (radius > 0) {
halftoneCtx.beginPath();
halftoneCtx.arc(x + dotSize / 2, y + dotSize / 2, radius, 0, Math.PI * 2, false);
halftoneCtx.fill();
}
}
}
// Composite the halftone pattern onto the main canvas
ctx.globalCompositeOperation = 'overlay';
ctx.globalAlpha = Math.max(0, Math.min(1, dotIntensity));
ctx.drawImage(halftoneCanvas, 0, 0);
// Reset context properties to default
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1.0;
}
return canvas;
}
Apply Changes