You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies various styles or frames an image in a browser mockup.
* Interprets "Image Style or Website Changer" by providing two modes:
* 1. "Style" Changer: Applies filters like grayscale, sepia, vintage, or maps image colors to a custom palette.
* 2. "Website" Changer: Places the image inside a decorative, realistic browser window mockup.
*
* @param {Image} originalImg The original image object to process.
* @param {string} [style='browser-mockup'] The style to apply. Can be 'browser-mockup', 'grayscale', 'sepia', 'invert', 'vintage', 'palette-map'.
* @param {string} [option1] First option, its meaning depends on the selected style.
* - For 'browser-mockup': theme ('light' or 'dark'). Default: 'light'.
* - For 'palette-map': A comma-separated list of hex colors (e.g., '#ff0000,#00ff00,#0000ff'). Default: '#e63946,#f1faee,#a8dadc,#457b9d,#1d3557'.
* @param {string} [option2] Second option, for 'browser-mockup' style only. The URL to display in the address bar. Default: 'https://example.com'.
* @param {string} [option3] Third option, for 'browser-mockup' style only. This parameter is not used in the current implementation but is reserved for future features like a tab title.
* @returns {HTMLCanvasElement} A canvas element containing the processed image.
*/
function processImage(originalImg, style = 'browser-mockup', option1, option2, option3) {
const canvas = document.createElement('canvas');
// Add willReadFrequently hint for performance in palette-map mode
const ctx = canvas.getContext('2d', { willReadFrequently: style === 'palette-map' });
// Helper to draw rounded rectangles, defined once for use in multiple cases.
const roundRect = (x, y, w, h, r) => {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
};
switch (style) {
case 'browser-mockup': {
const theme = option1 === undefined ? 'light' : option1;
const url = option2 === undefined ? 'https://example.com' : option2;
// --- Configuration ---
const PADDING = 40;
const HEADER_HEIGHT = 40;
const BORDER_RADIUS = 10;
const SHADOW_BLUR = 30;
const SHADOW_OFFSET_Y = 10;
const FONT_SIZE = 13;
const FONT_FAMILY = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
// --- Colors based on theme ---
const colors = theme === 'dark' ? {
header: '#3c3c3c',
body: '#2e2e2e',
urlBg: '#505050',
text: '#e0e0e0',
shadow: 'rgba(0, 0, 0, 0.6)',
} : {
header: '#e8e8e8',
body: '#ffffff',
urlBg: '#ffffff',
text: '#444444',
shadow: 'rgba(0, 0, 0, 0.2)',
};
const btnColors = { red: '#ff5f57', yellow: '#febc2e', green: '#28c840' };
// --- Canvas and element dimensions ---
const { naturalWidth: imgWidth, naturalHeight: imgHeight } = originalImg;
const frameWidth = imgWidth;
const frameHeight = imgHeight + HEADER_HEIGHT;
canvas.width = frameWidth + PADDING * 2;
canvas.height = frameHeight + PADDING * 2;
const frameX = PADDING;
const frameY = PADDING;
// --- Drawing ---
ctx.translate(0.5, 0.5); // For crisp lines
// 1. Shadow
ctx.shadowColor = colors.shadow;
ctx.shadowBlur = SHADOW_BLUR;
ctx.shadowOffsetY = SHADOW_OFFSET_Y;
// 2. Main frame body (this provides the rounded corners)
roundRect(frameX, frameY, frameWidth, frameHeight, BORDER_RADIUS);
ctx.fillStyle = colors.body;
ctx.fill();
// Reset shadow for inner elements
ctx.shadowColor = 'transparent';
// 3. Header
ctx.fillStyle = colors.header;
ctx.beginPath();
ctx.moveTo(frameX, frameY + HEADER_HEIGHT);
ctx.lineTo(frameX, frameY + BORDER_RADIUS);
ctx.arcTo(frameX, frameY, frameX + BORDER_RADIUS, frameY, BORDER_RADIUS);
ctx.lineTo(frameX + frameWidth - BORDER_RADIUS, frameY);
ctx.arcTo(frameX + frameWidth, frameY, frameX + frameWidth, frameY + BORDER_RADIUS, BORDER_RADIUS);
ctx.lineTo(frameX + frameWidth, frameY + HEADER_HEIGHT);
ctx.closePath();
ctx.fill();
// 4. Traffic light buttons
const btnRadius = 6;
const btnY = frameY + HEADER_HEIGHT / 2;
ctx.fillStyle = btnColors.red;
ctx.beginPath();
ctx.arc(frameX + 20, btnY, btnRadius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = btnColors.yellow;
ctx.beginPath();
ctx.arc(frameX + 45, btnY, btnRadius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = btnColors.green;
ctx.beginPath();
ctx.arc(frameX + 70, btnY, btnRadius, 0, Math.PI * 2);
ctx.fill();
// 5. Address Bar
const urlBarHeight = 24;
const urlBarY = frameY + (HEADER_HEIGHT - urlBarHeight) / 2;
const urlBarX = frameX + 95;
const urlBarWidth = frameWidth - 110;
ctx.fillStyle = colors.urlBg;
roundRect(urlBarX, urlBarY, urlBarWidth, urlBarHeight, 6);
ctx.fill();
ctx.font = `400 ${FONT_SIZE}px ${FONT_FAMILY}`;
ctx.fillStyle = colors.text;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(url, urlBarX + urlBarWidth / 2, urlBarY + urlBarHeight / 2 + 1, urlBarWidth - 20);
// 6. Draw the image content
ctx.drawImage(originalImg, frameX, frameY + HEADER_HEIGHT, imgWidth, imgHeight);
break;
}
case 'palette-map': {
const paletteStr = option1 === undefined ? '#e63946,#f1faee,#a8dadc,#457b9d,#1d3557' : option1;
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null;
};
const colorDistanceSq = (c1, c2) => Math.pow(c1.r - c2.r, 2) + Math.pow(c1.g - c2.g, 2) + Math.pow(c1.b - c2.b, 2);
const palette = paletteStr.split(',').map(hex => hexToRgb(hex.trim())).filter(Boolean);
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
if (palette.length === 0) break; // If palette is invalid, return original image.
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const pixelColor = { r: data[i], g: data[i + 1], b: data[i + 2] };
let closestColor = palette[0];
let minDistance = colorDistanceSq(pixelColor, closestColor);
for (let j = 1; j < palette.length; j++) {
const distance = colorDistanceSq(pixelColor, palette[j]);
if (distance < minDistance) {
minDistance = distance;
closestColor = palette[j];
}
}
data[i] = closestColor.r;
data[i + 1] = closestColor.g;
data[i + 2] = closestColor.b;
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'vintage':
case 'grayscale':
case 'sepia':
case 'invert': {
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
const filters = {
grayscale: 'grayscale(1)',
sepia: 'sepia(1)',
invert: 'invert(1)',
vintage: 'sepia(0.6) contrast(1.1) brightness(0.9) saturate(1.2)'
};
ctx.filter = filters[style];
ctx.drawImage(originalImg, 0, 0);
break;
}
default: {
// Fallback for unknown style: return the original image on the canvas
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
break;
}
}
return canvas;
}
Apply Changes