frontpage.
newsnewestaskshowjobs

Made with ♥ by @iamnishanth

Open Source @Github

fp.

Open in hackernews

Show HN: Veil – Dark mode PDFs without destroying images, runs in the browser

https://veil.simoneamico.com/
44•simoneamico•15h ago
Hi HN! here's a tool I just deployed that renders PDFs in dark mode without destroying the images. Internal and external links stay intact, and I decided to implement export since I'm not a fan of platform lock-in: you can view your dark PDF in your preferred reader, on any device. It's a side project born from a personal need first and foremost. When I was reading in the factory the books that eventually helped me get out of it, I had the problem that many study materials and books contained images and charts that forced me, with the dark readers available at the time, to always keep the original file in multitasking since the images became, to put it mildly, strange. I hope it can help some of you who have this same need. I think it could be very useful for researchers, but only future adoption will tell.

With that premise, I'd like to share the choices that made all of this possible. To do so, I'll walk through the three layers that veil creates from the original PDF:

- Layer 1: CSS filter. I use invert(0.86) hue rotate(180deg) on the main canvas. I use 0.86 instead of 1.0 because I found that full inversion produces a pure black and pure white that are too aggressive for prolonged reading. 0.86 yields a soft dark grey (around #242424, though it depends on the document's white) and a muted white (around #DBDBDB) for the text, which I found to be the most comfortable value for hours of reading.

- Layer 2: image protection. A second canvas is positioned on top of the first, this time with no filters. Through PDF.js's public API getOperatorList(), I walk the PDF's operator list and reconstruct the CTM stack, that is the save, restore and transform operations the PDF uses to position every object on the page. When I encounter a paintImageXObject (opcode 85 in PDF.js v5), the current transformation matrix gives me the exact bounds of the image. At that point I copy those pixels from a clean render onto the overlay. I didn't fork PDF.js because It would have become a maintenance nightmare given the length of the codebase and the frequent updates. Images also receive OCR treatment: text contained in charts and images becomes selectable, just like any other text on the page. At this point we have the text inverted and the images intact. But what if the page is already dark? Maybe the chapter title pages are black with white text? The next layer takes care of that.

- Layer 3: already-dark page detection. After rendering, the background brightness is measured by sampling the edges and corners of the page (where you're most likely to find pure background, without text or images in the way). The BT.601 formula is used to calculate perceived brightness by weighting the three color channels as the human eye sees them: green at 58.7%, red at 29.9%, blue at 11.4%. These weights reflect biology: the eye evolved in natural environments where distinguishing shades of green (vegetation, predators in the grass) was a matter of survival, while blue (sky, water) was less critical. If the average luminance falls below 40%, the page is flagged as already dark and the inversion is skipped, returning the original page. Presentation slides with dark backgrounds stay exactly as they are, instead of being inverted into something blinding.

Scanned documents are detected automatically and receive OCR via Tesseract.js, making text selectable and copyable even on PDFs that are essentially images. Everything runs locally, no framework was used, just vanilla JS, which is why it's an installable PWA that works offline too.

Here's the link to the app along with the repository: https://veil.simoneamico.com | https://github.com/simoneamico-ux-dev/veil

I hope veil can make your reading more pleasant. I'm open to any feedback. Thanks everyone

Comments

gwern•2h ago
Have you considered, since you can extract the images via the mask, selectively inverting them?

One can fairly reliably use a small NN to classify images by whether they should be inverted or just dimmed, and I've used it with great success for years now on my site: https://invertornot.com/ https://gwern.net/invertornot

---

On a side note, it'd be nice to have an API or something to let one 'compile' a PDF to dark-mode version PDF. Ephemeral browser-based is a drawback as often as a benefit.

simoneamico•1h ago
That's actually exactly where I started. The initial idea involved a YOLO nano model to classify images, deciding what to invert and what not to. It worked as a concept, but during the feasibility analysis I realized that for native PDFs it wasn't necessary: the format already tells you where the images are. I walk the page's operator list via getOperatorList() (PDF.js public API, no fork) and reconstruct the CTM stack, that is the save, restore and transform operations, until I hit a paintImageXObject. The current transformation matrix gives me the exact bounds. I copy those pixels from a clean render onto an overlay canvas with no filters, and the images stay intact. It's just arithmetic on transformation matrices, on a typical page it takes a few milliseconds.

Your approach with a classifier makes a lot more sense for the generic web, where you're dealing with arbitrary <img> tags with no structural metadata, and there you have no choice but to look at what's inside. PDFs are a more favorable problem.

A case where a classifier like yours would be an interesting complement is purely vector diagrams, drawn with PDF path operators, not raster images. Veil inverts those along with the text because from the format's perspective they're indistinguishable. In practice they're rare enough that the per-page toggle handles them, but it's the honest limitation of the approach.

gwern•1h ago
> In practice they're rare enough that the per-page toggle handles them, but it's the honest limitation of the approach.

I don't understand how you handle raster images. You simply cannot invert them blindly. So it sounds like you just bite the bullet of never inverting raster images, and accepting that you false-positive some vector-based diagrams? I don't see how that can justify your conclusion "it wasn't necessary". It sounds necessary to me.

simoneamico•1h ago
Actually, raster images are never inverted, they're protected. The CSS filter: invert() hits the entire canvas (text and images together), then the overlay paints the original image pixels back on top, restoring them. The result is: inverted text, images with their original colors.

The choice to never invert raster images isn't a compromise, it's the design decision. The problem veil solves is exactly that: every dark mode reader today inverts everything, and the result on photos, histology, color charts, scans is unusable. Preserving all images is the conservative choice, and for my target (people reading scientific papers, medical reports, technical manuals) it's the right one.

It's absolutely true that there's a subset of raster images, like diagrams with white backgrounds and black lines, that would benefit from inversion. I could be wrong, but in my experience they're a minority, and the cost of accidentally inverting the wrong one (a medical photo, a color chart) is much higher than the benefit of inverting a black and white diagram, from my point of view. For now the per-page toggle covers those cases.

ainch•2h ago
As a PhD student doing my fair share of midnight paper-reading I think I'm the exact target market - thank you for sharing!
simoneamico•1h ago
That really means a lot, ainch. I hope it makes your late-night sessions a little more bearable. If you find anything that doesn't work well with the papers you read, keep me posted
importjelly•24m ago
Doesn't work for me with this document: https://ajsonline.org/article/63137-double-star-discoveries-...

I just get a dark border around a block of white page with black text.

Show HN: I put an AI agent on a $7/month VPS with IRC as its transport layer

https://georgelarson.me/writing/2026-03-23-nullclaw-doorman/
113•j0rg3•4h ago•40 comments

Show HN: Fio: 3D World editor/game engine – inspired by Radiant and Hammer

https://github.com/ViciousSquid/Fio
40•vicioussquid•5h ago•3 comments

Show HN: Turbolite – a SQLite VFS serving sub-250ms cold JOIN queries from S3

https://github.com/russellromney/turbolite
118•russellthehippo•7h ago•25 comments

Show HN: Veil – Dark mode PDFs without destroying images, runs in the browser

https://veil.simoneamico.com/
44•simoneamico•15h ago•7 comments

Show HN: My 'pet' project, a Tinder-esque experience for rescuing dogs and cats

https://rescueapet.benswork.space
5•player_piano•1h ago•0 comments

Show HN: A list of websites and directories where you can promote your projects

https://promotestartup.com
2•wesammikhail•53m ago•0 comments

Show HN: Optio – Orchestrate AI coding agents in K8s to go from ticket to PR

https://github.com/jonwiggins/optio
75•jawiggins•1d ago•54 comments

Show HN: A plain-text cognitive architecture for Claude Code

https://lab.puga.com.br/cog/
141•marciopuga•1d ago•46 comments

Show HN: Sup AI, a confidence-weighted ensemble (52.15% on Humanity's Last Exam)

https://sup.ai
9•supai•11h ago•3 comments

Show HN: Orloj – agent infrastructure as code (YAML and GitOps)

https://github.com/OrlojHQ/orloj
19•An0n_Jon•21h ago•12 comments

Show HN: Layerleak – Like Trufflehog, but for Docker Hub

https://github.com/Brumbelow/layerleak
6•brumbelow•7h ago•8 comments

Show HN: Robust LLM extractor for websites in TypeScript

https://github.com/lightfeed/extractor
66•andrew_zhong•22h ago•45 comments

Show HN: Illustrative – AI pipeline that turns books into graphic novels

https://arv.in/illustrative/
5•adangit•6h ago•0 comments

Show HN: I took back Video.js after 16 years and we rewrote it to be 88% smaller

https://videojs.org/blog/videojs-v10-beta-hello-world-again
637•Heff•2d ago•138 comments

Show HN: Burn Room – End-to-End Encrypted Ephemeral SSH Chat

https://burnroom.chat
3•joematrix•7h ago•0 comments

Show HN: ReactNative.run – Browser Metro bundler that runs React Native

https://www.reactnative.run/
2•sanketsahu•7h ago•0 comments

Show HN: ProofShot – Give AI coding agents eyes to verify the UI they build

https://github.com/AmElmo/proofshot
155•jberthom•2d ago•96 comments

Show HN: AI Roundtable – Let 200 models debate your question

https://opper.ai/ai-roundtable/
109•felix089•2d ago•85 comments

Show HN: Yoink – Spotify to lossless with full metadata, self-hostable, ad-free

https://yoinkify.com
50•chasefrazier•1d ago•33 comments

Show HN: DuckDB community extension for prefiltered HNSW using ACORN-1

https://github.com/cigrainger/duckdb-hnsw-acorn
89•cigrainger•1d ago•7 comments

Show HN: Mantyx – A platform to orchestrate, manage, and share your agents

https://mantyx.io/
7•grillorafael•18h ago•0 comments

Show HN: Cloneify – AI assistant that runs your business from WhatsApp/Slack

https://cloneify.ai
4•ad-tech•14h ago•1 comments

Show HN: Email.md – Markdown to responsive, email-safe HTML

https://www.emailmd.dev/
372•dancablam•2d ago•94 comments

Show HN: Vizier – A physical design advisor for DuckDB

4•habedi0•10h ago•0 comments

Show HN: Cq – Stack Overflow for AI coding agents

https://blog.mozilla.ai/cq-stack-overflow-for-agents/
223•peteski22•3d ago•99 comments

Show HN: Pgsemantic – Point at your Postgres DB, get vector search instantly

https://github.com/varmabudharaju/pgsemantic
16•varmabudharaju•1d ago•1 comments

Show HN: Micro – apps without ads, algorithms or tracking

https://micro.mu
6•asim•10h ago•6 comments

Show HN: Gemini can now natively embed video, so I built sub-second video search

https://github.com/ssrajadh/sentrysearch
427•sohamrj•2d ago•108 comments

Show HN: NerdFlair, a Claude Code QoL Plugin

https://github.com/jcraigk/nerdflair
2•block_dagger•10h ago•1 comments

Show HN: Gridland: make terminal apps that also run in the browser

https://www.gridland.io/
104•rothific•2d ago•13 comments