<?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: tarunvashishth</title>
    <description>The latest articles on DEV Community by tarunvashishth (@tarunvashishth).</description>
    <link>https://dev.to/tarunvashishth</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%2F966574%2Fe8c06065-3596-4ab5-b05f-5206ee73d7b4.png</url>
      <title>DEV Community: tarunvashishth</title>
      <link>https://dev.to/tarunvashishth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tarunvashishth"/>
    <language>en</language>
    <item>
      <title>I lay down. A 0.23B open model sorted my screenshots folder.</title>
      <dc:creator>tarunvashishth</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:52:25 +0000</pubDate>
      <link>https://dev.to/tarunvashishth/i-lay-down-a-023b-open-model-sorted-my-screenshots-folder-2g65</link>
      <guid>https://dev.to/tarunvashishth/i-lay-down-a-023b-open-model-sorted-my-screenshots-folder-2g65</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Somewhere on your laptop there's a folder that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Screenshot 2026-10-03 at 13.04.11.png
Screenshot 2026-10-03 at 13.04.11 (2).jpg
Screenshot 2026-10-06 at 22.17.40.png
Screenshot 2026-10-09 at 10.02.55.png
IMG_4821.PNG
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every one of those was taken on purpose, to keep something. A receipt for a refund. The error message you meant to paste into an issue. The train ticket. Then they all got the same useless name, and now finding the receipt means opening forty files.&lt;/p&gt;

&lt;p&gt;Sorting that folder is the purest kind of chore: low stakes, mildly gross, and always postponed. In honour of International Sloth Day, I built something that does it while I lie down.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Screenshot Sloth&lt;/strong&gt; reads every screenshot in a folder with an open-source vision model, &lt;strong&gt;inside your browser tab&lt;/strong&gt;, and proposes a plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rename&lt;/strong&gt; each file for what's on it: &lt;code&gt;Screenshot 2026-10-03 at 13.04.11.png&lt;/code&gt; becomes &lt;code&gt;2026-10-03-receipt-tandoor-express.png&lt;/code&gt;, and &lt;code&gt;IMG_4821.PNG&lt;/code&gt; becomes &lt;code&gt;2026-10-11-ticket-shatabdi-express-12002.png&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File&lt;/strong&gt; them into folders: Receipts, Chats, Errors, Code, Tickets, Maps, Documents, Photos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag&lt;/strong&gt; the ones holding a one-time code, card number, Aadhaar, PAN, UPI ID or password, and put them in one &lt;code&gt;_Review (sensitive)&lt;/code&gt; folder so you can look once and delete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch duplicates&lt;/strong&gt; (that &lt;code&gt;(2).jpg&lt;/code&gt; you saved twice) and set the extras aside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You see the whole plan before anything moves. Fix any name, untick anything you want left alone, then apply. In Chrome and Edge it moves the files in place and logs every move, so &lt;strong&gt;Undo&lt;/strong&gt; puts every file back where it was. Other browsers get a sorted &lt;code&gt;.zip&lt;/code&gt;, and your originals stay put.&lt;/p&gt;

&lt;p&gt;It's for anyone with a Desktop full of screenshots, which is everyone. It's especially for people whose screenshots are mostly UPI receipts, OTP messages and WhatsApp chats, which is a lot of us in India.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://tarunvashishth.github.io/screenshot-sloth/" rel="noopener noreferrer"&gt;https://tarunvashishth.github.io/screenshot-sloth/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcl5k37pg5qbrbaj7le8i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcl5k37pg5qbrbaj7le8i.gif" alt="Screenshot Sloth reads 12 demo screenshots, flags an OTP and a card number, sets aside a duplicate and files the rest into folders with new names" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Try the demo desktop&lt;/strong&gt; to run it on 12 synthetic screenshots drawn in the page: a food-delivery receipt, a build error, a chat, an OTP SMS, a train ticket, a test credit card, a map, a saved-twice duplicate and more. All of it is fake data, so you can watch the whole flow without pointing it at your own files.&lt;/p&gt;

