Please bookmark this page to avoid losing your image tool!

Image Julia Set Fractal 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, realC = -0.7, imagC = 0.27015, maxIter = 100, zoom = 1.0, centerX = 0.0, centerY = 0.0, escapeRadius = 2.0, colorScheme = "blend", palette = "#FF0000,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF", insideColor = "#000000") {

    // Helper function to parse hex color string to {r, g, b} object
    function parseHexColor(hex) {
        hex = String(hex).replace(/^#/, ''); // Ensure hex is a string
        if (hex.length === 3) {
            hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
        }
        if (hex.length !== 6 || !/^[0-9a-fA-F]{6}$/.test(hex)) {
            // console.warn(`Invalid hex color: "${hex}". Using black.`);
            return { r: 0, g: 0, b: 0 }; // Default to black for invalid hex
        }
        const r = parseInt(hex.substring(0, 2), 16);
        const g = parseInt(hex.substring(2, 4), 16);
        const b = parseInt(hex.substring(4, 6), 16);
        return { r, g, b };
    }

    // Helper functions for HSL color manipulation (for 'hue_shift' colorScheme)
    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: h, s: s, l: l };
    }

    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 { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    const width = originalImg.naturalWidth;
    const height = originalImg.naturalHeight;
    canvas.width = width;
    canvas.height = height;

    // Use a temporary canvas to get original image data
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
    tempCtx.drawImage(originalImg, 0, 0);
    const originalImageData = tempCtx.getImageData(0, 0, width, height);
    const originalData = originalImageData.data;

    const outputImageData = ctx.createImageData(width, height);
    const outputData = outputImageData.data;

    const escapeRadiusSquared = escapeRadius * escapeRadius;
    const parsedInsideColor = parseHexColor(insideColor);

    let parsedPalette = [];
    if (colorScheme === "iterations_palette") {
        const colorStrings = String(palette).split(','); // Ensure palette is a string
        parsedPalette = colorStrings.map(s => parseHexColor(s.trim()));
        // Filter out nulls if parseHexColor could return null (it currently returns black for invalid)
        // parsedPalette = parsedPalette.filter(c => c !== null); 
        if (parsedPalette.length === 0) {
            parsedPalette.push({ r: 0, g: 0, b: 0 }); 
            parsedPalette.push({ r: 255, g: 255, b: 255 });
        }
    }
    
    // Coordinate mapping constants
    // This maps the image pixel coordinates to a rectangular region in the complex plane.
    // The aspect ratio of this region will match the image's aspect ratio.
    // The 'zoom' parameter scales this region. 'centerX' and 'centerY' translate it.
    // A common view for fractals is a square region like [-2, 2] x [-2, 2].
    // This mapping ensures the width of that region is (2.0 / zoom) and height (2.0 * aspect_ratio_correction / zoom)
    const baseScaleX = 2.0 / zoom; 
    const baseScaleY = (2.0 * height) / (width * zoom);


    for (let py = 0; py < height; py++) {
        for (let px = 0; px < width; px++) {
            const zR_init = ((px / width) - 0.5) * baseScaleX + centerX;
            const zI_init = ((py / height) - 0.5) * baseScaleY + centerY;

            let zR = zR_init;
            let zI = zI_init;
            let iter = 0;

            while (zR * zR + zI * zI < escapeRadiusSquared && iter < maxIter) {
                const zR_temp = zR * zR - zI * zI + realC;
                zI = 2 * zR * zI + imagC;
                zR = zR_temp;
                iter++;
            }

            const originalPixelIndex = (py * width + px) * 4;
            const rOrig = originalData[originalPixelIndex];
            const gOrig = originalData[originalPixelIndex + 1];
            const bOrig = originalData[originalPixelIndex + 2];
            const aOrig = originalData[originalPixelIndex + 3];

            let rOut, gOut, bOut;

            if (iter === maxIter) { // Point is in the set
                rOut = parsedInsideColor.r;
                gOut = parsedInsideColor.g;
                bOut = parsedInsideColor.b;
            } else { // Point escaped
                // Normalized iteration count for points outside the set [0, 1]
                // Handle maxIter=1 case to avoid division by zero.
                const factor = (maxIter <= 1 && iter === 0) ? 0 : iter / (maxIter - 1.0);
                const clampedFactor = Math.max(0, Math.min(1, factor));

                switch (colorScheme) {
                    case "blend":
                        rOut = Math.floor(rOrig * clampedFactor);
                        gOut = Math.floor(gOrig * clampedFactor);
                        bOut = Math.floor(bOrig * clampedFactor);
                        break;
                    case "grayscale":
                        const gray = Math.floor(255 * clampedFactor);
                        rOut = gray;
                        gOut = gray;
                        bOut = gray;
                        break;
                    case "iterations_palette":
                        if (parsedPalette.length === 1) {
                            rOut = parsedPalette[0].r;
                            gOut = parsedPalette[0].g;
                            bOut = parsedPalette[0].b;
                        } else if (parsedPalette.length > 1) { // Interpolate for multi-color palettes
                            const palettePos = clampedFactor * (parsedPalette.length - 1);
                            const idx1 = Math.floor(palettePos);
                            const idx2 = Math.min(idx1 + 1, parsedPalette.length - 1);
                            const frac = palettePos - idx1;

                            const c1 = parsedPalette[idx1];
                            const c2 = parsedPalette[idx2];

                            rOut = Math.floor(c1.r * (1 - frac) + c2.r * frac);
                            gOut = Math.floor(c1.g * (1 - frac) + c2.g * frac);
                            bOut = Math.floor(c1.b * (1 - frac) + c2.b * frac);
                        } else { // Fallback (should not be reached if default palette logic works)
                            rOut = rOrig; gOut = gOrig; bOut = bOrig;
                        }
                        break;
                    case "hue_shift":
                        const hsl = rgbToHsl(rOrig, gOrig, bOrig);
                        hsl.h = (hsl.h + clampedFactor) % 1.0; 
                        const rgbShifted = hslToRgb(hsl.h, hsl.s, hsl.l);
                        rOut = rgbShifted.r;
                        gOut = rgbShifted.g;
                        bOut = rgbShifted.b;
                        break;
                    default:
                        rOut = rOrig; gOut = gOrig; bOut = bOrig;
                        break;
                }
            }

            const outputPixelIndex = (py * width + px) * 4;
            outputData[outputPixelIndex] = rOut;
            outputData[outputPixelIndex + 1] = gOut;
            outputData[outputPixelIndex + 2] = bOut;
            outputData[outputPixelIndex + 3] = aOrig; // Preserve original alpha
        }
    }

    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 Julia Set Fractal Filter Effect Tool allows users to transform images into stunning fractal representations based on the Julia set mathematical concept. Users can adjust various parameters such as real and imaginary constants, zoom levels, and color schemes to customize the visual output. This tool can be used for creative purposes such as digital art production, graphic design experimentation, and exploring mathematical visualizations. It’s ideal for artists, designers, and educators looking to illustrate complex mathematical concepts through engaging imagery.

Leave a Reply

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