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!
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.