You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Creates a 3D product packaging mockup from an image.
*
* This function takes an image and projects it onto the front face of a 3D box.
* The box's perspective, color, and other attributes can be customized.
*
* @param {Image} originalImg The original JavaScript Image object to use for the packaging.
* @param {string} boxColor The base color of the box in HEX format (e.g., '#ffffff').
* @param {number} angle The angle of the box's perspective in degrees (0-90).
* @param {number} depthRatio The depth of the box as a ratio of its width (e.g., 0.5 for a depth half the width).
* @param {number} scale The scale of the image on the front face of the box (0-1).
* @param {number} shadowOpacity The opacity of the drop shadow (0-1). 0 to disable.
* @returns {HTMLCanvasElement} A canvas element containing the product packaging mockup.
*/
async function processImage(originalImg, boxColor = '#ffffff', angle = 30, depthRatio = 0.5, scale = 0.9, shadowOpacity = 0.3) {
/**
* Internal helper to lighten or darken a HEX color.
* @param {string} color The HEX color string.
* @param {number} percent A value from -1 (black) to 1 (white).
* @returns {string} The new shaded HEX color string.
*/
const shadeColor = (color, percent) => {
let R = parseInt(color.substring(1, 3), 16);
let G = parseInt(color.substring(3, 5), 16);
let B = parseInt(color.substring(5, 7), 16);
R = parseInt(R * (1.0 + percent));
G = parseInt(G * (1.0 + percent));
B = parseInt(B * (1.0 + percent));
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
R = (R > 0) ? R : 0;
G = (G > 0) ? G : 0;
B = (B > 0) ? B : 0;
const RR = ((R.toString(16).length === 1) ? "0" + R.toString(16) : R.toString(16));
const GG = ((G.toString(16).length === 1) ? "0" + G.toString(16) : G.toString(16));
const BB = ((B.toString(16).length === 1) ? "0" + B.toString(16) : B.toString(16));
return "#" + RR + GG + BB;
};
const imgW = originalImg.width;
const imgH = originalImg.height;
const rad = parseFloat(angle) * Math.PI / 180;
const boxDepth = imgW * parseFloat(depthRatio);
const dx = boxDepth * Math.cos(rad);
const dy = boxDepth * Math.sin(rad);
// Define the 8 vertices of the box in projected 2D space
const points = {
p0: { x: 0, y: 0 }, // front top left
p1: { x: imgW, y: 0 }, // front top right
p2: { x: imgW, y: imgH }, // front bottom right
p3: { x: 0, y: imgH }, // front bottom left
p4: { x: dx, y: -dy }, // back top left
p5: { x: imgW + dx, y: -dy }, // back top right
p6: { x: imgW + dx, y: imgH - dy }, // back bottom right
p7: { x: dx, y: imgH - dy } // back bottom left
};
// Find bounding box to determine canvas size
const allPoints = Object.values(points);
const minX = Math.min(...allPoints.map(p => p.x));
const maxX = Math.max(...allPoints.map(p => p.x));
const minY = Math.min(...allPoints.map(p => p.y));
const maxY = Math.max(...allPoints.map(p => p.y));
const padding = 50;
const shadowBlur = 20;
const shadowOffsetY = 30;
const canvasWidth = maxX - minX + padding * 2;
const canvasHeight = maxY - minY + padding * 2 + shadowOffsetY + shadowBlur;
// Offset to center the box in the canvas
const offsetX = -minX + padding;
const offsetY = -minY + padding;
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
// Apply global translation to center the drawing
ctx.translate(offsetX, offsetY);
// --- Draw Shadow ---
if (shadowOpacity > 0) {
ctx.save();
ctx.beginPath();
// Shadow is the projection of the bottom face, slightly offset and blurred
ctx.moveTo(points.p3.x, points.p3.y + shadowOffsetY);
ctx.lineTo(points.p2.x, points.p2.y + shadowOffsetY);
ctx.lineTo(points.p6.x, points.p6.y + shadowOffsetY);
ctx.lineTo(points.p7.x, points.p7.y + shadowOffsetY);
ctx.closePath();
ctx.fillStyle = `rgba(0, 0, 0, ${shadowOpacity})`;
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = shadowBlur;
ctx.shadowOffsetY = 5;
ctx.fill();
ctx.restore();
}
// --- Draw Box Faces (from back to front) ---
// Right side face (darker)
ctx.fillStyle = shadeColor(boxColor, -0.20);
ctx.beginPath();
ctx.moveTo(points.p1.x, points.p1.y);
ctx.lineTo(points.p5.x, points.p5.y);
ctx.lineTo(points.p6.x, points.p6.y);
ctx.lineTo(points.p2.x, points.p2.y);
ctx.closePath();
ctx.fill();
// Top face (lighter)
ctx.fillStyle = shadeColor(boxColor, 0.10);
ctx.beginPath();
ctx.moveTo(points.p0.x, points.p0.y);
ctx.lineTo(points.p4.x, points.p4.y);
ctx.lineTo(points.p5.x, points.p5.y);
ctx.lineTo(points.p1.x, points.p1.y);
ctx.closePath();
ctx.fill();
// Front face (base color and image)
ctx.fillStyle = boxColor;
ctx.fillRect(points.p0.x, points.p0.y, imgW, imgH);
// Draw the product image on the front face
const s = parseFloat(scale);
const scaledW = imgW * s;
const scaledH = imgH * s;
const imgX = (imgW - scaledW) / 2;
const imgY = (imgH - scaledH) / 2;
ctx.drawImage(originalImg, imgX, imgY, scaledW, scaledH);
// --- Draw Outlines for definition ---
ctx.strokeStyle = shadeColor(boxColor, -0.40);
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
// Right face outline
ctx.beginPath();
ctx.moveTo(points.p1.x, points.p1.y);
ctx.lineTo(points.p5.x, points.p5.y);
ctx.lineTo(points.p6.x, points.p6.y);
ctx.lineTo(points.p2.x, points.p2.y);
ctx.closePath();
ctx.stroke();
// Top face outline
ctx.beginPath();
ctx.moveTo(points.p0.x, points.p0.y);
ctx.lineTo(points.p4.x, points.p4.y);
ctx.lineTo(points.p5.x, points.p5.y);
ctx.lineTo(points.p1.x, points.p1.y);
ctx.closePath();
ctx.stroke();
// Front face outline
ctx.strokeRect(points.p0.x, points.p0.y, imgW, imgH);
return canvas;
}
Apply Changes