You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
colorsStr = "FF0000,FFFF00,00FF00,0000FF,FF00FF,00FFFF",
numSwirlsParam = 3,
swirlStrengthParam = 10,
bandFrequencyParam = 0.03,
irregularityParam = 0.2,
blendMode = "overlay"
) {
function hexToRgb(hex) {
const originalHex = hex;
// Ensure '#' prefix
hex = hex.startsWith('#') ? hex : '#' + hex;
// Validate hex color format (#RGB, #RRGGBB)
if (!/^#([0-9A-F]{3}|[0-9A-F]{6})$/i.test(hex)) {
// console.warn(`Invalid hex color: ${originalHex}`);
return null;
}
let r = 0, g = 0, b = 0;
if (hex.length === 4) { // #RGB shorthand
r = parseInt(hex[1] + hex[1], 16);
g = parseInt(hex[2] + hex[2], 16);
b = parseInt(hex[3] + hex[3], 16);
} else if (hex.length === 7) { // #RRGGBB
r = parseInt(hex.substring(1, 3), 16);
g = parseInt(hex.substring(3, 5), 16);
b = parseInt(hex.substring(5, 7), 16);
}
return { r, g, b };
}
const width = originalImg.width;
const height = originalImg.height;
if (width === 0 || height === 0) {
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = Math.max(1, width);
emptyCanvas.height = Math.max(1, height);
// console.warn("Original image has zero dimension. Returning small empty canvas.");
return emptyCanvas;
}
// Sanitize and validate numeric parameters
let numSwirls = typeof numSwirlsParam === 'string' ? parseFloat(numSwirlsParam) : numSwirlsParam;
numSwirls = (Number.isFinite(numSwirls) && numSwirls >= 1) ? Math.floor(numSwirls) : 3;
let swirlStrength = typeof swirlStrengthParam === 'string' ? parseFloat(swirlStrengthParam) : swirlStrengthParam;
swirlStrength = Number.isFinite(swirlStrength) ? swirlStrength : 10;
let bandFrequency = typeof bandFrequencyParam === 'string' ? parseFloat(bandFrequencyParam) : bandFrequencyParam;
bandFrequency = Number.isFinite(bandFrequency) ? bandFrequency : 0.03;
let irregularity = typeof irregularityParam === 'string' ? parseFloat(irregularityParam) : irregularityParam;
irregularity = (Number.isFinite(irregularity) && irregularity >= 0) ? irregularity : 0.2;
const irregularityRadianFactor = irregularity * Math.PI;
// Parse colors
const colorHexArray = colorsStr.split(',').map(c => c.trim()).filter(c => c.length > 0);
let parsedColors = colorHexArray.map(hex => hexToRgb(hex)).filter(color => color !== null);
if (parsedColors.length === 0) {
// Fallback to a default set of vibrant colors if input is empty or all invalid
parsedColors = ["FF5733", "FFC300", "33FF57", "3357FF", "C70039"].map(hex => hexToRgb(hex));
}
// Canvas setup
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Generate swirl centers
const swirlCenters = [];
for (let i = 0; i < numSwirls; i++) {
swirlCenters.push({
x: Math.random() * width,
y: Math.random() * height,
// Add random phase offsets for more variety per center's distortion pattern
phaseOffset1: Math.random() * 2 * Math.PI, // For distance-based part of distortion
phaseOffset2: Math.random() * 2 * Math.PI // For angle-based part of distortion
});
}
// Create ImageData for the tie-dye pattern
const patternData = ctx.createImageData(width, height);
const data = patternData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let totalValue = 0;
for (let i = 0; i < numSwirls; i++) {
const sc = swirlCenters[i];
const dx = x - sc.x;
const dy = y - sc.y;
const angle = Math.atan2(dy, dx);
const dist = Math.sqrt(dx * dx + dy * dy);
const basePhase = angle * swirlStrength + dist * bandFrequency;
// Applying irregularity as a phase distortion.
// The distortion term uses a combination of sines based on distance and angle,
// with random phase offsets for each swirl center to make patterns more varied.
const distFactorForIrregularity = 0.015; // Controls frequency of irregularity based on distance
const angleFactorForIrregularity = Math.abs(swirlStrength / 4) + 1; // Controls frequency of irregularity based on angle/swirlStrength
const distortion = irregularityRadianFactor *
Math.sin(dist * distFactorForIrregularity + sc.phaseOffset1) *
Math.cos(angle * angleFactorForIrregularity + sc.phaseOffset2);
totalValue += Math.sin(basePhase + distortion);
}
// Normalize totalValue: Math.sin sums numSwirls times, so range can be [-numSwirls, numSwirls]
let normalizedValue = (totalValue / numSwirls + 1) / 2; // Expected range [0, 1]
normalizedValue = Math.max(0, Math.min(1, normalizedValue)); // Clamp to [0,1] to be safe
const colorIndex = Math.floor(normalizedValue * parsedColors.length) % parsedColors.length;
const selectedColor = parsedColors[colorIndex];
const pixelIndex = (y * width + x) * 4;
data[pixelIndex] = selectedColor.r;
data[pixelIndex + 1] = selectedColor.g;
data[pixelIndex + 2] = selectedColor.b;
data[pixelIndex + 3] = 255; // Alpha (fully opaque)
}
}
// Drawing logic
const safeBlendMode = typeof blendMode === 'string' ? blendMode.toLowerCase() : "overlay";
if (safeBlendMode === "patternonly") {
ctx.putImageData(patternData, 0, 0);
} else {
// Draw original image first
ctx.drawImage(originalImg, 0, 0, width, height);
// Create a temporary canvas to hold the pattern. This is necessary for
// globalCompositeOperation to apply correctly when drawing generated pixel data.
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.putImageData(patternData, 0, 0);
// Set blend mode. Browsers usually default to "source-over" for invalid modes.
// A try-catch is defensive but often not strictly needed for this property.
try {
ctx.globalCompositeOperation = safeBlendMode;
} catch(e) {
// console.warn(`Invalid blend mode: '${safeBlendMode}'. Defaulting to 'overlay'.`);
ctx.globalCompositeOperation = "overlay"; // Fallback blend mode
}
ctx.drawImage(tempCanvas, 0, 0, width, height);
// Reset globalCompositeOperation to default for good practice
ctx.globalCompositeOperation = "source-over";
}
return canvas;
}
Apply Changes