Analyser/Docs
Documentation menu

Images

Everything the site does with still images: EXIF/metadata readout, histogram and colour analysis, OCR, GPS mapping, HEIC/RAW conversion, broken-image recovery, multi-image container extraction (ICO/MPO/TIFF), and the image-to-sound "sonify" tool. Written for anyone using the photo analysis features, and for engineers extending them. Source: photo.js, photo-convert.js, photo-recover.js, sonify.js, tiff.js, mpo.js, ico.js, embedded-images.js.

On this page

Photo metadata readout#

What it does. Drop a photo and Analyser shows file info (size, MIME, dimensions, exact aspect ratio, megapixels, an "≈3:2"-style common-ratio label), then every EXIF/IPTC/XMP/ICC field the file carries, grouped into Camera & lens, Exposure, Date/time, Image, Description (IPTC/XMP) and ICC profile sections.

How to reach it. Drop any recognised photo extension (JPEG, PNG, HEIC, RAW, etc.) - the metadata table appears automatically in the Photo section. Built in web/assets/js/renderers/photo.js via buildExifSections(), using the vendored exifr library.

How to use it. No interaction needed - it's a straight readout. Where a camera records the lifetime shutter-actuation count (Sony's encrypted maker-note tag, decrypted with Sony's own substitution cipher; Nikon's plaintext tag 0x00A7), it's surfaced under "Shutter count". Computational- photography wrappers are separately detected and called out: Apple ProRAW, Live Photo, Google/Samsung Motion Photo, Ultra HDR gain maps, and depth maps.

Notes / limits. exifr metadata parsing runs locally; nothing is uploaded. Sony shutter-count decryption is verified against an ILCE-6400A and a NEX-6; other modern Alpha bodies should resolve the same way but aren't individually tested. Nikon shutter-count reading is structurally validated but not yet confirmed against a real Nikon sample.

RGB histogram and dominant colours#

What it does. A per-channel (R/G/B) tonal-distribution histogram (0 = black, 255 = white) and an 8-swatch dominant-colour palette via colour quantisation, both computed from a canvas read of the decoded image.

How to reach it. Automatic for any decodable photo - appears as the "RGB histogram" card and a colour swatch row. Built in photo.js (getPixelData, dominantColors).

How to use it. Click the histogram to enlarge it. The dominant-colour palette renders as a row of click-to-copy swatches:

#2b2b2b
#c8b0a0
#7a5c3e
#d8d2c4
#4d6b7a
#a83e2c
#e6c95a
#6f7f5c

GPS map#

What it does. If the photo's EXIF carries GPS coordinates, plots the location on an interactive OpenStreetMap tile map with a marker.

How to reach it. Automatic when GPS EXIF is present - a map appears in the photo section's meta sidebar. Built in photo.js's makeMap().

Notes / limits. Lazy-loads Leaflet (JS + CSS) only when GPS data exists, so a GPS-less photo never pays for the map library.

OCR (text extraction)#

What it does. Scans the image for text using an on-device OCR engine and lists the recognised words/lines with confidence filtering.

How to reach it. Click Extract text in the photo section. Built in photo.js around the vendored Tesseract.js.

How to use it. Clicking Extract text opens a language-picker dialog (pickOcrLanguage()): pick from 32 languages (English is bundled and works offline; every other language streams from a CDN on first use, then is cached by the service worker - each entry shows its download size until cached). Tick "Remember for this session" to skip the picker on subsequent OCR runs in the same page load. The "?" button swaps in an explanation of how Tesseract works and its limitations. Confirm with Run OCR, or Cancel/Escape/backdrop-click to abort.

The language-picker dialog confirms with its own pair of buttons:

Notes / limits. Tesseract is designed for scanned documents - clean text on plain backgrounds. On ordinary photos it can hallucinate text from textures, foliage or noise; handwriting, stylised fonts, low contrast, and rotated/curved text all reduce accuracy. Best results: screenshots, signs, printed labels, document photos. Words under 60% confidence are filtered out.

QR code detection#

