You can edit the below JavaScript code to customize the image tool.
async function processImage(originalImg, lineColor = "black", backgroundColor = "white", lineWidth = 1, spacing = 8, segmentLength = 5, angle1 = 45, threshold1 = 170, angle2 = -45, threshold2 = 85) {
const w = originalImg.naturalWidth;
const h = originalImg.naturalHeight;
// Parameter validation and type conversion
spacing = Math.max(1, Number(spacing));
lineWidth = Math.max(0.1, Number(lineWidth)); // Canvas lineWidth 0 can be inconsistent
segmentLength = Math.max(1, Number(segmentLength));
threshold1 = Math.max(0, Math.min(255, Number(threshold1)));
threshold2 = Math.max(0, Math.min(255, Number(threshold2)));
angle1 = Number(angle1);
angle2 = Number(angle2);
// Create a temporary canvas to get pixel data from originalImg
// This is necessary because getImageData works on a canvas context.
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = w;
tempCanvas.height = h;
let imageData;
let sourcePixels;
if (w > 0 && h > 0) {
tempCtx.drawImage(originalImg, 0, 0, w, h);
try {
imageData = tempCtx.getImageData(0, 0, w, h);
sourcePixels = imageData.data;
} catch (e) {
// Handle potential security errors if the image is tainted (e.g., cross-origin)
console.error("Crosshatch filter: Could not get image data. Image might be cross-origin tainted.", e);
// Fallback: return a canvas with an error message
const errorCanvas = document.createElement('canvas');
errorCanvas.width = Math.max(200, w); // Ensure some minimum size for message
errorCanvas.height = Math.max(100, h);
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = "lightcoral";
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = "black";
errorCtx.font = "16px Arial";
errorCtx.textAlign = "center";
errorCtx.textBaseline = "middle";
errorCtx.fillText("Error: Cannot process image.", errorCanvas.width/2, errorCanvas.height/2 - 10);
errorCtx.fillText("Image may be cross-origin tainted.", errorCanvas.width/2, errorCanvas.height/2 + 10);
return errorCanvas;
}
} else {
// Handle cases where image dimensions are zero (e.g., image not loaded)
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = Math.max(1, w);
emptyCanvas.height = Math.max(1, h);
// optionally draw a small note or leave it blank
return emptyCanvas;
}
// Create the output canvas
const outputCanvas = document.createElement('canvas');
outputCanvas.width = w;
outputCanvas.height = h;
const outputCtx = outputCanvas.getContext('2d');
// Fill background
outputCtx.fillStyle = backgroundColor;
outputCtx.fillRect(0, 0, w, h);
// Set line properties for hatching
outputCtx.strokeStyle = lineColor;
outputCtx.lineWidth = lineWidth;
outputCtx.lineCap = "round"; // Makes dashed lines look a bit nicer
// Helper function to get brightness of a pixel (0-255, 0=black, 255=white)
function getBrightness(x, y) {
x = Math.floor(x);
y = Math.floor(y);
// If point is outside canvas, treat as white (no lines drawn there)
if (x < 0 || x >= w || y < 0 || y >= h) {
return 255;
}
const idx = (y * w + x) * 4; // Each pixel has 4 components (R,G,B,A)
const r = sourcePixels[idx];
const g = sourcePixels[idx + 1];
const b = sourcePixels[idx + 2];
// Standard luminance calculation
return 0.299 * r + 0.587 * g + 0.114 * b;
}
// Function to draw one set of hatch lines based on brightness
function drawHatchSet(angleDegrees, currentSpacing, brightnessThreshold) {
const angleRad = angleDegrees * Math.PI / 180;
const cosA = Math.cos(angleRad);
const sinA = Math.sin(angleRad);
// Vector defining the direction of the lines
const lineDirX = -sinA;
const lineDirY = cosA;
outputCtx.beginPath(); // Start a new path for all segments in this set for efficiency
// Determine the range of 'd' values. 'd' is the perpendicular distance from origin to a line.
// Calculated by projecting canvas corners onto the normal vector of the lines.
const d_projections = [
0, // Projection of (0,0)
w * cosA, // Projection of (w,0)
h * sinA, // Projection of (0,h)
w * cosA + h * sinA // Projection of (w,h)
];
const min_d_proj = Math.min(...d_projections);
const max_d_proj = Math.max(...d_projections);
// Estimate number of lines needed to cover the projection range, plus a margin
const numLinesEstimate = Math.ceil((max_d_proj - min_d_proj) / currentSpacing) + 2;
for (let i = -1; i < numLinesEstimate; i++) { // Iterate through conceptual parallel lines
const d = min_d_proj + i * currentSpacing;
// Iterate along this current conceptual line using a parameter 't'
// The line passes through M=(d*cosA, d*sinA) and is parallel to (lineDirX, lineDirY)
// The iteration range for 't' should cover the canvas diagonal to ensure full coverage.
const lineIterationLength = Math.sqrt(w*w + h*h) * 1.1; // A bit more than canvas diagonal
for (let t = -lineIterationLength / 2; t < lineIterationLength / 2; t += segmentLength) {
// Calculate the center point (currentX, currentY) of the current potential segment
const currentX = d * cosA + t * lineDirX;
const currentY = d * sinA + t * lineDirY;
const brightness = getBrightness(currentX, currentY);
// If the brightness at this point is below the threshold (i.e., dark enough), draw a segment
if (brightness < brightnessThreshold) {
const halfSeg = segmentLength / 2;
const startX = currentX - halfSeg * lineDirX;
const startY = currentY - halfSeg * lineDirY;
const endX = currentX + halfSeg * lineDirX;
const endY = currentY + halfSeg * lineDirY;
outputCtx.moveTo(startX, startY);
outputCtx.lineTo(endX, endY);
}
}
}
outputCtx.stroke(); // Draw all accumulated line segments for this set
}
// Draw the two sets of hatch lines.
// The first set is drawn for pixels darker than threshold1.
drawHatchSet(angle1, spacing, threshold1);
// The second set is drawn for pixels darker than threshold2.
// Typically, threshold2 is lower (represents darker areas) than threshold1,
// resulting in denser crosshatching in darker parts of the image.
drawHatchSet(angle2, spacing, threshold2);
return outputCanvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Crosshatch Filter allows users to apply a crosshatch effect to images, transforming them into artistic representations. Users can customize parameters such as line color, background color, line width, spacing, segment length, and angles for the hatch lines. This tool is particularly useful for graphic designers, artists, and anyone looking to create unique visual styles for digital images. It can be utilized for enhancing illustrations, creating textures for backgrounds, or simply for artistic experimentation with photos.