<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Zippic Blog]]></title><description><![CDATA[Zippic Blog]]></description><link>https://zippic.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Zippic Blog</title><link>https://zippic.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 19 Oct 2026 10:48:52 GMT</lastBuildDate><atom:link href="https://zippic.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Cropping Images in the Browser: Aspect-Ratio Math, Canvas, and Batch Presets Without Uploads]]></title><description><![CDATA[Cropping looks like the simplest image operation there is: pick a rectangle, keep the pixels inside it. In practice, most "why does my image look wrong?" bugs I see in front-end projects come from cro]]></description><link>https://zippic.hashnode.dev/cropping-images-in-the-browser-aspect-ratio-math-canvas-and-batch-presets-without-uploads</link><guid isPermaLink="true">https://zippic.hashnode.dev/cropping-images-in-the-browser-aspect-ratio-math-canvas-and-batch-presets-without-uploads</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[canvas]]></category><category><![CDATA[images]]></category><category><![CDATA[frontend]]></category><dc:creator><![CDATA[Zippic]]></dc:creator><pubDate>Sun, 18 Oct 2026 23:45:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad2488ee8a312a9efafafff/b9ff18d5-cf9f-4193-889f-3153786054dc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cropping looks like the simplest image operation there is: pick a rectangle, keep the pixels inside it. In practice, most "why does my image look wrong?" bugs I see in front-end projects come from cropping, not compression. A product photo gets stretched, an avatar is cut off at the forehead, or a 4:5 social post comes out at 1080×1349.</p>
<p>This post covers the math behind ratio-based cropping, how to do it with the Canvas API in a few lines, and the practical choices you face when cropping a whole batch to one size. At the end I'll show how I handle this in zippic, the in-browser image tool I build.</p>
<h2>Crop vs. resize vs. "fit"</h2>
<p>Three operations get mixed up all the time:</p>
<ul>
<li><strong>Crop</strong> removes pixels outside a rectangle. The aspect ratio can change; the remaining pixels are untouched.</li>
<li><strong>Resize</strong> keeps every pixel's content but changes the pixel grid (e.g. 4000×3000 → 1600×1200). The aspect ratio stays the same unless you stretch.</li>
<li><strong>Fit / fill</strong> combines the two: to turn a 4:3 photo into a 1:1 square at 1080×1080, you first <em>crop</em> to 1:1, then <em>resize</em> to 1080.</li>
</ul>
<p>If you skip the crop and just resize 4000×3000 straight to 1080×1080, you get the classic squashed image. Every "crop to a preset" feature is really crop-then-resize.</p>
<h2>The aspect-ratio math</h2>
<p>Say the source is <code>sw × sh</code> and the target ratio is <code>rw:rh</code>.</p>
<pre><code class="language-js">function centerCropRect(sw, sh, rw, rh) {
  const target = rw / rh;
  const source = sw / sh;
  let cw, ch;
  if (source &gt; target) {
    // source is wider than target: keep full height, trim the sides
    ch = sh;
    cw = Math.round(sh * target);
  } else {
    // source is taller (or equal): keep full width, trim top/bottom
    cw = sw;
    ch = Math.round(sw / target);
  }
  return { x: Math.round((sw - cw) / 2), y: Math.round((sh - ch) / 2), w: cw, h: ch };
}