What it does. Automatically scans every decoded photo for a QR code; if one is found, a "QR code detected" card shows the decoded data (and a clickable link if it's a URL).

How to reach it. Automatic - no button, runs on every photo. Built in photo.js's detectQrCode(), using the lazy-loaded vendored jsQR.

Notes / limits. Silent when no QR code is found (the card is simply never shown).

HEIC/HEIF and camera-RAW conversion#

What it does. Browsers can't decode HEIC/HEIF or most camera RAW formats directly, so Analyser converts them to a displayable form before running the normal photo analysis.

How to reach it. Automatic - drop a .heic/.heif or a RAW extension (.arw, .cr2, .cr3, .nef, .dng, .raf, .rw2, .orf, .pef, .sr2, .srw, .x3f, and other long-tail RAW formats). Implemented in photo-convert.js, called from photo.js.

How to use it. For HEIC, convertHeic() uses the vendored heic2any. For RAW, the fast path (extractRawPreview/extractRawJpegs) pulls the embedded JPEG preview most RAW files carry (no real decode needed); when that's unavailable or a full-quality render is wanted, click Demosaic RAW (full decode) to load the heavyweight ImageMagick-WASM (convertWithImageMagick/demosaicRaw) fallback for genuine sensor demosaicing. .x3f (Sigma Foveon) has its own dedicated preview extractor (extractX3fPreview).

Notes / limits. The embedded-preview fast path is what's shown by default for RAW files - it's the camera's own JPEG preview, not a full sensor reconstruction, so it may not exactly match what a dedicated RAW developer produces. Demosaic RAW downloads ImageMagick's WASM build (~15 MB) only on click.

Broken/truncated/corrupt image recovery#

What it does. When a recognised image fails to decode, Analyser diagnoses the damage and offers to salvage the maximum recoverable picture plus a downloadable repaired file, rather than a dead-end error.

How to reach it. Automatic - triggers when a JPEG/PNG/HEIF/AVIF (or an unrecognised blob) won't decode. Built in photo-recover.js (diagnoseImage, repairJpeg, repairPng, decodePngPartial, repairHeifContainer, carveImages), orchestrated by photo.js's renderPhotoRecovery().

How to use it. The "Broken image - salvage" card shows the detected real format (from signature bytes, regardless of extension) and what's wrong. Strategy by format:

  • JPEG - strips junk before the SOI marker and appends a missing EOI if the file is simply truncated; if the header itself (quantisation/Huffman tables) is damaged, prompts to Choose reference photo... - a healthy JPEG shot on the same camera/mode - and rebuilds the damaged header by borrowing its tables (extractJpegTables/spliceJpegHeader), so the scan data can then decode.
  • PNG - recovers as many image rows as possible (decodePngPartial/repairPng) and re-encodes them as a clean PNG; any unrecovered rows are blank.
  • HEIF/HEIC/AVIF - these formats store metadata at the front, so a tail-truncated file often keeps a decodable image once the oversized mdat box is clamped to the real file length (repairHeifContainer); HEIC output is then converted to JPEG via convertHeic.
  • Unrecognised blob / wrong extension / disk fragment - carves every embedded image signature out of the data (carveImages) and shows each as a thumbnail grid.

Every recovered/carved result gets Run full analysis (re-runs the full photo pipeline on the salvaged picture) and Download recovered image (or per-item Analyse/Download for carved fragments).

Broken image - salvage

Detected JPEG (from signature bytes) - truncated, missing EOI marker. Salvaged the recoverable region.

Notes / limits. Recovery is best-effort: a cut-off region shows as blank rather than being invented. If nothing can be salvaged, the card explains why rather than showing nothing.

Live Photo / Motion Photo playback#

What it does. Apple Live Photos, Google Motion Photo, and Samsung Motion Photo append a whole MP4/QuickTime clip after the still picture. The browser only paints the still - this locates the appended clip and plays it through the real video + audio renderers on demand.

How to reach it. A hidden Analyse live photo button appears beside the photo preview only if a trailing clip is detected (detectLiveVideo() in photo.js, checking XMP MicroVideoOffset/Container:Directory tags first, then a full ISO-BMFF box scan gated on marker text).

How to use it. Click Analyse live photo; it carves the trailer (via file.slice, no full in-memory copy) and renders it through the real video player plus, if the clip has a decodable audio track, the full audio analyser underneath.

Notes / limits. Detection distinguishes Google Motion Photo, Samsung Motion Photo, and Apple Live Photo by their respective markers/MIME hints.

RAW develop-settings sidecar (.xmp) import#

What it does. Reads a .xmp sidecar's non-destructive edit recipe (exposure, contrast, highlights, white balance, camera profile, and more - Adobe's Camera Raw Settings namespace) and shows it alongside the RAW photo.

How to reach it. Click Import XMP settings next to a RAW photo's analysis, then pick the .xmp file. Built in photo.js's parseDevelopSettings().