&lt;p&gt;The first run downloads the model once (about 350 MB, then cached). After that the demo goes like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Screenshot 2026-10-03 at 13.04.11.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Receipts/2026-10-03-receipt-tandoor-express.png&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Screenshot 2026-10-06 at 22.17.40.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Errors/2026-10-06-error-build-failed.png&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Screenshot_20261008-191502_WhatsApp.jpg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Chats/2026-10-08-chat-whatsapp-rohan.jpg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Screenshot 2026-10-09 at 10.02.55.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_Review (sensitive)/…&lt;/code&gt; flagged &lt;strong&gt;One-time code ••••••&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Screenshot 2026-10-12 at 09.12.31.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_Review (sensitive)/…&lt;/code&gt; flagged &lt;strong&gt;Card number •••• 1111&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Screenshot 2026-10-03 at 13.04.11 (2).jpg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_Duplicates/2026-10-03-receipt-tandoor-express.jpg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tarunvashishth" rel="noopener noreferrer"&gt;
        tarunvashishth
      &lt;/a&gt; / &lt;a href="https://github.com/tarunvashishth/screenshot-sloth" rel="noopener noreferrer"&gt;
        screenshot-sloth
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sorts your screenshots folder: an open-source vision model (Florence-2) reads each one in your browser, renames and files it, and flags OTPs and card numbers. Nothing is uploaded.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Screenshot Sloth 🦥&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Your screenshots folder, sorted while you lie down.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tarunvashishth/screenshot-sloth/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftarunvashishth%2Fscreenshot-sloth%2FHEAD%2Fdocs%2Fdemo.gif" alt="Screenshot Sloth sorting the demo desktop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://tarunvashishth.github.io/screenshot-sloth/" rel="nofollow noopener noreferrer"&gt;https://tarunvashishth.github.io/screenshot-sloth/&lt;/a&gt; (Chrome or Edge to sort in place; any browser for the demo)&lt;/p&gt;

&lt;p&gt;Point it at a folder full of &lt;code&gt;Screenshot 2026-10-03 at 14.22.01.png&lt;/code&gt; files. An open-source vision model
reads every one &lt;strong&gt;in your browser tab&lt;/strong&gt;, then proposes a plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rename&lt;/strong&gt; each file for what's on it: &lt;code&gt;2026-10-03-receipt-tandoor-express.png&lt;/code&gt;, &lt;code&gt;2026-10-06-error-build-failed.png&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File&lt;/strong&gt; them into folders: Receipts, Chats, Errors, Code, Tickets, Maps, Documents, Photos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag&lt;/strong&gt; the ones holding one-time codes, card numbers, Aadhaar, PAN, UPI IDs or passwords, and put them together
in &lt;code&gt;_Review (sensitive)&lt;/code&gt; so you can look once and delete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch duplicates&lt;/strong&gt; (the same screenshot saved twice) and set the extras aside in &lt;code&gt;_Duplicates&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You see the whole plan first, can fix any name or untick anything, then apply. Every move is logged to
&lt;code&gt;.screenshot-sloth-undo.json&lt;/code&gt; in the folder, and &lt;strong&gt;Undo&lt;/strong&gt; puts every file back.&lt;/p&gt;
&lt;p&gt;Nothing is…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tarunvashishth/screenshot-sloth" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;TypeScript and Vite, with no backend. The model runs in a Web Worker; &lt;code&gt;rules.ts&lt;/code&gt; holds everything that decides names, folders and flags, and it has its own tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The model reads; plain code decides
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/microsoft/Florence-2-base-ft" rel="noopener noreferrer"&gt;Florence-2-base-ft&lt;/a&gt; is Microsoft's MIT-licensed vision model, small enough (0.23B parameters) to run in a browser through &lt;a href="https://github.com/huggingface/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; on WebGPU, with a WASM fallback. I used it for my Week 1 entry too, and it's very good at one thing I needed here: &lt;code&gt;&amp;lt;OCR_WITH_REGION&amp;gt;&lt;/code&gt;, which returns every line of text &lt;em&gt;with its bounding box&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The boxes turned out to be the most useful part. A screen's title is almost always its biggest text, so the name comes from the tallest line that isn't phone chrome: not the clock, not the battery, not a "Start navigation" button. Filenames carry the rest. macOS and Windows names hold the date, and Android ones hold the app (&lt;code&gt;Screenshot_20261008-191502_WhatsApp.jpg&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I deliberately didn't ask the model to "categorise this screenshot". A 0.23B model's free-text guess is unpredictable, while OCR text plus small, testable rules is something I can debug. When a screenshot lands in the wrong folder, I can see why and write a test for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tall screenshots get read in strips
&lt;/h3&gt;

