Please bookmark this page to avoid losing your image tool!

Image Colored Pencil Filter Effect Tool

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, edgeThreshold = 80, colorLevels = 8, noiseFactor = 0.08, pencilStrokeColor = '#303030', pencilStrokeOpacity = 0.7) {
    const canvas = document.createElement('canvas');
    // Using { willReadFrequently: true } can be an optimization hint for browsers
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); 
    
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;

    if (originalImg.width === 0 || originalImg.height === 0) {
        console.warn("Image Colored Pencil Filter: Input image has zero width or height.");
        return canvas; // Return empty canvas
    }

    // Draw the original image onto the canvas. We'll get its pixel data from here.
    ctx.drawImage(originalImg, 0, 0, originalImg.width, originalImg.height);
    
    let imageData;
    try {
      // Get pixel data from the canvas
      imageData = ctx.getImageData(0, 0, originalImg.width, originalImg.height);
    } catch (e) {
      console.error("Image Colored Pencil Filter: Could not get image data from canvas. This might be due to cross-origin restrictions if the image is from a different domain. Error: ", e);
      // Fallback: return the canvas with the original image drawn, as filtering isn't possible.
      // To enable processing for cross-origin images, ensure the image element has `crossOrigin="Anonymous"` 
      // and the server provides appropriate CORS headers.
      return canvas;
    }
    
    const data = imageData.data;
    const width = originalImg.width;
    const height = originalImg.height;

    // This will hold the pixel data for the new image
    const outputPixelData = new Uint8ClampedArray(data.length);

    // Helper function to clamp values between min and max
    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    // Helper function to parse hex color string (e.g., '#RRGGBB' or '#RGB') to an RGB object
    const hexToRgb = (hex) => {
        let localHex = String(hex).trim();
        // Expand shorthand form (e.g. "#03F") to full form (e.g. "#0033FF")
        const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        localHex = localHex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
        
        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(localHex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    };
    
    const strokeRgb = hexToRgb(pencilStrokeColor) || { r: 48, g: 48, b: 48 }; // Default to #303030 if parsing fails

    // Create a grayscale version of the image. This is used for edge detection.
    const grayscaleData = new Uint8ClampedArray(width * height);
    for (let i = 0, j = 0; i < data.length; i += 4, j++) {
        // Standard luminance calculation for grayscale conversion
        grayscaleData[j] = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
    }

    // Sobel kernels for edge detection (approximates image gradient)
    const Gx = [
        [-1, 0, 1],
        [-2, 0, 2],
        [-1, 0, 1]
    ];
    const Gy = [
        [-1, -2, -1],
        [0, 0, 0],
        [1, 2, 1]
    ];

    // Ensure colorLevels is at least 2 for posterization calculation
    const finalColorLevels = Math.max(2, Math.floor(colorLevels));
    // Divisor for posterization: determines the size of color "steps"
    const posterizeDivisor = 255 / (finalColorLevels - 1);
    
    // This value controls how "sensitive" the stroke opacity is to edge magnitude.
    // A smaller value means strokes reach full pencilStrokeOpacity more quickly as magnitude increases beyond edgeThreshold.
    const edgeOpacityNormalizationFactor = 50; 

    // Process each pixel of the image
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const currentIndex = (y * width + x) * 4;

            const originalR = data[currentIndex];
            const originalG = data[currentIndex + 1];
            const originalB = data[currentIndex + 2];
            const originalA = data[currentIndex + 3];

            // 1. Posterize Color (reduce the number of distinct colors)
            let pr = Math.round(originalR / posterizeDivisor) * posterizeDivisor;
            let pg = Math.round(originalG / posterizeDivisor) * posterizeDivisor;
            let pb = Math.round(originalB / posterizeDivisor) * posterizeDivisor;

            // 2. Add Noise (simulates paper grain or pencil texture)
            if (noiseFactor > 0) {
                // Generates noise in the range: +/- (noiseFactor * 128)
                const noiseVal = (Math.random() - 0.5) * 2 * noiseFactor * 128; 
                pr = clamp(pr + noiseVal, 0, 255);
                pg = clamp(pg + noiseVal, 0, 255);
                pb = clamp(pb + noiseVal, 0, 255);
            }

            // 3. Edge Detection using Sobel operator on the grayscale image
            let magnitude = 0;
            // Avoid image borders for kernel operation as it references neighboring pixels
            if (x > 0 && x < width - 1 && y > 0 && y < height - 1) { 
                let sumX = 0;
                let sumY = 0;
                for (let ky = -1; ky <= 1; ky++) {
                    for (let kx = -1; kx <= 1; kx++) {
                        // Get grayscale value of neighboring pixels
                        const pixelGrayscaleVal = grayscaleData[(y + ky) * width + (x + kx)];
                        sumX += pixelGrayscaleVal * Gx[ky + 1][kx + 1];
                        sumY += pixelGrayscaleVal * Gy[ky + 1][kx + 1];
                    }
                }
                magnitude = Math.sqrt(sumX * sumX + sumY * sumY);
            }
            
            let finalR = pr;
            let finalG = pg;
            let finalB = pb;

            // 4. Apply Pencil Stroke if an edge is detected (magnitude > threshold)
            if (magnitude > edgeThreshold) {
                // Calculate opacity of the stroke based on how much the magnitude exceeds the threshold
                let currentEdgeBlend = pencilStrokeOpacity * Math.min(1, (magnitude - edgeThreshold) / edgeOpacityNormalizationFactor);
                currentEdgeBlend = Math.max(0, currentEdgeBlend); // Ensure blend factor is not negative
                
                // Blend the posterized/noised color with the specified pencilStrokeColor
                finalR = clamp(pr * (1 - currentEdgeBlend) + strokeRgb.r * currentEdgeBlend, 0, 255);
                finalG = clamp(pg * (1 - currentEdgeBlend) + strokeRgb.g * currentEdgeBlend, 0, 255);
                finalB = clamp(pb * (1 - currentEdgeBlend) + strokeRgb.b * currentEdgeBlend, 0, 255);
            }
            
            outputPixelData[currentIndex]     = finalR;
            outputPixelData[currentIndex + 1] = finalG;
            outputPixelData[currentIndex + 2] = finalB;
            outputPixelData[currentIndex + 3] = originalA; // Preserve original alpha
        }
    }

    // Create new ImageData from the processed pixel data and put it back onto the canvas
    const outputImageData = new ImageData(outputPixelData, width, height);
    ctx.putImageData(outputImageData, 0, 0);

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Colored Pencil Filter Effect Tool allows users to apply a creative colored pencil effect to their images. By processing the original image, the tool converts it into a stylized version that mimics the appearance of colored pencil drawings. This effect is achieved through techniques such as posterization, edge detection, and noise simulation, allowing for customizable parameters like edge sensitivity, color levels, noise factor, and pencil stroke color and opacity. This tool can be useful for artists, designers, and hobbyists looking to enhance their images with a unique artistic flair, create engaging social media content, or generate illustrations for digital projects.

Leave a Reply

Your email address will not be published. Required fields are marked *