drawImage(img, dx, dy) draws at natural size, (img, dx, dy, dw, dh) scales, and (img, sx, sy, sw, sh, dx, dy, dw, dh) crops a source rectangle into a target one, as sprite sheets need. Wait for img.decode() first. Scaling is smoothed unless you set imageSmoothingEnabled = false.
<canvas id="c" style="width:600px; height:96px"></canvas><script src="hidpi.js"></script>
<script>
const ctx = hidpi(c), img = Object.assign(new Image(), { src: 'booknest-logo.png' });
img.decode().then(() => {
ctx.drawImage(img, 0, 0, 300, 95); // whole image at half size
ctx.drawImage(img, 0, 0, 190, 190, 320, 0, 95, 95); // crop: source rect, target rect
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 90, 70, 24, 24, 440, 0, 96, 96); // a 24-pixel patch, four times
window.__done = true;
});
</script>
The logo is a 600 x 190 PNG of Transforms and the Logo's SVG; the patch shows its antialiased edges as hard squares.
<!doctype html>
<style>
body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
canvas { width: 100%; max-width: 600px; height: 150px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<label><input type="checkbox" id="smooth"> imageSmoothingEnabled for the zoomed patch</label>
<script>
function hidpi(c, r = devicePixelRatio) {
c.width = c.clientWidth * r; c.height = c.clientHeight * r;
const ctx = c.getContext('2d'); ctx.scale(r, r); return ctx;
}
const ctx = hidpi(document.getElementById('c'));
// A 600×190 BookNest logo, generated as an SVG data URL instead of fetching a PNG
const logo = `<svg xmlns="http://www.w3.org/2000/svg" width="600" height="190">
<rect width="600" height="190" fill="#fff3e0"/>
<g transform="translate(95 170)">
<rect x="-60" y="-150" width="26" height="110" fill="#1f5f8b" transform="rotate(-12)"/>
<rect x="-28" y="-160" width="26" height="120" fill="#e09a10"/>
<rect x="4" y="-140" width="26" height="100" fill="#3f7d3a" transform="rotate(10)"/>
<path d="M-80 -45 Q0 -20 80 -45 Q70 10 0 12 Q-70 10 -80 -45Z" fill="#263238"/>
</g>
<text x="200" y="118" font-family="Georgia" font-weight="bold" font-size="72" fill="#1f5f8b">BookNest</text>
</svg>`;
const img = new Image();
img.src = 'data:image/svg+xml,' + encodeURIComponent(logo);
function draw(smooth) {
ctx.clearRect(0, 0, 600, 150);
ctx.imageSmoothingEnabled = true;
ctx.drawImage(img, 0, 0, 300, 95); // (img, dx, dy, dw, dh): scaled to half
ctx.drawImage(img, 0, 0, 190, 190, 320, 0, 95, 95); // (img, sx, sy, sw, sh, dx, dy, dw, dh): crop
ctx.imageSmoothingEnabled = smooth;
ctx.drawImage(img, 196, 96, 24, 24, 440, 0, 144, 144); // a 24-pixel patch of the B, six times larger
ctx.font = '11px sans-serif'; ctx.fillStyle = '#263238';
ctx.fillText('scaled 600×190 → 300×95', 10, 115);
ctx.fillText('cropped 190×190 → 95×95', 300, 115);
ctx.strokeStyle = '#b5452f'; ctx.strokeRect(440, 0, 144, 144);
}
// Wait for decode() before drawing, or nothing appears
img.decode().then(() => draw(false));
document.getElementById('smooth').addEventListener('change', e => draw(e.target.checked));
</script>