real-or-screenshot
v1.2.4
Published
Detect whether an image is a real photo or a screenshot using heuristic analysis
Maintainers
Readme
real-or-screenshot
Is this image a real photo or a screenshot? Is there a person in it?
Answer in milliseconds — no ML model, no API key, no native addons.
npm install real-or-screenshotRequires Node.js ≥ 18. ESM only.
Supported formats: JPEG · PNG · WebP · AVIF · HEIC · GIF · BMP · TIFF and more.
Quick start
import { detect, detectPerson } from 'real-or-screenshot';
// Is it a photo or a screenshot?
const result = await detect('./image.jpg');
console.log(result.label); // 'real' | 'screenshot'
console.log(result.confidence); // 0..1
// Is there a person in the image?
const person = await detectPerson('./image.jpg');
console.log(person.detected); // true | false
console.log(person.confidence); // 0..1Accepts a file path, a URL, or a Buffer — same API for all three.
detect(input) — real photo vs screenshot
import { detect } from 'real-or-screenshot';
const result = await detect('./screenshot.png');
// result:
// {
// label: 'screenshot', ← 'real' or 'screenshot'
// confidence: 0.94, ← how sure we are (0 = not sure, 1 = very sure)
// signals: {
// noise: 0.12, ← low = flat/clean image (screenshot-like)
// resolution: 1, ← 1 = exact match to a known screen size
// exif: 0, ← 0 = no camera metadata found
// edgeUniformity: 0.87,← high = solid colour bar at the top (status bar)
// compression: 0.6, ← low = no JPEG block artifacts
// }
// }How it decides
The library runs 5 independent checks ("signals") and combines them with a weighted average:
| Signal | What it measures | Weight | |--------|-----------------|--------| | Noise | Pixel-to-pixel variation in the image centre. Real cameras always introduce sensor noise; screenshots are perfectly flat. | 30% | | EXIF | Camera metadata (make, model, ISO, GPS…). Present in real photos, absent in screenshots. | 25% | | Resolution | Whether width × height matches a known screen size (iPhone, Android, desktop…). | 20% | | Edge uniformity | How solid the top strip is. Screenshots have a perfectly uniform status bar; real photos don't. | 15% | | Compression | JPEG block-boundary artifacts. Camera JPEGs have a distinct pattern; screenshots are clean or saved as PNG. | 10% |
confidence = weighted average of all signals
label = confidence ≥ 0.5 ? 'screenshot' : 'real'Custom weights
const result = await detect('./image.jpg', {
weights: {
noise: 0.5, // trust noise more
exif: 0.5, // trust EXIF more
resolution: 0,
edgeUniformity: 0,
compression: 0,
},
});Values are normalised automatically — you just set the relative importance.
detectPerson(input) — is there a person?
import { detectPerson } from 'real-or-screenshot';
const result = await detectPerson('./photo.jpg');
// result:
// {
// detected: true, ← boolean answer
// confidence: 0.82, ← 0..1
// skinPixelRatio: 0.13 ← 13% of pixels look like skin
// }How it decides
Samples up to ~10 000 pixels evenly across the image and classifies each one using three colour-space rules:
| Rule | Good for | |------|----------| | RGB (Kovac et al.) | Medium and dark skin tones | | YCbCr | Light skin tones | | HSV | Extra coverage; filters out saturated non-skin colours (pure red, orange) |
| skinPixelRatio | Meaning |
|-----------------|---------|
| < 2% | No person |
| ~7% | Borderline (confidence ≈ 0.5) |
| ≥ 15% | Person clearly present (confidence → 1.0) |
Limitation: heuristic skin detection can produce false positives on images with large areas of warm earthy tones (wood, sand, sunsets). For production use cases requiring high accuracy, consider a dedicated ML model.
Input types
All functions accept the same input formats and image types:
// File path
await detect('/path/to/image.jpg');
await detect('/path/to/image.webp'); // WebP ✓
await detect('/path/to/image.avif'); // AVIF ✓
await detect('/path/to/image.heic'); // HEIC ✓
// Remote URL (fetched with built-in fetch)
await detect('https://example.com/photo.webp');
// Buffer
import { readFile } from 'node:fs/promises';
const buf = await readFile('./image.webp');
await detect(buf);Supported formats: JPEG, PNG, WebP, AVIF, HEIC/HEIF, GIF, BMP, TIFF, SVG and any format supported by sharp.
TypeScript
Types ship in index.d.ts — no @types/ package needed.
import { detect, detectPerson } from 'real-or-screenshot';
import type { DetectResult, PersonResult } from 'real-or-screenshot';
const result: DetectResult = await detect('./image.jpg');
const person: PersonResult = await detectPerson('./image.jpg');Advanced: individual analyzers
Each signal is exported as a standalone function for custom pipelines:
import {
analyzeNoise,
analyzeResolution,
analyzeExif,
analyzeEdges,
analyzeCompression,
analyzePerson,
} from 'real-or-screenshot';
import { loadImage } from 'real-or-screenshot/src/utils/loadImage.js';
const { image, buffer } = await loadImage('./photo.jpg');
analyzeNoise(image) // → number 0..1 (sync)
analyzeResolution(image) // → number 0..1 (sync)
await analyzeExif(buffer) // → number 0..1 (async)
analyzeEdges(image) // → number 0..1 (sync)
analyzeCompression(image, buffer)// → number 0..1 (sync)
analyzePerson(image) // → PersonResult (sync)License
MIT
