<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arhan Ahmad</title>
    <description>The latest articles on DEV Community by Arhan Ahmad (@arhan_ahmad_a67ef7bd2b992).</description>
    <link>https://dev.to/arhan_ahmad_a67ef7bd2b992</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4010774%2F192502fa-ff38-45ee-a6ad-f12613f6d8e2.png</url>
      <title>DEV Community: Arhan Ahmad</title>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arhan_ahmad_a67ef7bd2b992"/>
    <language>en</language>
    <item>
      <title>How to remove EXIF data from photos (and why you should before sharing)</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Tue, 07 Jul 2026 07:17:22 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/how-to-remove-exif-data-from-photos-and-why-you-should-before-sharing-3p72</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/how-to-remove-exif-data-from-photos-and-why-you-should-before-sharing-3p72</guid>
      <description>&lt;p&gt;Every photo you take with a smartphone embeds hidden metadata — the camera model, GPS coordinates, date and time, even the orientation you held the phone. This is called EXIF data, and most people don't know it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you should care
&lt;/h2&gt;

&lt;p&gt;When you share a photo online, that EXIF data goes with it unless you explicitly strip it. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A photo taken at your home reveals your exact GPS coordinates&lt;/li&gt;
&lt;li&gt;Product photos for your business leak the camera equipment you use&lt;/li&gt;
&lt;li&gt;Screenshots of private conversations are timestamped and geotagged&lt;/li&gt;
&lt;li&gt;Vacation photos posted in real-time show you're not home&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What EXIF data typically contains
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Camera make and model&lt;/li&gt;
&lt;li&gt;GPS latitude and longitude&lt;/li&gt;
&lt;li&gt;Date and time&lt;/li&gt;
&lt;li&gt;Focal length, aperture, ISO, shutter speed&lt;/li&gt;
&lt;li&gt;Flash settings&lt;/li&gt;
&lt;li&gt;Image dimensions and orientation&lt;/li&gt;
&lt;li&gt;Thumbnail (sometimes containing a smaller version of the image itself)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to remove it
&lt;/h2&gt;

&lt;p&gt;There are two approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Remove metadata only&lt;/strong&gt;&lt;br&gt;
This strips the EXIF, XMP, and IPTC data without re-encoding the image. No quality loss. File size actually decreases by removing the metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Re-save the image&lt;/strong&gt;&lt;br&gt;
Re-encoding the image creates a fresh file with no metadata. This is what happens when you use a compressor — the output file is clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The easiest way
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://toolboximage.com" rel="noopener noreferrer"&gt;ToolBox Image&lt;/a&gt; has a free EXIF viewer that shows all the hidden metadata in your photos, and every tool processes images client-side so your files never leave your device. Just drag your photo in and you'll see exactly what data is embedded.&lt;/p&gt;

&lt;p&gt;All free. No sign-up. No uploads.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>images</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Image formats explained: When to use JPG vs PNG vs WebP vs AVIF in 2026</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Tue, 07 Jul 2026 07:09:15 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/image-formats-explained-when-to-use-jpg-vs-png-vs-webp-vs-avif-in-2026-2mm4</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/image-formats-explained-when-to-use-jpg-vs-png-vs-webp-vs-avif-in-2026-2mm4</guid>
      <description>&lt;p&gt;Choosing the wrong image format costs you page speed, user experience, and bandwidth. Here's a practical guide to the four major formats and when each makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  JPEG (JPG) — Still the universal standard
&lt;/h2&gt;

&lt;p&gt;Best for: Photographs, complex images with many colors.&lt;/p&gt;

&lt;p&gt;JPEG uses lossy compression that works well for natural scenes. It's supported everywhere. The downsides: no transparency, no animation, and visible artefacts at very low quality settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: Background photos, product images on e-commerce, hero banners. Basically any photograph where file size matters more than perfection.&lt;/p&gt;

&lt;h2&gt;
  
  
  PNG — For precision
&lt;/h2&gt;

&lt;p&gt;Best for: Screenshots, logos, images with text, graphics with sharp edges.&lt;/p&gt;

