You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
gearColor = "rgba(100, 100, 100, 0.7)", // string: color of the gears
numGears = 12, // number: how many gears to draw
avgGearRadiusFactor = 0.08, // number: average gear radius as a factor of min(image_width, image_height)
gearRadiusVariationFactor = 0.04, // number: variation in gear radius factor
minTeeth = 5, // number: minimum teeth on a gear
maxTeeth = 12, // number: maximum teeth on a gear
toothDepthFactor = 0.3, // number: depth of teeth as a factor of gear's outer radius
holeSizeFactor = 0.3 // number: size of central hole as a factor of gear's outer radius
) {
// --- Parameter Parsing and Validation ---
const MIN_GEAR_TEETH = 3; // Absolute minimum teeth for a recognizable gear shape
const DEFAULT_FALLBACK_NUM_GEARS = 12;
const DEFAULT_FALLBACK_AVG_GEAR_RADIUS_FACTOR = 0.08;
const DEFAULT_FALLBACK_GEAR_RADIUS_VARIATION_FACTOR = 0.04;
const DEFAULT_FALLBACK_MIN_TEETH = 5;
const DEFAULT_FALLBACK_MAX_TEETH = 12;
const DEFAULT_FALLBACK_TOOTH_DEPTH_FACTOR = 0.3;
const DEFAULT_FALLBACK_HOLE_SIZE_FACTOR = 0.3;
const finalGearColor = String(gearColor);
let pNumGears = Number(numGears);
pNumGears = isNaN(pNumGears) ? DEFAULT_FALLBACK_NUM_GEARS : Math.max(0, pNumGears);
let pAvgGearRadiusFactor = Number(avgGearRadiusFactor);
pAvgGearRadiusFactor = isNaN(pAvgGearRadiusFactor) ? DEFAULT_FALLBACK_AVG_GEAR_RADIUS_FACTOR : Math.max(0.01, pAvgGearRadiusFactor);
let pGearRadiusVariationFactor = Number(gearRadiusVariationFactor);
pGearRadiusVariationFactor = isNaN(pGearRadiusVariationFactor) ? DEFAULT_FALLBACK_GEAR_RADIUS_VARIATION_FACTOR : Math.max(0, pGearRadiusVariationFactor);
let pMinTeeth = Number(minTeeth);
pMinTeeth = isNaN(pMinTeeth) ? DEFAULT_FALLBACK_MIN_TEETH : Math.max(MIN_GEAR_TEETH, pMinTeeth);
let pMaxTeeth = Number(maxTeeth);
pMaxTeeth = isNaN(pMaxTeeth) ? (pMinTeeth + 7) : Math.max(pMinTeeth, pMaxTeeth); // Default max related to min if NaN
if (pMaxTeeth < pMinTeeth) pMaxTeeth = pMinTeeth; // Ensure max is not less than min
let pToothDepthFactor = Number(toothDepthFactor);
pToothDepthFactor = isNaN(pToothDepthFactor) ? DEFAULT_FALLBACK_TOOTH_DEPTH_FACTOR : Math.min(0.9, Math.max(0.05, pToothDepthFactor)); // Clamp to [0.05, 0.9]
let pHoleSizeFactor = Number(holeSizeFactor);
pHoleSizeFactor = isNaN(pHoleSizeFactor) ? DEFAULT_FALLBACK_HOLE_SIZE_FACTOR : Math.min(0.95, Math.max(0, pHoleSizeFactor)); // Clamp to [0, 0.95]
// --- Canvas Setup ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
console.warn("Image Clock Gear Filter: Image has zero width or height.");
return canvas; // Return empty (or minimally sized) canvas
}
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// --- Helper function to draw a single gear ---
function drawGearInstance(context, x, y, outerRadius, teeth, toothDepth, holeRadiusVal, colorToUse, rotation) {
const innerRadius = outerRadius - toothDepth;
const angleStep = Math.PI / teeth; // Angle for one segment (half tooth or half valley)
context.save(); // Save current transform and style state
context.translate(x, y); // Move origin to gear center
context.rotate(rotation); // Rotate gear
context.beginPath();
// Move to the tip of the first tooth (aligned with the new x-axis after rotation)
context.moveTo(outerRadius, 0); // outerRadius * cos(0), outerRadius * sin(0)
for (let i = 0; i < teeth * 2; i++) {
const currentAngle = (i + 1) * angleStep;
// Alternate between inner and outer radius for vertices
const r = (i % 2 === 0) ? innerRadius : outerRadius;
context.lineTo(r * Math.cos(currentAngle), r * Math.sin(currentAngle));
}
context.closePath(); // Connect last point to the starting point
context.fillStyle = colorToUse;
context.fill();
// Punch out a central hole if holeRadiusVal is significant
// Check holeRadiusVal against a small epsilon relative to outerRadius
if (holeRadiusVal > (0.001 * outerRadius)) {
context.beginPath();
// Arc for the hole, centered at (0,0) due to translation
context.arc(0, 0, holeRadiusVal, 0, 2 * Math.PI, false);
const prevCompositeOp = context.globalCompositeOperation;
context.globalCompositeOperation = 'destination-out'; // New shapes erase existing content
context.fillStyle = "rgba(0,0,0,1)"; // Must be opaque to punch out effectively
context.fill();
context.globalCompositeOperation = prevCompositeOp; // Restore original composite operation
}
context.restore(); // Restore transform and style state
}
// --- Gear Generation and Placement ---
const baseDimension = Math.min(canvas.width, canvas.height); // Base size for relative calculations
for (let i = 0; i < pNumGears; i++) {
// Determine properties for this specific gear instance
const radiusFactor = pAvgGearRadiusFactor + (Math.random() - 0.5) * 2 * pGearRadiusVariationFactor;
let currentOuterRadius = baseDimension * Math.max(0.01, radiusFactor); // Ensure radius is positive
// Skip drawing if gear is too small to be visible or meaningful
if (currentOuterRadius < 1) { // Adjust threshold as needed (e.g., 0.5px)
continue;
}
const currentTeethCount = Math.floor(Math.random() * (pMaxTeeth - pMinTeeth + 1)) + pMinTeeth;
const currentToothDepthVal = currentOuterRadius * pToothDepthFactor;
const currentHoleRadiusVal = currentOuterRadius * pHoleSizeFactor;
const randomRotationVal = Math.random() * 2 * Math.PI; // Random rotation for each gear
// Determine gear placement: gears can be partially off-canvas.
// Center can range from (-0.5 * radius) to (canvas_dim + 0.5 * radius)
const placementWidth = canvas.width + currentOuterRadius;
const placementHeight = canvas.height + currentOuterRadius;
const gx_center = Math.random() * placementWidth - (currentOuterRadius * 0.5);
const gy_center = Math.random() * placementHeight - (currentOuterRadius * 0.5);
drawGearInstance(ctx, gx_center, gy_center, currentOuterRadius, currentTeethCount, currentToothDepthVal, currentHoleRadiusVal, finalGearColor, randomRotationVal);
}
return canvas;
}
Apply Changes