<?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: Mahendra Singh deora </title>
    <description>The latest articles on DEV Community by Mahendra Singh deora  (@viksa7726).</description>
    <link>https://dev.to/viksa7726</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%2F4190593%2F53096fd3-53ad-49ab-b57f-4cdeadab732e.png</url>
      <title>DEV Community: Mahendra Singh deora </title>
      <link>https://dev.to/viksa7726</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viksa7726"/>
    <language>en</language>
    <item>
      <title>How I built image and PDF tools that run in your browser, so the files stay on your device</title>
      <dc:creator>Mahendra Singh deora </dc:creator>
      <pubDate>Sun, 15 Nov 2026 06:40:48 +0000</pubDate>
      <link>https://dev.to/viksa7726/how-i-built-image-and-pdf-tools-that-run-in-your-browser-so-the-files-stay-on-your-device-3amh</link>
      <guid>https://dev.to/viksa7726/how-i-built-image-and-pdf-tools-that-run-in-your-browser-so-the-files-stay-on-your-device-3amh</guid>
      <description>&lt;p&gt;I didn't set out to build "another online tools site."&lt;/p&gt;

&lt;p&gt;I set out to stop a very specific kind of afternoon. In India, during admission and exam season, a school office or cyber café often gets one sheet with many photos and signatures on it. Someone then sits in a photo editor, cuts each one out, resizes it to 20 KB or 50 KB, and names the files. It takes ages, and the files are other people's identity documents.&lt;/p&gt;

&lt;p&gt;That is how Crop Desk started: a page where you can open that sheet in the browser, mark the boxes, and download the cropped files. The site is at &lt;a href="https://usecropdesk.com/" rel="noopener noreferrer"&gt;usecropdesk.com&lt;/a&gt;. It is free, you don't need an account, and it is an independent India-based project. It is not affiliated with any exam board or government department.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I didn't want to break
&lt;/h2&gt;

&lt;p&gt;Once you are dealing with ID photos, signatures, and PDFs of forms, uploading those files to a server "just to crop them" feels like the wrong default.&lt;/p&gt;

&lt;p&gt;So the product rule became: &lt;strong&gt;most tools should run in the browser&lt;/strong&gt;. The file is chosen on the user's device, processed there, and downloaded from there. It should not have to sit on my server for the tool to do its job.&lt;/p&gt;

&lt;p&gt;"Most tools" is the honest wording — I won't pretend every current and future feature is identical. The usual photo and document jobs are designed to stay on the device, and you can use them without signing up. An optional free account exists only to save your own work, such as typing-test history or a resume draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "in the browser" actually means for the user
&lt;/h2&gt;

&lt;p&gt;The flow is ordinary: open the page, pick a file, adjust the crop or the size, download the result. There is no "upload and wait for our server" step, because the work is not supposed to leave the device. In practice that uses what the browser already gives you — the file picker, in-memory image and PDF data, an on-screen preview, and a local download.&lt;/p&gt;

&lt;p&gt;The trade-offs are ordinary too. You need a reasonably modern browser, and a network connection to load the page. A very large file can strain an older phone. The site can also be installed as a Windows app from Chrome or Edge, or added to an iPhone home screen from Safari — that is the same website, in its own window.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools that grew out of the first one
&lt;/h2&gt;

&lt;p&gt;Once the bulk cropper existed, the next requests were predictable, because they come from the same forms.&lt;/p&gt;

&lt;p&gt;People needed to hit a &lt;strong&gt;20 KB, 50 KB, 100 KB or 200 KB&lt;/strong&gt; cap without destroying the face or the signature. A generic "compress this image" button is not enough. The preview has to be good enough that you can see whether a form is likely to reject the file.&lt;/p&gt;

&lt;p&gt;They needed passport-size photos: the usual India sizes, and also a dedicated &lt;strong&gt;2×2 inch US passport photo&lt;/strong&gt; page with a 4×6 print sheet.&lt;/p&gt;

&lt;p&gt;They needed everyday PDF jobs in the same place: compress (including common KB targets), merge, sign, watermark, and a few conversions such as Word to PDF.&lt;/p&gt;

&lt;p&gt;And, because this is India, they needed &lt;strong&gt;Hindi typing&lt;/strong&gt; (Kruti Dev 010 and Mangal/Inscript) and a Kruti Dev ↔ Unicode converter.&lt;/p&gt;