&lt;p&gt;PNG offers lossless compression with full transparency support. The tradeoff is file size — PNGs are typically 2-5x larger than JPEG for photographs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: UI elements, logos, screenshots, infographics. Anything with text or sharp edges where JPEG artefacts would be noticeable.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebP — The modern default
&lt;/h2&gt;

&lt;p&gt;Best for: Replacing both JPEG and PNG in most cases.&lt;/p&gt;

&lt;p&gt;WebP supports both lossy and lossless compression, plus transparency and animation. Lossy WebP is 25-35% smaller than JPEG at the same quality. Lossless WebP is 25-30% smaller than PNG.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: Everything new. Browser support is 97%+. Convert existing JPEGs and PNGs to WebP for immediate bandwidth savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  AVIF — The next generation
&lt;/h2&gt;

&lt;p&gt;Best for: Maximum compression, HDR images, wide color gamut.&lt;/p&gt;

&lt;p&gt;AVIF is 50-60% smaller than JPEG at the same quality. It supports HDR, 10/12-bit color, and transparency. The tradeoff is slower encoding (2-3x slower than WebP).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it&lt;/strong&gt;: Hero images, large photos, and when every kilobyte matters. Browser support is at 91% and growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick decision guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Best Format&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Photo on a website&lt;/td&gt;
&lt;td&gt;WebP or AVIF&lt;/td&gt;
&lt;td&gt;25-60% smaller than JPEG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logo with transparency&lt;/td&gt;
&lt;td&gt;PNG or WebP&lt;/td&gt;
&lt;td&gt;Needs alpha channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot&lt;/td&gt;
&lt;td&gt;Lossless WebP&lt;/td&gt;
&lt;td&gt;Smaller than PNG, same quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email attachment&lt;/td&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;Universal compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-commerce product photo&lt;/td&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;Best size-quality balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Social media image&lt;/td&gt;
&lt;td&gt;JPEG or PNG&lt;/td&gt;
&lt;td&gt;Platform re-encodes anyway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIF replacement&lt;/td&gt;
&lt;td&gt;WebP animated&lt;/td&gt;
&lt;td&gt;Smaller than GIF, better quality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The one tool you need
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://toolboximage.com" rel="noopener noreferrer"&gt;ToolBox Image&lt;/a&gt; lets you convert between all four formats instantly in your browser with no uploads. It also has target-size compression so you can set an exact file size limit. Free, no sign-up.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>images</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why I built a privacy-first image tool that runs entirely in your browser</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Tue, 07 Jul 2026 07:09:14 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/why-i-built-a-privacy-first-image-tool-that-runs-entirely-in-your-browser-4nkk</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/why-i-built-a-privacy-first-image-tool-that-runs-entirely-in-your-browser-4nkk</guid>
      <description>&lt;p&gt;Every time you upload a photo to an online image tool, you're sending it to someone's server. Maybe you're okay with that. But what if the image contains sensitive information?&lt;/p&gt;

&lt;p&gt;A screenshot of a private conversation. A photo with GPS coordinates embedded in the metadata. A design mockup for a product that hasn't launched yet.&lt;/p&gt;

&lt;p&gt;This is why I built ToolBox Image — a free image toolkit where every single tool runs 100% in your browser using Canvas API and WebAssembly. Nothing is uploaded. Nothing is stored. Nothing leaves your device.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;22 tools covering everything from basic compression to advanced features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compression&lt;/strong&gt;: JPEG, PNG, WebP, AVIF, GIF with target-size mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion&lt;/strong&gt;: Between any format, including HEIC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editing&lt;/strong&gt;: Resize, crop, rotate, sharpen, denoise, blur&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analysis&lt;/strong&gt;: EXIF viewer, color picker, dominant color extraction, format viewer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: Metadata stripper, image redactor (pixelate sensitive areas)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creation&lt;/strong&gt;: Passport photos, circle crops, watermarks, SVG optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;Most image tools follow the same pattern: upload → process on server → download. That means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your images sit on someone's server (for who knows how long)&lt;/li&gt;
&lt;li&gt;They can be accessed by third parties&lt;/li&gt;
&lt;li&gt;Large files take minutes to upload&lt;/li&gt;
&lt;li&gt;There's a file size limit on most free tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Browser-based processing solves all of these. The code runs locally using WebAssembly. The only thing you download is the optimized result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech
&lt;/h2&gt;

