The problem was simple but annoying.
Someone sends you a PDF contract. You signed it last month. Now you need your signature as a transparent PNG to paste into a new document — but you don't want to print, sign, scan, and repeat the whole process again.
So you Google "signature extractor online free."
**
What do you find?
**
Tools that upload your private legal documents to unknown servers
Tools that require account creation just to download
Tools that slap a watermark on your free download
Tools that only accept images — not PDFs
Tools that completely break on mobile
I decided to build something better. Here's how I did it and what I learned.
What the Tool Does
Free Signature Extractor — a browser-based tool that:
✅ Accepts PDF files directly (no conversion needed)
✅ Renders PDFs page by page using PDF.js
✅ Lets you manually draw a selection box around the signature
✅ Removes the paper background to give a clean transparent PNG
✅ Has an adjustable threshold slider for non-white paper backgrounds
✅ Works fully on mobile with touch events
✅ Never uploads your file anywhere — 100% local processing
🔗 Live tool: https://aitextextractors.com/free-signature-extractor-from-pdf-image-online/
The Tech Stack
No frameworks. No backend. No dependencies except one:
javascript
// PDF.js for rendering PDF pages to canvas
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
Everything else is vanilla JavaScript + HTML5 Canvas API.
How the Background Removal Works
This was the most interesting part to figure out.
The core idea: iterate over every pixel in the selected area, calculate its brightness, and make light pixels transparent while darkening dark pixels (the ink).
javascript
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Weighted brightness calculation (human eye perception)
const brightness = r * 0.299 + g * 0.587 + b * 0.114;
if (brightness > threshold) {
// Light pixel = paper background → make transparent
data[i + 3] = 0;
} else {
// Dark pixel = ink → slightly darken for cleaner output
data[i] = Math.max(0, r - 25);
data[i + 1] = Math.max(0, g - 35);
data[i + 2] = Math.max(0, b - 35);
data[i + 3] = 255;
}
}
Why weighted RGB instead of simple average?
The human eye is most sensitive to green, then red, then blue. Using 0.299R + 0.587G + 0.114B gives a perceptually accurate brightness value — which means the threshold works more naturally across different ink colors and paper tones.
The Threshold Slider Problem
A fixed threshold of 175 works fine for pure white paper under good lighting.
But real documents have:
Cream or yellowish aged paper
Grey shadows from camera angle
Coffee stains (yes, really)
Coloured paper backgrounds
Inconsistent mobile phone lighting
Solution: Let the user adjust it.
javascript
// User drags slider → threshold updates live
thresh.addEventListener('input', () => {
threshVal.textContent = thresh.value;
});
// On extract, read current slider value
const threshold = parseInt(thresh.value); // 120–240 range
This single addition made the tool work on documents that a fixed algorithm completely failed on.
Mobile Touch Support — The Bug I Almost Missed
The original implementation used only mouse events:
javascript
// ❌ This breaks completely on mobile
canvas.addEventListener('mousedown', onStart);
canvas.addEventListener('mousemove', onMove);
canvas.addEventListener('mouseup', onEnd);
Adding touch support required unified pointer handling:
javascript
// ✅ Unified mouse + touch handler
function getPos(e) {
const rect = canvasWrap.getBoundingClientRect();
const src = e.touches ? e.touches[0] : e;
return {
x: src.clientX - rect.left + canvasWrap.scrollLeft,
y: src.clientY - rect.top + canvasWrap.scrollTop
};
}
// Same handler works for both mouse and touch
canvasWrap.addEventListener('mousedown', onStart);
canvasWrap.addEventListener('touchstart', onStart, { passive: false });
canvasWrap.addEventListener('mousemove', onMove);
canvasWrap.addEventListener('touchmove', onMove, { passive: false });
passive: false is required here — otherwise preventDefault() cannot stop the page from scrolling while the user is trying to draw a selection box.
The Scale Factor Bug
This one was subtle and caused wrong crop coordinates on high-DPI displays and zoomed-in PDF pages.
javascript
// ❌ Wrong — calculated once at load time
let scaleFactor = canvas.width / canvas.clientWidth;
// ✅ Correct — calculated fresh at extraction time
extractBtn.addEventListener('click', () => {
const scaleFactor = mc.width / mc.clientWidth;
const actualX = parseInt(cropBox.style.left) * scaleFactor;
const actualY = parseInt(cropBox.style.top) * scaleFactor;
const actualW = parseInt(cropBox.style.width) * scaleFactor;
const actualH = parseInt(cropBox.style.height) * scaleFactor;
const imgData = ctx.getImageData(actualX, actualY, actualW, actualH);
// ...
});
The canvas internal resolution and its displayed CSS size are different — especially on Retina displays or when PDF pages are rendered at 2x scale for sharpness. Always recalculate at the moment of use.
What I Would Improve Next
Colored ink support
Currently the algorithm assumes dark ink on light paper. Blue or red pen signatures lose saturation. A better approach would be HSL-based isolation instead of pure brightness.Edge smoothing
The extracted PNG sometimes has jagged edges around the signature. A Gaussian blur pass on the alpha channel before output would smooth this considerably.Auto-detection
Using contrast analysis to automatically suggest the tightest possible crop box around the signature area — so users don't have to draw manually.
Try It
🔗 https://aitextextractors.com/free-signature-extractor-from-pdf-images/
No account. No watermark. No file uploads. Just open and use.
Would love to hear thoughts on the background removal algorithm — especially from anyone who has worked with OpenCV or similar for document processing. Is there a better browser-based approach for colored ink signatures?
Top comments (1)
Zero-server with no upload is the right architecture for this — nice build. I ran a quick passive check on the page (response headers + public HTML only, nothing intrusive) and found one thing worth fixing before this post picks up traffic:
The first link in your article 404s. It points to /free-signature-extractor-from-pdf-image-online/ (singular "image"), which returns 404 — the working URL is /free-signature-extractor-from-pdf-images/ (plural). Anyone clicking through from here hits your "Page Not Found" instead of the tool. (dev.to lets you edit the post URL list — 30-second fix.)
The live page has two