&lt;p&gt;Later I added only adjacent jobs: a resume builder with PDF download, a document scanner that flattens a phone photo, a signature background remover, a photo-and-signature joiner, HEIC to JPG, and image metadata removal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still tell people
&lt;/h2&gt;

&lt;p&gt;If an exam form says "photo under 20 KB, 3.5 × 4.5 cm, JPEG," the official notification is still the source of truth. I publish plain-language guides, and I try to keep them careful, but requirements change. I am not an exam body, and I don't want the site to sound like one.&lt;/p&gt;

&lt;p&gt;The other thing I still want to improve is first-visit clarity. If someone lands on the homepage, can they tell — without reading a privacy policy — that the file stays on their device? That matters more to me than adding another tool.&lt;/p&gt;

&lt;p&gt;If you try &lt;a href="https://usecropdesk.com/" rel="noopener noreferrer"&gt;Crop Desk&lt;/a&gt; and something in that flow is unclear, I would rather hear that than a compliment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance and reviewed by the maker.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Hindi Typing Test in the Browser: Mangal, Kruti Dev and Custom Text</title>
      <dc:creator>Mahendra Singh deora </dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:45:59 +0000</pubDate>
      <link>https://dev.to/viksa7726/hindi-typing-test-in-the-browser-mangal-kruti-dev-and-custom-text-20db</link>
      <guid>https://dev.to/viksa7726/hindi-typing-test-in-the-browser-mangal-kruti-dev-and-custom-text-20db</guid>
      <description>&lt;p&gt;Government typing exams in India (SSC, state clerk, court exams) often test Hindi typing in &lt;strong&gt;Mangal (Unicode/Inscript)&lt;/strong&gt; or &lt;strong&gt;Kruti Dev&lt;/strong&gt; layouts. Most free practice sites only support English, so I built a browser-based typing test that covers all three.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;English, Mangal and Kruti Dev modes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom text&lt;/strong&gt;: paste your own passage or upload a &lt;code&gt;.txt&lt;/code&gt; file and start testing immediately&lt;/li&gt;
&lt;li&gt;Long practice paragraphs, so text doesn't run out even in a 15-minute test&lt;/li&gt;
&lt;li&gt;WPM, accuracy and error highlighting&lt;/li&gt;
&lt;li&gt;Everything runs client-side; your text never leaves the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why custom text matters
&lt;/h2&gt;

&lt;p&gt;Exam passages have their own vocabulary (official notices, legal terms). Practising with a passage from a previous paper is far more useful than random sentences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cropdesk.co.in/krutidev-to-unicode/" rel="noopener noreferrer"&gt;Unicode to Kruti Dev converter&lt;/a&gt; that keeps English words, numbers and symbols like ₹ and % unchanged&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cropdesk.co.in/all-tools/" rel="noopener noreferrer"&gt;All free tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it here: &lt;a href="https://cropdesk.co.in/typing-test/" rel="noopener noreferrer"&gt;Crop Desk Typing Test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback welcome in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How I built a photo &amp; signature cropper that never uploads your files</title>
      <dc:creator>Mahendra Singh deora </dc:creator>
      <pubDate>Sat, 17 Oct 2026 13:58:07 +0000</pubDate>
      <link>https://dev.to/viksa7726/how-i-built-a-photo-signature-cropper-that-never-uploads-your-files-2b72</link>
      <guid>https://dev.to/viksa7726/how-i-built-a-photo-signature-cropper-that-never-uploads-your-files-2b72</guid>
      <description>&lt;p&gt;Online forms are picky. A passport photo must be 2×2 inches. A signature must sit under 20 KB. A school office has one scanned sheet with thirty student photos and needs thirty clean files by tomorrow.&lt;/p&gt;

&lt;p&gt;Most “free” tools solve that by uploading your ID photo to a server. I did not want that for Crop Desk, so every tool on &lt;a href="https://cropdesk.co.in" rel="noopener noreferrer"&gt;cropdesk.co.in&lt;/a&gt; runs in the browser. The sheet, the crop, the ZIP, and the JPEG encode all stay on the device.&lt;/p&gt;