&lt;p&gt;Florence squashes every input to 768×768. A 1179×2556 phone screenshot squashed that far loses all its small text, so each screenshot is read as up to three square-ish strips from the top down, and the boxes are mapped back to full-image coordinates so the "tallest line" comparison still works across strips.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sensitive data: a pattern isn't enough, so check the checksum
&lt;/h3&gt;

&lt;p&gt;Screenshots are full of long numbers: order IDs, phone numbers, amounts. Flagging every 16-digit number would cry wolf, so each finding has to pass a real check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cards&lt;/strong&gt; must pass the Luhn checksum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aadhaar&lt;/strong&gt; numbers must pass the Verhoeff checksum, which UIDAI uses for the last digit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PAN&lt;/strong&gt; must match its actual structure (the fourth letter is the holder type).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTPs&lt;/strong&gt; only count when a 4–8 digit code sits next to words like "OTP" or "verification code".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Findings are always shown masked (&lt;code&gt;•••• 1111&lt;/code&gt;), so the review screen doesn't become a second leak.&lt;/p&gt;

&lt;p&gt;My favourite bug: the demo's OTP message wasn't flagged. The model had read &lt;em&gt;"Your OTP for login is 482913."&lt;/em&gt; perfectly. My regex refused to match &lt;code&gt;482913&lt;/code&gt; because a full stop followed it, and I'd written that check to skip decimals like &lt;code&gt;1299.50&lt;/code&gt;. The model was fine; my rule was the bug. That's the argument for keeping the model's job small: the mistakes end up in code you can test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicates: "looks alike" is useless for screenshots
&lt;/h3&gt;

&lt;p&gt;My first version used a 64-bit perceptual hash (dHash), the textbook way to find near-duplicate images. On the demo it declared &lt;strong&gt;8 of 12 screenshots&lt;/strong&gt; copies of each other.&lt;/p&gt;

&lt;p&gt;The reason is obvious in hindsight. dHash encodes whether each pixel is brighter than its neighbour. A screenshot is mostly a flat white page with a little text, and a sunset photo is a smooth vertical gradient. Both hash to roughly zero. To a perceptual hash, all screenshots look the same.&lt;/p&gt;

