You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, style = 'impressionist', intensity = 5) {
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Make sure intensity is reasonable
intensity = Math.max(1, intensity);
if (style === 'sketch') {
// --- 1. Pencil Sketch Effect ---
// Draw the grayscale base
ctx.filter = 'grayscale(100%)';
ctx.drawImage(originalImg, 0, 0, width, height);
// Create the inverted blurred layer
const blurCanvas = document.createElement('canvas');
blurCanvas.width = width;
blurCanvas.height = height;
const bctx = blurCanvas.getContext('2d');
bctx.filter = `grayscale(100%) invert(100%) blur(${intensity}px)`;
bctx.drawImage(originalImg, 0, 0, width, height);
// Blend with Color Dodge to get the pencil lines
ctx.globalCompositeOperation = 'color-dodge';
ctx.drawImage(blurCanvas, 0, 0, width, height);
// To prevent washed-out sketch lines, multiply the canvas over itself
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
tempCanvas.getContext('2d').drawImage(canvas, 0, 0);
ctx.globalCompositeOperation = 'multiply';
ctx.globalAlpha = 0.6; // Deepens the contrast of the sketch lines
ctx.filter = 'none';
ctx.drawImage(tempCanvas, 0, 0, width, height);
// Reset properties
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
else if (style === 'watercolor') {
// --- 2. Ink and Wash (Watercolor) Effect ---
// Base canvas heavily saturates and slightly blurs colors
const baseCanvas = document.createElement('canvas');
baseCanvas.width = width;
baseCanvas.height = height;
const bctx = baseCanvas.getContext('2d');
bctx.filter = `saturate(150%) blur(${Math.max(1, intensity)}px) contrast(110%)`;
bctx.drawImage(originalImg, 0, 0, width, height);
ctx.drawImage(baseCanvas, 0, 0, width, height);
// Introduce high contrast grayscale edges over it
ctx.globalCompositeOperation = 'multiply';
ctx.globalAlpha = 0.6;
ctx.filter = `blur(${Math.max(1, intensity / 2)}px) grayscale(100%) contrast(200%)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
ctx.filter = 'none';
return canvas;
}
// --- 3. Impressionist Painter (Default "True Artist" Effect) ---
// Fill background with a blurred version of the image to ensure complete coverage seamlessly
ctx.filter = `blur(${intensity * 2}px)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none';
// Set up unblurred image data for sampling pure color and gradient edges
const origCanvas = document.createElement('canvas');
origCanvas.width = width;
origCanvas.height = height;
const octx = origCanvas.getContext('2d');
octx.drawImage(originalImg, 0, 0, width, height);
const imgData = octx.getImageData(0, 0, width, height);
const data = imgData.data;
// Cache Luminosity to easily perform edge detection calculations
const luma = new Float32Array(width * height);
for (let i = 0; i < width * height; i++) {
luma[i] = 0.299 * data[i * 4] + 0.587 * data[i * 4 + 1] + 0.114 * data[i * 4 + 2];
}
function getLuma(x, y) {
x = Math.max(0, Math.min(x, width - 1));
y = Math.max(0, Math.min(y, height - 1));
return luma[y * width + x];
}
const brushSize = intensity;
const strokeLength = intensity * 3;
// Calculate total brush strokes ensuring proportional coverage of the canvas
const numStrokes = Math.floor((width * height) / (brushSize * brushSize)) * 2;
ctx.lineCap = 'round';
// The artist paints the canvas!
for (let i = 0; i < numStrokes; i++) {
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
// Perform basic Sobel operator for orientation mapping
const tl = getLuma(x - 1, y - 1);
const tc = getLuma(x, y - 1);
const tr = getLuma(x + 1, y - 1);
const cl = getLuma(x - 1, y);
const cr = getLuma(x + 1, y);
const bl = getLuma(x - 1, y + 1);
const bc = getLuma(x, y + 1);
const br = getLuma(x + 1, y + 1);
const gx = -tl - 2 * cl - bl + tr + 2 * cr + br;
const gy = -tl - 2 * tc - tr + bl + 2 * bc + br;
const edgeMagnitude = Math.sqrt(gx * gx + gy * gy);
let angle;
// If in a smooth, flat area, paint with chaotic strokes.
// If bordering an edge, strictly follow the orientation of the shape.
if (edgeMagnitude < 15) {
angle = Math.random() * Math.PI * 2;
} else {
angle = Math.atan2(gy, gx) + Math.PI / 2;
}
const progress = i / numStrokes;
// Natural jitter for the human hand element
angle += (Math.random() - 0.5) * (0.2 + progress * 0.5);
const idx = (y * width + x) * 4;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
// Artist starts with big broad strokes and finishes the top layer with fine detailing
const scaleFactor = 2.4 - progress * 2.0;
const currentBrushSize = brushSize * scaleFactor * (0.8 + Math.random() * 0.4);
const currentStrokeLength = strokeLength * scaleFactor * (0.8 + Math.random() * 0.4);
ctx.lineWidth = currentBrushSize;
// Paint mixing properties via minor opacity tweaks per stroke
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${0.7 + Math.random() * 0.3})`;
const halfLen = currentStrokeLength / 2;
ctx.beginPath();
ctx.moveTo(x - Math.cos(angle) * halfLen, y - Math.sin(angle) * halfLen);
ctx.lineTo(x + Math.cos(angle) * halfLen, y + Math.sin(angle) * halfLen);
ctx.stroke();
}
return canvas;
}
Apply Changes