&lt;p&gt;This post walks through three pieces: canvas cropping, sheet auto-detection with thresholding and connected components, and hitting an exact KB target with a binary search on JPEG quality. The rest of the toolkit (PDF merge/split/compress, image↔PDF, format conversion, age calculator) follows the same privacy model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why client-side only
&lt;/h2&gt;

&lt;p&gt;The data path is intentionally short:&lt;/p&gt;

&lt;p&gt;File → &lt;code&gt;FileReader&lt;/code&gt; / &lt;code&gt;createImageBitmap&lt;/code&gt; → draw on &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; → &lt;code&gt;canvas.toBlob&lt;/code&gt; → download or JSZip.&lt;/p&gt;

&lt;p&gt;There is no upload step in the happy path. After the page loads, the tools keep working offline. Ads pay for hosting; there is no account and no “pro” paywall for basic crops.&lt;/p&gt;

&lt;p&gt;That constraint shaped the algorithms. No cloud vision API for box detection. No server-side ImageMagick for compression. Everything has to finish in a second or two on a mid-range Android phone, because that is where a lot of form uploads actually happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canvas cropping (the boring foundation)
&lt;/h2&gt;

&lt;p&gt;Every crop is the same pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the image into an &lt;code&gt;HTMLImageElement&lt;/code&gt; or &lt;code&gt;ImageBitmap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a canvas sized to the &lt;strong&gt;output&lt;/strong&gt; pixels (e.g. 600×600 for a US 2×2 at 300 DPI, or whatever the preset needs).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drawImage(source, sx, sy, sw, sh, 0, 0, dw, dh)&lt;/code&gt; to copy the selected region and scale it in one call.&lt;/li&gt;
&lt;li&gt;Export with &lt;code&gt;toBlob('image/jpeg', quality)&lt;/code&gt; or PNG/WebP.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Presets (US 2×2, 35×45 mm, Instagram, YouTube thumbnails, and similar) are mostly metadata: target width/height, optional print DPI label written into the file, and UI guides such as head-size lines for passport photos. The crop math does not care which portal you are filling — only the pixels you ask for.&lt;/p&gt;

&lt;p&gt;For print-oriented sizes, convert millimetres or inches to pixels with &lt;code&gt;px = mm / 25.4 * dpi&lt;/code&gt; (or inches × dpi). DPI itself does not change pixel data; some forms still check the EXIF/JFIF density tag, so a small metadata pass helps without re-encoding quality.&lt;/p&gt;

&lt;p&gt;Signature background removal is the same canvas pipeline with an extra pixel pass: after drawing the crop, treat near-white (or near-paper) samples as background — either fill them white for JPG forms, or set alpha to 0 when exporting PNG.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-detecting photos and signatures on a sheet
&lt;/h2&gt;

&lt;p&gt;The bulk tool is for a common real workflow: one A4 scan (or phone photo of a sheet) with a grid of passport photos and signature boxes. Manual cropping per person is slow and easy to mis-number when you are preparing a whole class.&lt;/p&gt;

&lt;p&gt;Rough pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Downscale&lt;/strong&gt; a working copy so luminance and labeling loops stay cheap (detection runs on the small image; crops still use full resolution).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold&lt;/strong&gt; to a binary mask. Scanned forms are usually dark ink/photos on light paper, so a luminance threshold — or a simple adaptive threshold when lighting is uneven — separates “ink/photo” from background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connected components&lt;/strong&gt; on the foreground mask (4-connectivity via union-find or two-pass labeling). Each blob gets a bounding box and area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt; dust, staple marks, and full-page borders using minimum area and aspect-ratio bounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify&lt;/strong&gt; remaining boxes: larger, squarer regions tend to be photos; wider, shorter ones tend to be signatures. Layout heuristics (Sheet A: signature above photo; Sheet B: photo above signature) pair them and sort in reading order (top-to-bottom, then left-to-right within a row).&lt;/li&gt;
&lt;li&gt;Draw green / dashed overlays so the user can nudge one box or all boxes, or scale them slightly, before export.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Edge cases are honest limitations: skewed phone photos, heavy shadows, patterned table backgrounds, and unusual form layouts. For those, Blank mode lets you draw boxes, add a grid, or reuse a saved layout from last week’s batch. Detection is a helper, not a guarantee.&lt;/p&gt;

