Please bookmark this page to avoid losing your image tool!

Image To Alcohol Marker Picture Converter

(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.
async function processImage(originalImg, numColors = 32, detailLevel = 8, outlineThickness = 2, saturation = 1.2) {

    // === HELPER FUNCTIONS SCOPED WITHIN THE MAIN FUNCTION ===

    /**
     * Converts an RGB color value to HSL.
     * @param {number} r - The red color value (0-255)
     * @param {number} g - The green color value (0-255)
     * @param {number} b - The blue color value (0-255)
     * @returns {Array<number>} - The HSL representation [h, s, l]
     */
    const rgbToHsl = (r, g, b) => {
        r /= 255; g /= 255; b /= 255;
        const max = Math.max(r, g, b), min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        if (max === min) {
            h = s = 0; // achromatic
        } else {
            const d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            h /= 6;
        }
        return [h, s, l];
    };

    /**
     * Converts an HSL color value to RGB.
     * @param {number} h - The hue
     * @param {number} s - The saturation
     * @param {number} l - The lightness
     * @returns {Array<number>} - The RGB representation [r, g, b]
     */
    const hslToRgb = (h, s, l) => {
        let r, g, b;
        if (s === 0) {
            r = g = b = l; // achromatic
        } else {
            const hue2rgb = (p, q, t) => {
                if (t < 0) t += 1;
                if (t > 1) t -= 1;
                if (t < 1/6) return p + (q - p) * 6 * t;
                if (t < 1/2) return q;
                if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
                return p;
            };
            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            const p = 2 * l - q;
            r = hue2rgb(p, q, h + 1/3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1/3);
        }
        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    };

    /**
     * K-Means clustering algorithm for color quantization.
     * @param {Array<Array<number>>} data - Array of [r,g,b] pixels
     * @param {number} k - The number of clusters (colors)
     * @returns {{palette: Array<Array<number>>, assignments: Array<number>}}
     */
    const kMeans = (data, k) => {
        // 1. Initialize centroids by picking k random points
        const centroids = [];
        const usedIndices = new Set();
        while (centroids.length < k && centroids.length < data.length) {
            const index = Math.floor(Math.random() * data.length);
            if (!usedIndices.has(index)) {
                centroids.push([...data[index]]);
                usedIndices.add(index);
            }
        }

        const assignments = new Array(data.length);
        const distance = (a, b) => (a[0] - b[0])**2 + (a[1] - b[1])**2 + (a[2] - b[2])**2;

        for (let iter = 0; iter < 10; iter++) { // Iterate 10 times
            // 2. Assign each point to the closest centroid
            for (let i = 0; i < data.length; i++) {
                let bestDist = Infinity;
                let bestIndex = 0;
                for (let j = 0; j < centroids.length; j++) {
                    const d = distance(data[i], centroids[j]);
                    if (d < bestDist) {
                        bestDist = d;
                        bestIndex = j;
                    }
                }
                assignments[i] = bestIndex;
            }

            // 3. Recalculate centroids
            const sums = Array.from({ length: k }, () => [0, 0, 0]);
            const counts = new Array(k).fill(0);
            for (let i = 0; i < data.length; i++) {
                const clusterIndex = assignments[i];
                sums[clusterIndex][0] += data[i][0];
                sums[clusterIndex][1] += data[i][1];
                sums[clusterIndex][2] += data[i][2];
                counts[clusterIndex]++;
            }

            for (let j = 0; j < k; j++) {
                if (counts[j] > 0) {
                    centroids[j] = [
                        Math.round(sums[j][0] / counts[j]),
                        Math.round(sums[j][1] / counts[j]),
                        Math.round(sums[j][2] / counts[j]),
                    ];
                }
            }
        }
        return { palette: centroids, assignments };
    };

    // === MAIN LOGIC START ===
    
    // 1. SETUP CANVAS
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const width = originalImg.width;
    const height = originalImg.height;
    canvas.width = width;
    canvas.height = height;

    // Create a working canvas for intermediate steps
    const workCanvas = document.createElement('canvas');
    workCanvas.width = width;
    workCanvas.height = height;
    const workCtx = workCanvas.getContext('2d');
    workCtx.drawImage(originalImg, 0, 0);

    // 2. APPLY SATURATION
    if (saturation !== 1.0) {
        const imageData = workCtx.getImageData(0, 0, width, height);
        const data = imageData.data;
        for (let i = 0; i < data.length; i += 4) {
            const [h, s, l] = rgbToHsl(data[i], data[i+1], data[i+2]);
            const newS = Math.max(0, Math.min(1, s * saturation));
            const [r, g, b] = hslToRgb(h, newS, l);
            data[i] = r; data[i+1] = g; data[i+2] = b;
        }
        workCtx.putImageData(imageData, 0, 0);
    }
    
    // 3. SIMPLIFY AND QUANTIZE
    const simplificationFactor = Math.max(2, 12 - Math.max(1, Math.min(10, detailLevel)));
    const smallWidth = Math.round(width / simplificationFactor);
    const smallHeight = Math.round(height / simplificationFactor);

    const smallCanvas = document.createElement('canvas');
    smallCanvas.width = smallWidth;
    smallCanvas.height = smallHeight;
    const smallCtx = smallCanvas.getContext('2d');

    smallCtx.drawImage(workCanvas, 0, 0, smallWidth, smallHeight);
    
    const smallImageData = smallCtx.getImageData(0, 0, smallWidth, smallHeight);
    const pixels = [];
    for (let i = 0; i < smallImageData.data.length; i += 4) {
        pixels.push([smallImageData.data[i], smallImageData.data[i+1], smallImageData.data[i+2]]);
    }

    const { palette, assignments } = kMeans(pixels, Math.max(2, numColors));

    const quantizedData = smallCtx.createImageData(smallWidth, smallHeight);
    for (let i = 0; i < assignments.length; i++) {
        const color = palette[assignments[i]];
        quantizedData.data[i * 4 + 0] = color[0];
        quantizedData.data[i * 4 + 1] = color[1];
        quantizedData.data[i * 4 + 2] = color[2];
        quantizedData.data[i * 4 + 3] = 255;
    }
    smallCtx.putImageData(quantizedData, 0, 0);

    // 4. RENDER FINAL IMAGE
    // Draw enlarged, pixelated (posterized) colors
    ctx.imageSmoothingEnabled = false;
    ctx.drawImage(smallCanvas, 0, 0, width, height);

    // Apply a slight blur to simulate marker color bleeding
    if (detailLevel < 10) { // Don't blur on max detail
      ctx.filter = `blur(0.75px)`;
      ctx.drawImage(canvas, 0, 0);
      ctx.filter = 'none';
    }

    // Add marker paper/texture
    const textureCanvas = document.createElement('canvas');
    textureCanvas.width = width;
    textureCanvas.height = height;
    const textureCtx = textureCanvas.getContext('2d');
    const textureData = textureCtx.createImageData(width, height);
    for (let i = 0; i < textureData.data.length; i += 4) {
        const val = Math.random() * 40 + 215; // Light gray noise
        textureData.data[i] = val;
        textureData.data[i+1] = val;
        textureData.data[i+2] = val;
        textureData.data[i+3] = 255;
    }
    textureCtx.putImageData(textureData, 0, 0);
    ctx.globalCompositeOperation = 'multiply';
    ctx.globalAlpha = 0.4;
    ctx.drawImage(textureCanvas, 0, 0);
    ctx.globalCompositeOperation = 'source-over';
    ctx.globalAlpha = 1.0;

    // Add outlines if enabled
    if (outlineThickness > 0) {
        const posterizedData = ctx.getImageData(0, 0, width, height).data;
        const outlineMaskCanvas = document.createElement('canvas');
        outlineMaskCanvas.width = width;
        outlineMaskCanvas.height = height;
        const outlineCtx = outlineMaskCanvas.getContext('2d');
        const outlineData = outlineCtx.createImageData(width, height);
        
        const threshold = 20;
        for (let y = 0; y < height - 1; y++) {
            for (let x = 0; x < width - 1; x++) {
                const i = (y * width + x) * 4;
                const i_right = (y * width + (x + 1)) * 4;
                const i_down = ((y + 1) * width + x) * 4;
                
                const isEdge = 
                    Math.abs(posterizedData[i] - posterizedData[i_right]) > threshold ||
                    Math.abs(posterizedData[i+1] - posterizedData[i_right+1]) > threshold ||
                    Math.abs(posterizedData[i+2] - posterizedData[i_right+2]) > threshold ||
                    Math.abs(posterizedData[i] - posterizedData[i_down]) > threshold ||
                    Math.abs(posterizedData[i+1] - posterizedData[i_down+1]) > threshold ||
                    Math.abs(posterizedData[i+2] - posterizedData[i_down+2]) > threshold;

                if (isEdge) {
                    outlineData.data[i] = 0;
                    outlineData.data[i+1] = 0;
                    outlineData.data[i+2] = 0;
                    outlineData.data[i+3] = 255;
                }
            }
        }
        outlineCtx.putImageData(outlineData, 0, 0);

        // Use a shadow trick to "thicken" the 1px outline
        ctx.save();
        ctx.shadowColor = 'rgba(0, 0, 0, 0.8)';
        ctx.shadowBlur = outlineThickness; 
        ctx.shadowOffsetX = width * 2; // Move shadow far away
        ctx.shadowOffsetY = 0;
        
        // Draw the mask off-screen to only render its shadow
        ctx.drawImage(outlineMaskCanvas, -width * 2, 0);
        ctx.drawImage(outlineMaskCanvas, -width * 2, 0); // Draw twice for solidity
        ctx.restore();
    }
    
    // 5. RETURN THE FINAL CANVAS
    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 To Alcohol Marker Picture Converter transforms standard images into artwork that mimics the appearance of alcohol marker illustrations. This tool allows users to specify various parameters such as the number of colors used for rendering, detail level, outline thickness, and color saturation. It’s ideal for artists and designers looking to create stylized artwork or for projects that require a unique, hand-drawn aesthetic. Use cases include illustration for graphic designs, educational materials, presentations, and digital artwork that seeks to replicate the look of traditional marker drawings.

Leave a Reply

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