Please bookmark this page to avoid losing your image tool!

Image Arrow Adder

(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, arrowsString = "50,50,150,150,red,3", defaultColor = "black", defaultThickness = 2, defaultHeadLength = 10, defaultHeadAngleDeg = 30) {
    const canvas = document.createElement('canvas');
    
    // Determine image dimensions. Handles HTMLImageElement and other canvas elements.
    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    if (imgWidth === 0 || imgHeight === 0) {
        console.warn("Image has zero dimensions. Cannot process. Returning 1x1 canvas.");
        canvas.width = 1; 
        canvas.height = 1;
        // Optionally fill with a color to indicate error state visually.
        // const ctxErr = canvas.getContext('2d');
        // if(ctxErr) { ctxErr.fillStyle = 'gray'; ctxErr.fillRect(0,0,1,1); }
        return canvas;
    }

    canvas.width = imgWidth;
    canvas.height = imgHeight;
    
    const ctx = canvas.getContext('2d');
    if (!ctx) {
        console.error("Could not get 2D context from canvas. Returning canvas.");
        return canvas; // Should not happen for '2d' context in standard browsers.
    }

    // Draw the original image onto the canvas
    try {
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Error drawing image on canvas:", e);
        // Optionally, indicate error on canvas, e.g., by filling with a specific color
        // ctx.fillStyle = 'rgba(255,0,0,0.5)'; // Semi-transparent red
        // ctx.fillRect(0,0,canvas.width, canvas.height);
        return canvas; // Return canvas, possibly (partially) drawn or empty
    }

    // Set line styles that will apply to all arrows
    ctx.lineCap = "round"; // Makes line ends and arrowhead points smoother
    ctx.lineJoin = "round"; // Makes connection points for paths smoother (more relevant for complex shapes)

    // Convert default head angle from degrees to radians once for efficiency
    const headAngleRad = defaultHeadAngleDeg * Math.PI / 180;

    // Nested helper function to draw a single arrow.
    // This function captures ctx, defaultHeadLength, and headAngleRad from the outer scope.
    function drawSingleArrow(x1, y1, x2, y2, color, thickness) {
        ctx.strokeStyle = color;
        ctx.lineWidth = thickness;
        
        // Line body
        ctx.beginPath(); // Begin a new path for the line body
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke(); // Draw the line body

        // Arrowhead
        const angle = Math.atan2(y2 - y1, x2 - x1); // Angle of the arrow line
        
        // Arrowhead lines will use the same strokeStyle and lineWidth.
        // A new path is started for the arrowhead to ensure it's drawn correctly without interference.
        ctx.beginPath(); // Begin a new path for the arrowhead
        
        // First barb of the arrowhead
        ctx.moveTo(x2, y2); // Start at the tip of the arrow
        ctx.lineTo(
            x2 - defaultHeadLength * Math.cos(angle - headAngleRad), 
            y2 - defaultHeadLength * Math.sin(angle - headAngleRad)
        );
        
        // Second barb of the arrowhead
        ctx.moveTo(x2, y2); // Start again at the tip of the arrow for the second barb
        ctx.lineTo(
            x2 - defaultHeadLength * Math.cos(angle + headAngleRad), 
            y2 - defaultHeadLength * Math.sin(angle + headAngleRad)
        );
        ctx.stroke(); // Draw both barbs of the arrowhead
    }

    // Validate arrowsString type (it might be null or other non-string if explicitly passed against defaults)
    if (typeof arrowsString !== 'string') {
        console.warn(`arrowsString parameter is not a string (type: ${typeof arrowsString}). No arrows will be drawn.`);
        arrowsString = ""; // Default to empty string to prevent errors, meaning no arrows.
    }

    const arrowDefinitions = arrowsString.split(';');
    for (const def of arrowDefinitions) {
        const trimmedDef = def.trim();
        if (trimmedDef === "") {
            // Skip empty definitions that can result from patterns like ";;" or trailing/leading semicolons.
            continue; 
        }
        
        const parts = trimmedDef.split(',').map(s => s.trim());
        
        if (parts.length < 4) {
            console.warn(`Skipping arrow definition: not enough parts (at least x1,y1,x2,y2 required). Definition: "${trimmedDef}"`);
            continue;
        }

        const x1 = parseFloat(parts[0]);
        const y1 = parseFloat(parts[1]);
        const x2 = parseFloat(parts[2]);
        const y2 = parseFloat(parts[3]);

        if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) {
            console.warn(`Skipping arrow definition: invalid coordinates. Ensure x1,y1,x2,y2 are numbers. Definition: "${trimmedDef}"`);
            continue;
        }

        // Color: use part 5 if present and non-empty, otherwise use defaultColor.
        // An empty string for color (e.g. "x,y,x,y,,thickness") will result in defaultColor.
        const color = (parts.length > 4 && parts[4]) ? parts[4] : defaultColor;
        
        // Thickness: use part 6 if present and a valid number, otherwise use defaultThickness.
        let thickness = defaultThickness; // Start with default
        if (parts.length > 5 && parts[5]) { // Check if thickness part exists and is not an empty string
            const parsedNum = parseFloat(parts[5]);
            if (!isNaN(parsedNum)) {
                thickness = parsedNum;
            } else {
                 console.warn(`Invalid thickness value "${parts[5]}" in arrow definition "${trimmedDef}". Using default thickness: ${defaultThickness}.`);
            }
        }
        
        if (thickness <= 0) {
            console.warn(`Arrow thickness must be positive (received: ${thickness}). Skipping arrow: "${trimmedDef}"`);
            continue;
        }

        drawSingleArrow(x1, y1, x2, y2, color, thickness);
    }

    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 Arrow Adder is a tool that allows users to annotate images by adding arrows to them. By specifying the start and end points of each arrow, along with customizable color and thickness, users can create dynamic visual aids for presentations, educational materials, or personal projects. This tool is particularly useful for highlighting areas of interest, directing attention to specific elements in the image, or illustrating concepts in a clear and engaging manner.

Leave a Reply

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