&lt;p&gt;When you confirm, each box is mapped back to full-resolution coordinates, cropped on a canvas, and named in order: &lt;code&gt;1_photo.jpg&lt;/code&gt;, &lt;code&gt;1_sign.jpg&lt;/code&gt;, &lt;code&gt;2_photo.jpg&lt;/code&gt;, … then packed with JSZip in the browser. Numbers follow box order on the sheet, so a numbered attendance list stays aligned with the ZIP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compressing to an exact KB target
&lt;/h2&gt;

&lt;p&gt;Upload portals say “photo max 50 KB,” not “quality 0.72.” JPEG size is not linear in the quality slider, but it &lt;strong&gt;is&lt;/strong&gt; monotonic at fixed dimensions: higher quality never produces a smaller file. That makes binary search a clean fit.&lt;/p&gt;

&lt;p&gt;Sketch of the approach used in the compressor:&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;fitQuality&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;targetBytes&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;floorQ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&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;ceilQ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.95&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;atCeil&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;canvasToJpeg&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;ceilQ&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;atCeil&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;targetBytes&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;atCeil&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;atFloor&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;canvasToJpeg&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;floorQ&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;atFloor&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;gt;&lt;/span&gt; &lt;span class="nx"&gt;targetBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// need smaller dimensions&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="nx"&gt;floorQ&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="nx"&gt;ceilQ&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;atFloor&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;mid&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;canvasToJpeg&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;mid&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;targetBytes&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;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// try higher quality&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// too big — lower quality&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;canvasToJpeg&lt;/code&gt; is just &lt;code&gt;canvas.toBlob(..., 'image/jpeg', q)&lt;/code&gt; wrapped in a Promise. Eight iterations are enough to land close to the best quality that still fits; more iterations give diminishing returns because the encoder’s quality scale is coarse.&lt;/p&gt;

&lt;p&gt;If even floor quality is too big, quality alone cannot win. Encoded size scales roughly with pixel area, so the fallback shrinks width and height by about &lt;code&gt;sqrt(targetBytes / currentBytes)&lt;/code&gt;, redraws onto a smaller canvas, and runs the quality search again. Landing a little under the target is normal and desirable — portals accept “≤ 50 KB,” and JPEG entropy depends on how detailed the picture is.&lt;/p&gt;

&lt;p&gt;The same pattern backs “resize and stay under N KB” flows: lock the required pixel size first when the form demands it, search quality, and only shrink further if the floor still overshoots.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDFs and the rest of the toolkit
&lt;/h2&gt;

&lt;p&gt;Not every rejection is about photos. PDF merge, split, rearrange, rotate, and compress use client-side PDF libraries with the same privacy model: parse in memory, rewrite pages, download. Image→PDF draws canvas pages into a PDF; PDF→image renders pages back out. JPG/PNG/WebP conversion is decode → canvas → encode (with a chosen background fill when flattening transparency to JPG). An age calculator sits alongside for forms that ask for exact age on a reference date.&lt;/p&gt;

&lt;p&gt;None of that requires your files on my server. Practical write-ups for passport sizes, KB limits, signatures, and HEIC quirks live at &lt;a href="https://cropdesk.co.in/guides/" rel="noopener noreferrer"&gt;cropdesk.co.in/guides&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not claiming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Auto-detect will not nail every crumpled phone photo or fancy coloured form.&lt;/li&gt;
&lt;li&gt;Hitting “exactly 50.0 KB” is less useful than “highest quality under 50 KB.”&lt;/li&gt;
&lt;li&gt;Crop Desk is not affiliated with any exam board, university, or government portal — always check the official notification for sizes and formats.&lt;/li&gt;
&lt;li&gt;The site is free and ad-supported; ads are how hosting stays paid without a sign-up wall.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;If you prepare form uploads, run a cyber café / CSC desk, teach a class that submits photos in bulk, or just hate uploading ID photos to random compressors, try the tools at &lt;strong&gt;&lt;a href="https://cropdesk.co.in" rel="noopener noreferrer"&gt;https://cropdesk.co.in&lt;/a&gt;&lt;/strong&gt;. Feedback on failed sheet detections, missing country presets, and PDF edge cases is welcome — those reports steer what gets fixed next.&lt;/p&gt;

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