You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, searchImgUrl = "", highlightColor = "red", highlightLineWidth = 2) {
const outputCanvas = document.createElement('canvas');
const initialErrorState = {
width: 300,
height: 100,
message: "An unknown error occurred.",
logToConsole: false,
consoleMsg: ""
};
if (!originalImg || typeof originalImg.width === 'undefined' || originalImg.naturalWidth === 0) {
initialErrorState.message = "Error: Original image is not valid or not loaded properly.";
outputCanvas.width = initialErrorState.width;
outputCanvas.height = initialErrorState.height;
const ctx = outputCanvas.getContext('2d');
ctx.fillStyle = "#f0f0f0";
ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
ctx.fillStyle = "black";
ctx.font = "14px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(initialErrorState.message, outputCanvas.width / 2, outputCanvas.height / 2);
return outputCanvas;
}
outputCanvas.width = originalImg.width;
outputCanvas.height = originalImg.height;
const ctxOutput = outputCanvas.getContext('2d');
try {
ctxOutput.drawImage(originalImg, 0, 0);
} catch (e) {
// This can happen if originalImg itself is from a tainted canvas
initialErrorState.message = "Error: Could not draw original image. It might be tainted.";
initialErrorState.logToConsole = true;
initialErrorState.consoleMsg = e.message;
// Fallback to drawing error on potentially resized canvas
outputCanvas.width = initialErrorState.width; // Reset to default error size
outputCanvas.height = initialErrorState.height;
// ctxOutput is already obtained, just re-clear and draw error
ctxOutput.fillStyle = "#f0f0f0";
ctxOutput.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
ctxOutput.fillStyle = "black";
ctxOutput.font = "14px Arial";
ctxOutput.textAlign = "center";
ctxOutput.textBaseline = "middle";
ctxOutput.fillText(initialErrorState.message, outputCanvas.width / 2, outputCanvas.height / 2);
if(initialErrorState.logToConsole) console.error(initialErrorState.message, initialErrorState.consoleMsg);
return outputCanvas;
}
const ow = originalImg.width;
const oh = originalImg.height;
function drawMessageOnCanvas(message) {
// Use outputCanvas, originalImg already drawn
ctxOutput.fillStyle = highlightColor;
const fontSize = Math.min(16, Math.max(10, Math.floor(ow / 25)));
ctxOutput.font = `${fontSize}px Arial`;
ctxOutput.textAlign = "center";
ctxOutput.textBaseline = "top";
const textYPosition = Math.min(oh * 0.1, 20); // Position text near top
ctxOutput.fillText(message, ow / 2, textYPosition);
}
if (!searchImgUrl || searchImgUrl.trim() === "") {
drawMessageOnCanvas("Search image URL not provided.");
return outputCanvas;
}
let searchImg;
try {
searchImg = await new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "Anonymous"; // Important for getImageData if URL is cross-origin
img.onload = () => resolve(img);
img.onerror = () => reject(new Error("Failed to load search image from URL."));
img.src = searchImgUrl;
});
} catch (error) {
drawMessageOnCanvas(error.message);
console.error(error);
return outputCanvas;
}
if (!searchImg.width || !searchImg.height || searchImg.naturalWidth === 0) {
drawMessageOnCanvas("Search image is invalid (e.g., 0 dimensions or failed to decode).");
return outputCanvas;
}
const sw = searchImg.width;
const sh = searchImg.height;
if (sw > ow || sh > oh) {
drawMessageOnCanvas(`Search image (${sw}x${sh}) is larger than original image (${ow}x${oh}). Not found.`);
return outputCanvas;
}
// Get image data for comparison
let originalImageData, searchImageData;
try {
const tempOriginalCanvas = document.createElement('canvas');
tempOriginalCanvas.width = ow;
tempOriginalCanvas.height = oh;
const tempOriginalCtx = tempOriginalCanvas.getContext('2d', { willReadFrequently: true });
tempOriginalCtx.drawImage(originalImg, 0, 0);
originalImageData = tempOriginalCtx.getImageData(0, 0, ow, oh);
} catch (e) {
// Handle potential CROS issue with originalImg if not caught by initial drawImage
ctxOutput.clearRect(0, 0, ow, oh); // Clear drawn originalImg
ctxOutput.fillStyle = "#f0f0f0";
ctxOutput.fillRect(0, 0, ow, oh); // Background for error
ctxOutput.fillStyle = "black";
const fontSize = Math.min(14, Math.max(10, Math.floor(ow / 30)));
ctxOutput.font = `${fontSize}px Arial`;
ctxOutput.textAlign = "center";
ctxOutput.textBaseline = "middle";
let msg = "Error: Original image cannot be processed. Possible cross-origin issue.";
if (originalImg.src && !originalImg.src.startsWith("data:") && (!originalImg.crossOrigin || originalImg.crossOrigin.toLowerCase() !== "anonymous")) {
msg = `Error: Original image from '${new URL(originalImg.src).hostname}' needs 'crossOrigin="anonymous"' attribute set *before* loading, or server must provide permissive CORS headers.`;
}
// Basic text wrapping
const words = msg.split(' ');
let line = '';
let textBlockY = oh/2 - ( (Math.ceil(msg.length / (ow*0.8 / (fontSize*0.6)))) * fontSize )/2 ; // Estimate Y start
for(let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctxOutput.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > ow * 0.9 && n > 0) {
ctxOutput.fillText(line, ow / 2, textBlockY);
line = words[n] + ' ';
textBlockY += fontSize * 1.2; // Line height
} else {
line = testLine;
}
}
ctxOutput.fillText(line, ow/2, textBlockY);
console.error("Error getting original image data:", e);
return outputCanvas;
}
try {
const tempSearchCanvas = document.createElement('canvas');
tempSearchCanvas.width = sw;
tempSearchCanvas.height = sh;
const tempSearchCtx = tempSearchCanvas.getContext('2d', { willReadFrequently: true });
tempSearchCtx.drawImage(searchImg, 0, 0);
searchImageData = tempSearchCtx.getImageData(0, 0, sw, sh);
} catch (e) {
// This could happen if server providing searchImgUrl doesn't send CORS headers
// despite img.crossOrigin = "Anonymous"
drawMessageOnCanvas("Error processing search image. Possible CORS issue with search image server.");
console.error("Error getting search image data:", e);
return outputCanvas;
}
const originalData = originalImageData.data;
const searchData = searchImageData.data;
let foundX = -1, foundY = -1;
// Search loop
for (let y = 0; y <= oh - sh; y++) {
for (let x = 0; x <= ow - sw; x++) {
let match = true;
for (let sy = 0; sy < sh; sy++) {
for (let sx = 0; sx < sw; sx++) {
const originalPixelStartIndex = ((y + sy) * ow + (x + sx)) * 4;
const searchPixelStartIndex = (sy * sw + sx) * 4;
if (originalData[originalPixelStartIndex] !== searchData[searchPixelStartIndex] || // R
originalData[originalPixelStartIndex + 1] !== searchData[searchPixelStartIndex + 1] || // G
originalData[originalPixelStartIndex + 2] !== searchData[searchPixelStartIndex + 2] || // B
originalData[originalPixelStartIndex + 3] !== searchData[searchPixelStartIndex + 3]) { // A
match = false;
break;
}
}
if (!match) break;
}
if (match) {
foundX = x;
foundY = y;
break;
}
}
if (foundX !== -1) break;
}
// Draw result (originalImg is already on ctxOutput)
if (foundX !== -1 && foundY !== -1) {
ctxOutput.strokeStyle = highlightColor;
ctxOutput.lineWidth = highlightLineWidth;
ctxOutput.strokeRect(foundX, foundY, sw, sh);
const foundTextFontSize = Math.max(10, Math.min(16, Math.floor(sw / 6), Math.floor(ow / 30)));
ctxOutput.font = `${foundTextFontSize}px Arial`;
ctxOutput.fillStyle = highlightColor;
const text = `Found: (${foundX},${foundY})`;
let tx = foundX;
let ty = foundY - 5 - highlightLineWidth;
ctxOutput.textAlign = 'left';
ctxOutput.textBaseline = 'bottom';
if (ty < foundTextFontSize) {
ty = foundY + sh + 5 + highlightLineWidth + foundTextFontSize;
ctxOutput.textBaseline = 'top';
}
if (ty < foundTextFontSize || ty > oh ) {
ty = oh / 2;
ctxOutput.textBaseline = 'middle';
}
const textWidth = ctxOutput.measureText(text).width;
if (tx + textWidth + 5 > ow) {
tx = Math.max(5, ow - textWidth - 5);
}
if (tx < 5) {
tx = 5;
}
ctxOutput.fillText(text, tx, ty);
} else {
drawMessageOnCanvas("Search image not found within original image.");
}
return outputCanvas;
}
Apply Changes