You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, whaleSize = "15", backgroundColor = "#021226", whaleDirection = "left", colorMode = "original") {
const w = originalImg.width;
const h = originalImg.height;
// Parse the size and enforce a safety limit to prevent the browser from freezing on huge images
let size = parseFloat(whaleSize);
if (isNaN(size) || size <= 0) size = 15;
// Dynamic minimum size calculated based on total image pixels, capping out at roughly ~100,000 whales max.
const minSize = Math.max(4, Math.sqrt((w * h) / 100000));
if (size < minSize) {
size = Math.ceil(minSize);
}
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Draw the image first to grab the original pixel data
ctx.drawImage(originalImg, 0, 0, w, h);
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
// Fill the background context
if (backgroundColor.toLowerCase() === 'transparent' || backgroundColor === '') {
ctx.clearRect(0, 0, w, h);
} else {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, w, h);
}
const dir = String(whaleDirection).toLowerCase();
const style = String(colorMode).toLowerCase();
// Determine mosaic spacing structure
const spacingX = size;
const spacingY = size * 0.6; // Packed tightly to accommodate the whale's vertical structure
let row = 0;
for (let y = spacingY / 2; y < h + spacingY; y += spacingY) {
// Staggered honeycomb-like offset
const offsetX = (row % 2 === 1) ? spacingX / 2 : 0;
for (let x = spacingX / 2 + offsetX; x < w + spacingX; x += spacingX) {
// Constrain pixel coordinates safely inside the canvas bounds
const px = Math.max(0, Math.min(Math.floor(x), w - 1));
const py = Math.max(0, Math.min(Math.floor(y), h - 1));
const offset = (py * w + px) * 4;
let r = data[offset];
let g = data[offset + 1];
let b = data[offset + 2];
const a = data[offset + 3];
// Skip largely transparent pixels
if (a < 128) continue;
if (style === "oceanblue") {
// Mix the original color with a deep, oceanic cyan/blue
r = Math.min(255, Math.floor(r * 0.3 + 10));
g = Math.min(255, Math.floor(g * 0.5 + 80));
b = Math.min(255, Math.floor(b * 0.7 + 140));
}
const color = `rgb(${r}, ${g}, ${b})`;
const brightness = (r * 0.299 + g * 0.587 + b * 0.114);
// Determine swimming direction randomness
let flip = false;
if (dir === "right") {
flip = true;
} else if (dir === "random" || dir === "both") {
flip = Math.random() > 0.5;
}
ctx.save();
ctx.translate(x, y);
let s = size / 100; // Whales are designed in roughly 100x100 space coords
if (flip) {
ctx.scale(-s, s);
} else {
ctx.scale(s, s);
}
// Draw organic, aesthetic whale shape
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(-40, 0); // Nose
ctx.bezierCurveTo(-40, -30, 20, -30, 30, -10); // Forehead and back
ctx.quadraticCurveTo(40, -10, 50, -20); // Upper tail
ctx.lineTo(45, 0); // V-cleft of the tail fin
ctx.lineTo(50, 20); // Bottom tail
ctx.quadraticCurveTo(40, 10, 30, 10); // Lower tail curve
ctx.bezierCurveTo(20, 30, -20, 30, -40, 10); // Belly
ctx.quadraticCurveTo(-30, 5, -40, 0); // Friendly mouth indent
ctx.fill();
// Draw the eye (contrast dynamically adjusted heavily against lightness limit)
if (brightness > 128) {
// Light-colored whale -> Dark eye with a white sparkle
ctx.fillStyle = '#000000';
ctx.beginPath(); ctx.arc(-20, -5, 4, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath(); ctx.arc(-21, -6, 1.5, 0, Math.PI * 2); ctx.fill();
} else {
// Dark-colored whale -> White eye with a dark pupil
ctx.fillStyle = '#ffffff';
ctx.beginPath(); ctx.arc(-20, -5, 4, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath(); ctx.arc(-19, -5, 2, 0, Math.PI * 2); ctx.fill();
}
ctx.restore();
}
row++;
}
return canvas;
}
Apply Changes