<?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: Devanshu Verma</title>
    <description>The latest articles on DEV Community by Devanshu Verma (@devanshu_verma).</description>
    <link>https://dev.to/devanshu_verma</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%2F2984386%2F6633aa96-f8ec-45fb-8069-9d8ee51bf992.jpeg</url>
      <title>DEV Community: Devanshu Verma</title>
      <link>https://dev.to/devanshu_verma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devanshu_verma"/>
    <language>en</language>
    <item>
      <title>I got tired of sketchy photo resizers, so I built one that never uploads your photo</title>
      <dc:creator>Devanshu Verma</dc:creator>
      <pubDate>Wed, 07 Oct 2026 13:00:51 +0000</pubDate>
      <link>https://dev.to/devanshu_verma/i-got-tired-of-sketchy-photo-resizers-so-i-built-one-that-never-uploads-your-photo-4971</link>
      <guid>https://dev.to/devanshu_verma/i-got-tired-of-sketchy-photo-resizers-so-i-built-one-that-never-uploads-your-photo-4971</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fformpic.devanshuverma.in%2Fopengraph-image" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fformpic.devanshuverma.in%2Fopengraph-image" alt="FormPic: crop and resize photos for forms" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've applied for anything in India (a passport, a PAN card, UPSC, NEET, a bank exam), you've seen some version of this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Upload photograph: 35×45 mm, JPEG, under 50 KB."&lt;br&gt;
"Upload signature: 60×20 mm, 10 to 20 KB."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You crop it in some app, export it, upload it, and the portal says no. Wrong dimensions. Or 53 KB. Try again.&lt;/p&gt;

&lt;p&gt;The usual fix is to search "photo resizer online" and pick whichever site loads first. Those sites are covered in ads, and most of them upload your photo to a server. It's your face, going into an ID application, sitting on a machine you know nothing about. That bugged me enough that I built my own: &lt;a href="https://formpic.devanshuverma.in/" rel="noopener noreferrer"&gt;FormPic&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You drop in a photo, pick the form, drag the crop box over your face and download. The file comes out at the exact pixel size the form wants and under the file size limit you set.&lt;/p&gt;

&lt;p&gt;The part I care about most: the photo never leaves your browser. There's no upload step at all. It's free, there's no account, and there's no watermark. Once the page has loaded it keeps working offline, and you can take the photo with your phone or laptop camera instead of uploading one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it here: &lt;a href="https://formpic.devanshuverma.in/" rel="noopener noreferrer"&gt;formpic.devanshuverma.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The sizes it knows about
&lt;/h2&gt;

&lt;p&gt;These are the presets right now:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document&lt;/th&gt;
&lt;th&gt;Print size&lt;/th&gt;
&lt;th&gt;Pixels&lt;/th&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Passport size&lt;/td&gt;
&lt;td&gt;35×45 mm&lt;/td&gt;
&lt;td&gt;413 × 531 px&lt;/td&gt;
&lt;td&gt;300 DPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;US passport &amp;amp; visa&lt;/td&gt;
&lt;td&gt;2×2 in&lt;/td&gt;
&lt;td&gt;600 × 600 px&lt;/td&gt;
&lt;td&gt;300 DPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PAN card&lt;/td&gt;
&lt;td&gt;25×35 mm&lt;/td&gt;
&lt;td&gt;197 × 276 px&lt;/td&gt;
&lt;td&gt;200 DPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPSC&lt;/td&gt;
&lt;td&gt;35×45 mm&lt;/td&gt;
&lt;td&gt;413 × 531 px&lt;/td&gt;
&lt;td&gt;300 DPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NEET&lt;/td&gt;
&lt;td&gt;35×45 mm&lt;/td&gt;
&lt;td&gt;413 × 531 px&lt;/td&gt;
&lt;td&gt;300 DPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IBPS&lt;/td&gt;
&lt;td&gt;35×45 mm&lt;/td&gt;
&lt;td&gt;200 × 230 px&lt;/td&gt;
&lt;td&gt;set in pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSC signature&lt;/td&gt;
&lt;td&gt;60×20 mm&lt;/td&gt;
&lt;td&gt;472 × 157 px&lt;/td&gt;
&lt;td&gt;200 DPI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your form isn't listed, you can type any width and height in pixels and lock the aspect ratio.&lt;/p&gt;

&lt;p&gt;What makes these annoying is that you have to get the aspect ratio, the pixel dimensions and the file size right at the same time. Most tools I tried handled one of them and left the rest to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works without a server
&lt;/h2&gt;

&lt;p&gt;A browser already has everything a photo resizer needs. You read the file, draw the cropped part onto a canvas at the target size, and encode it. With no backend there's nothing to store and nothing to leak, and no reason the tool can't run offline.&lt;/p&gt;

&lt;p&gt;Here's a simplified sketch of the crop step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cropAndResize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageSmoothingQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// source rect (the crop box) -&amp;gt; destination rect (the form size)&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting under 50 KB without guessing
&lt;/h3&gt;

&lt;p&gt;This was the fun bit. JPEG file size doesn't go up in a straight line with quality, so you can't just say "50 KB means quality 0.7." What does work is a binary search over the quality value until you find the highest one that still fits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fitToSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// null means it's too big even at the lowest quality&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight rounds gets you within about 0.4% of the best quality, which is plenty. If the photo is still too big at the lowest quality, there's a "Shrink to fit" option that lowers the pixel size too.&lt;/p&gt;

&lt;p&gt;A few smaller things I spent time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Picking a form preset switches the output to JPEG, since that's what nearly every portal asks for. PNG is still there if you want a lossless copy to print.&lt;/li&gt;
&lt;li&gt;The preset outlines are drawn to scale, so you can see the shape before you choose.&lt;/li&gt;
&lt;li&gt;The Download button shows the final file size. You find out it's 48 KB before you're stuck on a slow government site at midnight, not after.&lt;/li&gt;
&lt;li&gt;Nothing is kept. Close the tab and the photo is gone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;It won't change your background to white. I went back and forth on this, but doing it well in the browser is hard, and doing it badly looks worse than a plain wall. For now my advice is to stand in front of a white or light wall with even light. That fixes it most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's a passport photo in pixels?&lt;/strong&gt; 35 × 45 mm at 300 DPI is 413 × 531 px. Some portals want a different upload size, though, so check the form and type its numbers in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I get a photo under 20 KB?&lt;/strong&gt; Pick JPEG, set Max file size to 20, and download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it really not uploaded?&lt;/strong&gt; Open DevTools, go to the Network tab and try it. You won't see the image go anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell me what's missing
&lt;/h2&gt;

&lt;p&gt;I build and run FormPic on my own, and I add presets based on what people ask for. If your form or country needs a size that's not there, leave a comment with the form name, the size (mm or px) and any file size limit, and I'll add it.&lt;/p&gt;

&lt;p&gt;I'd also like to hear from other devs about the UX, or about how you'd handle background removal client-side without shipping a huge model.&lt;/p&gt;

&lt;p&gt;And if you know someone who's about to fight an exam portal, send them the link. It might save them an evening.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://formpic.devanshuverma.in/" rel="noopener noreferrer"&gt;formpic.devanshuverma.in&lt;/a&gt;&lt;/p&gt;

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