&lt;p&gt;Built with Astro 7, Tailwind CSS v4, and vanilla JavaScript for processing. Canvas API handles decode, encode, resize, and filters. Web Workers enable parallel processing for batch jobs (up to 200 images at once).&lt;/p&gt;

&lt;p&gt;No backend servers. No databases. No user accounts.&lt;/p&gt;

&lt;p&gt;The entire site is deployed on Cloudflare Pages and costs essentially nothing to run because there's no server-side processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you work with images regularly and value privacy, give it a try:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://toolboximage.com" rel="noopener noreferrer"&gt;https://toolboximage.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All tools are free. No sign-up. No limits. No tracking.&lt;/p&gt;

&lt;p&gt;I'd love to hear what you think — especially if there's a tool you'd like to see added.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>images</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to reduce image file size without losing quality (5 techniques)</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Tue, 07 Jul 2026 07:06:26 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/how-to-reduce-image-file-size-without-losing-quality-5-techniques-1la9</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/how-to-reduce-image-file-size-without-losing-quality-5-techniques-1la9</guid>
      <description>&lt;p&gt;The most common question I hear is, "How do I make this image smaller without making it look worse?" Here are five techniques that work every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Switch to a modern format
&lt;/h2&gt;

&lt;p&gt;This is the single biggest lever. Converting a JPEG to WebP saves 25-35% with no visible difference. Converting to AVIF saves 50-60%.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Remove invisible metadata
&lt;/h2&gt;

&lt;p&gt;Every photo from a modern smartphone carries embedded EXIF data — camera model, GPS coordinates, date, settings. This metadata can add 50-200 KB per image. Stripping it removes zero visual quality and protects your privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Resize to the display size
&lt;/h2&gt;

&lt;p&gt;The most common cause of oversized images is uploading a 4000-pixel-wide photo when the page displays it at 1200 pixels. That's over 10x the pixels needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use lossless compression for graphics
&lt;/h2&gt;

&lt;p&gt;For screenshots, logos, and images with text, lossy compression introduces visible artefacts around text edges. Use lossless WebP or PNG instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Target a specific file size
&lt;/h2&gt;

&lt;p&gt;Instead of guessing at quality sliders, tell the compressor the exact file size you need. Many platforms have hard limits: 8 MB for Discord, 25 MB for Gmail, 10 MB for WhatsApp.&lt;/p&gt;




&lt;p&gt;All five techniques are available for free at &lt;a href="https://toolboximage.com" rel="noopener noreferrer"&gt;ToolBox Image&lt;/a&gt; — a privacy-first image toolkit that runs entirely in your browser. Nothing leaves your device. No sign-up required.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>optimization</category>
      <category>images</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Browser-Based Passport Photo Maker with Country Presets</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Sat, 04 Jul 2026 14:48:47 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/building-a-browser-based-passport-photo-maker-with-country-presets-3g6g</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/building-a-browser-based-passport-photo-maker-with-country-presets-3g6g</guid>
      <description>&lt;h1&gt;
  
  
  Building a Browser-Based Passport Photo Maker
&lt;/h1&gt;

&lt;p&gt;Passport photos have strict government specifications — exact dimensions, specific background colors, and precise head positioning. Here's how to build a tool that handles all of this entirely in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Each country has different requirements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Size (mm)&lt;/th&gt;
&lt;th&gt;Size (px at 300 DPI)&lt;/th&gt;
&lt;th&gt;Background&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;US&lt;/td&gt;
&lt;td&gt;51×51&lt;/td&gt;
&lt;td&gt;600×600&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UK/EU&lt;/td&gt;
&lt;td&gt;35×45&lt;/td&gt;
&lt;td&gt;413×531&lt;/td&gt;
&lt;td&gt;Light gray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;India&lt;/td&gt;
&lt;td&gt;35×45&lt;/td&gt;
&lt;td&gt;413×531&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canada&lt;/td&gt;
&lt;td&gt;50×70&lt;/td&gt;
&lt;td&gt;591×827&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China&lt;/td&gt;
&lt;td&gt;33×48&lt;/td&gt;
&lt;td&gt;390×567&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Japan&lt;/td&gt;
&lt;td&gt;35×45&lt;/td&gt;
&lt;td&gt;413×531&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Australia&lt;/td&gt;
&lt;td&gt;35×45&lt;/td&gt;
&lt;td&gt;413×531&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;td&gt;35×45&lt;/td&gt;
&lt;td&gt;413×531&lt;/td&gt;
&lt;td&gt;Light gray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Germany&lt;/td&gt;
&lt;td&gt;35×45&lt;/td&gt;
&lt;td&gt;413×531&lt;/td&gt;
&lt;td&gt;Light gray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UAE&lt;/td&gt;
&lt;td&gt;43×55&lt;/td&gt;
&lt;td&gt;508×650&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Loading and Displaying the Image
&lt;/h2&gt;