&lt;p&gt;The fix was to require two signals: a duplicate has to &lt;strong&gt;look alike&lt;/strong&gt; (mean pixel difference on a 32×32 thumbnail) &lt;strong&gt;and read alike&lt;/strong&gt; (at least 80% overlap in the OCR'd words). A re-saved JPEG of the receipt passes both. A chat that happens to be equally white doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving files without losing any
&lt;/h3&gt;

&lt;p&gt;The File System Access API lets a web page work inside a folder you pick. Screenshot Sloth uses &lt;code&gt;FileSystemFileHandle.move()&lt;/code&gt; where the browser has it, and otherwise copies, checks the size, then removes the original. Every move goes into &lt;code&gt;.screenshot-sloth-undo.json&lt;/code&gt; in the folder, which is written even if a run stops halfway. Undo replays it backwards, and only removes folders it created if they're empty. I tested both paths against the browser's origin-private file system (same API, no real files at risk), including name collisions and folders that already existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;On the 12-screenshot demo desktop, on my Mac's GPU:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine time:&lt;/strong&gt; between 1 min 21 s and 2 min 13 s to read all 12 across my runs (after the one-time model download)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My time:&lt;/strong&gt; looking over the plan and clicking Apply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;By hand:&lt;/strong&gt; about 10 seconds a file to open it, read it, think of a name, type it and drag it, so 2 minutes for 12. A folder of a few hundred screenshots is an hour of that, which is exactly why nobody ever does it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it isn't faster than you, at about 7–11 seconds per screenshot. But a sloth doesn't need to be fast; it needs to be doing the work so you aren't. You start it, go make tea, and come back to a sorted folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Because screenshots are the most private files you own, and the only way to sort them without handing them to someone else is a model you can run yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about what's in that folder: OTPs, bank balances, card numbers, Aadhaar scans, boarding passes, private chats. You took them to keep something important, which is exactly why they're sensitive.&lt;/p&gt;

&lt;p&gt;Other people have scratched this itch. &lt;a href="https://dev.to/flavioespinoza/i-built-a-screenshot-organizer-that-uses-gpt-4v-to-make-my-chaos-searchable-1cm1"&gt;One screenshot organizer&lt;/a&gt; sends each image to GPT-4V. It works, but every OTP and card number in the folder then goes to an API, and the cost grows with every screenshot. &lt;a href="https://dev.to/bilal_official/i-was-drowning-in-500-screenshots-so-i-built-my-own-organizer-free-no-upload-no-login-286j"&gt;OrganizeShots&lt;/a&gt; runs in the browser and sorts by OCR into a zip. Screenshot Sloth builds on the same no-upload idea, adds content-based names, in-place moves with undo and checksum-verified flags, and is open all the way down: the code &lt;em&gt;and&lt;/em&gt; the model doing the reading.&lt;/p&gt;

&lt;p&gt;That openness matters for four concrete reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing leaves the tab.&lt;/strong&gt; After the one-time download, the model works with Wi-Fi off. Your screenshots are never uploaded, because there's nowhere to upload them to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can check that.&lt;/strong&gt; The model is MIT-licensed with published weights, and the app is MIT-licensed on GitHub. "Your files stay on your device" is something you can verify by reading the code or watching the network tab, not a promise you have to trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing per screenshot.&lt;/strong&gt; 12 or 1,200 cost the same, which makes it reasonable to just run it on everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's forkable.&lt;/strong&gt; Your screenshots aren't my screenshots. If yours are mostly Figma frames or Grafana dashboards, the categories are a small, readable rules file, and you can swap in a bigger Florence model or a different one entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed API could probably name the odd screenshot more cleverly. But the job here isn't to be clever about one screenshot. It's to be trusted with all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built Screenshot Sloth pairing with Claude Code. The first idea on our shortlist was an automatic subtitle syncer, until I remembered my own &lt;a href="https://github.com/tarunvashishth/cuelatch" rel="noopener noreferrer"&gt;Cuelatch&lt;/a&gt; already does that, so we switched to this. The agent did most of the implementation and helped draft this post. The bugs in it are real ones from the build: the dHash false positives and the missed OTP both showed up on the first demo runs and were fixed with tests. The timings come from actual runs, and the move-and-undo code was tested against the browser's sandboxed file system before anything touched a real folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Searching by content ("that error with &lt;code&gt;ECONNRESET&lt;/code&gt;") using the text it already read&lt;/li&gt;
&lt;li&gt;Reading past the third strip of very long scrolling screenshots&lt;/li&gt;
&lt;li&gt;A watch mode, so the folder stays sorted as new screenshots land&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Touch grass. Then check what your photo gives away.</title>
      <dc:creator>tarunvashishth</dc:creator>
      <pubDate>Wed, 07 Oct 2026 07:11:14 +0000</pubDate>
      <link>https://dev.to/tarunvashishth/touch-grass-then-check-what-your-photo-gives-away-3a69</link>
      <guid>https://dev.to/tarunvashishth/touch-grass-then-check-what-your-photo-gives-away-3a69</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Pixel Leak&lt;/strong&gt; checks an outdoor photo for clues to where you live before you post it. You drop in a photo, wait about ten seconds, and see what a stranger could learn from it. Then you blur those parts and download a clean copy.&lt;/p&gt;

&lt;p&gt;Most people already know the GPS tag is a problem, and plenty of tools strip it. But metadata isn't the only leak. The street sign behind your kid, the number on your gate and the plate on the car in your driveway are all readable by anyone who zooms in, and stripping EXIF does nothing about them.&lt;/p&gt;

&lt;p&gt;Pixel Leak checks both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The metadata:&lt;/strong&gt; GPS coordinates, capture time, device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pixels:&lt;/strong&gt; street and locality names, house numbers, PIN/ZIP/postcodes, phone numbers, number plates, people and screens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who it's for:&lt;/strong&gt; anyone who posts the outdoor part of their life. That includes runners whose route starts at the front door, parents posting the kids in the garden, people who share their morning walk, and anyone who has held back a nice photo because it showed a little too much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it fits Touch Grass:&lt;/strong&gt; the theme asks for builds where the screen is the shortest part of the experience. Pixel Leak is a ten-second stop between the walk and the post. It doesn't keep you on a screen. It gets you back outside faster, with one less thing to worry about when you share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://tarunvashishth.github.io/pixel-leak/" rel="noopener noreferrer"&gt;https://tarunvashishth.github.io/pixel-leak/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzg7g5q93vsnuce1b8oic.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzg7g5q93vsnuce1b8oic.gif" alt="Pixel Leak flags a street sign, number plate and house number in a photo, then blurs them" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To try it without using your own photo, click &lt;strong&gt;Try a sample photo&lt;/strong&gt;. The first visit downloads the model (about 350 MB), and your browser caches it after that. It runs fastest in Chrome or Edge with WebGPU, and falls back to CPU (slower) elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tarunvashishth" rel="noopener noreferrer"&gt;
        tarunvashishth
      &lt;/a&gt; / &lt;a href="https://github.com/tarunvashishth/pixel-leak" rel="noopener noreferrer"&gt;
        pixel-leak
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Check a photo for location leaks before you post it — EXIF and the pixels. Open-source vision model (Florence-2) running entirely in your browser; nothing is uploaded.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Pixel Leak&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tarunvashishth/pixel-leak/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftarunvashishth%2Fpixel-leak%2FHEAD%2Fdocs%2Fdemo.gif" alt="Pixel Leak scanning a photo, flagging a street sign, number plate and house number, then blurring them"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check a photo for location leaks before you post it. It covers the metadata &lt;strong&gt;and the pixels&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Stripping EXIF removes the GPS tag. It does nothing about the street sign, house number or number plate in the frame. Pixel Leak runs &lt;a href="https://huggingface.co/onnx-community/Florence-2-base-ft" rel="nofollow noopener noreferrer"&gt;Florence-2&lt;/a&gt; (MIT-licensed, ~230M params) entirely in your browser to read the photo the way a stranger would. It then lets you blur what it finds and download a clean copy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://tarunvashishth.github.io/pixel-leak/" rel="nofollow noopener noreferrer"&gt;https://tarunvashishth.github.io/pixel-leak/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nothing is uploaded.&lt;/strong&gt; The only network request is the one-time model download from Hugging Face (~350 MB, cached by the browser). After that you can go offline and it still works.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it checks&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Finds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EXIF (via &lt;code&gt;exifr&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;GPS coordinates, capture time, device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Florence-2 &lt;code&gt;&amp;lt;OCR_WITH_REGION&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;street / locality names, house numbers, PIN/ZIP/postcodes, phone numbers, emails, licence plates, other visible text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Florence-2 &lt;code&gt;&amp;lt;OD&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;people, licence plates, screens, signs, vehicles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Florence-2 &lt;code&gt;&amp;lt;MORE_DETAILED_CAPTION&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;a plain-language&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tarunvashishth/pixel-leak" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Everything runs in the browser tab. There's no backend at all, and the site is a static page on GitHub Pages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Metadata.&lt;/strong&gt; &lt;a href="https://github.com/MikeKovarik/exifr" rel="noopener noreferrer"&gt;&lt;code&gt;exifr&lt;/code&gt;&lt;/a&gt; reads GPS, capture time and device from the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pixels.&lt;/strong&gt; &lt;a href="https://huggingface.co/onnx-community/Florence-2-base-ft" rel="noopener noreferrer"&gt;Florence-2-base&lt;/a&gt; (about 230M parameters, MIT license) runs in a Web Worker through &lt;a href="https://github.com/huggingface/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt;. It does three passes over the photo:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;| Florence-2 task | What I use it for |&lt;br&gt;
   | --- | --- |&lt;br&gt;
   | &lt;code&gt;&amp;lt;OCR_WITH_REGION&amp;gt;&lt;/code&gt; | every piece of text, with a box around it |&lt;br&gt;
   | &lt;code&gt;&amp;lt;OD&amp;gt;&lt;/code&gt; | people, vehicles, screens, signs |&lt;br&gt;
   | &lt;code&gt;&amp;lt;MORE_DETAILED_CAPTION&amp;gt;&lt;/code&gt; | a plain-English "what a stranger sees" |&lt;/p&gt;

&lt;p&gt;On WebGPU the vision encoder runs in fp16 and the text encoder and decoder in 4-bit. Without WebGPU it falls back to 8-bit on CPU.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rules decide what counts as a leak.&lt;/strong&gt; A small, unit-tested rules file turns the model's output into findings. It looks for street suffixes (&lt;code&gt;Road&lt;/code&gt;, &lt;code&gt;Marg&lt;/code&gt;, &lt;code&gt;Sector&lt;/code&gt;, &lt;code&gt;Nagar&lt;/code&gt;, &lt;code&gt;Ave&lt;/code&gt;…), Indian PIN codes, US ZIPs, UK postcodes, Indian plate formats, phone numbers, emails and house numbers. Each finding gets a severity and a numbered box on the photo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean copy.&lt;/strong&gt; The photo is redrawn at full resolution on a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; with your chosen areas blurred, then re-encoded. Only pixels survive. I checked the output for EXIF, XMP and GPS markers, and none were there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A scan takes &lt;strong&gt;8–11 seconds&lt;/strong&gt; on my Mac with WebGPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Things I learned building it
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Don't let the model be the judge.&lt;/strong&gt; On my test photo, Florence-2's caption described a house number as "2218". The OCR pass on the same photo read it correctly as "221B". If I had built the leak detection on top of the caption, it would have been confidently wrong. So the model only reads, and plain rules decide what's sensitive. Every flag can be traced to a specific piece of text and a specific regex, and the rules run as tests in CI before every deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model boxes are approximate.&lt;/strong&gt; In my tests Florence-2's boxes landed near the text, but were sometimes a little offset or too wide. So the blur covers a padded area around each box rather than the exact box, and I apply the canvas blur twice so large sign lettering doesn't stay readable through a light blur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-encoding is a fair trade here.&lt;/strong&gt; My other project, &lt;a href="https://remove-exif.com" rel="noopener noreferrer"&gt;remove-exif.com&lt;/a&gt;, strips metadata byte by byte so the image data is never touched. Here that wasn't possible: once you blur pixels you have to re-encode them anyway. Doing it through a canvas also guarantees that no metadata block survives, because the canvas never had any.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Works offline" is easy to say, so I tested it.&lt;/strong&gt; I loaded the model, cut the browser's network, and ran a scan. It finished in 8.3 seconds and found every leak, with zero network requests during the scan.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it can't do (yet)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It flags clues. It can't promise anonymity.&lt;/strong&gt; Skylines, landmarks, shadows and reflections can still identify a place, and it doesn't reason about those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The object detector is the weakest part.&lt;/strong&gt; On my sample photo it caught every piece of text but didn't flag the (cartoon) car itself. Always look at the photo yourself before posting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small or angled text is hit and miss&lt;/strong&gt; for OCR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't open HEIC&lt;/strong&gt; (the iPhone default). For now, export the photo as JPEG first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Because the honest version of this tool can't be built on a closed API.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A cloud vision API would mean uploading the photo you're worried about, the one with your street name in it, to a third party so it can tell you whether the photo reveals your street. That defeats the point.&lt;/p&gt;

&lt;p&gt;An open-weight model changes that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You don't have to take my word for it.&lt;/strong&gt; The model downloads once, and after that nothing leaves the tab. Open DevTools, watch the Network panel and run a scan. Or turn off Wi-Fi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can stay free.&lt;/strong&gt; There's no API key, no per-image cost and no account. The whole thing is a static site, so hosting costs nothing and there's nothing to shut down when credits run out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's easy to fork for your own country.&lt;/strong&gt; The rules currently know Indian, US and UK address formats. Adding Brazilian CEPs or German plate formats is a regex in &lt;a href="https://github.com/tarunvashishth/pixel-leak/blob/main/src/rules.ts" rel="noopener noreferrer"&gt;one file&lt;/a&gt;. And if a better small vision model comes out next month, swapping it is a change to one worker file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built Pixel Leak pairing with Claude Code. I picked the idea from a shortlist built around my existing repos, and the agent did most of the implementation and helped draft this post. Every claim in it was checked against the running app before it went in: the scan times, the offline test, and the metadata check on the clean copy.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
