You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, chainSize = "16", bgColor = "#ffffff", colorMode = "original", strokeThickness = "1.5", invertBrightness = "0") {
// Parse parameters
const size = parseFloat(chainSize) || 16;
const thickness = parseFloat(strokeThickness) || 1.5;
const isMono = String(colorMode).trim().toLowerCase() === 'monochrome';
const invert = parseInt(invertBrightness, 10) === 1;
const w = originalImg.width;
const h = originalImg.height;
// Create main drawing canvas
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Handle background layer
let bg = String(bgColor).trim();
if (!bg.startsWith('#') && bg.length === 6) bg = '#' + bg;
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h);
// Analyze background to properly map contrast
const bgData = ctx.getImageData(0, 0, 1, 1).data;
const bgLum = bgData[0] * 0.299 + bgData[1] * 0.587 + bgData[2] * 0.114;
const isDarkBg = bgLum < 128; // If background is dark, bright pixels should be opaque
// Create temporary canvas to read original pixel data
const tCanvas = document.createElement('canvas');
tCanvas.width = w;
tCanvas.height = h;
const tCtx = tCanvas.getContext('2d', { willReadFrequently: true });
tCtx.drawImage(originalImg, 0, 0);
const imgData = tCtx.getImageData(0, 0, w, h).data;
// Helper to get pixel properties safely
function getPixel(x, y) {
const ix = Math.max(0, Math.min(w - 1, Math.round(x)));
const iy = Math.max(0, Math.min(h - 1, Math.round(y)));
const idx = (iy * w + ix) * 4;
return {
r: imgData[idx],
g: imgData[idx + 1],
b: imgData[idx + 2],
a: imgData[idx + 3]
};
}
// Set drawing styles
ctx.lineWidth = thickness;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Calculate dimensions for the alternating "chain links"
const radiusY = Math.max(2, size / 3);
const frontRadiusX = Math.max(3, size / 2);
const sideRadiusX = Math.max(1, size / 6);
const stepX = frontRadiusX;
// Add extra vertical spacing so overlapping chains are readable
const stepY = radiusY * 2 * 1.25;
// Draw the image completely constructed out of interconnected chain links
let rowNum = 0;
for (let y = stepY / 2; y < h + stepY; y += stepY) {
let isFront = true;
// Offset alternating rows to form a woven/chainmail look
let startX = -frontRadiusX;
if (rowNum % 2 === 1) {
startX -= stepX / 2;
}
for (let x = startX; x < w + frontRadiusX; x += stepX) {
const p = getPixel(x, y);
// Skip drawing if pixel is almost fully transparent
if (p.a > 10) {
const lum = (0.299 * p.r + 0.587 * p.g + 0.114 * p.b) / 255;
// Determine opacity mapping to create halftone-like shadowing
let baseAlpha = isDarkBg ? lum : (1.0 - lum);
if (invert) baseAlpha = 1.0 - baseAlpha;
// Merge opacity with image's original alpha layer
const finalAlpha = Math.min(1, baseAlpha * 1.5) * (p.a / 255);
// Discard rendering chains in empty or contrast-nullified coordinate zones
if (finalAlpha > 0.05) {
if (isMono) {
ctx.strokeStyle = `rgba(${isDarkBg ? '255,255,255' : '0,0,0'}, ${finalAlpha})`;
} else {
ctx.strokeStyle = `rgba(${p.r}, ${p.g}, ${p.b}, ${finalAlpha})`;
}
// Stroke alternating chain link ellipses
ctx.beginPath();
if (isFront) {
ctx.ellipse(x, y, frontRadiusX, radiusY, 0, 0, Math.PI * 2);
} else {
// Narrow ellipse gives the illusion of a side-facing interlocking link
ctx.ellipse(x, y, sideRadiusX, radiusY, 0, 0, Math.PI * 2);
}
ctx.stroke();
}
}
isFront = !isFront;
}
rowNum++;
}
return canvas;
}
Apply Changes