You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, annotationsData = '[]') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas dimensions from the image.
// Use naturalWidth/Height for the true image dimensions.
// Fallback to width/height if naturalWidth/Height are not available (e.g. not an HTMLImageElement or SVG).
canvas.width = originalImg.naturalWidth || originalImg.width || 0;
canvas.height = originalImg.naturalHeight || originalImg.height || 0;
// Handle cases where image dimensions are invalid (e.g., image not loaded or is 0x0)
if (canvas.width === 0 || canvas.height === 0) {
console.warn("Original image has zero dimensions. Returning a canvas with an error message.");
// Ensure canvas has some dimensions to display the error message
canvas.width = canvas.width === 0 ? 300 : canvas.width;
canvas.height = canvas.height === 0 ? 150 : canvas.height;
ctx.fillStyle = "#f0f0f0"; // Light gray background
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#333"; // Dark text color
ctx.font = "16px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Invalid image or dimensions", canvas.width / 2, canvas.height / 2);
return canvas;
}
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Parse annotationsData (JSON string)
let annotations;
try {
annotations = JSON.parse(annotationsData);
if (!Array.isArray(annotations)) {
console.error("AnnotationsData must be a JSON string representing an array. Defaulting to empty array.");
annotations = [];
}
} catch (e) {
console.error("Error parsing annotationsData JSON:", e, ". Defaulting to empty array.");
annotations = [];
}
// Process each annotation
annotations.forEach(ann => {
if (!ann || typeof ann !== 'object') {
console.warn("Skipping invalid annotation item (not an object):", ann);
return; // continue to next annotation
}
ctx.save(); // Save current canvas state
// Common styling properties from annotation, with defaults
const color = ann.color || 'red'; // Default color for strokes, text fill, arrow fill
switch (ann.type) {
case 'text': {
const x = ann.x;
const y = ann.y;
const text = ann.text;
// Ensure fontSize is a positive number, default to 16
const fontSize = (typeof ann.fontSize === 'number' && ann.fontSize > 0) ? ann.fontSize : 16;
const fontFamily = ann.fontFamily || 'Arial'; // Default font family
if (typeof x !== 'number' || typeof y !== 'number' || typeof text !== 'string') {
console.warn("Skipping text annotation due to missing/invalid properties (requires: x, y, text). Annotation:", ann);
ctx.restore(); // Restore canvas state before skipping
return;
}
ctx.fillStyle = color;
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.textAlign = ann.textAlign || 'left'; // Default: 'left'
ctx.textBaseline = ann.textBaseline || 'top'; // Default: 'top'
ctx.fillText(text, x, y);
break;
}
case 'rectangle': {
const x = ann.x;
const y = ann.y;
const width = ann.width;
const height = ann.height;
// Ensure lineWidth is a non-negative number, default to 2
const lineWidth = (typeof ann.lineWidth === 'number' && ann.lineWidth >= 0) ? ann.lineWidth : 2;
const fillColor = ann.fillColor; // Optional: if provided, rectangle will be filled
if (typeof x !== 'number' || typeof y !== 'number' || typeof width !== 'number' || typeof height !== 'number') {
console.warn("Skipping rectangle annotation due to missing/invalid properties (requires: x, y, width, height). Annotation:", ann);
ctx.restore();
return;
}
if (fillColor) {
ctx.fillStyle = fillColor;
ctx.fillRect(x, y, width, height);
}
if (lineWidth > 0) { // Only stroke if lineWidth is positive
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.strokeRect(x, y, width, height);
}
break;
}
case 'circle': {
const x = ann.x;
const y = ann.y;
const radius = ann.radius;
const lineWidth = (typeof ann.lineWidth === 'number' && ann.lineWidth >= 0) ? ann.lineWidth : 2;
const fillColor = ann.fillColor;
if (typeof x !== 'number' || typeof y !== 'number' || typeof radius !== 'number' || radius <= 0) {
console.warn("Skipping circle annotation due to missing/invalid properties (requires: x, y, radius > 0). Annotation:", ann);
ctx.restore();
return;
}
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
if (fillColor) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (lineWidth > 0) { // Only stroke if lineWidth is positive
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.stroke();
}
break;
}
case 'line': {
const x1 = ann.x1;
const y1 = ann.y1;
const x2 = ann.x2;
const y2 = ann.y2;
const lineWidth = (typeof ann.lineWidth === 'number' && ann.lineWidth >= 0) ? ann.lineWidth : 1; // Default 1 for lines
if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number') {
console.warn("Skipping line annotation due to missing/invalid properties (requires: x1, y1, x2, y2). Annotation:", ann);
ctx.restore();
return;
}
if (lineWidth > 0) { // Only draw line if lineWidth is positive
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
break;
}
case 'arrow': {
const x1 = ann.x1;
const y1 = ann.y1;
const x2 = ann.x2;
const y2 = ann.y2;
const lineWidth = (typeof ann.lineWidth === 'number' && ann.lineWidth >= 0) ? ann.lineWidth : 2;
// Ensure headLength is a positive number, default to 10
const headLength = (typeof ann.headLength === 'number' && ann.headLength > 0) ? ann.headLength : 10;
if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number') {
console.warn("Skipping arrow annotation due to missing/invalid properties (requires: x1, y1, x2, y2). Annotation:", ann);
ctx.restore();
return;
}
const dx = x2 - x1;
const dy = y2 - y1;
const angle = Math.atan2(dy, dx);
// Draw line part of the arrow
if (lineWidth > 0) {
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
// Draw arrowhead (filled with the main 'color')
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x2, y2); // Tip of the arrow
// Point 1 of arrowhead base
ctx.lineTo(x2 - headLength * Math.cos(angle - Math.PI / 6), y2 - headLength * Math.sin(angle - Math.PI / 6));
// Point 2 of arrowhead base
ctx.lineTo(x2 - headLength * Math.cos(angle + Math.PI / 6), y2 - headLength * Math.sin(angle + Math.PI / 6));
ctx.closePath(); // Close path to form a triangle
ctx.fill();
break;
}
default:
console.warn("Unknown annotation type:", ann.type, ". Annotation:", ann);
}
ctx.restore(); // Restore canvas state to before this annotation
});
return canvas;
}
Apply Changes