Metadata and privacy
A photograph from a phone usually carries the location it was taken, the device's serial number and the exact time. Pass the file through untouched and you have published all of that. Bmage removes metadata by default; you have to ask for it to be kept.
See what survives
Drop a photo straight off a phone - or use a generated one that carries a camera, a serial number and GPS coordinates. It is exported as a JPEG under each policy, and each export is decoded again to read back what is really in it. Nothing is uploaded anywhere.
The three policies
new ImageExportOptions { Metadata = MetadataPolicy.Remove } // the defaultnew ImageExportOptions { Metadata = MetadataPolicy.StripSensitive }new ImageExportOptions { Metadata = MetadataPolicy.Preserve }| Policy | Keeps | Removes |
|---|---|---|
Remove | nothing | EXIF, XMP, ICC profile, thumbnail |
StripSensitive | camera, lens, exposure, dates, copyright, description, ICC profile | GPS, serial numbers, owner name, user comments, maker notes, unique IDs, embedded thumbnail, XMP |
Preserve | everything the target format can carry | the embedded thumbnail, which would be stale |
StripSensitive drops XMP entirely: XMP can carry location and device identifiers of
its own, and there is no reliable way to filter it field by field. All three policies reset the
stored orientation to normal and update the recorded dimensions, because the exported pixels
are already upright and already the new size - leaving a rotation flag on rotated pixels is how
images end up sideways twice.
Reading what is there
using var image = await loader.LoadAsync(file);var exif = image.Metadata?.Exif;if (exif is not null){ Console.WriteLine($"{exif.Make} {exif.Model}"); Console.WriteLine($"Lens: {exif.LensModel}"); Console.WriteLine($"{exif.ExposureTime}s at f/{exif.FNumber}, ISO {exif.Iso}"); Console.WriteLine($"Taken {exif.DateTaken}"); if (exif.Location is { } location) Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");}// Every tag stays reachable, typed accessor or not.var artist = exif.Primary.Get(ExifTag.Artist)?.GetString();var custom = exif.Exif.Get(0x9C9B)?.GetString();The Headless API page shows the pattern for warning users: read the location, say it is there, and say what will happen to it.
Writing metadata
var exif = new ExifData() .WithPrimary(new ExifDirectory() .With(ExifTag.Artist, ExifValue.FromString("Sam Rivera")) .With(ExifTag.Copyright, ExifValue.FromString("© 2026 Sam Rivera")));image.Metadata = new ImageMetadata(exif);var encoded = await exporter.EncodeAsync(image, new ImageExportOptions{ Format = ImageFormat.Jpeg, Metadata = MetadataPolicy.Preserve,});ExifValue.FromString writes UTF-8. The EXIF specification calls the type ASCII, but
cameras and editors have written UTF-8 in these fields for years, and restricting to ASCII would
silently turn "©" into "?" and mangle any name with an accent.
Orientation
Phones do not rotate pixels when you turn the device; they record a rotation flag. An image that
looks upright in a photo viewer appears sideways on a canvas unless that flag is applied. Bmage
applies it during decoding by default; with AutoOrient = false it is reported
instead, so you can decide.
await loader.LoadAsync(file); // uprightawait loader.LoadAsync(file, new DecodeOptions { AutoOrient = false }); // as storedvar orientation = image.Metadata!.Orientation;if (orientation.SwapsDimensions()) { /* portrait photo stored as landscape */ }
The eight EXIF orientations form the dihedral group of the square, and
OrientationMath implements the algebra: Then composes two,
Inverse undoes one, SwapsDimensions says whether width and height
exchange, and ToMatrix gives the coordinate mapping.
What each format can carry
| Format | EXIF | ICC | XMP |
|---|---|---|---|
| JPEG | yes | yes | yes |
| PNG | yes (eXIf) | yes (iCCP) | yes (iTXt) |
| WebP | yes | yes | yes |
| AVIF | not written by this library | no | no |
| BMP, GIF | no | no | no |
Metadata is attached after encoding, by rewriting the container: the browser's encoders discard it, so it is re-inserted in C# according to your policy. If a rewrite fails, the correctly encoded image is returned without metadata rather than the whole export failing. Parsing is defensive throughout - bad offsets, circular references and truncated data are ignored, never thrown - because a corrupt EXIF block must never stop an image from loading.
A checklist for user uploads
- Leave the default
MetadataPolicy.Removeunless you have a reason. - If you keep metadata for a gallery, use
StripSensitive, notPreserve. - Tell users what is in their file before they publish it.
- Leave
AutoOrienton so the exported pixels match what the user saw. - Remember that redaction is about pixels, and metadata is a separate leak.