A powerful web tool to watermark, rename, resize, and convert images in bulk with drag-and-drop. Features include watermark customization, multiple resize modes, quality control, preview, progress bar, and ZIP download with confirmation.
Watermarking images protects your intellectual property, promotes your brand, and adds a professional touch to your visuals. This tool streamlines bulk processing for efficiency.
Drag and drop your images here or click to select files.
Built with HTML5 Canvas, JavaScript, and JSZip, this tool processes images client-side. It includes advanced resize algorithms, file size optimization, quality control for JPEG/WebP, and a download confirmation modal.
// Helper functions
function 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)
} : { r: 255, g: 255, b: 255 };
}
function getWatermarkPosition(position, width, height, padding) {
let x, y, align, baseline;
switch (position) {
case 'top-left': align = 'left'; baseline = 'top'; x = padding; y = padding; break;
case 'top-center': align = 'center'; baseline = 'top'; x = width / 2; y = padding; break;
case 'top-right': align = 'right'; baseline = 'top'; x = width - padding; y = padding; break;
case 'middle-left': align = 'left'; baseline = 'middle'; x = padding; y = height / 2; break;
case 'middle-center': align = 'center'; baseline = 'middle'; x = width / 2; y = height / 2; break;
case 'middle-right': align = 'right'; baseline = 'middle'; x = width - padding; y = height / 2; break;
case 'bottom-left': align = 'left'; baseline = 'bottom'; x = padding; y = height - padding; break;
case 'bottom-center': align = 'center'; baseline = 'bottom'; x = width / 2; y = height - padding; break;
case 'bottom-right': align = 'right'; baseline = 'bottom'; x = width - padding; y = height - padding; break;
}
return { x, y, align, baseline };
}
async function adjustToFileSize(img, targetSizeKB, format, watermarkText, watermarkParams, quality, maxIterations = 10) {
let adjustedQuality = quality;
let step = 0.1;
let iterations = 0;
let blob;
const { position, opacity, rgb, fontSize } = watermarkParams;
while (iterations < maxIterations) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
if (watermarkText) {
ctx.font = \`${fontSize}px Arial\`;
ctx.fillStyle = \`rgba(\${rgb.r}, \${rgb.g}, \${rgb.b}, \${opacity})\`;
const pos = getWatermarkPosition(position, canvas.width, canvas.height, 20);
ctx.textAlign = pos.align;
ctx.textBaseline = pos.baseline;
ctx.fillText(watermarkText, pos.x, pos.y);
}
blob = await new Promise(resolve => canvas.toBlob(resolve, format, adjustedQuality));
if (!blob) throw new Error('Failed to create blob');
const sizeKB = blob.size / 1024;
if (Math.abs(sizeKB - targetSizeKB) < 10 || iterations === maxIterations - 1) break;
if (sizeKB > targetSizeKB) adjustedQuality -= step;
else adjustedQuality += step;
step /= 2;
iterations++;
}
return blob;
}
// Core logic in updatePreview and processImages (see script below)






