Table of Contents

Class ExifData

Namespace
Egami.Metadata
Assembly
Egami.dll

A parsed EXIF block (a TIFF structure, CIPA DC-008): the tags of IFD0, the EXIF, GPS and interoperability sub-IFDs and IFD1 with its thumbnail, with typed getters for the common ones. An unedited block writes back byte for byte; after an edit it is laid out again, keeping the maker note at its old offset when it fits, since maker notes often hold offsets of their own.

public sealed class ExifData
Inheritance
ExifData
Inherited Members

Examples

using Raster photo = Raster.Load("sideways.jpg");
ExifData exif = photo.Metadata.Exif ?? new ExifData();

// Typed values go in with their EXIF type; fractions are exact numerator / denominator pairs.
exif.Set(ExifKey.ExposureTime, new ExifFraction(1, 250));
exif.Set(ExifKey.Artist, "A. Photographer");
if (exif.TryGet(ExifKey.ExposureTime, out ExifEntry? entry) && entry.Type == ExifType.Rational)
{
    Console.WriteLine($"Exposure {entry.AsFraction()} s = {entry.AsFraction().ToDouble()} s, in the {entry.Key.Ifd} IFD");
}

if (exif.Location is GeoPosition where)
{
    Console.WriteLine($"Taken at {where.Latitude:F5}, {where.Longitude:F5}");
}

Constructors

ExifData()

Creates an empty block, little-endian, to fill with Set(ExifKey, string) and the other setters.

public ExifData()

Properties

CameraMake

Gets the camera maker, or null.

public string? CameraMake { get; }

Property Value

string

CameraModel

Gets the camera model, or null.

public string? CameraModel { get; }

Property Value

string

DateTaken

Gets when the photo was taken: DateTimeOriginal, else DateTime, or null.

public DateTime? DateTaken { get; }

Property Value

DateTime?

Entries

Gets every entry, IFD by IFD (IFD0, EXIF, GPS, interoperability, IFD1), in the order stored.

public IReadOnlyList<ExifEntry> Entries { get; }

Property Value

IReadOnlyList<ExifEntry>

IsBigEndian

Gets whether values are stored big-endian (Motorola order) rather than little-endian (Intel order).

public bool IsBigEndian { get; }

Property Value

bool

IsDamaged

Gets whether the block was damaged when read: an offset outside it, a loop between IFDs, or more data than allowed. What could be read is available; the original bytes are kept and written back unless the block is edited.

public bool IsDamaged { get; }

Property Value

bool

Location

Gets the GPS position, or null when the block has no complete latitude and longitude.

public GeoPosition? Location { get; }

Property Value

GeoPosition?

Orientation

Gets or sets the orientation (IFD0 0x0112). Missing or out-of-range values read as Normal.

public ImageOrientation Orientation { get; set; }

Property Value

ImageOrientation

Exceptions

ArgumentOutOfRangeException

The value set is not one of the EXIF values 1 to 8.

Thumbnail

Gets the embedded thumbnail (usually a small JPEG), or empty.

public ReadOnlyMemory<byte> Thumbnail { get; }

Property Value

ReadOnlyMemory<byte>

Warnings

Gets what was wrong with a damaged block, and what changed when an edited block was written, as sentences.

public IReadOnlyList<string> Warnings { get; }

Property Value

IReadOnlyList<string>

Methods

Copy()

Returns an independent copy.

public ExifData Copy()

Returns

ExifData

The copy.

Parse(ReadOnlySpan<byte>, SafetyLimits?)

Reads an EXIF block: a TIFF header and its IFDs, without the "Exif\0\0" prefix JPEG puts before it. Never throws: values past MaxMetadataBytes in all are dropped and the block is marked damaged.

public static ExifData Parse(ReadOnlySpan<byte> tiff, SafetyLimits? limits = null)

Parameters

tiff ReadOnlySpan<byte>

The block.

limits SafetyLimits

The limits whose MaxMetadataBytes caps the values copied. Defaults to Default.

Returns

ExifData

The parsed block; IsDamaged says whether it was all readable.

Examples

// EXIF from somewhere other than an image file (a database column, a sidecar), with a cap on the bytes of values read.
ExifData exif = ExifData.Parse(tiff.Span, SafetyLimits.Default with { MaxMetadataBytes = 64 * 1024 });
foreach (string warning in exif.Warnings)
{
    Console.WriteLine($"EXIF: {warning}");
}

ReadThumbnail(EgamiContext?)

Decodes the IFD1 thumbnail with Egami's JPEG reader under the context's safety limits. The pixels are as stored: apply the main image's orientation (Orientation) to show it upright.

public Raster<Rgb8>? ReadThumbnail(EgamiContext? context = null)

Parameters

context EgamiContext

The context whose limits apply. Defaults to Default.

Returns

Raster<Rgb8>

The thumbnail, or null when there is no JPEG thumbnail (uncompressed ones are kept as bytes). The caller must dispose it.

Examples

using Raster photo = Raster.Load("sideways.jpg");

