You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, paletteColorsStr = "85,50,25;180,120,40;150,80,30;210,105,30;0,0,0;240,220,180", outlineColorStr = "0,0,0", outlineThickness = 2) {
// 1. Parse and validate parameters
let parsedPalette = [];
if (paletteColorsStr && typeof paletteColorsStr === 'string' && paletteColorsStr.trim() !== "") {
parsedPalette = paletteColorsStr.split(';').map(cStr =>
cStr.split(',').map(nStr => Number(nStr.trim()))
).filter(cArr => cArr.length === 3 && cArr.every(n => !isNaN(n) && n >= 0 && n <= 255));
}
if (parsedPalette.length === 0) { // Fallback to a hardcoded default
parsedPalette = [[85,50,25], [180,120,40], [150,80,30], [210,105,30], [0,0,0], [240,220,180]];
}
let parsedOutlineColor = [];
if (outlineColorStr && typeof outlineColorStr === 'string' && outlineColorStr.trim() !== "") {
const tempColor = outlineColorStr.split(',').map(nStr => Number(nStr.trim()));
if (tempColor.length === 3 && tempColor.every(n => !isNaN(n) && n >= 0 && n <= 255)) {
parsedOutlineColor = tempColor;
}
}
if (parsedOutlineColor.length === 0) { // Fallback
parsedOutlineColor = [0,0,0]; // Default to black
}
let tempThickness = Number(outlineThickness);
if (isNaN(tempThickness)) {
// If originalImg parameter for outlineThickness (e.g. from a text input) was not a number, use default.
// Note: The function signature gives default `2` if `outlineThickness` is undefined.
// This handles `null`, `""` (becomes 0 from Number()), or explicitly non-numeric strings.
tempThickness = 2;
}
const finalOutlineThickness = Math.max(0, Math.round(tempThickness));
// 2. Create canvas and draw original image
const canvas = document.createElement('canvas');
// Ensure originalImg is loaded and has dimensions
if (!originalImg || typeof originalImg.width !== 'number' || typeof originalImg.height !== 'number' || originalImg.width === 0 || originalImg.height === 0) {
console.error("Invalid or unloaded image provided.");
// Return a small, empty canvas or throw an error
canvas.width = 1;
canvas.height = 1;
return canvas;
}
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(originalImg, 0, 0);
// 3. Get image data
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error getting ImageData. If image is cross-origin, ensure it has 'Access-Control-Allow-Origin' header.", e);
return canvas; // Return canvas with original image if processing is not possible
}
const data = imageData.data;
const W = canvas.width;
const H = canvas.height;
// 4. Create buffer for quantized image
const quantizedData = new Uint8ClampedArray(data.length);
// 5. Color Quantization Pass
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
const a = data[i+3];
if (a === 0) { // Preserve fully transparent pixels
quantizedData[i] = 0;
quantizedData[i+1] = 0;
quantizedData[i+2] = 0;
quantizedData[i+3] = 0;
continue;
}
let minDist = Infinity;
let chosenColor = parsedPalette[0];
for (const pColor of parsedPalette) {
const dist = Math.sqrt(
Math.pow(r - pColor[0], 2) +
Math.pow(g - pColor[1], 2) +
Math.pow(b - pColor[2], 2)
);
if (dist < minDist) {
minDist = dist;
chosenColor = pColor;
}
}
quantizedData[i] = chosenColor[0];
quantizedData[i+1] = chosenColor[1];
quantizedData[i+2] = chosenColor[2];
quantizedData[i+3] = a;
}
if (finalOutlineThickness === 0) { // If no outline, just put quantized data and return
ctx.putImageData(new ImageData(quantizedData, W, H), 0, 0);
return canvas;
}
// 6. Create Edge Map (based on quantizedData)
const edgeMap = new Array(W * H).fill(false);
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const idx = (y * W + x) * 4;
if (quantizedData[idx+3] === 0) continue; // Ignore transparent pixels for edge source
const rC = quantizedData[idx];
const gC = quantizedData[idx+1];
const bC = quantizedData[idx+2];
let isBaseEdge = false;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < W && ny >= 0 && ny < H) {
const nidx = (ny * W + nx) * 4;
if (quantizedData[nidx+3] === 0) { // Edge with a transparent area
isBaseEdge = true;
break;
}
if (quantizedData[nidx] !== rC || quantizedData[nidx+1] !== gC || quantizedData[nidx+2] !== bC) {
isBaseEdge = true;
break;
}
} else { // Pixel is at the image boundary, consider it an edge
isBaseEdge = true;
break;
}
}
if (isBaseEdge) break;
}
if (isBaseEdge) {
edgeMap[y * W + x] = true;
}
}
}
// 7. Final Output Pass (applying outlines based on edgeMap and thickness)
const outputData = new Uint8ClampedArray(data.length);
const radius = Math.floor((finalOutlineThickness - 1) / 2);
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const pixelIdx = (y * W + x) * 4;
if (quantizedData[pixelIdx+3] === 0) { // Preserve transparency
outputData[pixelIdx] = 0; outputData[pixelIdx+1] = 0; outputData[pixelIdx+2] = 0; outputData[pixelIdx+3] = 0;
continue;
}
let applyOutline = false;
// Check neighborhood in edgeMap for an edge pixel
// The window size is (2*radius+1) x (2*radius+1), which corresponds to finalOutlineThickness for odd values
for (let ky = -radius; ky <= radius; ky++) {
for (let kx = -radius; kx <= radius; kx++) {
const currentX = x + kx;
const currentY = y + ky;
if (currentX >= 0 && currentX < W && currentY >= 0 && currentY < H) {
if (edgeMap[currentY * W + currentX]) {
applyOutline = true;
break;
}
}
}
if (applyOutline) break;
}
if (applyOutline) {
outputData[pixelIdx] = parsedOutlineColor[0];
outputData[pixelIdx + 1] = parsedOutlineColor[1];
outputData[pixelIdx + 2] = parsedOutlineColor[2];
outputData[pixelIdx + 3] = quantizedData[pixelIdx + 3];
} else {
outputData[pixelIdx] = quantizedData[pixelIdx];
outputData[pixelIdx + 1] = quantizedData[pixelIdx + 1];
outputData[pixelIdx + 2] = quantizedData[pixelIdx + 2];
outputData[pixelIdx + 3] = quantizedData[pixelIdx + 3];
}
}
}
// 8. Put manipulated data back to canvas
ctx.putImageData(new ImageData(outputData, W, H), 0, 0);
// 9. Return canvas
return canvas;
}
Apply Changes