Please bookmark this page to avoid losing your image tool!

Image Polynesian Tapa Cloth 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,
    fillPaletteStr = "A0522D,D2B48C,F5DEB3,8B4513", // Default: Sienna, Tan, Wheat, SaddleBrown
    lineColorHex = "301A0A", // Default: Very Dark Brown (almost black)
    lineThickness = 2,
    cellSize = 24, 
    patternDensity = 0.8, 
    textureStrength = 0.05 
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    // Ensure canvas dimensions are from natural dimensions if available
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    // --- Utility functions ---
    function hexToRgb(hexString) {
        let hex = String(hexString).trim(); 
        if (hex.startsWith('#')) {
            hex = hex.substring(1);
        }
        
        if (!/^[0-9A-Fa-f]{6}$/i.test(hex)) {
            console.warn("Invalid hex color string:", hexString, "- using black as fallback.");
            return { r: 0, g: 0, b: 0 }; 
        }
        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 };
    }

    function colorDistanceSquared(c1, c2) { // c1, c2 are {r,g,b} objects
        const dr = c1.r - c2.r;
        const dg = c1.g - c2.g;
        const db = c1.b - c2.b;
        return dr * dr + dg * dg + db * db; // Squared Euclidean distance for speed
    }

    // Parse fill palette colors
    let fillPalette = String(fillPaletteStr).split(',')
        .map(hex => hex.trim())
        .filter(hex => hex.length > 0) // Ensure not empty string from " ,, "
        .map(hex => hexToRgb(hex));

    // Fallback if palette parsing fails or results in an empty palette
    if (fillPalette.length === 0) {
        console.warn("Fill palette is empty or invalid, using default fill colors.");
        fillPalette.push(hexToRgb("A0522D")); 
        fillPalette.push(hexToRgb("D2B48C")); 
    }
    const lineColorRgb = hexToRgb(lineColorHex);

    // 1. Draw original image to canvas
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    // 2. Apply Color Quantization using the fillPalette
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;

    for (let i = 0; i < data.length; i += 4) {
        if (data[i+3] === 0) continue; // Skip fully transparent pixels

        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        const originalPixelColor = { r, g, b };

        let closestColor = fillPalette[0];
        let minDistance = colorDistanceSquared(originalPixelColor, closestColor);

        for (let j = 1; j < fillPalette.length; j++) {
            const dist = colorDistanceSquared(originalPixelColor, fillPalette[j]);
            if (dist < minDistance) {
                minDistance = dist;
                closestColor = fillPalette[j];
            }
        }
        data[i] = closestColor.r;
        data[i + 1] = closestColor.g;
        data[i + 2] = closestColor.b;
    }
    ctx.putImageData(imageData, 0, 0); // Quantized image is now on canvas

    // 3. Overlay Tapa Patterns
    ctx.strokeStyle = `rgb(${lineColorRgb.r}, ${lineColorRgb.g}, ${lineColorRgb.b})`;
    ctx.fillStyle = `rgb(${lineColorRgb.r}, ${lineColorRgb.g}, ${lineColorRgb.b})`; // For filled pattern elements like dots
    ctx.lineWidth = Math.max(1, Number(lineThickness) || 1); // Ensure lineWidth is at least 1

    const currentCellSize = Math.max(8, Number(cellSize) || 24); // Minimum cell size
    const numCellsX = Math.ceil(canvas.width / currentCellSize);
    const numCellsY = Math.ceil(canvas.height / currentCellSize);
    const currentPatternDensity = Math.max(0, Math.min(1, Number(patternDensity) || 0.8));


    for (let cy = 0; cy < numCellsY; cy++) {
        for (let cx = 0; cx < numCellsX; cx++) {
            if (Math.random() > currentPatternDensity) continue; 

            const x = cx * currentCellSize;
            const y = cy * currentCellSize;
            
            // Calculate average brightness of the cell from the quantized image data
            let sumBrightness = 0;
            let pixelsInCell = 0;
            for (let offY = 0; offY < currentCellSize && y + offY < canvas.height; offY++) {
                for (let offX = 0; offX < currentCellSize && x + offX < canvas.width; offX++) {
                    const Gx = x + offX;
                    const Gy = y + offY;
                    const idx = (Gy * canvas.width + Gx) * 4;
                    if (data[idx+3] > 0) { // Consider only opaque pixels
                        sumBrightness += (data[idx] + data[idx+1] + data[idx+2]) / 3;
                        pixelsInCell++;
                    }
                }
            }
            const avgBrightness = pixelsInCell > 0 ? sumBrightness / pixelsInCell / 255 : 0.5; // Normalized 0-1

            ctx.beginPath(); // Start a new path for each cell's pattern elements (except for fillRect)
            const patternTypeRand = Math.random(); 

            // Darker areas get more complex/dense patterns
            if (avgBrightness < 0.35) { 
                if (patternTypeRand < 0.33) { // Cross-hatch
                    ctx.moveTo(x, y); ctx.lineTo(x + currentCellSize, y + currentCellSize);
                    ctx.moveTo(x + currentCellSize, y); ctx.lineTo(x, y + currentCellSize);
                } else if (patternTypeRand < 0.66) { // Multiple Parallel Lines (horizontal)
                    for (let k = 1; k <= 3; k++) { // 3 lines
                        ctx.moveTo(x, Math.round(y + currentCellSize * k / 4));
                        ctx.lineTo(x + currentCellSize, Math.round(y + currentCellSize * k / 4));
                    }
                } else { // Zig-zag / Chevrons
                    ctx.moveTo(x, Math.round(y + currentCellSize * 0.25));
                    ctx.lineTo(Math.round(x + currentCellSize * 0.5), Math.round(y + currentCellSize * 0.75));
                    ctx.lineTo(x + currentCellSize, Math.round(y + currentCellSize * 0.25));
                }
            } else if (avgBrightness < 0.65) { // Medium brightness areas
                if (patternTypeRand < 0.5) { // Single Diagonal
                    if (Math.random() < 0.5) {ctx.moveTo(x, y); ctx.lineTo(x + currentCellSize, y + currentCellSize);} // \
                    else {ctx.moveTo(x + currentCellSize, y); ctx.lineTo(x, y + currentCellSize);} // /
                } else { // Small Center Cross
                    const cX = Math.round(x + currentCellSize / 2); const cY = Math.round(y + currentCellSize / 2);
                    const armLength = Math.round(currentCellSize / 4);
                    ctx.moveTo(cX - armLength, cY); ctx.lineTo(cX + armLength, cY);
                    ctx.moveTo(cX, cY - armLength); ctx.lineTo(cX, cY + armLength);
                }
            } else { // Lighter areas (simplest patterns or dots)
                if (patternTypeRand < 0.5) { // Draw a few dots
                    const numDots = Math.floor(Math.random() * 3) + 1; // 1 to 3 dots
                    const dotSize = Math.max(1, Math.floor(ctx.lineWidth / 2) || 1);
                    for(let k=0; k<numDots; k++) {
                        // fillRect draws immediately, does not add to current path.
                        ctx.fillRect(
                            Math.round(x + Math.random() * (currentCellSize - dotSize)), 
                            Math.round(y + Math.random() * (currentCellSize - dotSize)), 
                            dotSize, dotSize
                        );
                    }
                } else { // Cell border/frame
                    ctx.rect( // Adds to current path
                        Math.round(x + ctx.lineWidth/2), 
                        Math.round(y + ctx.lineWidth/2), 
                        Math.round(currentCellSize - ctx.lineWidth), 
                        Math.round(currentCellSize - ctx.lineWidth)
                    );
                }
            }
            // Stroke the path created by moveTo/lineTo/rect. Dots (fillRect) are already drawn.
            ctx.stroke(); 
        }
    }

    // 4. Apply Texture (subtle noise)
    const currentTextureStrength = Math.max(0, Math.min(1, Number(textureStrength) || 0.05));
    if (currentTextureStrength > 0) {
        const texturedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const texData = texturedImageData.data;
        const noiseMultiplier = 50; // Scales strength: 0.1 strength => noise range +/- 2.5 RGB
                                    
        for (let i = 0; i < texData.length; i += 4) {
            if (texData[i+3] === 0) continue; // Skip transparent

            // Apply same noise value to R, G, B to affect lightness more than hue
            const noiseVal = (Math.random() - 0.5) * currentTextureStrength * noiseMultiplier;
            texData[i] = Math.max(0, Math.min(255, texData[i] + noiseVal));
            texData[i + 1] = Math.max(0, Math.min(255, texData[i + 1] + noiseVal));
            texData[i + 2] = Math.max(0, Math.min(255, texData[i + 2] + noiseVal));
        }
        ctx.putImageData(texturedImageData, 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 Polynesian Tapa Cloth Filter Effect Tool allows users to apply a unique artistic filter inspired by traditional Polynesian tapa cloth patterns to their images. This tool utilizes a custom color palette and overlay patterns to transform images into visually striking artworks. It is suitable for various applications such as creating wallpapers, enhancing social media posts, designing art prints, or adding an exotic touch to personal photography. Users can customize elements like line color, thickness, cell size, and pattern density to achieve their desired aesthetic, making it ideal for creatives looking to explore cultural art styles.

Leave a Reply

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