<?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: ZephyrTran</title>
    <description>The latest articles on DEV Community by ZephyrTran (@zephyrtran).</description>
    <link>https://dev.to/zephyrtran</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%2F4067011%2F6214bc76-522d-4a61-8f86-9c018f2249dc.jpg</url>
      <title>DEV Community: ZephyrTran</title>
      <link>https://dev.to/zephyrtran</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zephyrtran"/>
    <language>en</language>
    <item>
      <title>Running an AI image upscaler &amp; sharpener 100% in the browser (TensorFlow.js)</title>
      <dc:creator>ZephyrTran</dc:creator>
      <pubDate>Tue, 11 Aug 2026 03:47:29 +0000</pubDate>
      <link>https://dev.to/zephyrtran/running-an-ai-image-upscaler-sharpener-100-in-the-browser-tensorflowjs-47b8</link>
      <guid>https://dev.to/zephyrtran/running-an-ai-image-upscaler-sharpener-100-in-the-browser-tensorflowjs-47b8</guid>
      <description>&lt;p&gt;I wanted to sharpen and upscale images &lt;strong&gt;without uploading them to some server&lt;/strong&gt;. It turns out you can run the whole ML model right in the browser — the image never leaves the device. Here's what I learned shipping it as two free tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TensorFlow.js&lt;/strong&gt; (WebGL backend) for inference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UpscalerJS&lt;/strong&gt; as a thin wrapper around an &lt;strong&gt;ESRGAN&lt;/strong&gt; super-resolution model&lt;/li&gt;
&lt;li&gt;All loaded from a CDN — no build step, no backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core is one call: &lt;code&gt;new Upscaler({ model })&lt;/code&gt;, then &lt;code&gt;await upscaler.upscale(img, { patchSize: 64, padding: 4, progress })&lt;/code&gt;. The &lt;code&gt;patchSize&lt;/code&gt; option is the important one — more on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas I hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory.&lt;/strong&gt; Running 4× on a large image tries to allocate a huge tensor and the tab dies. The fix is &lt;code&gt;patchSize&lt;/code&gt; — process the image in tiles and stitch them back, so memory stays bounded regardless of input size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model choice matters more than I expected.&lt;/strong&gt; I benchmarked three ESRGAN variants on the same image: &lt;code&gt;slim&lt;/code&gt; is fast (~2.5s on a small image) but slightly soft, &lt;code&gt;medium&lt;/code&gt; had visible tiling artifacts (rejected), and &lt;code&gt;thick&lt;/code&gt; was clearly the sharpest but ~3× slower. So I default to &lt;code&gt;slim&lt;/code&gt; and offer &lt;code&gt;thick&lt;/code&gt; as a "max detail" mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lightweight upscalers smooth the image.&lt;/strong&gt; ESRGAN-slim enlarges cleanly but the result can look soft. A small unsharp-mask pass afterward restores the bite without an obvious "sharpened" halo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharpen vs upscale are different jobs.&lt;/strong&gt; To make a sharpener that keeps the original size, I run the same model then draw the result back down to native dimensions — the AI detail survives the downscale, so you get a clearer image at the same size. That became the &lt;a href="https://snapvi.app/sharpen-image" rel="noopener noreferrer"&gt;AI photo sharpener&lt;/a&gt;; the enlarge-2×/4× version is the &lt;a href="https://snapvi.app/image-upscaler" rel="noopener noreferrer"&gt;AI image upscaler&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keep it client-side?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt; — the image is never uploaded. Great for documents, IDs, personal photos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt; — it's a static site on Cloudflare Pages, so there's no inference server to pay for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off is a one-time model download and slower runs on weak phones, which I gate with size caps and a fast/max toggle.&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the TF.js side — what would you run in-browser next?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>machinelearning</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a 100% client-side image &amp; PDF toolkit — no uploads, no server</title>
      <dc:creator>ZephyrTran</dc:creator>
      <pubDate>Fri, 07 Aug 2026 07:05:15 +0000</pubDate>
      <link>https://dev.to/zephyrtran/i-built-a-100-client-side-image-pdf-toolkit-no-uploads-no-server-133f</link>
      <guid>https://dev.to/zephyrtran/i-built-a-100-client-side-image-pdf-toolkit-no-uploads-no-server-133f</guid>
      <description>&lt;p&gt;I kept reaching for online tools to compress an image, convert a PDF, or grab audio from a video — and every one of them uploads your file to a server. For anything remotely personal, that felt wrong. So I built &lt;a href="https://snapvi.app" rel="noopener noreferrer"&gt;Snapvi&lt;/a&gt;: a set of free image / PDF / video tools that run &lt;strong&gt;entirely in the browser&lt;/strong&gt;. Your file never leaves your device.&lt;/p&gt;

&lt;p&gt;A few things I learned building it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;canvas&lt;/code&gt; + &lt;code&gt;toBlob&lt;/code&gt;&lt;/strong&gt; covers a surprising amount: format conversion (incl. WebP), resizing, cropping, collage — all client-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pdf-lib&lt;/strong&gt; and &lt;strong&gt;pdf.js&lt;/strong&gt; handle merge / split / compress / page-numbering / PDF↔image without a backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Audio &lt;code&gt;decodeAudioData&lt;/code&gt;&lt;/strong&gt; + a small JS MP3 encoder (lamejs) extracts audio from a video to MP3/WAV locally.&lt;/li&gt;
&lt;li&gt;For the AI tools (background removal, &lt;a href="https://snapvi.app/image-upscaler" rel="noopener noreferrer"&gt;image upscaler&lt;/a&gt;, &lt;a href="https://snapvi.app/sharpen-image" rel="noopener noreferrer"&gt;sharpener&lt;/a&gt;) I run the models in-browser via TensorFlow.js — the image still never uploads. The trade-off is the model download + slower on weak phones, which I gate with size caps and a "fast vs max" toggle.&lt;/li&gt;
&lt;li&gt;Lossless EXIF/GPS stripping is just parsing the JPEG APP segments and dropping them — no re-encode. Turned that into a &lt;a href="https://snapvi.app/remove-metadata" rel="noopener noreferrer"&gt;metadata remover&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a static site on Cloudflare Pages, so hosting is basically free and there's no server to leak anything.&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the client-side approach — what would you want to see run in-browser next?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
