Photo To Scanned Pdf logo

Photo To Scanned Pdf

CommunityPopular
daymade
photo-to-scanned-pdf

Two pipelines ending at a scanner-look PDF. (1) Phone photos of paper documents (contracts, stamped certificates, receipts, forms, handwritten notes) → clean scanner-quality PDF: perspective rectification + noteshrink whitening + A4 assembly + mandatory whole-document check. Trigger: "把照片 做成扫描件", "photos to scanned PDF", "make this look scanned", "手机拍的 文档转 PDF", "盖章文件扫描", replacing pages in an existing scanned PDF, any CamScanner-like request. (2) A digital document with no signature yet (rendered docx/PDF, confirmation form, contract draft) → make it look hand-signed and scanned: synthesize a handwriting-style signature, composite it onto the signature line, apply the same scan-look post-processing. Trigger: "帮我做个手写签名", "生成签名盖到这份文件上", "做成签过字的扫描件", "synthesize a signature", any request for a document that needs to look signed without a real photographed signature. Do NOT hand-roll levels/contrast enhancement for scan-look — tried and rejected twice; this skill's pipeline is the proven one.

Overview

Publisherdaymade
Repositoryclaude-code-skills
Skill namephoto-to-scanned-pdf
Stars
1.4K
Forks
219
Bundled files
5
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 5 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by daymade on GitHub. Read the source before you install it.

Installation

Install the Photo To Scanned Pdf AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/daymade/claude-code-skills.git /tmp/claude-code-skills
mkdir -p .claude/skills
cp -r /tmp/claude-code-skills/daymade-docs/photo-to-scanned-pdf .claude/skills/photo-to-scanned-pdf
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Photo To Scanned Pdf in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Photo To Scanned Pdf on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Photo To Scanned Pdf is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Photo → Scanned PDF

Two related pipelines, same destination look, different starting point: phone photos of paper documents, or a digital document that needs a synthetic signature before it looks signed. The pipelines that work, and the failure modes that ship wrong PDFs if skipped.

Which one do you need?

The input is...Use
Phone photos of an already-signed/stamped paper documentThis file, main pipeline below
A digital document (docx/PDF) with no signature yet, and you need to make it look hand-signedreferences/digital-signature-synthesis.md
photos ──► rectify (photo_to_scan.py --raw)
       ──► ORDER BY CONTENT, detect colored paper   ← agent eyes, not filenames
       ──► enhance: noteshrink (white batch with -g │ colored pages separately,
                                after white-balance pre-pass)
       ──► assemble_pdf.py → A4 PDF
       ──► make_contact_sheet.py → READ IT, verify EVERY page   ← mandatory

Division of labor: scripts carry execution; you (the agent) carry the two judgment steps — content-based page ordering, and whole-document verification. Neither can be automated away: filenames lie about order, and per-page spot checks miss wrong-slot bugs. The digital-signature branch shares this same philosophy with its own two judgment calls — see the reference file.

Step 0 — Dependencies

bash
which pdftoppm || brew install poppler   # contact sheet + any PDF rendering
uvx noteshrink --help | head -3          # first run builds it (~30 s)

Scripts are uv run single-file scripts (PEP 723); OpenCV/PIL/img2pdf resolve automatically on first run.

Step 1 — Rectify

bash
uv run <skill>/scripts/photo_to_scan.py --raw --out-dir work --prefix page \
    photo1.jpg photo2.jpg ...

Expected: one page_NN.jpg per photo, each tagged [quad]. A [FULLFRAME-fallback] tag means the paper outline wasn't found (busy background, page cut off) — view that photo and decide: retake, or accept the uncropped frame.

The script handles EXIF rotation internally (cv2.imread ignores EXIF; phone photos come rotated — this silently produces sideways pages if you rectify with raw OpenCV).

Step 2 — Order by content, detect colored paper (agent judgment)

Read every rectified image (batch of ~6 per message) and record two things:

  1. Its identity — date, title, page number, whatever distinguishes pages. Batch-exported photos (WeChat, AirDrop) get timestamps of the export moment, often all within one second — filename order is meaningless. Real case: 17 photos turned out to be in exact reverse document order; only content reading caught it.
  2. Its paper color — white, or colored (blue/yellow/pink stock)? Colored pages take a different path in Step 3. If unsure, sample programmatically: mean RGB of a blank region; B > R + 25 ⇒ blue-ish paper.

Build the final page order as an explicit list before proceeding. If pages are supposed to match an external register (an invoice list, a session table), cross-check identity against it now — missing/duplicate pages found here cost seconds; found after delivery they cost a redo.

Step 3 — Enhance (noteshrink, split by paper color)

White-paper pages — one batch, global palette:

bash
uvx noteshrink -w -g -K -q -b ns -c "true" page_03.jpg page_01.jpg page_07.jpg ...
# inputs IN FINAL PAGE ORDER → outputs ns0000.png, ns0001.png, ... in that order
  • -w white background, -g one global palette (uniform ink/stamp color across pages), -K keep given order, -c "true" skips its internal PDF step (we assemble ourselves).
  • Pass filenames explicitly. zsh does not word-split $VAR — a file list in a variable arrives as one giant "filename", noteshrink exits without output, and -q keeps it silent. Verify outputs exist (ls ns0*.png) rather than trusting stdout.

