You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, realC = -0.7, imagC = 0.27015, maxIter = 100, zoom = 1.0, centerX = 0.0, centerY = 0.0, escapeRadius = 2.0, colorScheme = "blend", palette = "#FF0000,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF", insideColor = "#000000") {
// Helper function to parse hex color string to {r, g, b} object
function parseHexColor(hex) {
hex = String(hex).replace(/^#/, ''); // Ensure hex is a string
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length !== 6 || !/^[0-9a-fA-F]{6}$/.test(hex)) {
// console.warn(`Invalid hex color: "${hex}". Using black.`);
return { r: 0, g: 0, b: 0 }; // Default to black for invalid hex
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return { r, g, b };
}
// Helper functions for HSL color manipulation (for 'hue_shift' colorScheme)
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h, s: s, l: l };
}
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
// Use a temporary canvas to get original image data
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.drawImage(originalImg, 0, 0);
const originalImageData = tempCtx.getImageData(0, 0, width, height);
const originalData = originalImageData.data;
const outputImageData = ctx.createImageData(width, height);
const outputData = outputImageData.data;
const escapeRadiusSquared = escapeRadius * escapeRadius;
const parsedInsideColor = parseHexColor(insideColor);
let parsedPalette = [];
if (colorScheme === "iterations_palette") {
const colorStrings = String(palette).split(','); // Ensure palette is a string
parsedPalette = colorStrings.map(s => parseHexColor(s.trim()));
// Filter out nulls if parseHexColor could return null (it currently returns black for invalid)
// parsedPalette = parsedPalette.filter(c => c !== null);
if (parsedPalette.length === 0) {
parsedPalette.push({ r: 0, g: 0, b: 0 });
parsedPalette.push({ r: 255, g: 255, b: 255 });
}
}
// Coordinate mapping constants
// This maps the image pixel coordinates to a rectangular region in the complex plane.
// The aspect ratio of this region will match the image's aspect ratio.
// The 'zoom' parameter scales this region. 'centerX' and 'centerY' translate it.
// A common view for fractals is a square region like [-2, 2] x [-2, 2].
// This mapping ensures the width of that region is (2.0 / zoom) and height (2.0 * aspect_ratio_correction / zoom)
const baseScaleX = 2.0 / zoom;
const baseScaleY = (2.0 * height) / (width * zoom);
for (let py = 0; py < height; py++) {
for (let px = 0; px < width; px++) {
const zR_init = ((px / width) - 0.5) * baseScaleX + centerX;
const zI_init = ((py / height) - 0.5) * baseScaleY + centerY;
let zR = zR_init;
let zI = zI_init;
let iter = 0;
while (zR * zR + zI * zI < escapeRadiusSquared && iter < maxIter) {
const zR_temp = zR * zR - zI * zI + realC;
zI = 2 * zR * zI + imagC;
zR = zR_temp;
iter++;
}
const originalPixelIndex = (py * width + px) * 4;
const rOrig = originalData[originalPixelIndex];
const gOrig = originalData[originalPixelIndex + 1];
const bOrig = originalData[originalPixelIndex + 2];
const aOrig = originalData[originalPixelIndex + 3];
let rOut, gOut, bOut;
if (iter === maxIter) { // Point is in the set
rOut = parsedInsideColor.r;
gOut = parsedInsideColor.g;
bOut = parsedInsideColor.b;
} else { // Point escaped
// Normalized iteration count for points outside the set [0, 1]
// Handle maxIter=1 case to avoid division by zero.
const factor = (maxIter <= 1 && iter === 0) ? 0 : iter / (maxIter - 1.0);
const clampedFactor = Math.max(0, Math.min(1, factor));
switch (colorScheme) {
case "blend":
rOut = Math.floor(rOrig * clampedFactor);
gOut = Math.floor(gOrig * clampedFactor);
bOut = Math.floor(bOrig * clampedFactor);
break;
case "grayscale":
const gray = Math.floor(255 * clampedFactor);
rOut = gray;
gOut = gray;
bOut = gray;
break;
case "iterations_palette":
if (parsedPalette.length === 1) {
rOut = parsedPalette[0].r;
gOut = parsedPalette[0].g;
bOut = parsedPalette[0].b;
} else if (parsedPalette.length > 1) { // Interpolate for multi-color palettes
const palettePos = clampedFactor * (parsedPalette.length - 1);
const idx1 = Math.floor(palettePos);
const idx2 = Math.min(idx1 + 1, parsedPalette.length - 1);
const frac = palettePos - idx1;
const c1 = parsedPalette[idx1];
const c2 = parsedPalette[idx2];
rOut = Math.floor(c1.r * (1 - frac) + c2.r * frac);
gOut = Math.floor(c1.g * (1 - frac) + c2.g * frac);
bOut = Math.floor(c1.b * (1 - frac) + c2.b * frac);
} else { // Fallback (should not be reached if default palette logic works)
rOut = rOrig; gOut = gOrig; bOut = bOrig;
}
break;
case "hue_shift":
const hsl = rgbToHsl(rOrig, gOrig, bOrig);
hsl.h = (hsl.h + clampedFactor) % 1.0;
const rgbShifted = hslToRgb(hsl.h, hsl.s, hsl.l);
rOut = rgbShifted.r;
gOut = rgbShifted.g;
bOut = rgbShifted.b;
break;
default:
rOut = rOrig; gOut = gOrig; bOut = bOrig;
break;
}
}
const outputPixelIndex = (py * width + px) * 4;
outputData[outputPixelIndex] = rOut;
outputData[outputPixelIndex + 1] = gOut;
outputData[outputPixelIndex + 2] = bOut;
outputData[outputPixelIndex + 3] = aOrig; // Preserve original alpha
}
}
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Apply Changes