You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, posterizeLevels = 6, blurAmount = 2, vibrancy = 1.3, strokeOpacity = 0.5) {
const levels = parseInt(posterizeLevels, 10) || 6;
const blurPx = parseFloat(blurAmount) || 2;
const saturation = parseFloat(vibrancy) || 1.3;
const sOpacity = parseFloat(strokeOpacity) || 0.5;
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Color Layer (Saturation, Blur to simulate ink bleed, and Posterization)
const colorCanvas = document.createElement('canvas');
colorCanvas.width = width;
colorCanvas.height = height;
const cCtx = colorCanvas.getContext('2d');
// Fill with white base first to handle transparent PNGs
cCtx.fillStyle = '#ffffff';
cCtx.fillRect(0, 0, width, height);
// Apply saturate and initial blur for marker bleed
cCtx.filter = `saturate(${saturation * 100}%) blur(${blurPx}px)`;
cCtx.drawImage(originalImg, 0, 0);
cCtx.filter = 'none';
// Posterize to simulate distinct marker colors mapping onto one another
const colorData = cCtx.getImageData(0, 0, width, height);
const data = colorData.data;
const levelFactor = 255 / (levels - 1);
// Process posterization with slight luminosity bumps to match vivid marker inks
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.round(data[i] / levelFactor) * levelFactor; // R
data[i+1] = Math.round(data[i+1] / levelFactor) * levelFactor; // G
data[i+2] = Math.round(data[i+2] / levelFactor) * levelFactor; // B
}
cCtx.putImageData(colorData, 0, 0);
// 2. Line Art / Fineliner Pass (Sobel Edge Detection)
const edgeCanvas = document.createElement('canvas');
edgeCanvas.width = width;
edgeCanvas.height = height;
const eCtx = edgeCanvas.getContext('2d');
eCtx.fillStyle = '#ffffff';
eCtx.fillRect(0, 0, width, height);
eCtx.drawImage(originalImg, 0, 0);
const edgeImgData = eCtx.getImageData(0, 0, width, height);
const eData = edgeImgData.data;
const grayscale = new Uint8ClampedArray(width * height);
// Convert to grayscale
for (let i = 0; i < eData.length; i += 4) {
grayscale[i/4] = eData[i] * 0.299 + eData[i+1] * 0.587 + eData[i+2] * 0.114;
}
const sobelData = new Uint8ClampedArray(eData.length);
sobelData.fill(255); // Fill with white background initially
const kernelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
const kernelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let px = 0, py = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const val = grayscale[(y + ky) * width + (x + kx)];
const weightIdx = (ky + 1) * 3 + (kx + 1);
px += val * kernelX[weightIdx];
py += val * kernelY[weightIdx];
}
}
const magnitude = Math.sqrt(px * px + py * py);
// Enhance and invert for marker-like ink stroke thickness
const invMag = 255 - (magnitude * 2.0);
const clamped = Math.max(0, Math.min(255, invMag));
const idx = (y * width + x) * 4;
sobelData[idx] = clamped;
sobelData[idx+1] = clamped;
sobelData[idx+2] = clamped;
sobelData[idx+3] = 255; // Alpha solid
}
}
edgeImgData.data.set(sobelData);
eCtx.putImageData(edgeImgData, 0, 0);
// 3. Marker Stroke Texture Layer (Directional Stretched Noise)
const texCanvas = document.createElement('canvas');
texCanvas.width = width;
texCanvas.height = height;
const tCtx = texCanvas.getContext('2d');
// Generate base random noise
const noiseDim = Math.max(width, height) * 2;
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = noiseDim;
noiseCanvas.height = noiseDim;
const nCtx = noiseCanvas.getContext('2d');
const noiseData = nCtx.createImageData(noiseDim, noiseDim);
const nData = noiseData.data;
for(let i = 0; i < nData.length; i += 4) {
const val = 180 + Math.random() * 75; // Gray to White grain
nData[i] = val;
nData[i+1] = val;
nData[i+2] = val;
nData[i+3] = 255;
}
nCtx.putImageData(noiseData, 0, 0);
// Transform and stretch the noise to look like diagonal marker strokes
tCtx.fillStyle = '#ffffff';
tCtx.fillRect(0, 0, width, height);
tCtx.save();
tCtx.translate(width / 2, height / 2);
tCtx.rotate(45 * Math.PI / 180);
tCtx.scale(1, 15); // Stretch aggressively on Y to create stroke lines
tCtx.drawImage(noiseCanvas, -noiseDim / 2, -noiseDim / 2);
tCtx.restore();
// 4. Final Composition
// Base is bright white sketchbook paper
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, width, height);
// Map the colors softly to overcome hard posterization boundaries
ctx.filter = `blur(1px)`;
ctx.drawImage(colorCanvas, 0, 0);
ctx.filter = 'none';
// Blend the marker streak texture
ctx.globalCompositeOperation = 'multiply';
ctx.globalAlpha = sOpacity;
ctx.drawImage(texCanvas, 0, 0);
// Map the line art sketch on top
ctx.globalCompositeOperation = 'multiply';
ctx.globalAlpha = 0.85; // Fineliner pen ink
ctx.drawImage(edgeCanvas, 0, 0);
// Reset settings
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1.0;
return canvas;
}
Apply Changes