&lt;p&gt;First, load the user's image and display it with an interactive crop overlay:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
js
function loadImage(file) {
  return new Promise((resolve) =&amp;gt; {
    const img = new Image();
    img.onload = () =&amp;gt; resolve(img);
    img.src = URL.createObjectURL(file);
  });
}
Interactive Crop Window
The crop window is an absolutely positioned div overlaid on the image. Users can drag it to reposition and resize using corner handles:
class CropWindow {
  constructor(container, image, preset) {
    this.container = container;
    this.image = image;
    this.preset = preset;
    this.x = 0;
    this.y = 0;
    this.w = 200;
    this.h = 260;
    this.initHandles();
    this.initKeyboard();
  }

  initHandles() {
    // 4 corner handles for resize
    const handles = ['nw', 'ne', 'sw', 'se'];
    handles.forEach(pos =&amp;gt; {
      const handle = document.createElement('div');
      handle.className = `crop-handle ${pos}`;
      handle.addEventListener('pointerdown', (e) =&amp;gt; this.startResize(e, pos));
      this.container.appendChild(handle);
    });
  }

  initKeyboard() {
    document.addEventListener('keydown', (e) =&amp;gt; {
      const step = e.shiftKey ? 10 : 2;
      switch(e.key) {
        case 'ArrowUp': this.y = Math.max(0, this.y - step); break;
        case 'ArrowDown': this.y = Math.min(this.maxY, this.y + step); break;
        case 'ArrowLeft': this.x = Math.max(0, this.x - step); break;
        case 'ArrowRight': this.x = Math.min(this.maxX, this.x + step); break;
      }
      this.render();
    });
  }
}
Background Replacement
To change the background color, we scan edge pixels and replace similar colors:
function replaceBackground(canvas, targetColor, tolerance = 30) {
  const ctx = canvas.getContext('2d');
  const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = data.data;

  // Sample background color from corners
  const bg = sampleCornerPixels(pixels, canvas.width, canvas.height);

  for (let i = 0; i &amp;lt; pixels.length; i += 4) {
    const diff = colorDistance(pixels, bg, i);
    if (diff &amp;lt; tolerance) {
      pixels[i] = targetColor.r;     // R
      pixels[i + 1] = targetColor.g; // G
      pixels[i + 2] = targetColor.b; // B
    }
  }

  ctx.putImageData(data, 0, 0);
}
Print Layout Generation
For users who want to print at a photo lab, we need to arrange multiple copies on a 4×6 inch (1024×1536 px at 300 DPI) print sheet:
function generatePrintLayout(singlePhoto, preset, sheetWidth = 1500, sheetHeight = 1000) {
  const canvas = document.createElement('canvas');
  canvas.width = sheetWidth;
  canvas.height = sheetHeight;
  const ctx = canvas.getContext('2d');

  // Fill with white background
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, sheetWidth, sheetHeight);

  // Calculate how many photos fit
  const cols = Math.floor(sheetWidth / (preset.outW + 20));
  const rows = Math.floor(sheetHeight / (preset.outH + 20));

  for (let row = 0; row &amp;lt; rows; row++) {
    for (let col = 0; col &amp;lt; cols; col++) {
      const x = col * (preset.outW + 20) + 10;
      const y = row * (preset.outH + 20) + 10;
      ctx.drawImage(singlePhoto, x, y, preset.outW, preset.outH);
    }
  }

  return canvas;
}
Full Tool
The ToolBox Image Passport Photo Maker (https://toolboximage.com/tools/passport/) implements all of this with 10 country presets, 4 background colors, arrow key fine positioning, and both single photo + print layout downloads. All client-side, zero uploads.
Try it at toolboximage.com/tools/passport/ (https://toolboximage.com/tools/passport/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Reading and Stripping EXIF Metadata in the Browser with exifr</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Sat, 04 Jul 2026 10:13:37 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/reading-and-stripping-exif-metadata-in-the-browser-with-exifr-19dg</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/reading-and-stripping-exif-metadata-in-the-browser-with-exifr-19dg</guid>
      <description>&lt;h1&gt;
  
  
  Reading and Stripping EXIF Metadata in the Browser
&lt;/h1&gt;

&lt;p&gt;Every photo you take with a smartphone contains hidden data — GPS coordinates, camera settings, timestamps, and sometimes even the camera's serial number. When you share that photo online without stripping metadata, you're broadcasting your private information to anyone who knows where to look.&lt;/p&gt;

&lt;p&gt;Here's how to read and remove EXIF data entirely in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing EXIF with &lt;code&gt;exifr&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/MikeKovarik/exifr" rel="noopener noreferrer"&gt;exifr&lt;/a&gt; library is the best option for client-side EXIF parsing. It's fast, tree-shakeable, and supports JPEG, TIFF, and HEIC:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
npm install exifr
import * as exifr from 'exifr';

async function readMetadata(file) {
  // Parse all available metadata
  const metadata = await exifr.parse(file);
  console.log(metadata);

  // Parse specific sections
  const gps = await exifr.gps(file);
  const camera = await exifr.parse(file, ['Make', 'Model', 'ISO', 'FNumber']);

  return { metadata, gps, camera };
}
What You Can Extract
// Camera info
{
  Make: 'Canon',
  Model: 'EOS R5',
  ISOSpeedRatings: 800,
  FNumber: 2.8,
  FocalLength: 50,
  ExposureTime: 0.004,  // 1/250s
  LensModel: 'RF 50mm f/1.2L'
}

// GPS data
{
  latitude: 28.6139,
  longitude: 77.2090,
  altitude: 216
}

// Image details
{
  DateTimeOriginal: '2024-03-15T14:30:00.000Z',
  Software: 'Adobe Lightroom',
  ColorSpace: 'sRGB'
}
Stripping Metadata with Canvas
The simplest way to strip all metadata is to re-encode the image through a canvas element. Canvas outputs always strip EXIF/XMP/IPTC data:
async function stripMetadata(file) {
  const img = await loadImage(URL.createObjectURL(file));
  const canvas = document.createElement('canvas');
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  canvas.getContext('2d').drawImage(img, 0, 0);

  return new Promise((resolve) =&amp;gt; {
    canvas.toBlob((blob) =&amp;gt; {
      const clean = new File([blob], file.name, { type: file.type });
      resolve(clean);
    }, file.type, 1.0);
  });
}

function loadImage(src) {
  return new Promise((resolve, reject) =&amp;gt; {
    const img = new Image();
    img.onload = () =&amp;gt; resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}
Important caveat: This re-encodes the image, which is a lossy operation for JPEG. The quality degradation is minimal at 1.0, but it's not pixel-identical to the original. For most use cases (social media, web uploads), this is an acceptable trade-off for privacy.
Organized Display
Raw EXIF output is a flat object with 80+ fields. Users benefit from categorization:
const categories = {
  'Camera': ['Make', 'Model', 'ISOSpeedRatings', 'FNumber', 'FocalLength', 'ExposureTime', 'Flash'],
  'GPS': ['latitude', 'longitude', 'altitude'],
  'File Info': ['Format', 'FileSize', 'ImageWidth', 'ImageHeight'],
  'Image Details': ['DateTimeOriginal', 'Software', 'ColorSpace', 'ResolutionUnit'],
};
The Production Tool
The ToolBox Image Metadata Viewer (https://toolboximage.com/tools/metadata/) implements this with organized cards, GPS coordinates with map links, and a one-click "Strip Metadata" button. All processing happens in your browser — your images never leave your device.
Try it at toolboximage.com/tools/metadata/ (https://toolboximage.com/tools/metadata/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Browser-Based Image Resizer with Step-Down Scaling and Crop</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Sat, 04 Jul 2026 10:12:31 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/building-a-browser-based-image-resizer-with-step-down-scaling-and-crop-15ik</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/building-a-browser-based-image-resizer-with-step-down-scaling-and-crop-15ik</guid>
      <description>&lt;h1&gt;
  
  
  Building a Browser-Based Image Resizer
&lt;/h1&gt;

&lt;p&gt;Image resizing sounds trivial — scale the width, scale the height, done. But there's a surprising amount of detail involved in making it work well across different image sizes and aspect ratios.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive Approach (And Why It Fails)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
js
// Don't do this for large images
ctx.drawImage(img, 0, 0, targetW, targetH);
This works fine for small images. But scaling a 6000×4000 px photo down to 800×600 px in one shot causes:
- Massive memory usage — the full-resolution image is decoded into memory
- Bicubic artifacts — single-step downscaling creates visible moiré patterns
- Browser-dependent quality — each browser's internal algorithm differs
Step-Down Scaling
Halve the dimensions repeatedly until you're close to the target:
function stepDownResize(img, targetW, targetH) {
  let cw = img.naturalWidth;
  let ch = img.naturalHeight;
  let src = img;

  while (cw &amp;gt; targetW * 2 || ch &amp;gt; targetH * 2) {
    cw = Math.floor(cw / 2);
    ch = Math.floor(ch / 2);
    const temp = document.createElement('canvas');
    temp.width = cw;
    temp.height = ch;
    temp.getContext('2d').drawImage(src, 0, 0, cw, ch);
    src = temp;
  }

  const final = document.createElement('canvas');
  final.width = targetW;
  final.height = targetH;
  final.getContext('2d').drawImage(src, 0, 0, targetW, targetH);
  return final;
}
This produces significantly better quality because each half-step preserves more detail. It also uses less peak memory.
Three Resize Modes
A good resizer needs three modes:
function calcDimensions(srcW, srcH, tgtW, tgtH, mode) {
  const srcRatio = srcW / srcH;
  const tgtRatio = tgtW / tgtH;
  let w = tgtW, h = tgtH;
  let offX = 0, offY = 0;

  if (mode === 'fit') {
    if (srcRatio &amp;gt; tgtRatio) {
      h = Math.round(tgtW / srcRatio);
      offY = Math.round((tgtH - h) / 2);
    } else {
      w = Math.round(tgtH * srcRatio);
      offX = Math.round((tgtW - w) / 2);
    }
  } else if (mode === 'fill') {
    if (srcRatio &amp;gt; tgtRatio) {
      w = Math.round(tgtH * srcRatio);
      offX = Math.round((tgtW - w) / 2);
    } else {
      h = Math.round(tgtW / srcRatio);
      offY = Math.round((tgtH - h) / 2);
    }
  }
  // 'stretch' — use tgtW, tgtH directly, no offset

  return { w, h, offX, offY };
}
Fit — scales to fit inside target, adds padding. Fill — scales to fill target, crops excess. Stretch — forces exact dimensions.
Interactive Crop Box
The resizer now includes an optional crop overlay on the source image. When enabled, the crop region is extracted first, then resized to the target dimensions. The crop box uses pointer events with 4 corner handles, arrow key nudge (2 px normal, 10 px with Shift), and edge snap.
Social Media Presets
Instead of remembering platform dimensions:
const presets = [
  { label: 'Instagram Square', w: 1080, h: 1080 },
  { label: 'Instagram Portrait', w: 1080, h: 1350 },
  { label: 'Facebook Post', w: 1200, h: 630 },
  { label: 'YouTube Thumbnail', w: 1280, h: 720 },
  { label: 'LinkedIn Banner', w: 1584, h: 396 },
];
Select a preset, and width/height auto-fill. Apply with one click.
The Tool
The ToolBox Image Resizer (https://toolboximage.com/tools/resizer/) implements all of these techniques — social media presets, aspect ratio lock, three resize modes, crop box, and format selection with quality control. All client-side, zero uploads.
Try it at toolboximage.com/tools/resizer/ (https://toolboximage.com/tools/resizer/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Client-Side Image Format Conversion — JPEG, PNG, WebP, AVIF in the Browser</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Sat, 04 Jul 2026 09:44:21 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/client-side-image-format-conversion-jpeg-png-webp-avif-in-the-browser-421e</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/client-side-image-format-conversion-jpeg-png-webp-avif-in-the-browser-421e</guid>
      <description>&lt;h1&gt;
  
  
  Client-Side Image Format Conversion
&lt;/h1&gt;

&lt;p&gt;Converting an image from one format to another usually means opening Photoshop, GIMP, or uploading to a service. But the browser can do it natively — and it's surprisingly simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;The same &lt;code&gt;canvas.toBlob()&lt;/code&gt; that handles compression also handles format conversion:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
js
async function convertImage(file, targetFormat, quality = 0.92) {
  const mimeMap = {
    jpeg: 'image/jpeg',
    png: 'image/png',
    webp: 'image/webp',
    avif: 'image/avif',
  };

  const img = await loadImage(URL.createObjectURL(file));
  const canvas = document.createElement('canvas');
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  canvas.getContext('2d').drawImage(img, 0, 0);

  return new Promise((resolve) =&amp;gt; {
    canvas.toBlob((blob) =&amp;gt; resolve(blob), mimeMap[targetFormat], quality);
  });
}

function loadImage(src) {
  return new Promise((resolve, reject) =&amp;gt; {
    const img = new Image();
    img.onload = () =&amp;gt; resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}
That's the entire conversion logic. The browser handles all the format-specific encoding internally.
What Happens to Transparency?
When converting PNG (with transparency) to JPEG (no transparency), the transparent areas become white or black depending on the browser:
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
Detecting Format Support
Not all browsers support all output formats. AVIF is still rolling out:
async function getSupportedFormats() {
  const canvas = document.createElement('canvas');
  canvas.width = 1;
  canvas.height = 1;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'red';
  ctx.fillRect(0, 0, 1, 1);

  const formats = ['image/jpeg', 'image/png', 'image/webp', 'image/avif'];
  const results = {};

  for (const mime of formats) {
    results[mime] = await new Promise((resolve) =&amp;gt; {
      canvas.toBlob((blob) =&amp;gt; resolve(blob?.type === mime), mime, 0.5);
    });
  }
  return results;
}
Batch Conversion Flow
async function batchConvert(files, targetFormat, quality) {
  const results = [];
  for (const file of files) {
    const blob = await convertImage(file, targetFormat, quality);
    const name = file.name.replace(/\.[^.]+$/, '') + '.' + targetFormat;
    results.push({ blob, name });
  }
  return results;
}
Supported Conversion Matrix
Source
JPEG
PNG
WebP
AVIF
GIF
SVG
HEIC/HEIF Input
iPhone photos in HEIC format are trickier — browsers can't decode them natively through &amp;lt;img&amp;gt;. The workaround is to use the image/heic MIME support detection (growing in Chrome) or decode via a WASM library like libheif-js.
The Tool
If you'd rather use a ready-made solution, ToolBox Image Converter (https://toolboximage.com/tools/converter/) handles all of this with a drag-drop UI, batch ZIP export, HEIC support, and adjustable quality — all client-side, zero uploads.
Try it at toolboximage.com/tools/converter/ (https://toolboximage.com/tools/converter/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Compress Images in the Browser with Canvas API (No Uploads, No Server)</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Sat, 04 Jul 2026 09:42:49 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/how-to-compress-images-in-the-browser-with-canvas-api-no-uploads-no-server-28j9</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/how-to-compress-images-in-the-browser-with-canvas-api-no-uploads-no-server-28j9</guid>
      <description>&lt;h1&gt;
  
  
  How to Compress Images in the Browser with Canvas API
&lt;/h1&gt;

&lt;p&gt;Every image you upload to a "free" online compressor is sent to a server — often without you knowing what happens to it afterward. For a tool that processes your private photos, that's a terrible design.&lt;/p&gt;

&lt;p&gt;Here's how to build (or use) an image compressor that runs entirely in the browser using the HTML5 Canvas API. No uploads, no server costs, and unlimited file sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Technique: Canvas &lt;code&gt;toBlob()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The key API is &lt;code&gt;HTMLCanvasElement.toBlob()&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
js
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

const img = new Image();
img.onload = () =&amp;gt; {
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  ctx.drawImage(img, 0, 0);

  canvas.toBlob((blob) =&amp;gt; {
    const url = URL.createObjectURL(blob);
  }, 'image/jpeg', 0.8);
};
img.src = 'your-image.jpg';
The second parameter is the MIME type (image/jpeg, image/png, image/webp, image/avif). The third is quality (0–1).
Step-Down Resizing for Large Images
If you're compressing a 6000×4000 px photo, drawing it at full resolution onto a canvas can eat 70+ MB of memory. Step-down resizing halves the dimensions repeatedly:
function stepDownEncode(img, maxDim, quality) {
  let w = img.naturalWidth;
  let h = img.naturalHeight;
  let src = img;

  while (w &amp;gt; maxDim * 2 || h &amp;gt; maxDim * 2) {
    w = Math.floor(w / 2);
    h = Math.floor(h / 2);
    const temp = document.createElement('canvas');
    temp.width = w;
    temp.height = h;
    temp.getContext('2d').drawImage(src, 0, 0, w, h);
    src = temp;
  }

  const canvas = document.createElement('canvas');
  canvas.width = w;
  canvas.height = h;
  canvas.getContext('2d').drawImage(src, 0, 0, w, h);

  return new Promise((resolve) =&amp;gt; {
    canvas.toBlob((blob) =&amp;gt; resolve(blob), 'image/jpeg', quality);
  });
}
This prevents memory crashes and actually produces better quality (step-down preserves more detail than a single jump).
Comparing Real-World Results
Format  Avg Original    Avg Compressed  Avg Savings
JPEG → JPEG (Q80) 3.2 MB  0.8 MB  75%
PNG → WebP (Q85)  4.8 MB  0.6 MB  87%
JPEG → AVIF (Q70) 3.2 MB  0.4 MB  87%
WebP consistently beats JPEG at the same visual quality by 25–35%. AVIF beats WebP by another 20–30%.
The Production Tool
If you don't want to build your own, the ToolBox Image Compressor (https://toolboximage.com/compressor/) implements all of this with a clean UI — drag &amp;amp; drop, batch processing (up to 200 images), side-by-side preview, and target-size compression. It's free, processes everything locally, and supports JPEG/PNG/WebP/AVIF/GIF input.
Key Takeaways
1. canvas.toBlob() is all you need for basic compression
2. Step-down resizing prevents memory issues on large images
3. WebP offers the best quality/size tradeoff for web use today
4. Client-side compression means zero server cost and zero privacy risk
Try the tool at toolboximage.com/compressor/ (https://toolboximage.com/compressor/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Privacy-First Image Compressor with WebAssembly</title>
      <dc:creator>Arhan Ahmad</dc:creator>
      <pubDate>Wed, 01 Jul 2026 10:45:32 +0000</pubDate>
      <link>https://dev.to/arhan_ahmad_a67ef7bd2b992/building-a-privacy-first-image-compressor-with-webassembly-1dfg</link>
      <guid>https://dev.to/arhan_ahmad_a67ef7bd2b992/building-a-privacy-first-image-compressor-with-webassembly-1dfg</guid>
      <description>&lt;p&gt;Most "free" image compressors upload your files to a server, cap you at 10 images, or watermark your output. I wanted something different.&lt;/p&gt;

&lt;p&gt;So I compiled Mozilla's mozJPEG, Google's libwebp, and libaom (AVIF) to WebAssembly using Emscripten. Each image compresses on a separate Web Worker, saturating all CPU cores. On an 8-core laptop, 8 images compress simultaneously. A batch of 200 photos finishes in seconds.&lt;/p&gt;

&lt;p&gt;No uploads. No limits. No data ever leaves your device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://toolboximage.com" rel="noopener noreferrer"&gt;https://toolboximage.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
