Please bookmark this page to avoid losing your image tool!

Image Marble Texture 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.
async function processImage(
    originalImg,
    veinPeriodX = 64, // Defines the approximate width of marble veins horizontally, in pixels. Lower values = denser veins.
    veinPeriodY = 128, // Defines the approximate height of marble veins vertically, in pixels. Lower values = denser veins.
    turbulenceFactor = 1.5, // Controls the amount of chaotic distortion in the veins. Higher values lead to more swirling. Range: 0 (straight veins) to ~5.
    noiseScale = 0.03, // Scale of the underlying noise pattern that creates turbulence. Smaller values = larger, smoother noise features. Range: ~0.001 to ~0.1.
    noiseOctaves = 4, // Number of noise layers summed for turbulence. Higher values add more detail but increase computation time. Range: 1 to 8.
    noisePersistence = 0.5, // Determines how much each successive octave contributes to the noise detail. Range: ~0.2 to ~0.8.
    noiseLacunarity = 2.0, // Controls how much the frequency increases for each successive octave of noise. Range: ~1.5 to ~3.0.
    veinColor1Str = "220,220,230", // Primary color of the marble (e.g., the lighter part or body of the marble), as an "R,G,B" string.
    veinColor2Str = "40,40,60",   // Secondary color of the marble (e.g., the darker part or the veins/fractures), as an "R,G,B" string.
    mixOriginalOpacity = 0.0, // Blending factor with the original image. 0.0 means 100% marble texture, 1.0 means 100% original image. Range: 0.0 to 1.0.
    randomSeed = 0 // Seed for the random noise generator. Use 0 for a different pattern each time, or any other number for a fixed, reproducible pattern.
) {
    // Parse color strings to [R, G, B] arrays. Added trim() for robustness.
    // Fallback to 0 for invalid numbers to prevent NaN issues in color calculations.
    const color1 = veinColor1Str.split(',').map(s => Number(s.trim()) || 0);
    const color2 = veinColor2Str.split(',').map(s => Number(s.trim()) || 0);

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

    // Draw the original image first if it needs to be mixed or its alpha channel is used
    ctx.drawImage(originalImg, 0, 0, width, height);
    const originalImageData = ctx.getImageData(0, 0, width, height);
    const originalData = originalImageData.data;

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

    // Initialize the random seed
    const seed = (randomSeed === 0) ? Math.random() * 100000 : Number(randomSeed);

    // --- Noise generation functions (Perlin-like value noise) ---
    // These are defined inside processImage to keep the solution self-contained.

    // Pseudo-random number generator function
    const _rand = (ix, iy, currentSeed) => {
        // Using fixed large numbers for a simple hash-like behavior.
        // ix and iy are expected to be integers.
        const RND_A = 134775813;
        const RND_C = 1;
        const RND_M = 2147483648; // 2^31 for positive results

        let val = (ix * 12345 + iy * 67890 + currentSeed * 101112);
        // Ensure val is positive before modulo, and handle large numbers if intermediate is negative
        val = ((val % RND_M) + RND_M) % RND_M; 
        val = (val * RND_A + RND_C) % RND_M;
        return (val / RND_M); // Result in [0, 1)
    };

    // Linear interpolation
    const _lerp = (a, b, t) => a + t * (b - a);

    // Fade function (Perlin's improved version: 6t^5 - 15t^4 + 10t^3) to smooth interpolation
    const _fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);

    // Coherent noise function (generates a single "octave" of noise)
    const _smoothNoise = (x, y, currentSeed) => {
        const ix = Math.floor(x); // Integer part of x
        const iy = Math.floor(y); // Integer part of y
        const fx = x - ix;        // Fractional part of x
        const fy = y - iy;        // Fractional part of y

        // Get random values at the corners of the unit cell
        const r00 = _rand(ix, iy, currentSeed);
        const r10 = _rand(ix + 1, iy, currentSeed);
        const r01 = _rand(ix, iy + 1, currentSeed);
        const r11 = _rand(ix + 1, iy + 1, currentSeed);

        // Apply fade function to fractional parts for smoother interpolation
        const u = _fade(fx);
        const v = _fade(fy);

        // Interpolate between the four corner values
        return _lerp(_lerp(r00, r10, u), _lerp(r01, r11, u), v);
    };

    // Fractal Brownian Motion (FBM) - sums multiple octaves of noise
    const _fbm = (x, y, currentSeed, octaves, persistence, lacunarity) => {
        let total = 0;
        let frequency = 1.0; // Initial frequency for the first octave
        let amplitude = 1.0; // Initial amplitude for the first octave
        let maxValue = 0;    // Used to normalize the result to an approximate [0,1] range

        for (let i = 0; i < octaves; i++) {
            // Add noise from this octave, using a different seed component (currentSeed + i)
            total += _smoothNoise(x * frequency, y * frequency, currentSeed + i) * amplitude;
            maxValue += amplitude; // Accumulate max possible amplitude for normalization
            amplitude *= persistence; // Reduce amplitude for next octave
            frequency *= lacunarity; // Increase frequency for next octave
        }
        
        // Avoid division by zero if octaves = 0 or persistence makes amplitude sum to 0
        if (maxValue === 0) return 0; 
        return total / maxValue; // Normalize
    };
    // --- End of noise functions ---

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            // 1. Calculate turbulence value using FBM
            // noiseScale adjusts the "zoom" level of the turbulence pattern
            const turbulenceValue = _fbm(x * noiseScale, y * noiseScale, seed, noiseOctaves, noisePersistence, noiseLacunarity);

            // 2. Create the marble vein pattern using sine waves distorted by turbulence
            // Map turbulenceValue from [0,1] to [-1,1] to control phase shift direction
            const turbulencePhaseShift = (turbulenceValue - 0.5) * 2.0; 

            // Argument for the sine function. Veins are formed by sine waves.
            // Turbulence distorts these waves.
            const sineWaveArgument =
                (x * 2 * Math.PI / Math.max(1, veinPeriodX)) + // Horizontal wave component
                (y * 2 * Math.PI / Math.max(1, veinPeriodY)) + // Vertical wave component
                turbulenceFactor * Math.PI * turbulencePhaseShift; // Turbulence-induced phase shift
            
            const sineValue = Math.sin(sineWaveArgument);

            // Map sineValue from [-1, 1] to [0, 1] to use as an interpolation factor between colors
            const marblePatternFactor = (sineValue + 1) / 2;

            const idx = (y * width + x) * 4; // Pixel index in ImageData array

            // 3. Determine pixel color by interpolating between two vein colors
            const marbleR = color1[0] * marblePatternFactor + color2[0] * (1 - marblePatternFactor);
            const marbleG = color1[1] * marblePatternFactor + color2[1] * (1 - marblePatternFactor);
            const marbleB = color1[2] * marblePatternFactor + color2[2] * (1 - marblePatternFactor);

            // 4. Blend with original image if mixOriginalOpacity is greater than 0
            if (mixOriginalOpacity > 0 && mixOriginalOpacity <= 1) {
                const originalR = originalData[idx];
                const originalG = originalData[idx + 1];
                const originalB = originalData[idx + 2];

                outputData[idx]     = marbleR * (1 - mixOriginalOpacity) + originalR * mixOriginalOpacity;
                outputData[idx + 1] = marbleG * (1 - mixOriginalOpacity) + originalG * mixOriginalOpacity;
                outputData[idx + 2] = marbleB * (1 - mixOriginalOpacity) + originalB * mixOriginalOpacity;
            } else { // If mixOriginalOpacity is 0 or invalid, use 100% marble color
                outputData[idx]     = marbleR;
                outputData[idx + 1] = marbleG;
                outputData[idx + 2] = marbleB;
            }
            // Preserve the original alpha channel
            outputData[idx + 3] = originalData[idx + 3]; 
        }
    }

    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 Marble Texture Filter allows users to transform images into a marble-like texture by applying customizable parameters for vein patterns and colors. This tool can be useful for graphic designers, artists, and architects looking to create unique backgrounds, enhance images with artistic effects, or visualize materials in product designs. Users can adjust the density and characteristics of the marble veins, mix the textured output with the original image, and choose specific colors to achieve their desired aesthetic.

Leave a Reply

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