You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, edgeThreshold = 100, stoneColorStr = "210,200,190", carvingColorStr = "70,60,50") {
// Helper function to parse color strings like "r,g,b"
// Falls back to defaultRgbArray if parsing fails or format is incorrect.
function parseColor(colorString, defaultRgbArray) {
if (typeof colorString !== 'string') {
return defaultRgbArray;
}
const parts = colorString.split(',').map(s => parseInt(s.trim(), 10));
if (parts.length === 3 && parts.every(p => !isNaN(p) && p >= 0 && p <= 255)) {
return parts;
}
return defaultRgbArray; // Fallback color
}
// Helper function to get grayscale pixel from grayscale data array.
// Handles boundary conditions by clamping coordinates (edge pixel replication).
function getPixelGray_internal(grayData, x, y, w, h) {
const clampedX = Math.max(0, Math.min(x, w - 1));
const clampedY = Math.max(0, Math.min(y, h - 1));
return grayData[clampedY * w + clampedX];
}
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (width === 0 || height === 0) {
return canvas; // Return empty canvas for zero-size image
}
// Parse the stone and carving colors using the helper.
// The default values for stoneColorStr and carvingColorStr are valid and will be parsed correctly.
// The fallbacks in parseColor are for cases where a user might provide an invalid string.
const stoneColor = parseColor(stoneColorStr, [210, 200, 190]);
const carvingColor = parseColor(carvingColorStr, [70, 60, 50]);
ctx.drawImage(originalImg, 0, 0);
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
// Handle potential security errors if image is cross-origin and canvas is tainted
console.error("Error getting ImageData:", e);
// Draw a simple error message on the canvas or return it as is
ctx.clearRect(0, 0, width, height); // Clear any partial drawing
ctx.fillStyle = "red";
ctx.font = "16px Arial";
ctx.textAlign = "center";
ctx.fillText("Error processing image.", width / 2, height / 2);
return canvas;
}
const originalData = imageData.data;
// Create a 1D array to store grayscale values of the image
const grayscaleData = new Uint8ClampedArray(width * height);
for (let i = 0; i < originalData.length; i += 4) {
const r = originalData[i];
const g = originalData[i + 1];
const b = originalData[i + 2];
// Standard luminance calculation
grayscaleData[i / 4] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
}
const outputImageData = ctx.createImageData(width, height);
const outputData = outputImageData.data;
// Apply Sobel operator to detect edges
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Sobel Gx kernel
const gx = (
-1 * getPixelGray_internal(grayscaleData, x - 1, y - 1, width, height) + 1 * getPixelGray_internal(grayscaleData, x + 1, y - 1, width, height) +
-2 * getPixelGray_internal(grayscaleData, x - 1, y, width, height) + 2 * getPixelGray_internal(grayscaleData, x + 1, y, width, height) +
-1 * getPixelGray_internal(grayscaleData, x - 1, y + 1, width, height) + 1 * getPixelGray_internal(grayscaleData, x + 1, y + 1, width, height)
);
// Sobel Gy kernel
const gy = (
-1 * getPixelGray_internal(grayscaleData, x - 1, y - 1, width, height) - 2 * getPixelGray_internal(grayscaleData, x, y - 1, width, height) - 1 * getPixelGray_internal(grayscaleData, x + 1, y - 1, width, height) +
1 * getPixelGray_internal(grayscaleData, x - 1, y + 1, width, height) + 2 * getPixelGray_internal(grayscaleData, x, y + 1, width, height) + 1 * getPixelGray_internal(grayscaleData, x + 1, y + 1, width, height)
);
const gradientMagnitude = Math.sqrt(gx * gx + gy * gy);
const pixelIdx = (y * width + x) * 4;
// If gradient magnitude is above threshold, it's an "edge" (carving)
// Otherwise, it's "stone" (background)
if (gradientMagnitude > edgeThreshold) {
outputData[pixelIdx] = carvingColor[0];
outputData[pixelIdx + 1] = carvingColor[1];
outputData[pixelIdx + 2] = carvingColor[2];
} else {
outputData[pixelIdx] = stoneColor[0];
outputData[pixelIdx + 1] = stoneColor[1];
outputData[pixelIdx + 2] = stoneColor[2];
}
// Preserve original alpha channel
outputData[pixelIdx + 3] = originalData[pixelIdx + 3];
}
}
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Apply Changes