You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, beakStrength = "50", magicHue = "280", magicIntensity = "50") {
const canvas = document.createElement("canvas");
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
// Draw original image to extract pixel data
ctx.drawImage(originalImg, 0, 0);
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
// Output mapped pixel data
const outputData = new Uint8ClampedArray(data.length);
// Filter controls
const bStrength = parseFloat(beakStrength) / 100; // Expected bounds around 0-1
const mHue = parseFloat(magicHue) % 360;
const mInt = parseFloat(magicIntensity);
// Geometry logic for the 'beak' distortion
const cx = w / 2;
const cy = h / 2;
const maxR = Math.min(w, h) * 0.45;
// Helper to get RGB from HSL
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 * 255, g * 255, b * 255];
}
const magicRGB = hslToRgb(mHue / 360, 1, 0.6);
// Get bilinearly interpolated pixel values for smoother distortion mapping
function getPixelBilinear(u, v) {
let x0 = Math.floor(u);
let y0 = Math.floor(v);
let x1 = Math.min(x0 + 1, w - 1);
let y1 = Math.min(y0 + 1, h - 1);
x0 = Math.max(0, x0);
y0 = Math.max(0, y0);
let dx = u - x0;
let dy = v - y0;
let w00 = (1 - dx) * (1 - dy);
let w10 = dx * (1 - dy);
let w01 = (1 - dx) * dy;
let w11 = dx * dy;
let idx00 = (y0 * w + x0) * 4;
let idx10 = (y0 * w + x1) * 4;
let idx01 = (y1 * w + x0) * 4;
let idx11 = (y1 * w + x1) * 4;
let r = data[idx00] * w00 + data[idx10] * w10 + data[idx01] * w01 + data[idx11] * w11;
let g = data[idx00+1] * w00 + data[idx10+1] * w10 + data[idx01+1] * w01 + data[idx11+1] * w11;
let b = data[idx00+2] * w00 + data[idx10+2] * w10 + data[idx01+2] * w01 + data[idx11+2] * w11;
let a = data[idx00+3] * w00 + data[idx10+3] * w10 + data[idx01+3] * w01 + data[idx11+3] * w11;
return [r, g, b, a];
}
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
let dx = x - cx;
let dy = y - cy;
let r = Math.sqrt(dx * dx + dy * dy);
let u = x;
let v = y;
if (r < maxR && bStrength > 0) {
let nr = r / maxR;
let angle = Math.atan2(dy, dx);
// Downward focusing geometry to simulate a beak pulling down
let downFactor = Math.max(0, Math.sin(angle));
let sharpen = Math.pow(downFactor, 3);
let dropoff = Math.pow(1 - nr, 2);
// Y-pull sources pixels from higher up, pulling the center downwards
let pullY = bStrength * maxR * 1.5 * sharpen * dropoff;
v = y - pullY;
// X-pull sources from further apart symmetrically, pinching horizontally
let pullX = bStrength * dx * Math.pow(downFactor, 2) * dropoff;
u = x + pullX;
}
let pxColor = getPixelBilinear(u, v);
let destIdx = (y * w + x) * 4;
outputData[destIdx] = pxColor[0];
outputData[destIdx + 1] = pxColor[1];
outputData[destIdx + 2] = pxColor[2];
outputData[destIdx + 3] = pxColor[3];
// Magical Aura Tinting around the beak area
if (r < maxR && mInt > 0) {
let tintFactor = Math.pow(1 - (r / maxR), 2) * 0.3 * (mInt / 100);
outputData[destIdx] += (magicRGB[0] - outputData[destIdx]) * tintFactor;
outputData[destIdx + 1] += (magicRGB[1] - outputData[destIdx + 1]) * tintFactor;
outputData[destIdx + 2] += (magicRGB[2] - outputData[destIdx + 2]) * tintFactor;
}
}
}
const outImgData = new ImageData(outputData, w, h);
ctx.putImageData(outImgData, 0, 0);
// Magical effect mapping (Stars and Sparkles overlay)
function drawMagicStar(context, x, y, size, hue) {
context.save();
context.translate(x, y);
context.rotate(Math.random() * Math.PI); // Randomize tilt
context.beginPath();
let spikes = 4;
let rot = (Math.PI / 2) * 3;
let step = Math.PI / spikes;
context.moveTo(0, -size);
for (let i = 0; i < spikes; i++) {
context.lineTo(Math.cos(rot) * size, Math.sin(rot) * size);
rot += step;
// The inner intersection point of the star
context.lineTo(Math.cos(rot) * (size * 0.15), Math.sin(rot) * (size * 0.15));
rot += step;
}
context.lineTo(0, -size);
context.closePath();
context.shadowBlur = size * 2.5;
context.shadowColor = `hsl(${hue}, 100%, 65%)`;
context.fillStyle = "white";
context.fill();
context.restore();
}
if (mInt > 0) {
ctx.globalCompositeOperation = "screen"; // Ethereal Magic Bleed
let numStars = (mInt / 100) * 80; // Scale stars by intensity
for (let i = 0; i < numStars; i++) {
// Random clustering focusing near or below center (where the 'beak' drops)
let rAngle = Math.random() * Math.PI * 2;
let rDist = Math.random() * maxR * 1.5;
// Bias downwards
if (Math.sin(rAngle) > 0) rDist *= 0.5;
let px = cx + Math.cos(rAngle) * rDist;
let py = cy + Math.sin(rAngle) * rDist;
let size = Math.random() * (Math.max(w, h) * 0.015) + (Math.max(w, h) * 0.005);
drawMagicStar(ctx, px, py, size, mHue);
}
ctx.globalCompositeOperation = "source-over"; // Reset composite
}
return canvas;
}
Apply Changes