Animated image frame stepping (GIF/WebP)#

What it does. For animated GIF/WebP dropped as a photo, steps through frame-by-frame, analyses any single frame as a standalone photo, builds a thumbnail contact sheet of every frame, and can reverse the whole animation.

How to reach it. Drop an animated GIF or WebP. Frame decoding is gif-frames.js (hand-rolled LZW decoder)/webp-frames.js (browser ImageDecoder/WebCodecs); re-encoding for the reverse feature is gif-encode.js.

How to use it. Prev/Next step through frames; Analyse frame runs the current frame through the full photo pipeline; Generate contact sheet (shown once there are 8+ frames) builds a thumbnail grid; Reverse plays/downloads the animation backwards (Download reversed for GIF).

Sonify (image to sound)#

What it does. Turns any picture into audio by reading it as a spectrogram: x = time, y = frequency, brightness = loudness. The inverse of the audio module's spectrogram view.

How to reach it. Click Sonify (play as spectrogram) on a photo (also reachable from a video frame - see video.md). Implemented in sonify.js, lazy-imported by photo.js.

How to use it. Two synthesis engines: Oscillator bank (one sine tone per frequency row, robust on any picture, the default) or Griffin-Lim (iteratively refines phase to better match a genuine spectrogram image, more faithful but heavier). Controls: Mode (arbitrary image vs. a real spectrogram plot), Invert (bright=loud vs. dark=loud), Axis (log/linear frequency spacing), Min/Max frequency, Length (click the readout to type an exact value past the slider's range), and an Advanced panel (sample rate, FFT size, window function, Griffin-Lim iteration count, gamma brightness curve, left/right channel source for stereo, colourmap decoding and dB scale for real-spectrogram mode). The source image preview updates live as controls change. Click Render & play to synthesise and immediately play the result through the site's full Sound analysis (waveform, spectrogram, LUFS, etc. - see audio.md); Analyse WAV does the same without auto-playing; Download WAV saves the rendered audio. Clicking the source-image preview seeks the audio to that point (and the reverse: playback drives an accent-coloured playhead over the image).

The tool is reached from a button beside the photo, and its Axis control is a segmented toggle (the selected option carries the accent fill):

Notes / limits. Purely on-device Web Audio synthesis; no libraries beyond the site's own hand-written FFT (shared with spectrogram.js).

Multi-image container extraction (ICO, MPO, multi-page TIFF)#

What it does. Several still-image formats pack more than one picture into a single file - an ICO's size ladder, an MPO stereo/multi-angle pair, a scanned multi-page TIFF - but a browser <img> only ever paints one. Each format gets its own pure-parsing extractor that hands its images to a shared "Embedded images" display card.

How to reach it. Automatic - drop a .ico/.cur (ico.js), a .mpo stereo/multi-picture JPEG (mpo.js), or a multi-page .tif/.tiff (tiff.js, which only spins up ImageMagick to render pages when there genuinely are 2+ pages - a single-page TIFF shows nothing extra).

How to use it. Every extracted image, on a transparency checkerboard, gets its size/format/byte-count caption, a Download button, and (when wired into the main results container) an Analyse button that re-runs the full photo pipeline on that one extracted image.

Notes / limits. ICO extraction rebuilds a minimal single-image icon blob per directory entry so the browser's native decoder paints exactly that entry (PNG-compressed and classic BMP/DIB alike, with transparency); pure parsing, no WASM, instant and offline. MPO reads the CIPA Multi-Picture Format (MPF) index in the JPEG's APP2 segment. Multi-page TIFF first walks the IFD chain in pure JS just to learn the page count and size (cheap, no decode) before deciding whether ImageMagick rendering is worth it.

Browser-undecodable image formats#

What it does. For image formats no browser can decode (JPEG XL, TIFF, JPEG 2000, TGA, DDS, OpenEXR, Radiance HDR, PCX, SGI, Sun Raster, GIMP XCF, CorelDRAW, WMF/EMF, farbfeld, and PSD/PSB when not otherwise handled), Analyser shows a clear "browser limitation" banner plus whatever EXIF/IPTC/XMP/ICC metadata exifr can still read from the raw bytes, rather than a bare decode error.

How to reach it. Automatic - triggers when the browser's <img> fails to decode a recognised-but-undisplayable extension. Built in photo.js's renderUndisplayableImage()/undecodableImageBanner().

Notes / limits. The file itself is not necessarily broken - converting it to PNG/JPEG with an external tool usually makes it viewable.