Please bookmark this page to avoid losing your image tool!

Image Triangulation 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, pointCount = 250, strokeColor = "rgba(0,0,0,0.1)", strokeWidth = 0.5, addEdgePointsStr = "true") {
    if (!originalImg || typeof originalImg.naturalWidth === 'undefined' || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
        console.error("Original image is not loaded or has zero dimensions.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 250;
        errorCanvas.height = 60;
        const errorCtx = errorCanvas.getContext('2d');
        errorCtx.fillStyle = "#f0f0f0"; 
        errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        errorCtx.fillStyle = "red";
        errorCtx.font = "12px Arial";
        errorCtx.fillText("Error: Invalid image input.", 10, 20);
        errorCtx.fillText("Please ensure the image is fully loaded", 10, 35);
        errorCtx.fillText("and has dimensions greater than zero.", 10, 50);
        return errorCanvas;
    }

    const addEdgePoints = addEdgePointsStr.toLowerCase() === 'true';
    const imgWidth = originalImg.naturalWidth;
    const imgHeight = originalImg.naturalHeight;

    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = imgWidth;
    outputCanvas.height = imgHeight;
    const ctx = outputCanvas.getContext('2d');

    // Create a temporary canvas to draw the original image and get pixel data
    const inputCanvas = document.createElement('canvas');
    inputCanvas.width = imgWidth;
    inputCanvas.height = imgHeight;
    const inputCtx = inputCanvas.getContext('2d', { willReadFrequently: true }); // Optimization for frequent getImageData
    inputCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    
    let Delaunay;
    try {
        // Check if d3 and d3.Delaunay are already globally available
        if (typeof self.d3 !== 'undefined' && typeof self.d3.Delaunay !== 'undefined') {
            Delaunay = self.d3.Delaunay;
        } else {
            // Dynamically import d3-delaunay library from CDN
            const d3DelaunayModule = await import('https://cdn.jsdelivr.net/npm/d3-delaunay@6/+esm');
            Delaunay = d3DelaunayModule.Delaunay;
            
            // Fallback if the module attached itself to a global d3 object but Delaunay wasn't directly exported
            if (!Delaunay && typeof self.d3 !== 'undefined' && self.d3.Delaunay) {
                 Delaunay = self.d3.Delaunay;
            }

            if (!Delaunay) { // Ensure Delaunay class was loaded
                throw new Error("Delaunay class not found in the imported d3-delaunay module.");
            }
        }
    } catch (e) {
        console.error("Failed to load d3-delaunay library:", e);
        ctx.drawImage(originalImg, 0, 0); // Draw original image asa fallback
        ctx.fillStyle = "rgba(255, 0, 0, 0.7)";
        ctx.fillRect(0, imgHeight / 2 - 20, imgWidth, 40);
        ctx.fillStyle = "white";
        ctx.font = "bold 16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("Error: Delaunay library failed to load.", imgWidth / 2, imgHeight / 2 + 6);
        return outputCanvas;
    }

    const points = [];
    let numRandomPointsTarget = pointCount; // Target number of points from parameter

    if (addEdgePoints) {
        points.push(
            [0, 0], [imgWidth, 0], [0, imgHeight], [imgWidth, imgHeight], // Corners
            [imgWidth / 2, 0], [imgWidth, imgHeight / 2], [imgWidth / 2, imgHeight], [0, imgHeight / 2] // Mid-points of edges
        );
        // Adjust the number of random points to generate based on fixed points added
        numRandomPointsTarget = Math.max(0, pointCount - points.length);
    } else {
        numRandomPointsTarget = Math.max(0, pointCount); // Ensure non-negative
    }
    
    for (let i = 0; i < numRandomPointsTarget; i++) {
        points.push([Math.random() * imgWidth, Math.random() * imgHeight]);
    }

    // Delaunay triangulation requires at least 3 points.
    // Add random points if current count is less than 3.
    let safeguardIteration = 0; // Prevents infinite loop in rare edge cases (e.g. imgWidth/Height = 0, though guarded)
    while (points.length < 3 && safeguardIteration < 10) { 
        points.push([Math.random() * imgWidth, Math.random() * imgHeight]);
        safeguardIteration++;
    }

    if (points.length < 3) {
        console.warn("Not enough points (<3) for triangulation, even after safeguards. Drawing original image.");
        ctx.drawImage(originalImg, 0, 0);
        return outputCanvas;
    }
    
    const delaunay = Delaunay.from(points);
    const triangles = delaunay.triangles; // This is an array of point indices: [t0, t1, t2, t0, t1, t2, ...]

    if (!triangles || triangles.length === 0) {
        // This can happen if all points are collinear, for example.
        console.warn("Delaunay triangulation resulted in no triangles. Drawing original image.");
        ctx.drawImage(originalImg, 0, 0);
        // For debugging, one might want to draw the points:
        // ctx.fillStyle = "red"; points.forEach(p => { ctx.beginPath(); ctx.arc(p[0],p[1],2,0,Math.PI*2); ctx.fill(); });
        return outputCanvas;
    }

    for (let i = 0; i < triangles.length; i += 3) {
        const p1_idx = triangles[i];
        const p2_idx = triangles[i+1];
        const p3_idx = triangles[i+2];
        
        // Get vertex coordinates from the points array
        const v1 = points[p1_idx];
        const v2 = points[p2_idx];
        const v3 = points[p3_idx];

        // Basic check, though delaunay.triangles should provide valid indices.
        if (!v1 || !v2 || !v3) {
            console.warn("Skipping a triangle due to missing vertex data for indices:", p1_idx, p2_idx, p3_idx);
            continue;
        }

        // Calculate centroid of the triangle to sample color
        const cx = (v1[0] + v2[0] + v3[0]) / 3;
        const cy = (v1[1] + v2[1] + v3[1]) / 3;

        // Clamp coordinates and floor them for getImageData
        const sampleX = Math.max(0, Math.min(imgWidth - 1, Math.floor(cx)));
        const sampleY = Math.max(0, Math.min(imgHeight - 1, Math.floor(cy)));
        
        let r=0, g=0, b=0, a=255; // Default to opaque black if pixel data access fails
        try {
            const pixelData = inputCtx.getImageData(sampleX, sampleY, 1, 1).data;
            r = pixelData[0]; 
            g = pixelData[1]; 
            b = pixelData[2]; 
            a = pixelData[3];
        } catch (e) {
            // This can happen if the canvas is tainted (e.g., cross-origin image loaded without CORS)
            console.warn(`Failed to get pixel data at (${sampleX}, ${sampleY}) for triangle centroid. Using default color. Error: ${e.message}`);
        }

        // Draw the triangle
        ctx.beginPath();
        ctx.moveTo(v1[0], v1[1]);
        ctx.lineTo(v2[0], v2[1]);
        ctx.lineTo(v3[0], v3[1]);
        ctx.closePath();
        
        ctx.fillStyle = `rgba(${r},${g},${b},${a / 255})`;
        ctx.fill();

        // Apply stroke if specified
        if (strokeWidth > 0 && strokeColor && strokeColor.toLowerCase() !== 'transparent' && strokeColor !== '') {
            ctx.strokeStyle = strokeColor;
            ctx.lineWidth = strokeWidth;
            ctx.stroke();
        }
    }

    return outputCanvas;
}

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 Triangulation Filter transforms images into a triangulated representation by dividing the image into triangular sections. Users can customize key parameters, such as the number of points used for triangulation, stroke color, and stroke width for the outlines of the triangles. This tool is useful for creating artistic effects, enhancing visual design for graphics, or generating unique textures for digital art or web design. It allows users to apply a creative and abstract filter to their images, resulting in a visually interesting reinterpretation of the original image.

Leave a Reply

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