Please bookmark this page to avoid losing your image tool!

Image Annotation Tool

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Annotation Tool allows users to add various annotations to their images using a web-based canvas. With this tool, users can overlay text, shapes (rectangles, circles), lines, and arrows directly onto images. It is useful for educational purposes, such as marking up diagrams or images for presentations, providing feedback on images, creating instructional materials, or enhancing images for social media. The tool supports flexible customization of annotations, including color, size, and style, making it versatile for different applications.

Leave a Reply

Your email address will not be published. Required fields are marked *