Please bookmark this page to avoid losing your image tool!

Grand Theft Auto Style Photo Filter

(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, posterizationLevelsParam = 4, outlineStrengthParam = 120, saturationBoostParam = 1.2) {

    const posterizationLevels = Math.max(2, Math.floor(Number(posterizationLevelsParam)));
    const outlineStrength = Number(outlineStrengthParam);
    const saturationBoost = Number(saturationBoostParam);

    // Helper function to convert RGB to HSL
    function 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];
    }

    // Helper function to convert HSL to RGB
    function 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)];
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimization hint
    
    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    canvas.width = imgWidth;
    canvas.height = imgHeight;

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Error getting ImageData: ", e);
        // Fallback: draw an error message on the canvas
        ctx.clearRect(0,0,canvas.width, canvas.height);
        ctx.fillStyle = "rgba(200, 0, 0, 0.7)";
        ctx.fillRect(0,0,canvas.width,canvas.height);
        ctx.fillStyle = "white";
        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";

        const lines = `Error processing image.
This might be due to cross-origin restrictions if the image isn't hosted on the same domain.
Details: ${e.message}`.split('\n');
        
        lines.forEach((line, index) => {
            ctx.fillText(line, canvas.width / 2, canvas.height / 2 - (lines.length-1)*10 + index*20);
        });
        return canvas;
    }
    
    const data = imageData.data;
    
    const posterizedDataArray = new Uint8ClampedArray(data.length);
    const posterizeFactor = 255 / (posterizationLevels - 1);

    // --- Stage 1: Saturation Boost and Posterization ---
    for (let i = 0; i < data.length; i += 4) {
        let r = data[i];
        let g = data[i + 1];
        let b = data[i + 2];

        if (saturationBoost !== 1.0) {
            let [h, s, l] = rgbToHsl(r, g, b);
            s = Math.max(0, Math.min(1, s * saturationBoost)); // Boost/reduce saturation, clamp to [0, 1]
            [r, g, b] = hslToRgb(h, s, l);
        }

        posterizedDataArray[i]     = Math.round(r / posterizeFactor) * posterizeFactor;
        posterizedDataArray[i + 1] = Math.round(g / posterizeFactor) * posterizeFactor;
        posterizedDataArray[i + 2] = Math.round(b / posterizeFactor) * posterizeFactor;
        posterizedDataArray[i + 3] = data[i + 3]; 
    }

    const finalData = new Uint8ClampedArray(data.length);
    const width = canvas.width;
    const height = canvas.height;

    const Gx_kernel = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
    const Gy_kernel = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];

    // --- Stage 2: Outline Detection (Sobel on grayscale of posterized image) ---
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const idx = (y * width + x) * 4;
            let gx_lum = 0;
            let gy_lum = 0;

            for (let ky = -1; ky <= 1; ky++) {
                for (let kx = -1; kx <= 1; kx++) {
                    const nX = Math.max(0, Math.min(width - 1, x + kx));
                    const nY = Math.max(0, Math.min(height - 1, y + ky));
                    
                    const nIdx = (nY * width + nX) * 4;
                    const rN = posterizedDataArray[nIdx];
                    const gN = posterizedDataArray[nIdx + 1];
                    const bN = posterizedDataArray[nIdx + 2];
                    const luminance = 0.299 * rN + 0.587 * gN + 0.114 * bN;

                    gx_lum += luminance * Gx_kernel[ky + 1][kx + 1];
                    gy_lum += luminance * Gy_kernel[ky + 1][kx + 1];
                }
            }

            const magnitude = Math.sqrt(gx_lum * gx_lum + gy_lum * gy_lum);

            if (magnitude > outlineStrength) {
                finalData[idx] = 0;     // Black outline
                finalData[idx + 1] = 0;
                finalData[idx + 2] = 0;
            } else {
                finalData[idx] = posterizedDataArray[idx];
                finalData[idx + 1] = posterizedDataArray[idx + 1];
                finalData[idx + 2] = posterizedDataArray[idx + 2];
            }
            finalData[idx + 3] = posterizedDataArray[idx + 3]; // Alpha
        }
    }

    data.set(finalData);
    ctx.putImageData(imageData, 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 Grand Theft Auto Style Photo Filter allows users to apply a distinctive artistic effect to images, reminiscent of the popular video game series. This tool enhances photos by posterizing colors, boosting saturation, and adding bold outlines to create a stylized and graphic look. It is ideal for those looking to give their images a unique flair, making it suitable for social media posts, digital art projects, or any creative endeavor that benefits from an eye-catching aesthetic. Users can customize the level of posterization, outline strength, and saturation to achieve their desired artistic effect.

Leave a Reply

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