You can edit the below JavaScript code to customize the image tool.
Apply Changes
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;
}
Apply Changes