centerCropRect(4000, 3000, 1, 1);  // { x: 500, y: 0, w: 3000, h: 3000 }
centerCropRect(4000, 3000, 16, 9); // { x: 0, y: 375, w: 4000, h: 2250 }
</code></pre>
<p>The crop rectangle is the largest one with the target ratio that fits inside the source. Centering it is a sensible default for batches; for single images you usually want the user to drag it.</p>
<p>Once you have the crop, you still need an <strong>output size</strong>. With a ratio alone (say 16:9) there are several reasonable ways to pick it:</p>
<ul>
<li><strong>By width</strong>: fix the output width (e.g. 1920) and derive the height (1080).</li>
<li><strong>By height</strong>: fix the height and derive the width.</li>
<li><strong>By the smaller or larger side</strong>: useful when a batch mixes portrait and landscape files and you care about, say, "the short side should be 1080".</li>
</ul>
<p>Rounding matters here. <code>1080 / (4/5)</code> is exactly 1350, but derived values like <code>1000 * 9 / 16 = 562.5</code> are not integers. Pick one rounding rule and apply it consistently, otherwise images in the same batch can differ by a pixel.</p>
<h2>Doing the crop with Canvas</h2>
<p>The nine-argument form of <code>drawImage</code> does crop and resize in one call:</p>
<pre><code class="language-js">async function cropToBlob(file, rect, outW, outH, type = 'image/jpeg', quality = 0.85) {
  const bitmap = await createImageBitmap(file);
  const canvas = new OffscreenCanvas(outW, outH);
  const ctx = canvas.getContext('2d');
  ctx.imageSmoothingQuality = 'high';
  ctx.drawImage(bitmap, rect.x, rect.y, rect.w, rect.h, 0, 0, outW, outH);
  bitmap.close();
  return canvas.convertToBlob({ type, quality });
}
</code></pre>
<p>A few things that bite people:</p>
<ol>
<li><strong>EXIF orientation.</strong> Phone photos are often stored sideways with an orientation flag. Current browsers apply that flag when decoding by default, but if you compute crop rectangles from the raw file's width and height you can end up cropping the wrong axis. Measure the decoded bitmap, not the file header.</li>
<li><strong>Memory.</strong> A 4000×3000 RGBA image is about 48 MB once decoded. Decoding a whole folder at once is how tabs crash. Process files one at a time (or a few at a time in a worker) and call <code>bitmap.close()</code>.</li>
<li><strong>Re-encoding is lossy.</strong> Cropping is lossless in theory, but exporting a JPEG or WebP re-encodes the result. Choose the quality on purpose instead of accepting whatever the default is.</li>
<li><strong>DPI is metadata, not pixels.</strong> A print shop that asks for "300 DPI" cares about pixel count relative to physical size. 4×6 inches at 300 DPI is 1200×1800 pixels. Canvas exports don't write a DPI value for you, so if a print workflow checks the header you need a tool that sets it.</li>
</ol>
<h2>Batch cropping: one configuration, many files</h2>
<p>The single-image case is a UI problem (draggable rectangle, rotate, flip). The batch case is a configuration problem. When I crop a folder of product shots or social images, I want to decide:</p>
<ul>
<li><strong>Fixed size or ratio?</strong> Fixed size ("every image 1200×1200") is what marketplaces and CMS templates usually want. Ratio ("every image 4:5, size derived") is better when sources vary a lot.</li>
<li><strong>Global or per-file?</strong> One setting for everything, with the option to override the odd file whose subject isn't centered.</li>
<li><strong>Output quality</strong>, so a 50-image batch doesn't silently balloon in size.</li>
</ul>
<h2>How zippic does it</h2>
<p>zippic is a free image toolkit that runs in the browser. Its crop tools work locally, so your files are never uploaded and there's no sign-up.</p>
<p><strong><a href="https://zippic.app/image-cropper?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=crop">Image Cropper</a></strong> (single image):</p>
<ul>
<li>Drag-to-crop, or turn on <strong>Fixed Size</strong> and type an exact width and height in pixels. The aspect-ratio lock keeps width and height proportional.</li>
<li>Rotate, flip horizontally, flip vertically, and reset.</li>
<li><strong>DPI</strong> options: 72, 96, 150, 300, 600 and 1200.</li>
<li>Output as JPG, WebP or AVIF, with a quality slider from 50% to 100% (75% by default). It accepts JPG, PNG and AVIF.</li>
</ul>
<p><img src="https://cdn.hashnode.com/uploads/covers/6ad2488ee8a312a9efafafff/38b96ae2-3ade-44f0-a61f-3f20fd531085.png" alt="zippic image cropper with Fixed Size and DPI controls" /></p>
<p><strong><a href="https://zippic.app/bulk-image-cropper?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=crop">Bulk Image Crop</a></strong> (many images, one configuration):</p>
<ul>
<li><strong>Fixed Size</strong> mode with presets, including Square 1:1 (1000×1000), Instagram Post (1080×1080), Instagram Story (1080×1920), Facebook Post (1200×630), Twitter Header (1500×500), LinkedIn Post (1200×627), YouTube Thumbnail (1280×720), print sizes (4×6, 5×7 and 8×10 inch, A4, A3), plus a custom width and height.</li>
<li><strong>Ratio Crop</strong> mode with 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, 5:4, 4:5, 21:9 or a custom ratio. A <strong>Size Calculation Method</strong> setting picks how the output size is derived: Auto, By Width, By Height, By Smaller Side, or By Larger Side. That's the same choice described above.</li>
<li><strong>Global Apply</strong>: switch it off to give one file its own settings.</li>
<li>A before/after preview per file, a quality slider (85% by default), and <strong>Download All</strong>.</li>
<li>The page recommends no more than about 50 images per batch to keep the browser responsive.</li>
</ul>
<p><img src="https://cdn.hashnode.com/uploads/covers/6ad2488ee8a312a9efafafff/1fcb4e90-7179-4d60-a54f-9fdcd3d743a4.png" alt="zippic bulk image crop configuration" /></p>
<h2>Quick checklist</h2>
<ul>
<li>Crop to the target <strong>ratio</strong> first, then resize. Never stretch.</li>
<li>Measure the <strong>decoded</strong> image, not the file header, before computing rectangles.</li>
<li>Pick one <strong>rounding</strong> rule for derived sizes in a batch.</li>
<li>Process big batches <strong>sequentially</strong> and free bitmaps as you go.</li>
<li>Set <strong>quality</strong> deliberately when re-encoding.</li>
<li>For print, think <strong>pixels = inches × DPI</strong>.</li>
</ul>
<h2>FAQ</h2>
<p><strong>Is cropping in the browser slower than on a server?</strong>
For typical photos, decode, crop and encode take far less time than uploading and downloading the file. The limit is device memory, which is why batches should be processed in chunks.</p>
<p><strong>Does cropping reduce quality?</strong>
The kept pixels aren't changed, but saving as JPG, WebP or AVIF re-encodes them. Use a higher quality setting for images you'll edit again.</p>
<p><strong>Why does my batch have different sizes?</strong>
You're probably using ratio mode with mixed portrait and landscape sources. Use fixed size, or derive the size from a consistent side.</p>
<hr />
<p>I'm the developer of zippic. If you try the cropper or bulk crop on real-world files and something behaves unexpectedly, I'd really like to hear about it in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[WebP vs AVIF vs JPEG in 2026: Which Image Format Should You Use (and How to Convert Without Uploading Anything)]]></title><description><![CDATA[Images are still the heaviest part of most web pages. A blog post with a hero image and a few screenshots can easily weigh more than all of its HTML, CSS and JavaScript combined. Choosing the right fo]]></description><link>https://zippic.hashnode.dev/webp-vs-avif-vs-jpeg-in-2026-which-image-format-should-you-use-and-how-to-convert-without-uploading-anything</link><guid isPermaLink="true">https://zippic.hashnode.dev/webp-vs-avif-vs-jpeg-in-2026-which-image-format-should-you-use-and-how-to-convert-without-uploading-anything</guid><category><![CDATA[webdev]]></category><category><![CDATA[performance]]></category><category><![CDATA[images]]></category><category><![CDATA[webp]]></category><category><![CDATA[AVIF]]></category><dc:creator><![CDATA[Zippic]]></dc:creator><pubDate>Sat, 17 Oct 2026 06:51:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad2488ee8a312a9efafafff/319c744d-48a5-4ef1-a15a-70d1b6366224.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Images are still the heaviest part of most web pages. A blog post with a hero image and a few screenshots can easily weigh more than all of its HTML, CSS and JavaScript combined. Choosing the right format and compressing sensibly is one of the cheapest performance wins you can get, and it directly affects metrics like Largest Contentful Paint.</p>
<p>The good news: in 2026 the format question is much simpler than it was a few years ago. WebP and AVIF are both supported by every major modern browser, so the old "but Safari doesn't support it" worry is mostly gone. The question now is less "can I use it?" and more "which one fits this image?"</p>
<p>This guide compares JPEG, WebP and AVIF (plus a quick word on PNG and GIF), explains when to use each, and walks through a practical workflow for converting and compressing your site's images, including how to do it entirely in your browser without uploading files to anyone's server.</p>
<h2>The quick comparison</h2>
<table>
<thead>
<tr>
<th></th>
<th>JPEG</th>
<th>WebP</th>
<th>AVIF</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Compression (lossy)</strong></td>
<td>Baseline; decent, but the oldest</td>
<td>Typically noticeably smaller than JPEG at similar visual quality</td>
<td>Usually the smallest of the three at similar visual quality, especially at lower bitrates</td>
</tr>
<tr>
<td><strong>Lossless mode</strong></td>
<td>No</td>
<td>Yes (often beats PNG)</td>
<td>Yes, though not always smaller than WebP lossless</td>
</tr>
<tr>
<td><strong>Transparency (alpha)</strong></td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td><strong>Animation</strong></td>
<td>No</td>
<td>Yes (a good GIF replacement)</td>
<td>Yes (supported, but tooling is less mature)</td>
</tr>
<tr>
<td><strong>Browser support</strong></td>
<td>Universal</td>
<td>All modern browsers</td>
<td>All modern browsers (Chrome, Edge, Firefox, Safari 16+)</td>
</tr>
<tr>
<td><strong>Encode speed</strong></td>
<td>Very fast</td>
<td>Fast</td>
<td>Slow, especially at high effort settings</td>
</tr>
<tr>
<td><strong>Decode speed</strong></td>
<td>Very fast</td>
<td>Fast</td>
<td>A bit slower; rarely noticeable for typical web images</td>
</tr>
<tr>
<td><strong>HDR / wide color</strong></td>
<td>Limited</td>
<td>Limited (8-bit)</td>
<td>Yes (10/12-bit, HDR)</td>
</tr>
<tr>
<td><strong>Best for</strong></td>
<td>Maximum compatibility, email, legacy systems</td>
<td>General-purpose web images, transparency, animation</td>
<td>Large photos, hero images, bandwidth-sensitive pages</td>
</tr>
</tbody></table>
<p>Exact savings depend heavily on the image. A noisy photo, a flat illustration and a screenshot full of text all compress differently, so treat any "X% smaller" claim you read online, including vague ones like the above, as a starting hypothesis to test on your own images.</p>
<h2>When to use each format</h2>
<h3>Use AVIF when file size matters most</h3>
<p>AVIF is based on the AV1 video codec and is generally the most efficient of the three for photographs. It shines on large hero images, product photos and image-heavy galleries, where shaving bytes has a real effect on load time. It also handles smooth gradients well at low quality settings, where JPEG tends to show banding and blocky artifacts.</p>
<p>The trade-offs:</p>
<ul>
<li><strong>Encoding is slow.</strong> For a handful of images that doesn't matter. For thousands of images in a build pipeline it can, so pick a moderate speed/effort setting.</li>
<li><strong>Very fine detail can soften.</strong> At aggressive settings AVIF tends to smooth textures rather than produce JPEG-style blocks. That usually looks better, but check images with fine text or hair-like detail.</li>
<li><strong>Some non-browser software still lags.</strong> Older image editors, CMS plugins, or social media platforms may not accept AVIF uploads.</li>
</ul>
<h3>Use WebP as your safe, all-round default</h3>
<p>WebP is the pragmatic choice. It compresses better than JPEG, supports transparency and animation, encodes quickly, and is accepted by most tools and platforms today. If you want one format for everything and don't want to think too hard, WebP is it.</p>
<p>WebP is particularly good for:</p>
<ul>
<li><strong>Images with transparency</strong>: logos, product cutouts and UI elements that would otherwise be large PNGs.</li>
<li><strong>Screenshots and graphics</strong>: WebP lossless often beats PNG on file size.</li>
<li><strong>Animations</strong>: an animated WebP is usually far smaller than the equivalent GIF.</li>
</ul>
<h3>Use JPEG when compatibility beats everything</h3>
<p>JPEG isn't dead. It's still the right choice when the image leaves the browser: email newsletters (many email clients have patchy support for newer formats), files you're handing to clients, uploads to platforms that recompress anyway, and as the final fallback in a <code>&lt;picture&gt;</code> element. Modern encoders like MozJPEG also squeeze noticeably more out of JPEG than older defaults, so a well-encoded JPEG is still respectable.</p>
<h3>What about PNG and GIF?</h3>
<ul>
<li><strong>PNG</strong>: keep it for source files, or when you need lossless output and the destination doesn't accept WebP. For web delivery, WebP lossless or a high-quality lossy WebP/AVIF is usually much smaller.</li>
<li><strong>GIF</strong>: for the web, replace it. Animated WebP, or a short muted <code>&lt;video&gt;</code> (MP4/WebM), is typically a fraction of the size with better colors.</li>
</ul>
<h2>Quality settings that actually work</h2>
<p>Quality numbers are not comparable across encoders. "Quality 80" in one tool is not "quality 80" in another, and the same number means different things for JPEG, WebP and AVIF. That said, these are reasonable starting points:</p>
<ul>
<li><strong>JPEG</strong>: around 75–82 is a sweet spot for most photos.</li>
<li><strong>WebP</strong>: around 75–82 for photos; try lossless for screenshots, logos and flat graphics.</li>
<li><strong>AVIF</strong>: you can often go lower than with JPEG/WebP (many people land somewhere in the 50–70 range, depending on the encoder's scale) and still look clean. Compare visually.</li>
</ul>
<p>The method that matters more than the number: <strong>open the original and the compressed version side by side at 100% zoom</strong>, then lower quality until you can see a difference, and step back up a notch. Do this for a few representative images (a photo, a screenshot, an illustration) and use those settings as your defaults.</p>
<h2>Resize before you compress</h2>
<p>The single biggest mistake isn't the format, it's dimensions. A 4000px-wide photo straight off a phone, displayed in a 800px-wide column, wastes most of its bytes no matter how good the codec is.</p>
<p>Rules of thumb:</p>
<ul>
<li>Find the largest size the image is actually displayed at (check your layout's max content width).</li>
<li>Export at that width, plus a 2x version for high-density screens if the image is important.</li>
<li>For full-width hero images, a few widths (for example 640, 1280 and 1920px) cover most devices.</li>
</ul>
<h2>Serving modern formats with <code>&lt;picture&gt;</code> and <code>srcset</code></h2>
<p>Combine format fallback and responsive sizes so each browser picks the best file it supports:</p>
<pre><code class="language-html">&lt;picture&gt;
  &lt;source
    type="image/avif"
    srcset="hero-640.avif 640w, hero-1280.avif 1280w, hero-1920.avif 1920w"
    sizes="(max-width: 800px) 100vw, 800px"&gt;
  &lt;source
    type="image/webp"
    srcset="hero-640.webp 640w, hero-1280.webp 1280w, hero-1920.webp 1920w"
    sizes="(max-width: 800px) 100vw, 800px"&gt;
  &lt;img
    src="hero-1280.jpg"
    srcset="hero-640.jpg 640w, hero-1280.jpg 1280w, hero-1920.jpg 1920w"
    sizes="(max-width: 800px) 100vw, 800px"
    width="1280" height="720"
    alt="Describe the image here"
    fetchpriority="high"&gt;
&lt;/picture&gt;
</code></pre>
<p>A few details worth keeping:</p>
<ul>
<li><strong>Order matters</strong>: the browser uses the first <code>&lt;source&gt;</code> it supports, so put AVIF first, then WebP, with JPEG in the <code>&lt;img&gt;</code>.</li>
<li><strong>Always set <code>width</code> and <code>height</code></strong> to prevent layout shift.</li>
<li><strong>Use <code>loading="lazy"</code></strong> for images below the fold, and <strong>don't</strong> lazy-load your LCP/hero image; give it <code>fetchpriority="high"</code> instead.</li>
<li>If your browser support targets are all modern, you can reasonably skip the JPEG fallback and serve WebP as the <code>&lt;img&gt;</code> source. Many sites keep it anyway for robustness.</li>
</ul>
<h2>A practical workflow for converting your site's images</h2>
<p>Here's a simple process that works for a blog, a portfolio or a small marketing site:</p>
<ol>
<li><strong>Keep originals.</strong> Store high-quality masters (JPEG at high quality, PNG, or RAW) somewhere separate. Never re-compress an already compressed file repeatedly; quality degrades each time.</li>
<li><strong>Resize</strong> to the widths you actually need.</li>
<li><strong>Convert</strong> photos to AVIF and WebP (and JPEG if you need the fallback). Convert logos, icons and screenshots to WebP (lossless or high-quality lossy), or keep SVG for vector artwork.</li>
<li><strong>Compare visually</strong> at 100% on a few samples and tune quality.</li>
<li><strong>Batch the rest</strong> with the settings you settled on.</li>
<li><strong>Wire up</strong> <code>&lt;picture&gt;</code>/<code>srcset</code>, then check the result with Lighthouse or your browser's network panel.</li>
</ol>
<h3>Option 1: Convert in the browser, with no uploads</h3>
<p>Many online converters upload your images to a server, process them there, and send them back. That's slower for big batches, and it's a real concern for client work, unreleased product shots, or anything under NDA.</p>
<p>Browser-based tools that run on WebAssembly avoid that entirely: the encoder runs on your own machine, inside the browser tab, and files never leave your device.</p>
<p><a href="https://zippic.app">Zippic</a> is one such tool. It's free, needs no sign-up, and compresses and converts JPG, PNG, WebP, AVIF, GIF, BMP and even RAW files locally via WebAssembly. It handles batch processing, which is handy for step 5 above: drop in a folder's worth of images, pick the output format and quality, and download the results. The interface is available in 11 languages, which helps if you work with international teams.</p>
<p><strong>Squoosh</strong> (from the Chrome team) is another excellent in-browser option, and it's great for step 4: its side-by-side slider makes it easy to compare codecs and quality settings on a single image. It's designed around one image at a time, so for larger batches a batch-oriented tool or the command line is more convenient.</p>
<h3>Option 2: The command line and build pipelines</h3>
<p>If you're automating, the standard encoders are free and scriptable:</p>
<pre><code class="language-bash"># WebP at quality 80
cwebp -q 80 input.jpg -o output.webp

# AVIF (libavif); check `avifenc --help` for the quality/speed flags in your version
avifenc input.jpg output.avif
</code></pre>
<p>Libraries such as <code>sharp</code> (Node.js) or <code>libvips</code>, and image plugins for frameworks and static site generators, can generate all your sizes and formats at build time. Many CDNs and image services can also convert on the fly based on the browser's <code>Accept</code> header.</p>
<p>The command line is ideal for repeatable pipelines. A browser tool like <a href="https://zippic.app">Zippic</a> is often faster for one-off jobs: a batch of blog images, a client's photo set, or converting a few RAW or HEIC files without installing anything.</p>
<h2>The short answer</h2>
<ul>
<li><strong>Default to WebP</strong> for most web images; it's fast, widely supported and handles transparency and animation.</li>
<li><strong>Use AVIF</strong> for large photos and hero images where every kilobyte counts, with WebP or JPEG as a fallback.</li>
<li><strong>Keep JPEG</strong> for email, external platforms and as the universal fallback.</li>
<li><strong>Resize first</strong>, start around quality 75–82 for JPEG/WebP, and trust your eyes over the numbers.</li>
</ul>
<p>Pick a few representative images from your site today, convert them, and compare. You'll likely find meaningful savings, and your visitors on slow mobile connections will notice.</p>
]]></content:encoded></item><item><title><![CDATA[How In-Browser Image Compression Works with WebAssembly]]></title><description><![CDATA[For years, "compress this image online" meant "upload it to someone's server." That's no longer necessary. Modern browsers can run the same battle-tested C, C++ and Rust codecs that servers use, thank]]></description><link>https://zippic.hashnode.dev/how-in-browser-image-compression-works-with-webassembly</link><guid isPermaLink="true">https://zippic.hashnode.dev/how-in-browser-image-compression-works-with-webassembly</guid><category><![CDATA[WebAssembly]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[performance]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[images]]></category><dc:creator><![CDATA[Zippic]]></dc:creator><pubDate>Fri, 16 Oct 2026 15:56:16 GMT</pubDate><content:encoded><![CDATA[<p>For years, "compress this image online" meant "upload it to someone's server." That's no longer necessary. Modern browsers can run the same battle-tested C, C++ and Rust codecs that servers use, thanks to WebAssembly. This article walks through how in-browser image compression works, what the main pieces are, and where the limits lie.</p>
<h3>Why do it in the browser at all?</h3>
<p>Three reasons come up again and again:</p>
<ol>
<li><strong>Privacy.</strong> The file never leaves the user's device. There's nothing to store, log or leak on the server side.</li>
<li><strong>No upload latency.</strong> For large photos or big batches, uploading and downloading often takes longer than the compression itself.</li>
<li><strong>Cost and scale.</strong> Image encoding is CPU-heavy. Pushing it to the client means the server only has to serve static files.</li>
</ol>
<p>The catch is that you inherit the client's hardware, memory and browser quirks. More on that later.</p>
<h3>The built-in option: Canvas</h3>
<p>Before reaching for WebAssembly, it's worth knowing what the browser already gives you. You can draw an image onto a <code>&lt;canvas&gt;</code> (or <code>OffscreenCanvas</code>) and call <code>toBlob()</code> / <code>convertToBlob()</code> with a MIME type and a quality value:</p>
<pre><code class="language-js">const bitmap = await createImageBitmap(file);
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
canvas.getContext('2d').drawImage(bitmap, 0, 0);
const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.8 });
</code></pre>
<p>This is fast and needs no extra code, but it has real limits:</p>
<ul>
<li>Output formats depend on the browser (JPEG and PNG are universal; WebP and AVIF encoding support varies).</li>
<li>You get almost no encoder control: one quality number, no chroma subsampling options, no progressive JPEG tuning, no PNG optimization levels.</li>
<li>Results vary between browsers because each ships its own encoder.</li>
</ul>
<p>For a serious compressor you want consistent, tunable, best-in-class encoders. That's where WebAssembly comes in.</p>
<h3>Compiling real codecs to WebAssembly</h3>
<p>WebAssembly (WASM) is a compact binary format that browsers execute at near-native speed in a sandbox. Existing codec libraries can be compiled to it:</p>
<ul>
<li><strong>MozJPEG</strong> (C) — Mozilla's JPEG encoder, which typically produces smaller files than a baseline libjpeg at the same visual quality, using techniques like trellis quantization and optimized progressive scans.</li>
<li><strong>OxiPNG</strong> (Rust) — a lossless PNG optimizer that tries different filters and compression strategies to shrink PNGs without changing pixels. Often paired with a lossy quantizer such as libimagequant when you want palette reduction.</li>
<li><strong>libwebp</strong> (C) — Google's reference WebP encoder/decoder, supporting both lossy and lossless modes.</li>
<li><strong>libavif</strong> (C), typically with an AV1 encoder like libaom or rav1e — for AVIF, which often achieves strong compression but is slow to encode.</li>
</ul>
<p>C/C++ libraries are usually compiled with <strong>Emscripten</strong>; Rust crates with <code>wasm-pack</code>/<code>wasm-bindgen</code>. Google's open-source Squoosh project popularized this approach and is a great reference.</p>
<p>A typical pipeline looks like this:</p>
<ol>
<li><strong>Decode</strong> the input to raw RGBA pixels (via <code>createImageBitmap</code>, or a WASM decoder for formats the browser can't read, such as HEIC or camera RAW).</li>
<li><strong>Process</strong> — resize, crop, rotate, watermark — usually on raw pixel buffers.</li>
<li><strong>Encode</strong> the pixels with the chosen WASM encoder and settings.</li>
<li><strong>Return</strong> a <code>Blob</code> the user can download, entirely client-side.</li>
</ol>
<p>Data crosses the JS/WASM boundary as bytes in WASM linear memory: you copy the pixel buffer in, call the encoder, and copy the output bytes back out.</p>
<h3>Keep the UI responsive: Web Workers</h3>
<p>Encoding a 24-megapixel photo with MozJPEG or AVIF can take from hundreds of milliseconds to many seconds. Do that on the main thread and the page freezes. The standard answer is <strong>Web Workers</strong>:</p>
<pre><code class="language-js">// main.js
const worker = new Worker(new URL('./encode-worker.js', import.meta.url), { type: 'module' });
worker.postMessage({ pixels, width, height, options }, [pixels.buffer]);
worker.onmessage = (e) =&gt; saveBlob(e.data);
</code></pre>
<p>A few practical points:</p>
<ul>
<li><strong>Transfer, don't copy.</strong> Passing <code>ArrayBuffer</code>s in the transfer list moves ownership to the worker without duplicating megabytes of data.</li>
<li><strong>Use a worker pool for batches.</strong> <code>navigator.hardwareConcurrency</code> gives a hint for how many workers to spin up. Too many and you'll run out of memory before you run out of CPU.</li>
<li><strong>Multithreaded WASM</strong> (WASM threads with <code>SharedArrayBuffer</code>) can speed up encoders like AVIF, but requires the page to be cross-origin isolated (<code>Cross-Origin-Opener-Policy</code> and <code>Cross-Origin-Embedder-Policy</code> headers), which can break embedding of third-party resources. Many apps ship a single-threaded fallback.</li>
<li><strong>SIMD</strong> support in WASM is widely available in modern browsers and gives meaningful speedups for codec inner loops; feature-detect it and load the right build.</li>
</ul>
<h3>Memory: the real constraint</h3>
<p>Raw pixels are big. A 6000×4000 image as RGBA is 6000 × 4000 × 4 ≈ 96 MB — before the encoder allocates its own working buffers. Things to keep in mind:</p>
<ul>
<li>32-bit WebAssembly has a 4 GB address space ceiling, but practical limits are often much lower, especially on mobile browsers and older devices, where tabs can be killed under memory pressure.</li>
<li>WASM memory can grow but not shrink. A long-lived worker that processed one huge image may keep that memory reserved. Terminating and recreating workers between large jobs is a simple way to release it.</li>
<li>For batches, limit concurrency by estimated memory (width × height × 4 × some overhead), not just by CPU count.</li>
<li>Free intermediate <code>ImageBitmap</code>s (<code>bitmap.close()</code>) and revoke object URLs you no longer need.</li>
</ul>
<h3>Trade-offs vs. server-side compression</h3>
<p>In-browser compression isn't strictly better; it's a different set of trade-offs.</p>
<p><strong>Client-side wins:</strong></p>
<ul>
<li>Files never leave the device — a strong privacy story.</li>
<li>No upload/download time; works offline once loaded (especially with a service worker).</li>
<li>Near-zero marginal server cost.</li>
</ul>
<p><strong>Server-side wins:</strong></p>
<ul>
<li>Predictable performance on dedicated hardware; slow phones aren't your problem.</li>
<li>No large WASM bundles to download up front (codec binaries can be hundreds of KB to a few MB each, so lazy-load them per format).</li>
<li>Easier to run in automated pipelines (CI, CMS uploads, CDNs).</li>
<li>Full control over codec versions and native multithreading without browser header constraints.</li>
</ul>
<p>A reasonable rule of thumb: for user-facing, ad-hoc compression where privacy matters, the browser is a great fit. For automated, high-volume asset pipelines, server-side or build-time tools still make sense.</p>
<h3>Putting it into practice</h3>
<p>I've been building <a href="https://zippic.app">Zippic</a>, a free image compressor and converter that applies this approach: everything is processed in the browser with WebAssembly, with no uploads and no sign-up. It handles JPG, PNG, WebP, GIF, AVIF, BMP and RAW, plus batch compression and a few conversion tools. If you try it, I'd genuinely like to hear how it compares with server-based tools on your images.</p>
<h3>Wrapping up</h3>
<p>The ingredients for serious in-browser image compression are all there: mature codecs compiled to WebAssembly, Web Workers for responsiveness, and careful memory management. Start with Canvas for simple cases, move to WASM codecs when you need quality and control, and design around memory from day one. Your users' files can stay on their own machines.</p>
]]></content:encoded></item></channel></rss>