Colored-paper pages — separate, with white-balance pre-pass:

bash
uv run <skill>/scripts/photo_to_scan.py --out-dir work --prefix wb colored_photo.jpg   # no --raw
uvx noteshrink -w -g -K -q -b nc -c "true" work/wb_01.jpg

Two distinct failure modes force this split (both shipped as bugs before the rule existed):

  1. Colored pages inside the -g batch poison the whole document — the paper color enters the global palette and white pages come out with tinted shadows/artifacts.
  2. noteshrink alone on colored paper whitens the background but not the foreground cast — black ink photographed on blue stock reads blue-purple, a red stamp reads maroon. The default (non---raw) mode of photo_to_scan.py divides out the paper color first, so ink returns to black and stamps to red.

Step 4 — Assemble

bash
uv run <skill>/scripts/assemble_pdf.py --out scanned.pdf \
    ns0000.png ns0001.png nc0000.png ns0002.png ...   # FINAL page order

Expected: OK scanned.pdf (N pages, ~0.05 MB/page). Edge crop (default 24px top / 12px sides at 200 dpi) removes the sliver of desk surface that rectification drags in along page borders; document margins dwarf it.

Step 5 — Verify the WHOLE document (mandatory, not optional)

bash
uv run <skill>/scripts/make_contact_sheet.py scanned.pdf --out contact.png

Read contact.png and check every page: identity sequence complete and correct (each date/title where it should be, no duplicates, none missing), no off-color page, stamps/signatures present. Then spot-read 1–2 pages at full resolution for text sharpness.

Why whole-document, every time: two shipped-bug stories from the session this skill was distilled from —

  • A page-replacement task wrote the new page into the wrong slot (an off-by-one in a copy command), silently overwriting a neighboring page. The per-page check of the replaced slots passed; the clobbered neighbor was only caught by the user.
  • A palette-poisoning bug (Step 3 #1) tinted pages that were not being edited. Checking only the edited pages missed it.

The cost asymmetry is absolute: contact sheet = one Read; a wrong page in a delivered PDF = redo + lost trust. "I verified the pages I changed" is not verification.

Replacing pages in an existing scanned PDF

Keep the per-page enhanced PNGs (ns*/nc*) as the working set. To replace page k: process the new photo through Steps 1–3, overwrite that page's PNG, re-run Steps 4–5. When copying into numbered slots, mind the mapping — slot numbers shift when photo order was reversed; derive the slot from the page's content identity, never from its position in the photo batch. Then the Step 5 full check is what actually protects you.

Troubleshooting

SymptomCause / fix
Output "doesn't look scanned" — gray haze, soft textYou hand-rolled levels/curves/divide enhancement. Don't — two attempts were rejected by a real user before switching to noteshrink (background sampling + palette quantization is what produces the flat-white scan look).
White pages have tinted shadowsA colored-paper page was inside the -g batch. Re-run whites-only batch (Step 3).
Ink looks blue/purple, stamp looks maroon on a colored pagenoteshrink got the colored page raw. Insert the white-balance pre-pass (photo_to_scan.py without --raw).
noteshrink produced no output, no errorFile list passed via an unquoted shell variable under zsh (no word splitting), or paths with spaces. Pass explicit filenames; check ls ns0*.png.
Page sideways / upside downEXIF ignored somewhere upstream, or the quad landed landscape. photo_to_scan.py corrects EXIF + rotates to portrait; upside-down pages it cannot know — catch at Step 2 and rotate the source photo.
[FULLFRAME-fallback] on a photoPaper outline not detected (low contrast vs table, page cut off). Retake against a dark background, or accept full frame + rely on edge crop.
Thin dark strip along page edge in the PDFDesk surface dragged in by rectification. Raise --crop-top/--crop-side in assemble_pdf.py.
Pages in wrong order in the PDFFilename-order assumption. Order comes from Step 2 content reading, passed explicitly to noteshrink (-K) and assemble_pdf.py.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Photo To Scanned Pdf AI skill do?

Two pipelines ending at a scanner-look PDF. (1) Phone photos of paper documents (contracts, stamped certificates, receipts, forms, handwritten notes) → clean scanner-quality PDF: perspective rectification + noteshrink whitening + A4 assembly + mandatory whole-document check. Trigger: "把照片 做成扫描件", "photos to scanned PDF", "make this look scanned", "手机拍的 文档转 PDF", "盖章文件扫描", replacing pages in an existing scanned PDF, any CamScanner-like request. (2) A digital document with no signature yet (rendered docx/PDF, confirmation form, contract draft) → make it look hand-signed and scanned: synthesiz...

Why use Photo To Scanned Pdf on TypingMind?

Because you install it once and use it with any model. Photo To Scanned Pdf is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Photo To Scanned Pdf in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/daymade/claude-code-skills/tree/main/daymade-docs/photo-to-scanned-pdf. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Photo To Scanned Pdf?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Photo To Scanned Pdf?

As many as you like. As long as a model supports skills, you can use Photo To Scanned Pdf with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Photo To Scanned Pdf AI skill free?

Yes. It is published on GitHub by daymade under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