You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, lineSpacing = 3, lineThickness = 1, brightnessThreshold = 128, lineColor = "black", backgroundColor = "white") {
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
// Initialize canvas with 0x0 dimensions if original image has no dimensions
// This prevents errors if width/height are undefined or non-numeric.
canvas.width = Number(width) || 0;
canvas.height = Number(height) || 0;
const ctx = canvas.getContext('2d');
// If image dimensions are invalid or zero, return an empty (possibly 0x0) canvas.
if (canvas.width === 0 || canvas.height === 0) {
return canvas;
}
// Draw background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Temporary canvas for source image pixel data
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const tempCtx = tempCanvas.getContext('2d', {
willReadFrequently: true,
// Desynchronized can sometimes help with performance by reducing blocking on the main thread,
// but might not be universally supported or beneficial. Standard is false.
// desynchronized: true
});
tempCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
imageData = tempCtx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Could not get image data. This might be due to CORS policy if the image is from another domain and the canvas becomes tainted. Error:", e);
// Draw an informative error message on the output canvas
ctx.fillStyle = "rgba(200,0,0,0.8)"; // Error indication background
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.font = `bold ${Math.min(24, Math.max(10, canvas.width / 20))}px sans-serif`;
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const messages = ["Error processing image.", "(Possibly CORS or security restriction)"];
const lineHeight = parseInt(ctx.font, 10) * 1.2; // Approximate line height
ctx.fillText(messages[0], canvas.width/2, canvas.height/2 - lineHeight/2);
ctx.font = `${Math.min(16, Math.max(8, canvas.width / 25))}px sans-serif`;
ctx.fillText(messages[1], canvas.width/2, canvas.height/2 + lineHeight/2);
return canvas;
}
const pixels = imageData.data;
// Sanitize parameters to ensure they are within reasonable bounds
lineSpacing = Math.max(1, Math.floor(Number(lineSpacing) || 3));
lineThickness = Math.max(1, Math.floor(Number(lineThickness) || 1));
brightnessThreshold = Math.max(0, Math.min(255, Number(brightnessThreshold) || 128));
ctx.strokeStyle = String(lineColor) || "black";
ctx.lineWidth = lineThickness;
// 'round' line caps make line ends softer, which can look more natural for an engraving effect.
// Other options: 'butt' (default, sharp square ends), 'square' (adds a square cap).
ctx.lineCap = 'round';
// Determine the starting y-coordinate for drawing lines.
// This centers the sampling band for the line's perceived thickness around its drawn y-coordinate.
const initialY = Math.max(0, Math.floor((lineThickness - 1) / 2));
for (let y = initialY; y < canvas.height; y += lineSpacing) {
// Small optimization: if y has already passed canvas height due to large lineSpacing.
if (y >= canvas.height && lineSpacing > 0) break;
let segmentStartX = -1; // Tracks the beginning x-coordinate of a continuous dark line segment
for (let x = 0; x < canvas.width; x++) {
let graySum = 0;
let count = 0; // Number of pixels sampled for averaging
// Define the vertical band of pixels to sample for average brightness.
// This band is centered at the current line's y-coordinate and has a height of `lineThickness`.
const halfEffectiveThickness = (lineThickness - 1) / 2;
const y_scan_start = Math.max(0, Math.round(y - halfEffectiveThickness));
const y_scan_end = Math.min(canvas.height - 1, Math.round(y + halfEffectiveThickness));
for (let current_scan_y = y_scan_start; current_scan_y <= y_scan_end; current_scan_y++) {
// Calculate the index for the red component of the pixel (current_scan_y, x)
const R_idx = (current_scan_y * canvas.width + x) * 4;
const r_val = pixels[R_idx];
const g_val = pixels[R_idx + 1];
const b_val = pixels[R_idx + 2];
// Standard luminance calculation (weighted average for grayscale)
graySum += (0.299 * r_val + 0.587 * g_val + 0.114 * b_val);
count++;
}
const avgGray = (count > 0) ? (graySum / count) : 255; // Default to white if no samples (should not happen in normal flow)
if (avgGray < brightnessThreshold) {
// Current area is dark enough to draw a line segment
if (segmentStartX === -1) {
segmentStartX = x; // Start a new segment at this x-coordinate
}
// If this is the last pixel in the row and a segment is active, draw it
if (x === canvas.width - 1 && segmentStartX !== -1) {
ctx.beginPath();
ctx.moveTo(segmentStartX, y);
ctx.lineTo(x, y); // Draw to the current x (which is the end of the row)
ctx.stroke();
// segmentStartX will be reset at the start of the next row or if a light pixel is encountered
}
} else {
// Current area is too light; if a segment was active, end and draw it
if (segmentStartX !== -1) {
ctx.beginPath();
ctx.moveTo(segmentStartX, y);
// Draw the line up to the pixel *before* the current light one (x-1)
ctx.lineTo(x - 1, y);
ctx.stroke();
segmentStartX = -1; // Reset segment tracking
}
}
}
}
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 Engraving Filter Tool transforms ordinary images into artistic engravings by applying a filter that simulates engraved lines. Users can adjust parameters such as line spacing, thickness, brightness threshold, line color, and background color to customize the engraving effect. This tool is ideal for artists, graphic designers, or anyone looking to create unique visual designs for prints, posters, or digital art while giving a classic engraved look to their images.