Image
IMAGE RESIZER
Resize images by pixels or percentage in your browser. No upload, no server. Aspect ratio lock, presets, live preview.
Drop image here
or click to upload
or click to upload
Upload an image to preview
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
// ── State ────────────────────────────────────────────────────────────────
let origImg = null; // HTMLImageElement
let origW = 0;
let origH = 0;
let origSize = 0; // bytes
let origType = '';
let mode = 'pixels'; // 'pixels' | 'percent'
let format = 'jpeg';
let quality = 92;
let fitMode = 'stretch'; // 'stretch' | 'contain' | 'cover'
let locked = true;
let bgColor = '#000000';
let debounceTimer = null;
let currentBlobURL = null;
let currentResultW = 0;
let currentResultH = 0;
// ── Upload handling ──────────────────────────────────────────────────────
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
uploadZone.addEventListener('dragover', e => { e.preventDefault(); uploadZone.classList.add('drag-over'); });
uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('drag-over'));
uploadZone.addEventListener('drop', e => {
e.preventDefault();
uploadZone.classList.remove('drag-over');
const f = e.dataTransfer.files[0];
if (f && f.type.startsWith('image/')) loadFile(f);
});
fileInput.addEventListener('change', () => {
if (fileInput.files[0]) loadFile(fileInput.files[0]);
});
function loadFile(file) {
origSize = file.size;
origType = file.type;
const reader = new FileReader();
reader.onload = e => {
const img = new Image();
img.onload = () => {
origImg = img;
origW = img.naturalWidth;
origH = img.naturalHeight;
onImageLoaded();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
function onImageLoaded() {
// Update info pills
document.getElementById('origDimsText').textContent = origW + ' × ' + origH + ' px';
document.getElementById('origSizeText').textContent = fmtBytes(origSize);
document.getElementById('origTypeText').textContent = origType.replace('image/', '').toUpperCase();
document.getElementById('imgInfo').classList.remove('hidden');
// Show controls
const cb = document.getElementById('controlsBody');
cb.classList.remove('hidden');
cb.style.display = 'flex';
// Set default dimensions
document.getElementById('inputW').value = origW;
document.getElementById('inputH').value = origH;
document.getElementById('inputPct').value = 100;
updatePctResult();
clearErrors();
scheduleRender();
}
// ── Mode ─────────────────────────────────────────────────────────────────
function setMode(m) {
mode = m;
document.getElementById('tabPixels').classList.toggle('active', m === 'pixels');
document.getElementById('tabPercent').classList.toggle('active', m === 'percent');
document.getElementById('pixelsMode').classList.toggle('hidden', m !== 'pixels');
document.getElementById('percentMode').classList.toggle('hidden', m !== 'percent');
updateFitVisibility();
scheduleRender();
}
// ── Lock ─────────────────────────────────────────────────────────────────
function toggleLock() {
locked = !locked;
const btn = document.getElementById('lockBtn');
btn.classList.toggle('locked', locked);
btn.title = locked ? 'Aspect ratio locked' : 'Aspect ratio unlocked';
if (locked && origW) {
// sync height to current width
const w = parseInt(document.getElementById('inputW').value) || origW;
document.getElementById('inputH').value = Math.round(w * (origH / origW));
}
updateFitVisibility();
scheduleRender();
}
function updateFitVisibility() {
const show = (mode === 'pixels' && !locked);
document.getElementById('fitModeRow').classList.toggle('hidden', !show);
if (!show) {
// hide bg color too
document.getElementById('bgColorRow').classList.add('hidden');
} else {
document.getElementById('bgColorRow').classList.toggle('hidden', fitMode !== 'contain');
}
}
// ── Pixel inputs ─────────────────────────────────────────────────────────
function onWidthInput() {
if (!origImg) return;
const w = parseInt(document.getElementById('inputW').value);
if (locked && w && origW) {
document.getElementById('inputH').value = Math.round(w * (origH / origW));
}
scheduleRender();
}
function onHeightInput() {
if (!origImg) return;
const h = parseInt(document.getElementById('inputH').value);
if (locked && h && origH) {
document.getElementById('inputW').value = Math.round(h * (origW / origH));
}
scheduleRender();
}
// ── Percentage input ──────────────────────────────────────────────────────
function onPctInput() {
updatePctResult();
scheduleRender();
}
function updatePctResult() {
if (!origImg) return;
const pct = parseFloat(document.getElementById('inputPct').value) || 100;
const rw = Math.round(origW * pct / 100);
const rh = Math.round(origH * pct / 100);
document.getElementById('pctResult').innerHTML =
'Result: <span>' + rw + ' × ' + rh + ' px</span>';
}
// ── Format ────────────────────────────────────────────────────────────────
function setFormat(f) {
format = f;
['jpeg','png','webp'].forEach(x => {
document.getElementById('fmt' + x.charAt(0).toUpperCase() + x.slice(1))
.classList.toggle('active', x === f);
});
// Quality only relevant for jpeg / webp
const showQ = (f === 'jpeg' || f === 'webp');
document.getElementById('qualityRow').classList.toggle('hidden', !showQ);
scheduleRender();
}
// ── Quality ───────────────────────────────────────────────────────────────
function onQualityChange() {
quality = parseInt(document.getElementById('qualitySlider').value);
document.getElementById('qualityVal').textContent = quality;
scheduleRender();
}
// ── Fit mode ──────────────────────────────────────────────────────────────
function setFit(f) {
fitMode = f;
['stretch','contain','cover'].forEach(x => {
document.getElementById('fit' + x.charAt(0).toUpperCase() + x.slice(1))
.classList.toggle('active', x === f);
});
document.getElementById('bgColorRow').classList.toggle('hidden', f !== 'contain');
scheduleRender();
}
// ── Background color ──────────────────────────────────────────────────────
function onBgColorChange() {
bgColor = document.getElementById('bgColorPicker').value;
document.getElementById('bgColorText').value = bgColor;
scheduleRender();
}
function onBgColorText() {
const v = document.getElementById('bgColorText').value.trim();
if (/^#[0-9a-fA-F]{6}$/.test(v)) {
bgColor = v;
document.getElementById('bgColorPicker').value = v;
scheduleRender();
}
}
// ── Presets ───────────────────────────────────────────────────────────────
function applyPreset(w, h) {
if (!origImg) return;
setMode('pixels');
locked = false;
document.getElementById('lockBtn').classList.remove('locked');
document.getElementById('lockBtn').title = 'Aspect ratio unlocked';
updateFitVisibility();
document.getElementById('inputW').value = w;
document.getElementById('inputH').value = h;
clearErrors();
scheduleRender();
}
// ── Validation ────────────────────────────────────────────────────────────
function clearErrors() {
document.getElementById('inputW').classList.remove('input-error');
document.getElementById('inputH').classList.remove('input-error');
document.getElementById('dimError').classList.add('hidden');
}
function validateDims(w, h) {
const errs = [];
if (!w || w < 1) errs.push('Width must be at least 1 px.');
if (!h || h < 1) errs.push('Height must be at least 1 px.');
if (w > 8000) errs.push('Width exceeds 8000 px max.');
if (h > 8000) errs.push('Height exceeds 8000 px max.');
if (errs.length) {
const el = document.getElementById('dimError');
el.textContent = errs.join(' ');
el.classList.remove('hidden');
if (!w || w < 1 || w > 8000) document.getElementById('inputW').classList.add('input-error');
if (!h || h < 1 || h > 8000) document.getElementById('inputH').classList.add('input-error');
return false;
}
clearErrors();
return true;
}
// ── Debounced render ──────────────────────────────────────────────────────
function scheduleRender() {
if (!origImg) return;
clearTimeout(debounceTimer);
showSpinner(true);
debounceTimer = setTimeout(renderPreview, 400);
}
function showSpinner(on) {
document.getElementById('previewSpinner').classList.toggle('hidden', !on);
}
// ── Canvas render ─────────────────────────────────────────────────────────
function getTargetDims() {
if (mode === 'percent') {
const pct = parseFloat(document.getElementById('inputPct').value) || 100;
return {
w: Math.max(1, Math.round(origW * pct / 100)),
h: Math.max(1, Math.round(origH * pct / 100))
};
} else {
return {
w: parseInt(document.getElementById('inputW').value) || 0,
h: parseInt(document.getElementById('inputH').value) || 0
};
}
}
function renderPreview() {
if (!origImg) return;
const { w, h } = getTargetDims();
if (mode === 'pixels' && !validateDims(w, h)) {
showSpinner(false);
return;
}
const canvas = document.getElementById('workCanvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, w, h);
const effectiveFit = (mode === 'pixels' && !locked) ? fitMode : 'stretch';
if (effectiveFit === 'stretch') {
ctx.drawImage(origImg, 0, 0, w, h);
} else if (effectiveFit === 'contain') {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, w, h);
const scale = Math.min(w / origW, h / origH);
const dw = origW * scale;
const dh = origH * scale;
const dx = (w - dw) / 2;
const dy = (h - dh) / 2;
ctx.drawImage(origImg, dx, dy, dw, dh);
} else if (effectiveFit === 'cover') {
const scale = Math.max(w / origW, h / origH);
const sw = w / scale;
const sh = h / scale;
const sx = (origW - sw) / 2;
const sy = (origH - sh) / 2;
ctx.drawImage(origImg, sx, sy, sw, sh, 0, 0, w, h);
}
const mimeType = 'image/' + format;
const q = (format === 'jpeg' || format === 'webp') ? quality / 100 : undefined;
canvas.toBlob(blob => {
if (!blob) { showSpinner(false); return; }
// Revoke old URL
if (currentBlobURL) URL.revokeObjectURL(currentBlobURL);
currentBlobURL = URL.createObjectURL(blob);
currentResultW = w;
currentResultH = h;
const img = document.getElementById('previewImg');
img.src = currentBlobURL;
img.classList.remove('hidden');
document.getElementById('previewPlaceholder').classList.add('hidden');
// Meta
document.getElementById('previewMeta').classList.remove('hidden');
document.getElementById('previewDims').innerHTML =
'<span>' + w + ' × ' + h + ' px</span> · ' + fmtBytes(blob.size);
// Upscale warning
const isUpscaled = (w > origW || h > origH);
document.getElementById('upscaleWarning').classList.toggle('hidden', !isUpscaled);
// Download button
updateDownloadBtn(w, h);
document.getElementById('downloadBtn').classList.remove('hidden');
showSpinner(false);
}, mimeType, q);
}
// ── Download ──────────────────────────────────────────────────────────────
function updateDownloadBtn(w, h) {
const ext = format === 'jpeg' ? 'jpg' : format;
document.getElementById('downloadBtn').textContent =
'Download ' + format.toUpperCase() + ' — ' + w + ' × ' + h;
}
function doDownload() {
if (!currentBlobURL) return;
const ext = format === 'jpeg' ? 'jpg' : format;
const a = document.createElement('a');
a.href = currentBlobURL;
a.download = 'toolpad-resized-' + currentResultW + 'x' + currentResultH + '.' + ext;
a.click();
}
// ── Utilities ─────────────────────────────────────────────────────────────
function fmtBytes(n) {
if (n < 1024) return n + ' B';
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
return (n / (1024 * 1024)).toFixed(2) + ' MB';
}