Format probing, metadata inspection, and pixel statistics
Before running an image pipeline or accepting an external upload, applications usually need to answer two distinct kinds of questions:
- Header probing: What format is this file? What are its width, height, band count, page count, EXIF Orientation, and ICC profile status? These queries parse only the container header without decoding full pixels.
- Pixel sampling and statistics: What is the RGB value at coordinate
(x, y)? What is the average brightness, standard deviation, entropy, or dominant colour across the image? These queries trigger actual pixel decoding.
Header-only format and metadata probing
Section titled “Header-only format and metadata probing”Because libvips opens images lazily, reading Width(), Height(), Bands(), Pages(), or sharp(input).metadata() inspects only the header bytes. Use header probing at upload gateways to enforce dimension limits (such as rejecting images whose total pixel count exceeds your memory budget) before any pixel decompression runs.
In govips, you can also inspect magic bytes with vips.DetermineImageType(buf) and verify runtime codec availability with vips.IsTypeSupported(imgType).
package main
import ( "fmt" "os"
"github.com/davidbyttow/govips/v2/vips")
func main() { if err := vips.Startup(nil); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer vips.Shutdown()
image, err := vips.NewImageFromFile("input.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer image.Close()
fmt.Printf("format=%v size=%dx%d bands=%d alpha=%v icc=%v exif=%v pages=%d orientation=%d\n", vips.ImageTypes[image.Format()], image.Width(), image.Height(), image.Bands(), image.HasAlpha(), image.HasProfile(), image.HasExif(), image.Pages(), image.Orientation(), )}import sharp from 'sharp';
const meta = await sharp('input.jpg').metadata();
console.log({ format: meta.format, width: meta.width, height: meta.height, channels: meta.channels, space: meta.space, hasAlpha: meta.hasAlpha, hasProfile: meta.hasProfile, orientation: meta.orientation, pages: meta.pages ?? 1,});import pyvips
image = pyvips.Image.new_from_file("input.jpg")
print({ "loader": image.get("vips-loader"), "width": image.width, "height": image.height, "bands": image.bands, "interpretation": image.interpretation, "has_alpha": image.hasalpha(), "pages": image.get_n_pages(), "fields": image.get_fields()[:8],})#!/usr/bin/env bashset -euo pipefail
# Print all header and metadata fieldsvipsheader -a input.jpg
# Query individual fields for shell scriptsvipsheader -f width input.jpgvipsheader -f height input.jpgMetadata retention, selective stripping, and binding defaults
Section titled “Metadata retention, selective stripping, and binding defaults”The four interfaces do not share the same default metadata policy on export:
sharpstrips most metadata by default (EXIF, XMP, IPTC) to reduce output size and remove private camera tags; call.keepMetadata(),.keepExif(),.keepIccProfile(), or.withExifMerge()when metadata must be preserved.govips,pyvips, andvips CLIpreserve input metadata by default; to strip metadata, setExportParams.StripMetadata = true, callimage.RemoveMetadata(), or passkeep="none"(orstrip=True) to the saver.
In govips, image.RemoveMetadata(keepFields...) accepts an allowlist of field names — calling image.RemoveMetadata("icc-profile-data") strips private camera EXIF and GPS tags while keeping the embedded ICC colour profile intact.
// Strip EXIF / XMP / IPTC metadata while keeping the embedded ICC profileif err := image.RemoveMetadata("icc-profile-data"); err != nil { return err}// sharp strips EXIF by default; explicitly keep the ICC profile to preserve colour accuracyawait sharp('input.jpg') .keepIccProfile() .webp({ quality: 82 }) .toFile('clean-with-icc.webp');# Retain only the ICC profile on export; strip EXIF, XMP, and IPTCimage = pyvips.Image.new_from_file("input.jpg")image.write_to_file("clean-with-icc.webp", Q=82, keep="icc")vips copy input.jpg 'clean-with-icc.webp[Q=82,keep=icc]'Single-pixel sampling and global statistics (GetPoint / Stats)
Section titled “Single-pixel sampling and global statistics (GetPoint / Stats)”Beyond header fields, libvips computes pixel samples and summary statistics without exporting the full image to your application layer:
- Single-pixel sampling (
GetPoint/getpoint): reads band values at(x, y), useful for automated test assertions, eyedropper background detection, or border checks. - Global and per-band statistics (
Stats/Average/HistogramEntropy): computes minimum, maximum, mean, standard deviation, and histogram entropy.sharp.stats()additionally computes asharpnessestimate and thedominantsRGB colour.
// 1. Sample pixel band values ([]float64) at coordinate (969, 646)pixel, err := image.GetPoint(969, 646)if err != nil { return err}
// 2. Compute whole-image average, minimum, and histogram entropyavg, err := image.Average()if err != nil { return err}entropy, err := image.HistogramEntropy()if err != nil { return err}fmt.Printf("pixel(969,646)=%v avg=%.2f entropy=%.4f\n", pixel, avg, entropy)import sharp from 'sharp';
const stats = await sharp('input.jpg').stats();
console.log({ isOpaque: stats.isOpaque, entropy: stats.entropy, sharpness: stats.sharpness, dominant: stats.dominant, // { r, g, b } redMean: stats.channels[0].mean,});import pyvips
image = pyvips.Image.new_from_file("input.jpg")
pixel = image.getpoint(969, 646)avg = image.avg()std_dev = image.deviate()entropy = image.hist_entropy()print({"pixel": pixel, "avg": avg, "std": std_dev, "entropy": entropy})# Read RGB band values at coordinate (969, 646)vips getpoint input.jpg 969 646
# Compute whole-image mean, standard deviation, and histogram entropyvips avg input.jpgvips deviate input.jpgvips hist_entropy input.jpg| Capability | govips v2.20.0 | sharp 0.35.5 | pyvips 3.2.0 | vips CLI 8.17.3 |
|---|---|---|---|---|
| Magic-byte format sniff | DetermineImageType(buf) |
(await sharp(buf).metadata()).format |
Image.find_load_buffer(buf) |
vipsheader -f vips-loader |
| Single-pixel sample | img.GetPoint(x, y) |
No dedicated method (use extract({ left: x, top: y, width: 1, height: 1 }).raw()) |
img.getpoint(x, y) or img(x, y) |
vips getpoint in x y |
| Whole-image mean / extrema | img.Average(), img.Min(), img.Stats() |
(await img.stats()).channels |
img.avg(), img.min(), img.max(), img.stats() |
vips avg, vips min, vips max, vips stats |
| Entropy / dominant colour | img.HistogramEntropy() |
stats.entropy, stats.dominant, stats.sharpness |
img.hist_entropy() |
vips hist_entropy |