// The thumbnail as stored, if the file has one; ReadThumbnail returns null otherwise.
using (Raster<Rgb8>? stored = photo.Metadata.Exif?.ReadThumbnail())
{
    Console.WriteLine(stored is null ? "No thumbnail" : $"Thumbnail {stored.Width} x {stored.Height}");
}

// Turning the image drops the thumbnail, which would show sideways; make a new one from the result.
photo.Apply(ops => ops.AutoOrient());
photo.Metadata.Exif?.SetThumbnail(photo);
photo.Save("upright.jpg");

Exceptions

CorruptDataException

The thumbnail is damaged.

LimitExceededException

The thumbnail is larger than the context's limits allow.

Remove(ExifKey)

Removes a tag. Removing GpsInfo or ExifInfo removes that whole sub-IFD.

public bool Remove(ExifKey key)

Parameters

key ExifKey

The tag and IFD.

Returns

bool

Whether anything was removed.

RemoveThumbnail()

Removes the thumbnail and the rest of IFD1, as operations do once the image no longer matches it.

public bool RemoveThumbnail()

Returns

bool

Whether there was one.

Set(ExifKey, ExifType, ReadOnlySpan<byte>)

Sets a value of any type from its raw bytes, in this block's byte order.

public void Set(ExifKey key, ExifType type, ReadOnlySpan<byte> value)

Parameters

key ExifKey

The tag and IFD.

type ExifType

The TIFF type.

value ReadOnlySpan<byte>

The bytes: a whole number of values of type.

Exceptions

ArgumentException

The type is unknown or the bytes are not a whole number of values.

Set(ExifKey, params ReadOnlySpan<ExifFraction>)

Sets one or more fractions: RATIONAL type, or SRATIONAL when any part is negative.

public void Set(ExifKey key, params ReadOnlySpan<ExifFraction> values)

Parameters

key ExifKey

The tag and IFD.

values ReadOnlySpan<ExifFraction>

The fractions; each part must fit 32 bits.

Exceptions

ArgumentException

values is empty.

ArgumentOutOfRangeException

A numerator or denominator does not fit 32 bits.

Set(ExifKey, string)

Sets a text value: ASCII type, with other characters stored as UTF-8, as many programs do. The Windows XP tags are stored as UTF-16 in BYTE values, as Explorer writes them, and the encoded strings (UserComment, GpsProcessingMethod, GpsAreaInformation) as UNDEFINED with an ASCII prefix when the text is ASCII and a UNICODE one otherwise.

public void Set(ExifKey key, string value)

Parameters

key ExifKey

The tag and IFD.

value string

The text.

Set(ExifKey, ushort)

Sets a 16-bit unsigned value (SHORT type).

public void Set(ExifKey key, ushort value)

Parameters

key ExifKey

The tag and IFD.

value ushort

The value.

Set(ExifKey, uint)

Sets a 32-bit unsigned value (LONG type).

public void Set(ExifKey key, uint value)

Parameters

key ExifKey

The tag and IFD.

value uint

The value.

SetThumbnail(Raster, int, int)

Makes a thumbnail from raster: converted to sRGB, resized to fit maxSide (never enlarged), encoded as a baseline 4:2:0 JPEG and stored in IFD1 with compression 6. 160 pixels is the DCF thumbnail size. Operations drop a thumbnail that no longer matches the image; this rebuilds it in one line.

public void SetThumbnail(Raster raster, int maxSide = 160, int quality = 75)

Parameters

raster Raster

The image, usually the one this block belongs to after its operations. Its first frame is used.

maxSide int

The longest side, in pixels. Defaults to 160.

quality int

The JPEG quality, 1 to 100. Defaults to 75.

Examples

using Raster photo = Raster.Load("sideways.jpg");

// The thumbnail as stored, if the file has one; ReadThumbnail returns null otherwise.
using (Raster<Rgb8>? stored = photo.Metadata.Exif?.ReadThumbnail())
{
    Console.WriteLine(stored is null ? "No thumbnail" : $"Thumbnail {stored.Width} x {stored.Height}");
}

// Turning the image drops the thumbnail, which would show sideways; make a new one from the result.
photo.Apply(ops => ops.AutoOrient());
photo.Metadata.Exif?.SetThumbnail(photo);
photo.Save("upright.jpg");

Exceptions

ArgumentNullException

raster is null.

ArgumentOutOfRangeException

maxSide is below 1, or quality is outside 1 to 100.

ToBytes()

Returns the block as bytes: the bytes it was read from if nothing was edited, else the block laid out again. A maker note that cannot stay at its old offset adds a warning to Warnings.

public ReadOnlyMemory<byte> ToBytes()

Returns

ReadOnlyMemory<byte>

The TIFF bytes.

TryGet(ExifKey, out ExifEntry?)

Returns the entry for key, if the block has it.

public bool TryGet(ExifKey key, out ExifEntry? entry)

Parameters

key ExifKey

The tag and IFD.

entry ExifEntry

The entry, or null.

Returns

bool

Whether the block has the tag.