You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, frequency1 = 5, angle1 = 0, frequency2 = 6, angle2 = 15, type = "multiplicative", strength = 0.5) {
// Parse and validate parameters
let numFrequency1 = parseFloat(frequency1);
if (isNaN(numFrequency1) || numFrequency1 <= 0) numFrequency1 = 5; // Frequencies should be positive
let numAngle1 = parseFloat(angle1);
if (isNaN(numAngle1)) numAngle1 = 0;
let numFrequency2 = parseFloat(frequency2);
if (isNaN(numFrequency2) || numFrequency2 <= 0) numFrequency2 = 6; // Frequencies should be positive
let numAngle2 = parseFloat(angle2);
if (isNaN(numAngle2)) numAngle2 = 15;
let numStrength = parseFloat(strength);
if (isNaN(numStrength)) numStrength = 0.5;
numStrength = Math.max(0, Math.min(1, numStrength)); // Clamp strength between 0 and 1
const strType = String(type).toLowerCase();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimization hint for frequent getImageData/putImageData
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
// Handle cases where the image dimensions are not valid
if (width === 0 || height === 0) {
canvas.width = 0;
canvas.height = 0;
return canvas; // Return an empty canvas
}
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, width, height);
// Get image data to manipulate pixels
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// Convert angles from degrees to radians
const rad1 = numAngle1 * Math.PI / 180;
const rad2 = numAngle2 * Math.PI / 180;
// Calculate k-factor for sine waves.
// numFrequencyX determines lines per 100 pixels.
// Period of sin(k*x) is 2*PI/k. We want Period = 100/numFrequencyX.
// So, k = 2*PI / (100/numFrequencyX) = (2*PI*numFrequencyX)/100.
const f1_k = (2 * Math.PI * numFrequency1) / 100;
const f2_k = (2 * Math.PI * numFrequency2) / 100;
// Pre-calculate cosine and sine for angles for rotation
const cos1 = Math.cos(rad1);
const sin1 = Math.sin(rad1);
const cos2 = Math.cos(rad2);
const sin2 = Math.sin(rad2);
// Iterate over each pixel
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Calculate the projection of (x,y) onto the direction perpendicular to the wave lines.
// This is equivalent to a rotated coordinate system for each wave pattern.
const proj1 = x * cos1 + y * sin1;
const proj2 = x * cos2 + y * sin2;
// Calculate wave values, normalized to [0, 1]
// Math.sin results in [-1, 1]. (sin(v) + 1) / 2 scales this to [0, 1].
const waveVal1 = (Math.sin(proj1 * f1_k) + 1) / 2;
const waveVal2 = (Math.sin(proj2 * f2_k) + 1) / 2;
let moireModulation;
switch (strType) {
case "additive":
// Average of the two wave patterns
moireModulation = (waveVal1 + waveVal2) / 2;
break;
case "subtractive":
// Absolute difference, highlights where patterns diverge
moireModulation = Math.abs(waveVal1 - waveVal2);
break;
case "multiplicative":
default:
// Product of the two wave patterns, common for Moiré
moireModulation = waveVal1 * waveVal2;
break;
}
// Apply strength:
// If strength = 0, finalModulation = 1 (original image brightness factor)
// If strength = 1, finalModulation = moireModulation (full Moiré effect)
const finalModulation = (1 - numStrength) + (numStrength * moireModulation);
const index = (y * width + x) * 4; // Index for R, G, B, A components
// Modulate R, G, B channels. Alpha is preserved.
// Ensure pixel values are integers [0, 255]
data[index] = Math.round(Math.max(0, Math.min(255, data[index] * finalModulation)));
data[index+1] = Math.round(Math.max(0, Math.min(255, data[index+1] * finalModulation)));
data[index+2] = Math.round(Math.max(0, Math.min(255, data[index+2] * finalModulation)));
}
}
// Write the modified pixel data back to the canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes