You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, width = 800, height = 600, moonColor = '#ffedb3', skyDark = '#060a14', skyLight = '#1b233a') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use originalImg dimensions if available and valid, otherwise fallback
const cw = (originalImg && originalImg.width > 0) ? originalImg.width : Number(width) || 800;
const ch = (originalImg && originalImg.height > 0) ? originalImg.height : Number(height) || 600;
canvas.width = cw;
canvas.height = ch;
// 1. Draw base sky (Original image used as base texture if provided)
if (originalImg && originalImg.complete && originalImg.width > 0) {
ctx.drawImage(originalImg, 0, 0, cw, ch);
ctx.globalCompositeOperation = 'multiply';
} else {
ctx.fillStyle = skyLight;
ctx.fillRect(0, 0, cw, ch);
}
// Overlay dark sky gradient
const skyG = ctx.createLinearGradient(0, 0, 0, ch);
skyG.addColorStop(0, skyDark);
skyG.addColorStop(1, skyLight);
ctx.fillStyle = skyG;
ctx.fillRect(0, 0, cw, ch);
ctx.globalCompositeOperation = 'source-over';
// 2. Map scattered stars
ctx.fillStyle = '#ffffff';
const starCount = (cw * ch) / 3000;
for (let i = 0; i < starCount; i++) {
let sx = Math.random() * cw;
let sy = Math.random() * ch * 0.7;
let sr = Math.random() * 1.5;
ctx.globalAlpha = Math.random() * 0.8 + 0.2;
ctx.beginPath();
ctx.arc(sx, sy, sr, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1.0;
// 3. Shooting stars
for (let i = 0; i < 3; i++) {
let stx = Math.random() * cw;
let sty = Math.random() * ch * 0.4;
let len = 40 + Math.random() * 60;
let gradST = ctx.createLinearGradient(stx, sty, stx - len, sty + len * 0.5);
gradST.addColorStop(0, 'rgba(255,255,255,0.8)');
gradST.addColorStop(1, 'rgba(255,255,255,0)');
ctx.beginPath();
ctx.moveTo(stx, sty);
ctx.lineTo(stx - len, sty + len * 0.5);
ctx.lineWidth = 1.5;
ctx.strokeStyle = gradST;
ctx.stroke();
}
// 4. Glowing Moon
const mx = cw * 0.65;
const my = ch * 0.35;
const mr = ch * 0.12;
ctx.shadowBlur = Math.max(30, cw * 0.05);
ctx.shadowColor = moonColor;
const mGrad = ctx.createRadialGradient(mx - mr * 0.2, my - mr * 0.2, mr * 0.1, mx, my, mr);
mGrad.addColorStop(0, '#ffffff');
mGrad.addColorStop(0.7, moonColor);
mGrad.addColorStop(1, '#e3b878');
ctx.beginPath();
ctx.arc(mx, my, mr, 0, Math.PI * 2);
ctx.fillStyle = mGrad;
ctx.fill();
ctx.shadowBlur = 0; // reset shadow
// Craters on the moon (subtle details)
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.beginPath(); ctx.arc(mx - mr * 0.3, my + mr * 0.2, mr * 0.2, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(mx + mr * 0.4, my - mr * 0.1, mr * 0.25, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(mx + mr * 0.1, my + mr * 0.4, mr * 0.15, 0, Math.PI * 2); ctx.fill();
// Nighttime mist crossing the moon
ctx.fillStyle = 'rgba(200, 210, 220, 0.08)';
ctx.beginPath();
ctx.ellipse(mx, my + mr * 0.2, mr * 1.5, mr * 0.2, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(mx - mr * 0.5, my + mr * 0.5, mr * 1.8, mr * 0.15, -0.1, 0, Math.PI * 2);
ctx.fill();
// Function to draw rugged parallax mountains
function drawJaggedMountains(numPoints, color, yBase, heightVar) {
ctx.fillStyle = color;
ctx.beginPath();
let pts = [];
for (let i = 0; i <= numPoints; i++) {
pts.push({
x: (cw / numPoints) * i,
y: yBase - Math.random() * heightVar
});
}
ctx.moveTo(0, ch);
ctx.lineTo(0, pts[0].y);
for (let i = 0; i < pts.length - 1; i++) {
let p1 = pts[i], p2 = pts[i + 1];
let segments = 4;
for (let j = 1; j <= segments; j++) {
let nx = p1.x + (p2.x - p1.x) * (j / segments);
let ny = p1.y + (p2.y - p1.y) * (j / segments);
if (j !== segments) ny += (Math.random() * heightVar * 0.3) - (heightVar * 0.15);
ctx.lineTo(nx, ny);
}
}
ctx.lineTo(cw, pts[pts.length - 1].y);
ctx.lineTo(cw, ch);
ctx.closePath();
ctx.fill();
}
// Function to layout scene mist
function addMist(yTop, colorTpl, opacity) {
let grad = ctx.createLinearGradient(0, yTop, 0, ch);
grad.addColorStop(0, colorTpl.replace('OP', '0'));
grad.addColorStop(1, colorTpl.replace('OP', opacity));
ctx.fillStyle = grad;
ctx.fillRect(0, yTop, cw, ch - yTop);
}
// 5. Build up parallax mountain ranges & valley mists
drawJaggedMountains(8, '#0b0f19', ch * 0.65, ch * 0.2);
addMist(ch * 0.5, 'rgba(150, 160, 180, OP)', '0.3');
drawJaggedMountains(6, '#06080d', ch * 0.75, ch * 0.15);
addMist(ch * 0.6, 'rgba(120, 130, 150, OP)', '0.35');
// 6. Foreground cliff base with rugged edges
ctx.fillStyle = '#010103';
ctx.beginPath();
ctx.moveTo(0, ch);
ctx.lineTo(0, ch * 0.5);
// Plateau structure (where the wolf stands)
ctx.lineTo(cw * 0.05, ch * 0.48);
ctx.lineTo(cw * 0.15, ch * 0.52);
ctx.lineTo(cw * 0.25, ch * 0.49);
ctx.lineTo(cw * 0.35, ch * 0.50); // Plateau ends here
ctx.lineTo(cw * 0.45, ch * 0.55);
ctx.lineTo(cw * 0.55, ch * 0.85); // Drop off
ctx.lineTo(cw * 0.65, ch);
ctx.closePath();
ctx.fill();
// 7. Render dynamic silhouettes of pine trees
function drawPineTree(x, y, tWidth, tHeight) {
ctx.fillStyle = '#010103';
ctx.beginPath();
let sections = 4;
ctx.moveTo(x, y - tHeight);
for (let i = 1; i <= sections; i++) {
let yy = y - tHeight + (tHeight / sections) * i;
let ww = (tWidth / 2) * (i / sections);
ctx.lineTo(x + ww, yy);
if (i < sections) ctx.lineTo(x + ww * 0.4, yy - tHeight * 0.1);
}
ctx.lineTo(x + tWidth * 0.1, y);
ctx.lineTo(x - tWidth * 0.1, y);
for (let i = sections; i >= 1; i--) {
let yy = y - tHeight + (tHeight / sections) * i;
let ww = (tWidth / 2) * (i / sections);
if (i < sections) ctx.lineTo(x - ww * 0.4, yy - tHeight * 0.1);
ctx.lineTo(x - ww, yy);
}
ctx.closePath();
ctx.fill();
}
drawPineTree(cw * 0.04, ch * 0.48, cw * 0.05, ch * 0.18);
drawPineTree(cw * 0.12, ch * 0.50, cw * 0.04, ch * 0.15);
drawPineTree(cw * 0.18, ch * 0.51, cw * 0.06, ch * 0.2);
drawPineTree(cw * 0.48, ch * 0.65, cw * 0.07, ch * 0.25);
drawPineTree(cw * 0.52, ch * 0.75, cw * 0.08, ch * 0.28);
drawPineTree(cw * 0.58, ch * 0.88, cw * 0.06, ch * 0.22);
// 8. Generate Highly Realistic Wolf Howling Silhouette
const wolfPoints = [
{x: 15, y: 5, fur: 0}, {x: 17, y: 8, fur: 0},
{x: 20, y: 10, fur: 0}, {x: 16, y: 12, fur: 0},
{x: 20, y: 25, fur: 2}, {x: 22, y: 40, fur: 3},
{x: 18, y: 80, fur: 0.5}, {x: 25, y: 80, fur: 0},
{x: 25, y: 55, fur: 0.5}, {x: 35, y: 55, fur: 3},
{x: 42, y: 60, fur: 1}, {x: 45, y: 80, fur: 0.5},
{x: 53, y: 80, fur: 0}, {x: 50, y: 65, fur: 0.5},
{x: 55, y: 45, fur: 2}, {x: 62, y: 55, fur: 3},
{x: 64, y: 65, fur: 4}, {x: 60, y: 75, fur: 4},
{x: 55, y: 60, fur: 4}, {x: 50, y: 40, fur: 2},
{x: 45, y: 35, fur: 2}, {x: 35, y: 28, fur: 2},
{x: 28, y: 18, fur: 2}, {x: 28, y: 12, fur: 1},
{x: 25, y: 5, fur: 0}, {x: 22, y: 12, fur: 0},
{x: 20, y: 13, fur: 0}, {x: 17, y: 8, fur: 0}
];
ctx.save();
// Position Wolf correctly aligned on the plateau edge
const plateauX = cw * 0.3;
const plateauY = ch * 0.495;
const wScale = (ch * 0.25) / 75;
ctx.translate(plateauX - (40 * wScale), plateauY - (80 * wScale));
ctx.scale(wScale, wScale);
ctx.fillStyle = '#010103';
ctx.beginPath();
ctx.moveTo(wolfPoints[0].x, wolfPoints[0].y);
for (let i = 0; i < wolfPoints.length; i++) {
let p1 = wolfPoints[i];
let p2 = wolfPoints[(i + 1) % wolfPoints.length];
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
let dist = Math.sqrt(dx * dx + dy * dy);
let segments = p1.fur > 0 ? Math.max(1, Math.floor(dist / 2)) : 1;
for (let j = 1; j <= segments; j++) {
let nx = p1.x + dx * (j / segments);
let ny = p1.y + dy * (j / segments);
// Generate fuzzy fur edges
if (j !== segments && p1.fur > 0) {
let px = -dy / dist;
let py = dx / dist;
let variance = (Math.random() * p1.fur) - (p1.fur / 2);
nx += px * variance;
ny += py * variance;
}
ctx.lineTo(nx, ny);
}
}
ctx.closePath();
ctx.fill();
ctx.restore();
// 9. Foremost ground fog overlapping cliff structure
addMist(ch * 0.65, 'rgba(70, 80, 95, OP)', '0.5');
// 10. Frame Vignette to increase dramatic focal realism
const vig = ctx.createRadialGradient(cw / 2, ch / 2, cw * 0.25, cw / 2, ch / 2, Math.max(cw, ch) * 0.85);
vig.addColorStop(0, 'rgba(0,0,0,0)');
vig.addColorStop(1, 'rgba(0,0,0,0.8)');
ctx.fillStyle = vig;
ctx.fillRect(0, 0, cw, ch);
return canvas;
}
Apply Changes