mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Exif support Former-commit-id: 21e226e781e516c4c2f028d609ddc662bb083f9d Former-commit-id: 73a1a23ba9ebf37421b80f3890cf12a5c86fe848 Former-commit-id: 446b1ed069e6f7316f92801e629d1dfdff28ce29af/merge-core
committed by
GitHub
17 changed files with 3271 additions and 19 deletions
@ -1 +1 @@ |
|||
25b2ab2a0acfb874ca8ac8ae979fc6b1bc9bf466 |
|||
09f4618eaade2900f7174b9ab400deb0a25bd813 |
|||
@ -0,0 +1,78 @@ |
|||
// <copyright file="ExifDataType.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
///<summary>
|
|||
/// Specifies exif data types.
|
|||
///</summary>
|
|||
public enum ExifDataType |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown
|
|||
/// </summary>
|
|||
Unknown, |
|||
|
|||
/// <summary>
|
|||
/// Byte
|
|||
/// </summary>
|
|||
Byte, |
|||
|
|||
/// <summary>
|
|||
/// Ascii
|
|||
/// </summary>
|
|||
Ascii, |
|||
|
|||
/// <summary>
|
|||
/// Short
|
|||
/// </summary>
|
|||
Short, |
|||
|
|||
/// <summary>
|
|||
/// Long
|
|||
/// </summary>
|
|||
Long, |
|||
|
|||
/// <summary>
|
|||
/// Rational
|
|||
/// </summary>
|
|||
Rational, |
|||
|
|||
/// <summary>
|
|||
/// SignedByte
|
|||
/// </summary>
|
|||
SignedByte, |
|||
|
|||
/// <summary>
|
|||
/// Undefined
|
|||
/// </summary>
|
|||
Undefined, |
|||
|
|||
/// <summary>
|
|||
/// SignedShort
|
|||
/// </summary>
|
|||
SignedShort, |
|||
|
|||
/// <summary>
|
|||
/// SignedLong
|
|||
/// </summary>
|
|||
SignedLong, |
|||
|
|||
/// <summary>
|
|||
/// SignedRational
|
|||
/// </summary>
|
|||
SignedRational, |
|||
|
|||
/// <summary>
|
|||
/// SingleFloat
|
|||
/// </summary>
|
|||
SingleFloat, |
|||
|
|||
/// <summary>
|
|||
/// DoubleFloat
|
|||
/// </summary>
|
|||
DoubleFloat |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// <copyright file="ExifParts.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System; |
|||
|
|||
///<summary>
|
|||
/// Specifies which parts will be written when the profile is added to an image.
|
|||
///</summary>
|
|||
[Flags] |
|||
public enum ExifParts |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// None
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// IfdTags
|
|||
/// </summary>
|
|||
IfdTags = 1, |
|||
|
|||
/// <summary>
|
|||
/// ExifTags
|
|||
/// </summary>
|
|||
ExifTags = 4, |
|||
|
|||
/// <summary>
|
|||
/// GPSTags
|
|||
/// </summary>
|
|||
GPSTags = 8, |
|||
|
|||
/// <summary>
|
|||
/// All
|
|||
/// </summary>
|
|||
All = IfdTags | ExifTags | GPSTags |
|||
} |
|||
} |
|||
@ -0,0 +1,227 @@ |
|||
// <copyright file="ExifProfile.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Class that can be used to access an Exif profile.
|
|||
/// </summary>
|
|||
public sealed class ExifProfile |
|||
{ |
|||
private byte[] data; |
|||
private Collection<ExifValue> values; |
|||
private List<ExifTag> invalidTags; |
|||
private int thumbnailOffset; |
|||
private int thumbnailLength; |
|||
|
|||
///<summary>
|
|||
/// Initializes a new instance of the <see cref="ExifProfile"/> class.
|
|||
///</summary>
|
|||
///<param name="data">The byte array to read the exif profile from.</param>
|
|||
public ExifProfile() |
|||
: this((byte[])null) |
|||
{ |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Initializes a new instance of the <see cref="ExifProfile"/> class.
|
|||
///</summary>
|
|||
///<param name="data">The byte array to read the exif profile from.</param>
|
|||
public ExifProfile(byte[] data) |
|||
{ |
|||
Parts = ExifParts.All; |
|||
BestPrecision = false; |
|||
this.data = data; |
|||
this.invalidTags = new List<ExifTag>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExifProfile"/> class
|
|||
/// by making a copy from another exif profile.
|
|||
/// </summary>
|
|||
/// <param name="other">The other exif profile, where the clone should be made from.</param>
|
|||
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
|
|||
public ExifProfile(ExifProfile other) |
|||
{ |
|||
Guard.NotNull(other, nameof(other)); |
|||
|
|||
Parts = other.Parts; |
|||
BestPrecision = other.BestPrecision; |
|||
|
|||
this.thumbnailLength = other.thumbnailLength; |
|||
this.thumbnailOffset = other.thumbnailOffset; |
|||
this.invalidTags = new List<ExifTag>(other.invalidTags); |
|||
if (other.values != null) |
|||
{ |
|||
this.values = new Collection<ExifValue>(); |
|||
foreach(ExifValue value in other.values) |
|||
{ |
|||
this.values.Add(new ExifValue(value)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.data = other.data; |
|||
} |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Specifies if rationals should be stored with the best precision possible. This is disabled
|
|||
/// by default, setting this to true will have an impact on the performance.
|
|||
///</summary>
|
|||
public bool BestPrecision |
|||
{ |
|||
get; |
|||
set; |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Specifies which parts will be written when the profile is added to an image.
|
|||
///</summary>
|
|||
public ExifParts Parts |
|||
{ |
|||
get; |
|||
set; |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Returns the tags that where found but contained an invalid value.
|
|||
///</summary>
|
|||
public IEnumerable<ExifTag> InvalidTags |
|||
{ |
|||
get |
|||
{ |
|||
return this.invalidTags; |
|||
} |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Returns the values of this exif profile.
|
|||
///</summary>
|
|||
public IEnumerable<ExifValue> Values |
|||
{ |
|||
get |
|||
{ |
|||
InitializeValues(); |
|||
return this.values; |
|||
} |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Returns the thumbnail in the exif profile when available.
|
|||
///</summary>
|
|||
/// <typeparam name="T">The pixel format.</typeparam>
|
|||
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
|
|||
public Image<T, TP> CreateThumbnail<T, TP>() |
|||
where T : IPackedVector<TP> |
|||
where TP : struct |
|||
{ |
|||
InitializeValues(); |
|||
|
|||
if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) |
|||
return null; |
|||
|
|||
if (this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) |
|||
return null; |
|||
|
|||
using (MemoryStream memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) |
|||
{ |
|||
return new Image<T, TP>(memStream); |
|||
} |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Returns the value with the specified tag.
|
|||
///</summary>
|
|||
///<param name="tag">The tag of the exif value.</param>
|
|||
public ExifValue GetValue(ExifTag tag) |
|||
{ |
|||
foreach (ExifValue exifValue in Values) |
|||
{ |
|||
if (exifValue.Tag == tag) |
|||
return exifValue; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Removes the value with the specified tag.
|
|||
///</summary>
|
|||
///<param name="tag">The tag of the exif value.</param>
|
|||
public bool RemoveValue(ExifTag tag) |
|||
{ |
|||
InitializeValues(); |
|||
|
|||
for (int i = 0; i < this.values.Count; i++) |
|||
{ |
|||
if (this.values[i].Tag == tag) |
|||
{ |
|||
this.values.RemoveAt(i); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Sets the value of the specified tag.
|
|||
///</summary>
|
|||
///<param name="tag">The tag of the exif value.</param>
|
|||
///<param name="value">The value.</param>
|
|||
public void SetValue(ExifTag tag, object value) |
|||
{ |
|||
foreach (ExifValue exifValue in Values) |
|||
{ |
|||
if (exifValue.Tag == tag) |
|||
{ |
|||
exifValue.Value = value; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
ExifValue newExifValue = ExifValue.Create(tag, value); |
|||
this.values.Add(newExifValue); |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Converts this instance to a byte array.
|
|||
///</summary>
|
|||
public byte[] ToByteArray() |
|||
{ |
|||
if (this.values == null) |
|||
return data; |
|||
|
|||
if (this.values.Count == 0) |
|||
return null; |
|||
|
|||
ExifWriter writer = new ExifWriter(this.values, Parts, BestPrecision); |
|||
return writer.GetData(); |
|||
} |
|||
|
|||
private void InitializeValues() |
|||
{ |
|||
if (this.values != null) |
|||
return; |
|||
|
|||
if (this.data == null) |
|||
{ |
|||
this.values = new Collection<ExifValue>(); |
|||
return; |
|||
} |
|||
|
|||
ExifReader reader = new ExifReader(); |
|||
this.values = reader.Read(this.data); |
|||
this.invalidTags = new List<ExifTag>(reader.InvalidTags); |
|||
this.thumbnailOffset = (int)reader.ThumbnailOffset; |
|||
this.thumbnailLength = (int)reader.ThumbnailLength; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,426 @@ |
|||
// <copyright file="ExifReader.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
using System.Text; |
|||
|
|||
internal sealed class ExifReader |
|||
{ |
|||
private delegate TDataType ConverterMethod<TDataType>(byte[] data); |
|||
|
|||
private byte[] data; |
|||
private Collection<ExifTag> invalidTags = new Collection<ExifTag>(); |
|||
private uint index; |
|||
private bool isLittleEndian; |
|||
private uint exifOffset; |
|||
private uint gpsOffset; |
|||
private uint startIndex; |
|||
|
|||
public uint ThumbnailLength |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
public uint ThumbnailOffset |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
private int RemainingLength |
|||
{ |
|||
get |
|||
{ |
|||
if (this.index >= this.data.Length) |
|||
return 0; |
|||
|
|||
return this.data.Length - (int)this.index; |
|||
} |
|||
} |
|||
|
|||
public Collection<ExifValue> Read(byte[] data) |
|||
{ |
|||
Collection<ExifValue> result = new Collection<ExifValue>(); |
|||
|
|||
this.data = data; |
|||
|
|||
if (GetString(4) == "Exif") |
|||
{ |
|||
if (GetShort() != 0) |
|||
return result; |
|||
|
|||
this.startIndex = 6; |
|||
} |
|||
else |
|||
{ |
|||
this.index = 0; |
|||
} |
|||
|
|||
this.isLittleEndian = GetString(2) == "II"; |
|||
|
|||
if (GetShort() != 0x002A) |
|||
return result; |
|||
|
|||
uint ifdOffset = GetLong(); |
|||
AddValues(result, ifdOffset); |
|||
|
|||
uint thumbnailOffset = GetLong(); |
|||
GetThumbnail(thumbnailOffset); |
|||
|
|||
if (this.exifOffset != 0) |
|||
AddValues(result, this.exifOffset); |
|||
|
|||
if (this.gpsOffset != 0) |
|||
AddValues(result, this.gpsOffset); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public IEnumerable<ExifTag> InvalidTags |
|||
{ |
|||
get |
|||
{ |
|||
return this.invalidTags; |
|||
} |
|||
} |
|||
|
|||
private void AddValues(Collection<ExifValue> values, uint index) |
|||
{ |
|||
this.index = this.startIndex + index; |
|||
ushort count = GetShort(); |
|||
|
|||
for (ushort i = 0; i < count; i++) |
|||
{ |
|||
ExifValue value = CreateValue(); |
|||
if (value == null) |
|||
continue; |
|||
|
|||
bool duplicate = false; |
|||
foreach (ExifValue val in values) |
|||
{ |
|||
if (val.Tag == value.Tag) |
|||
{ |
|||
duplicate = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (duplicate) |
|||
continue; |
|||
|
|||
if (value.Tag == ExifTag.SubIFDOffset) |
|||
{ |
|||
if (value.DataType == ExifDataType.Long) |
|||
this.exifOffset = (uint)value.Value; |
|||
} |
|||
else if (value.Tag == ExifTag.GPSIFDOffset) |
|||
{ |
|||
if (value.DataType == ExifDataType.Long) |
|||
this.gpsOffset = (uint)value.Value; |
|||
} |
|||
else |
|||
values.Add(value); |
|||
} |
|||
} |
|||
|
|||
private object ConvertValue(ExifDataType dataType, byte[] data, uint numberOfComponents) |
|||
{ |
|||
if (data == null || data.Length == 0) |
|||
return null; |
|||
|
|||
switch (dataType) |
|||
{ |
|||
case ExifDataType.Unknown: |
|||
return null; |
|||
case ExifDataType.Ascii: |
|||
return ToString(data); |
|||
case ExifDataType.Byte: |
|||
if (numberOfComponents == 1) |
|||
return ToByte(data); |
|||
else |
|||
return data; |
|||
case ExifDataType.DoubleFloat: |
|||
if (numberOfComponents == 1) |
|||
return ToDouble(data); |
|||
else |
|||
return ToArray(dataType, data, ToDouble); |
|||
case ExifDataType.Long: |
|||
if (numberOfComponents == 1) |
|||
return ToLong(data); |
|||
else |
|||
return ToArray(dataType, data, ToLong); |
|||
case ExifDataType.Rational: |
|||
if (numberOfComponents == 1) |
|||
return ToRational(data); |
|||
else |
|||
return ToArray(dataType, data, ToRational); |
|||
case ExifDataType.Short: |
|||
if (numberOfComponents == 1) |
|||
return ToShort(data); |
|||
else |
|||
return ToArray(dataType, data, ToShort); |
|||
case ExifDataType.SignedByte: |
|||
if (numberOfComponents == 1) |
|||
return ToSignedByte(data); |
|||
else |
|||
return ToArray(dataType, data, ToSignedByte); |
|||
case ExifDataType.SignedLong: |
|||
if (numberOfComponents == 1) |
|||
return ToSignedLong(data); |
|||
else |
|||
return ToArray(dataType, data, ToSignedLong); |
|||
case ExifDataType.SignedRational: |
|||
if (numberOfComponents == 1) |
|||
return ToSignedRational(data); |
|||
else |
|||
return ToArray(dataType, data, ToSignedRational); |
|||
case ExifDataType.SignedShort: |
|||
if (numberOfComponents == 1) |
|||
return ToSignedShort(data); |
|||
else |
|||
return ToArray(dataType, data, ToSignedShort); |
|||
case ExifDataType.SingleFloat: |
|||
if (numberOfComponents == 1) |
|||
return ToSingle(data); |
|||
else |
|||
return ToArray(dataType, data, ToSingle); |
|||
case ExifDataType.Undefined: |
|||
if (numberOfComponents == 1) |
|||
return ToByte(data); |
|||
else |
|||
return data; |
|||
default: |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
private ExifValue CreateValue() |
|||
{ |
|||
if (RemainingLength < 12) |
|||
return null; |
|||
|
|||
ExifTag tag = ToEnum(GetShort(), ExifTag.Unknown); |
|||
ExifDataType dataType = ToEnum(GetShort(), ExifDataType.Unknown); |
|||
object value = null; |
|||
|
|||
if (dataType == ExifDataType.Unknown) |
|||
return new ExifValue(tag, dataType, value, false); |
|||
|
|||
uint numberOfComponents = (uint)GetLong(); |
|||
|
|||
uint size = numberOfComponents * ExifValue.GetSize(dataType); |
|||
byte[] data = GetBytes(4); |
|||
|
|||
if (size > 4) |
|||
{ |
|||
uint oldIndex = this.index; |
|||
this.index = ToLong(data) + this.startIndex; |
|||
if (RemainingLength < size) |
|||
{ |
|||
this.invalidTags.Add(tag); |
|||
this.index = oldIndex; |
|||
return null; |
|||
} |
|||
value = ConvertValue(dataType, GetBytes(size), numberOfComponents); |
|||
this.index = oldIndex; |
|||
} |
|||
else |
|||
{ |
|||
value = ConvertValue(dataType, data, numberOfComponents); |
|||
} |
|||
|
|||
bool isArray = value != null && numberOfComponents > 1; |
|||
return new ExifValue(tag, dataType, value, isArray); |
|||
} |
|||
|
|||
private TEnum ToEnum<TEnum>(int value, TEnum defaultValue) |
|||
where TEnum : struct |
|||
{ |
|||
TEnum enumValue = (TEnum)(object)value; |
|||
if (Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Any(v => v.Equals(enumValue))) |
|||
return enumValue; |
|||
|
|||
return defaultValue; |
|||
} |
|||
|
|||
private byte[] GetBytes(uint length) |
|||
{ |
|||
if (this.index + length > (uint)this.data.Length) |
|||
return null; |
|||
|
|||
byte[] data = new byte[length]; |
|||
Array.Copy(this.data, (int)this.index, data, 0, (int)length); |
|||
this.index += length; |
|||
|
|||
return data; |
|||
} |
|||
|
|||
private uint GetLong() |
|||
{ |
|||
return ToLong(GetBytes(4)); |
|||
} |
|||
|
|||
private ushort GetShort() |
|||
{ |
|||
return ToShort(GetBytes(2)); |
|||
} |
|||
|
|||
private string GetString(uint length) |
|||
{ |
|||
return ToString(GetBytes(length)); |
|||
} |
|||
|
|||
private void GetThumbnail(uint offset) |
|||
{ |
|||
Collection<ExifValue> values = new Collection<ExifValue>(); |
|||
AddValues(values, offset); |
|||
|
|||
foreach (ExifValue value in values) |
|||
{ |
|||
if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) |
|||
ThumbnailOffset = (uint)value.Value + this.startIndex; |
|||
else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) |
|||
ThumbnailLength = (uint)value.Value; |
|||
} |
|||
} |
|||
|
|||
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, Byte[] data, |
|||
ConverterMethod<TDataType> converter) |
|||
{ |
|||
int dataTypeSize = (int)ExifValue.GetSize(dataType); |
|||
int length = data.Length / dataTypeSize; |
|||
|
|||
TDataType[] result = new TDataType[length]; |
|||
byte[] buffer = new byte[dataTypeSize]; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize); |
|||
|
|||
result.SetValue(converter(buffer), i); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static byte ToByte(byte[] data) |
|||
{ |
|||
return data[0]; |
|||
} |
|||
|
|||
private double ToDouble(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 8)) |
|||
return default(double); |
|||
|
|||
return BitConverter.ToDouble(data, 0); |
|||
} |
|||
|
|||
private uint ToLong(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 4)) |
|||
return default(uint); |
|||
|
|||
return BitConverter.ToUInt32(data, 0); |
|||
} |
|||
|
|||
private ushort ToShort(byte[] data) |
|||
{ |
|||
|
|||
if (!ValidateArray(data, 2)) |
|||
return default(ushort); |
|||
|
|||
return BitConverter.ToUInt16(data, 0); |
|||
} |
|||
|
|||
private float ToSingle(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 4)) |
|||
return default(float); |
|||
|
|||
return BitConverter.ToSingle(data, 0); |
|||
} |
|||
|
|||
private static string ToString(byte[] data) |
|||
{ |
|||
string result = Encoding.UTF8.GetString(data, 0, data.Length); |
|||
int nullCharIndex = result.IndexOf('\0'); |
|||
if (nullCharIndex != -1) |
|||
result = result.Substring(0, nullCharIndex); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private double ToRational(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 8, 4)) |
|||
return default(double); |
|||
|
|||
uint numerator = BitConverter.ToUInt32(data, 0); |
|||
uint denominator = BitConverter.ToUInt32(data, 4); |
|||
|
|||
return numerator / (double)denominator; |
|||
} |
|||
|
|||
private sbyte ToSignedByte(byte[] data) |
|||
{ |
|||
return unchecked((sbyte)data[0]); |
|||
} |
|||
|
|||
private int ToSignedLong(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 4)) |
|||
return default(int); |
|||
|
|||
return BitConverter.ToInt32(data, 0); |
|||
} |
|||
|
|||
private double ToSignedRational(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 8, 4)) |
|||
return default(double); |
|||
|
|||
int numerator = BitConverter.ToInt32(data, 0); |
|||
int denominator = BitConverter.ToInt32(data, 4); |
|||
|
|||
return numerator / (double)denominator; |
|||
} |
|||
|
|||
private short ToSignedShort(byte[] data) |
|||
{ |
|||
if (!ValidateArray(data, 2)) |
|||
return default(short); |
|||
|
|||
return BitConverter.ToInt16(data, 0); |
|||
} |
|||
|
|||
private bool ValidateArray(byte[] data, int size) |
|||
{ |
|||
return ValidateArray(data, size, size); |
|||
} |
|||
|
|||
private bool ValidateArray(byte[] data, int size, int stepSize) |
|||
{ |
|||
if (data == null || data.Length < size) |
|||
return false; |
|||
|
|||
if (this.isLittleEndian == BitConverter.IsLittleEndian) |
|||
return true; |
|||
|
|||
for (int i = 0; i < data.Length; i += stepSize) |
|||
{ |
|||
Array.Reverse(data, i, stepSize); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,927 @@ |
|||
// <copyright file="ExifTag.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
/// <summary>
|
|||
/// All exif tags from the Exif standard 2.2
|
|||
/// </summary>
|
|||
public enum ExifTag |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown
|
|||
/// </summary>
|
|||
Unknown = 0xFFFF, |
|||
|
|||
/// <summary>
|
|||
/// SubIFDOffset
|
|||
/// </summary>
|
|||
SubIFDOffset = 0x8769, |
|||
|
|||
/// <summary>
|
|||
/// GPSIFDOffset
|
|||
/// </summary>
|
|||
GPSIFDOffset = 0x8825, |
|||
|
|||
|
|||
/// <summary>
|
|||
/// ImageWidth
|
|||
/// </summary>
|
|||
ImageWidth = 0x0100, |
|||
|
|||
/// <summary>
|
|||
/// ImageLength
|
|||
/// </summary>
|
|||
ImageLength = 0x0101, |
|||
|
|||
/// <summary>
|
|||
/// BitsPerSample
|
|||
/// </summary>
|
|||
BitsPerSample = 0x0102, |
|||
|
|||
/// <summary>
|
|||
/// Compression
|
|||
/// </summary>
|
|||
Compression = 0x0103, |
|||
|
|||
/// <summary>
|
|||
/// PhotometricInterpretation
|
|||
/// </summary>
|
|||
PhotometricInterpretation = 0x0106, |
|||
|
|||
/// <summary>
|
|||
/// Threshholding
|
|||
/// </summary>
|
|||
Threshholding = 0x0107, |
|||
|
|||
/// <summary>
|
|||
/// CellWidth
|
|||
/// </summary>
|
|||
CellWidth = 0x0108, |
|||
|
|||
/// <summary>
|
|||
/// CellLength
|
|||
/// </summary>
|
|||
CellLength = 0x0109, |
|||
|
|||
/// <summary>
|
|||
/// FillOrder
|
|||
/// </summary>
|
|||
FillOrder = 0x010A, |
|||
|
|||
/// <summary>
|
|||
/// ImageDescription
|
|||
/// </summary>
|
|||
ImageDescription = 0x010E, |
|||
|
|||
/// <summary>
|
|||
/// Make
|
|||
/// </summary>
|
|||
Make = 0x010F, |
|||
|
|||
/// <summary>
|
|||
/// Model
|
|||
/// </summary>
|
|||
Model = 0x0110, |
|||
|
|||
/// <summary>
|
|||
/// StripOffsets
|
|||
/// </summary>
|
|||
StripOffsets = 0x0111, |
|||
|
|||
/// <summary>
|
|||
/// Orientation
|
|||
/// </summary>
|
|||
Orientation = 0x0112, |
|||
|
|||
/// <summary>
|
|||
/// SamplesPerPixel
|
|||
/// </summary>
|
|||
SamplesPerPixel = 0x0115, |
|||
|
|||
/// <summary>
|
|||
/// RowsPerStrip
|
|||
/// </summary>
|
|||
RowsPerStrip = 0x0116, |
|||
|
|||
/// <summary>
|
|||
/// StripByteCounts
|
|||
/// </summary>
|
|||
StripByteCounts = 0x0117, |
|||
|
|||
/// <summary>
|
|||
/// MinSampleValue
|
|||
/// </summary>
|
|||
MinSampleValue = 0x0118, |
|||
|
|||
/// <summary>
|
|||
/// MaxSampleValue
|
|||
/// </summary>
|
|||
MaxSampleValue = 0x0119, |
|||
|
|||
/// <summary>
|
|||
/// XResolution
|
|||
/// </summary>
|
|||
XResolution = 0x011A, |
|||
|
|||
/// <summary>
|
|||
/// YResolution
|
|||
/// </summary>
|
|||
YResolution = 0x011B, |
|||
|
|||
/// <summary>
|
|||
/// PlanarConfiguration
|
|||
/// </summary>
|
|||
PlanarConfiguration = 0x011C, |
|||
|
|||
/// <summary>
|
|||
/// FreeOffsets
|
|||
/// </summary>
|
|||
FreeOffsets = 0x0120, |
|||
|
|||
/// <summary>
|
|||
/// FreeByteCounts
|
|||
/// </summary>
|
|||
FreeByteCounts = 0x0121, |
|||
|
|||
/// <summary>
|
|||
/// GrayResponseUnit
|
|||
/// </summary>
|
|||
GrayResponseUnit = 0x0122, |
|||
|
|||
/// <summary>
|
|||
/// GrayResponseCurve
|
|||
/// </summary>
|
|||
GrayResponseCurve = 0x0123, |
|||
|
|||
/// <summary>
|
|||
/// ResolutionUnit
|
|||
/// </summary>
|
|||
ResolutionUnit = 0x0128, |
|||
|
|||
/// <summary>
|
|||
/// Software
|
|||
/// </summary>
|
|||
Software = 0x0131, |
|||
|
|||
/// <summary>
|
|||
/// DateTime
|
|||
/// </summary>
|
|||
DateTime = 0x0132, |
|||
|
|||
/// <summary>
|
|||
/// Artist
|
|||
/// </summary>
|
|||
Artist = 0x013B, |
|||
|
|||
/// <summary>
|
|||
/// HostComputer
|
|||
/// </summary>
|
|||
HostComputer = 0x013C, |
|||
|
|||
/// <summary>
|
|||
/// ColorMap
|
|||
/// </summary>
|
|||
ColorMap = 0x0140, |
|||
|
|||
/// <summary>
|
|||
/// ExtraSamples
|
|||
/// </summary>
|
|||
ExtraSamples = 0x0152, |
|||
|
|||
/// <summary>
|
|||
/// Copyright
|
|||
/// </summary>
|
|||
Copyright = 0x8298, |
|||
|
|||
|
|||
/// <summary>
|
|||
/// DocumentName
|
|||
/// </summary>
|
|||
DocumentName = 0x010D, |
|||
|
|||
/// <summary>
|
|||
/// PageName
|
|||
/// </summary>
|
|||
PageName = 0x011D, |
|||
|
|||
/// <summary>
|
|||
/// XPosition
|
|||
/// </summary>
|
|||
XPosition = 0x011E, |
|||
|
|||
/// <summary>
|
|||
/// YPosition
|
|||
/// </summary>
|
|||
YPosition = 0x011F, |
|||
|
|||
/// <summary>
|
|||
/// T4Options
|
|||
/// </summary>
|
|||
T4Options = 0x0124, |
|||
|
|||
/// <summary>
|
|||
/// T6Options
|
|||
/// </summary>
|
|||
T6Options = 0x0125, |
|||
|
|||
/// <summary>
|
|||
/// PageNumber
|
|||
/// </summary>
|
|||
PageNumber = 0x0129, |
|||
|
|||
/// <summary>
|
|||
/// TransferFunction
|
|||
/// </summary>
|
|||
TransferFunction = 0x012D, |
|||
|
|||
/// <summary>
|
|||
/// Predictor
|
|||
/// </summary>
|
|||
Predictor = 0x013D, |
|||
|
|||
/// <summary>
|
|||
/// WhitePoint
|
|||
/// </summary>
|
|||
WhitePoint = 0x013E, |
|||
|
|||
/// <summary>
|
|||
/// PrimaryChromaticities
|
|||
/// </summary>
|
|||
PrimaryChromaticities = 0x013F, |
|||
|
|||
/// <summary>
|
|||
/// HalftoneHints
|
|||
/// </summary>
|
|||
HalftoneHints = 0x0141, |
|||
|
|||
/// <summary>
|
|||
/// TileWidth
|
|||
/// </summary>
|
|||
TileWidth = 0x0142, |
|||
|
|||
/// <summary>
|
|||
/// TileLength
|
|||
/// </summary>
|
|||
TileLength = 0x0143, |
|||
|
|||
/// <summary>
|
|||
/// TileOffsets
|
|||
/// </summary>
|
|||
TileOffsets = 0x0144, |
|||
|
|||
/// <summary>
|
|||
/// TileByteCounts
|
|||
/// </summary>
|
|||
TileByteCounts = 0x0145, |
|||
|
|||
/// <summary>
|
|||
/// BadFaxLines
|
|||
/// </summary>
|
|||
BadFaxLines = 0x0146, |
|||
|
|||
/// <summary>
|
|||
/// CleanFaxData
|
|||
/// </summary>
|
|||
CleanFaxData = 0x0147, |
|||
|
|||
/// <summary>
|
|||
/// ConsecutiveBadFaxLines
|
|||
/// </summary>
|
|||
ConsecutiveBadFaxLines = 0x0148, |
|||
|
|||
/// <summary>
|
|||
/// InkSet
|
|||
/// </summary>
|
|||
InkSet = 0x014C, |
|||
|
|||
/// <summary>
|
|||
/// InkNames
|
|||
/// </summary>
|
|||
InkNames = 0x014D, |
|||
|
|||
/// <summary>
|
|||
/// NumberOfInks
|
|||
/// </summary>
|
|||
NumberOfInks = 0x014E, |
|||
|
|||
/// <summary>
|
|||
/// DotRange
|
|||
/// </summary>
|
|||
DotRange = 0x0150, |
|||
|
|||
/// <summary>
|
|||
/// TargetPrinter
|
|||
/// </summary>
|
|||
TargetPrinter = 0x0151, |
|||
|
|||
/// <summary>
|
|||
/// SampleFormat
|
|||
/// </summary>
|
|||
SampleFormat = 0x0153, |
|||
|
|||
/// <summary>
|
|||
/// SMinSampleValue
|
|||
/// </summary>
|
|||
SMinSampleValue = 0x0154, |
|||
|
|||
/// <summary>
|
|||
/// SMaxSampleValue
|
|||
/// </summary>
|
|||
SMaxSampleValue = 0x0155, |
|||
|
|||
/// <summary>
|
|||
/// TransferRange
|
|||
/// </summary>
|
|||
TransferRange = 0x0156, |
|||
|
|||
/// <summary>
|
|||
/// ClipPath
|
|||
/// </summary>
|
|||
ClipPath = 0x0157, |
|||
|
|||
/// <summary>
|
|||
/// XClipPathUnits
|
|||
/// </summary>
|
|||
XClipPathUnits = 0x0158, |
|||
|
|||
/// <summary>
|
|||
/// YClipPathUnits
|
|||
/// </summary>
|
|||
YClipPathUnits = 0x0159, |
|||
|
|||
/// <summary>
|
|||
/// Indexed
|
|||
/// </summary>
|
|||
Indexed = 0x015A, |
|||
|
|||
/// <summary>
|
|||
/// JPEGTables
|
|||
/// </summary>
|
|||
JPEGTables = 0x015B, |
|||
|
|||
/// <summary>
|
|||
/// OPIProxy
|
|||
/// </summary>
|
|||
OPIProxy = 0x015F, |
|||
|
|||
/// <summary>
|
|||
/// ProfileType
|
|||
/// </summary>
|
|||
ProfileType = 0x0191, |
|||
|
|||
/// <summary>
|
|||
/// FaxProfile
|
|||
/// </summary>
|
|||
FaxProfile = 0x0192, |
|||
|
|||
/// <summary>
|
|||
/// CodingMethods
|
|||
/// </summary>
|
|||
CodingMethods = 0x0193, |
|||
|
|||
/// <summary>
|
|||
/// VersionYear
|
|||
/// </summary>
|
|||
VersionYear = 0x0194, |
|||
|
|||
/// <summary>
|
|||
/// ModeNumber
|
|||
/// </summary>
|
|||
ModeNumber = 0x0195, |
|||
|
|||
/// <summary>
|
|||
/// Decode
|
|||
/// </summary>
|
|||
Decode = 0x01B1, |
|||
|
|||
/// <summary>
|
|||
/// DefaultImageColor
|
|||
/// </summary>
|
|||
DefaultImageColor = 0x01B2, |
|||
|
|||
/// <summary>
|
|||
/// JPEGProc
|
|||
/// </summary>
|
|||
JPEGProc = 0x0200, |
|||
|
|||
/// <summary>
|
|||
/// JPEGInterchangeFormat
|
|||
/// </summary>
|
|||
JPEGInterchangeFormat = 0x0201, |
|||
|
|||
/// <summary>
|
|||
/// JPEGInterchangeFormatLength
|
|||
/// </summary>
|
|||
JPEGInterchangeFormatLength = 0x0202, |
|||
|
|||
/// <summary>
|
|||
/// JPEGRestartInterval
|
|||
/// </summary>
|
|||
JPEGRestartInterval = 0x0203, |
|||
|
|||
/// <summary>
|
|||
/// JPEGLosslessPredictors
|
|||
/// </summary>
|
|||
JPEGLosslessPredictors = 0x0205, |
|||
|
|||
/// <summary>
|
|||
/// JPEGPointTransforms
|
|||
/// </summary>
|
|||
JPEGPointTransforms = 0x0206, |
|||
|
|||
/// <summary>
|
|||
/// JPEGQTables
|
|||
/// </summary>
|
|||
JPEGQTables = 0x0207, |
|||
|
|||
/// <summary>
|
|||
/// JPEGDCTables
|
|||
/// </summary>
|
|||
JPEGDCTables = 0x0208, |
|||
|
|||
/// <summary>
|
|||
/// JPEGACTables
|
|||
/// </summary>
|
|||
JPEGACTables = 0x0209, |
|||
|
|||
/// <summary>
|
|||
/// YCbCrCoefficients
|
|||
/// </summary>
|
|||
YCbCrCoefficients = 0x0211, |
|||
|
|||
/// <summary>
|
|||
/// YCbCrSubsampling
|
|||
/// </summary>
|
|||
YCbCrSubsampling = 0x0212, |
|||
|
|||
/// <summary>
|
|||
/// YCbCrPositioning
|
|||
/// </summary>
|
|||
YCbCrPositioning = 0x0213, |
|||
|
|||
/// <summary>
|
|||
/// ReferenceBlackWhite
|
|||
/// </summary>
|
|||
ReferenceBlackWhite = 0x0214, |
|||
|
|||
/// <summary>
|
|||
/// StripRowCounts
|
|||
/// </summary>
|
|||
StripRowCounts = 0x022F, |
|||
|
|||
/// <summary>
|
|||
/// XMP
|
|||
/// </summary>
|
|||
XMP = 0x02BC, |
|||
|
|||
/// <summary>
|
|||
/// ImageID
|
|||
/// </summary>
|
|||
ImageID = 0x800D, |
|||
|
|||
/// <summary>
|
|||
/// ImageLayer
|
|||
/// </summary>
|
|||
ImageLayer = 0x87AC, |
|||
|
|||
|
|||
/// <summary>
|
|||
/// ExposureTime
|
|||
/// </summary>
|
|||
ExposureTime = 0x829A, |
|||
|
|||
/// <summary>
|
|||
/// FNumber
|
|||
/// </summary>
|
|||
FNumber = 0x829D, |
|||
|
|||
/// <summary>
|
|||
/// ExposureProgram
|
|||
/// </summary>
|
|||
ExposureProgram = 0x8822, |
|||
|
|||
/// <summary>
|
|||
/// SpectralSensitivity
|
|||
/// </summary>
|
|||
SpectralSensitivity = 0x8824, |
|||
|
|||
/// <summary>
|
|||
/// ISOSpeedRatings
|
|||
/// </summary>
|
|||
ISOSpeedRatings = 0x8827, |
|||
|
|||
/// <summary>
|
|||
/// OECF
|
|||
/// </summary>
|
|||
OECF = 0x8828, |
|||
|
|||
/// <summary>
|
|||
/// ExifVersion
|
|||
/// </summary>
|
|||
ExifVersion = 0x9000, |
|||
|
|||
/// <summary>
|
|||
/// DateTimeOriginal
|
|||
/// </summary>
|
|||
DateTimeOriginal = 0x9003, |
|||
|
|||
/// <summary>
|
|||
/// DateTimeDigitized
|
|||
/// </summary>
|
|||
DateTimeDigitized = 0x9004, |
|||
|
|||
/// <summary>
|
|||
/// ComponentsConfiguration
|
|||
/// </summary>
|
|||
ComponentsConfiguration = 0x9101, |
|||
|
|||
/// <summary>
|
|||
/// CompressedBitsPerPixel
|
|||
/// </summary>
|
|||
CompressedBitsPerPixel = 0x9102, |
|||
|
|||
/// <summary>
|
|||
/// ShutterSpeedValue
|
|||
/// </summary>
|
|||
ShutterSpeedValue = 0x9201, |
|||
|
|||
/// <summary>
|
|||
/// ApertureValue
|
|||
/// </summary>
|
|||
ApertureValue = 0x9202, |
|||
|
|||
/// <summary>
|
|||
/// BrightnessValue
|
|||
/// </summary>
|
|||
BrightnessValue = 0x9203, |
|||
|
|||
/// <summary>
|
|||
/// ExposureBiasValue
|
|||
/// </summary>
|
|||
ExposureBiasValue = 0x9204, |
|||
|
|||
/// <summary>
|
|||
/// MaxApertureValue
|
|||
/// </summary>
|
|||
MaxApertureValue = 0x9205, |
|||
|
|||
/// <summary>
|
|||
/// SubjectDistance
|
|||
/// </summary>
|
|||
SubjectDistance = 0x9206, |
|||
|
|||
/// <summary>
|
|||
/// MeteringMode
|
|||
/// </summary>
|
|||
MeteringMode = 0x9207, |
|||
|
|||
/// <summary>
|
|||
/// LightSource
|
|||
/// </summary>
|
|||
LightSource = 0x9208, |
|||
|
|||
/// <summary>
|
|||
/// Flash
|
|||
/// </summary>
|
|||
Flash = 0x9209, |
|||
|
|||
/// <summary>
|
|||
/// FocalLength
|
|||
/// </summary>
|
|||
FocalLength = 0x920A, |
|||
|
|||
/// <summary>
|
|||
/// SubjectArea
|
|||
/// </summary>
|
|||
SubjectArea = 0x9214, |
|||
|
|||
/// <summary>
|
|||
/// MakerNote
|
|||
/// </summary>
|
|||
MakerNote = 0x927C, |
|||
|
|||
/// <summary>
|
|||
/// UserComment
|
|||
/// </summary>
|
|||
UserComment = 0x9286, |
|||
|
|||
/// <summary>
|
|||
/// SubsecTime
|
|||
/// </summary>
|
|||
SubsecTime = 0x9290, |
|||
|
|||
/// <summary>
|
|||
/// SubsecTimeOriginal
|
|||
/// </summary>
|
|||
SubsecTimeOriginal = 0x9291, |
|||
|
|||
/// <summary>
|
|||
/// SubsecTimeDigitized
|
|||
/// </summary>
|
|||
SubsecTimeDigitized = 0x9292, |
|||
|
|||
/// <summary>
|
|||
/// FlashpixVersion
|
|||
/// </summary>
|
|||
FlashpixVersion = 0xA000, |
|||
|
|||
/// <summary>
|
|||
/// ColorSpace
|
|||
/// </summary>
|
|||
ColorSpace = 0xA001, |
|||
|
|||
/// <summary>
|
|||
/// PixelXDimension
|
|||
/// </summary>
|
|||
PixelXDimension = 0xA002, |
|||
|
|||
/// <summary>
|
|||
/// PixelYDimension
|
|||
/// </summary>
|
|||
PixelYDimension = 0xA003, |
|||
|
|||
/// <summary>
|
|||
/// RelatedSoundFile
|
|||
/// </summary>
|
|||
RelatedSoundFile = 0xA004, |
|||
|
|||
/// <summary>
|
|||
/// FlashEnergy
|
|||
/// </summary>
|
|||
FlashEnergy = 0xA20B, |
|||
|
|||
/// <summary>
|
|||
/// SpatialFrequencyResponse
|
|||
/// </summary>
|
|||
SpatialFrequencyResponse = 0xA20C, |
|||
|
|||
/// <summary>
|
|||
/// FocalPlaneXResolution
|
|||
/// </summary>
|
|||
FocalPlaneXResolution = 0xA20E, |
|||
|
|||
/// <summary>
|
|||
/// FocalPlaneYResolution
|
|||
/// </summary>
|
|||
FocalPlaneYResolution = 0xA20F, |
|||
|
|||
/// <summary>
|
|||
/// FocalPlaneResolutionUnit
|
|||
/// </summary>
|
|||
FocalPlaneResolutionUnit = 0xA210, |
|||
|
|||
/// <summary>
|
|||
/// SubjectLocation
|
|||
/// </summary>
|
|||
SubjectLocation = 0xA214, |
|||
|
|||
/// <summary>
|
|||
/// ExposureIndex
|
|||
/// </summary>
|
|||
ExposureIndex = 0xA215, |
|||
|
|||
/// <summary>
|
|||
/// SensingMethod
|
|||
/// </summary>
|
|||
SensingMethod = 0xA217, |
|||
|
|||
/// <summary>
|
|||
/// FileSource
|
|||
/// </summary>
|
|||
FileSource = 0xA300, |
|||
|
|||
/// <summary>
|
|||
/// SceneType
|
|||
/// </summary>
|
|||
SceneType = 0xA301, |
|||
|
|||
/// <summary>
|
|||
/// CFAPattern
|
|||
/// </summary>
|
|||
CFAPattern = 0xA302, |
|||
|
|||
/// <summary>
|
|||
/// CustomRendered
|
|||
/// </summary>
|
|||
CustomRendered = 0xA401, |
|||
|
|||
/// <summary>
|
|||
/// ExposureMode
|
|||
/// </summary>
|
|||
ExposureMode = 0xA402, |
|||
|
|||
/// <summary>
|
|||
/// WhiteBalance
|
|||
/// </summary>
|
|||
WhiteBalance = 0xA403, |
|||
|
|||
/// <summary>
|
|||
/// DigitalZoomRatio
|
|||
/// </summary>
|
|||
DigitalZoomRatio = 0xA404, |
|||
|
|||
/// <summary>
|
|||
/// FocalLengthIn35mmFilm
|
|||
/// </summary>
|
|||
FocalLengthIn35mmFilm = 0xA405, |
|||
|
|||
/// <summary>
|
|||
/// SceneCaptureType
|
|||
/// </summary>
|
|||
SceneCaptureType = 0xA406, |
|||
|
|||
/// <summary>
|
|||
/// GainControl
|
|||
/// </summary>
|
|||
GainControl = 0xA407, |
|||
|
|||
/// <summary>
|
|||
/// Contrast
|
|||
/// </summary>
|
|||
Contrast = 0xA408, |
|||
|
|||
/// <summary>
|
|||
/// Saturation
|
|||
/// </summary>
|
|||
Saturation = 0xA409, |
|||
|
|||
/// <summary>
|
|||
/// Sharpness
|
|||
/// </summary>
|
|||
Sharpness = 0xA40A, |
|||
|
|||
/// <summary>
|
|||
/// DeviceSettingDescription
|
|||
/// </summary>
|
|||
DeviceSettingDescription = 0xA40B, |
|||
|
|||
/// <summary>
|
|||
/// SubjectDistanceRange
|
|||
/// </summary>
|
|||
SubjectDistanceRange = 0xA40C, |
|||
|
|||
/// <summary>
|
|||
/// ImageUniqueID
|
|||
/// </summary>
|
|||
ImageUniqueID = 0xA420, |
|||
|
|||
|
|||
/// <summary>
|
|||
/// GPSVersionID
|
|||
/// </summary>
|
|||
GPSVersionID = 0x0000, |
|||
|
|||
/// <summary>
|
|||
/// GPSLatitudeRef
|
|||
/// </summary>
|
|||
GPSLatitudeRef = 0x0001, |
|||
|
|||
/// <summary>
|
|||
/// GPSLatitude
|
|||
/// </summary>
|
|||
GPSLatitude = 0x0002, |
|||
|
|||
/// <summary>
|
|||
/// GPSLongitudeRef
|
|||
/// </summary>
|
|||
GPSLongitudeRef = 0x0003, |
|||
|
|||
/// <summary>
|
|||
/// GPSLongitude
|
|||
/// </summary>
|
|||
GPSLongitude = 0x0004, |
|||
|
|||
/// <summary>
|
|||
/// GPSAltitudeRef
|
|||
/// </summary>
|
|||
GPSAltitudeRef = 0x0005, |
|||
|
|||
/// <summary>
|
|||
/// GPSAltitude
|
|||
/// </summary>
|
|||
GPSAltitude = 0x0006, |
|||
|
|||
/// <summary>
|
|||
/// GPSTimestamp
|
|||
/// </summary>
|
|||
GPSTimestamp = 0x0007, |
|||
|
|||
/// <summary>
|
|||
/// GPSSatellites
|
|||
/// </summary>
|
|||
GPSSatellites = 0x0008, |
|||
|
|||
/// <summary>
|
|||
/// GPSStatus
|
|||
/// </summary>
|
|||
GPSStatus = 0x0009, |
|||
|
|||
/// <summary>
|
|||
/// GPSMeasureMode
|
|||
/// </summary>
|
|||
GPSMeasureMode = 0x000A, |
|||
|
|||
/// <summary>
|
|||
/// GPSDOP
|
|||
/// </summary>
|
|||
GPSDOP = 0x000B, |
|||
|
|||
/// <summary>
|
|||
/// GPSSpeedRef
|
|||
/// </summary>
|
|||
GPSSpeedRef = 0x000C, |
|||
|
|||
/// <summary>
|
|||
/// GPSSpeed
|
|||
/// </summary>
|
|||
GPSSpeed = 0x000D, |
|||
|
|||
/// <summary>
|
|||
/// GPSTrackRef
|
|||
/// </summary>
|
|||
GPSTrackRef = 0x000E, |
|||
|
|||
/// <summary>
|
|||
/// GPSTrack
|
|||
/// </summary>
|
|||
GPSTrack = 0x000F, |
|||
|
|||
/// <summary>
|
|||
/// GPSImgDirectionRef
|
|||
/// </summary>
|
|||
GPSImgDirectionRef = 0x0010, |
|||
|
|||
/// <summary>
|
|||
/// GPSImgDirection
|
|||
/// </summary>
|
|||
GPSImgDirection = 0x0011, |
|||
|
|||
/// <summary>
|
|||
/// GPSMapDatum
|
|||
/// </summary>
|
|||
GPSMapDatum = 0x0012, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestLatitudeRef
|
|||
/// </summary>
|
|||
GPSDestLatitudeRef = 0x0013, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestLatitude
|
|||
/// </summary>
|
|||
GPSDestLatitude = 0x0014, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestLongitudeRef
|
|||
/// </summary>
|
|||
GPSDestLongitudeRef = 0x0015, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestLongitude
|
|||
/// </summary>
|
|||
GPSDestLongitude = 0x0016, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestBearingRef
|
|||
/// </summary>
|
|||
GPSDestBearingRef = 0x0017, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestBearing
|
|||
/// </summary>
|
|||
GPSDestBearing = 0x0018, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestDistanceRef
|
|||
/// </summary>
|
|||
GPSDestDistanceRef = 0x0019, |
|||
|
|||
/// <summary>
|
|||
/// GPSDestDistance
|
|||
/// </summary>
|
|||
GPSDestDistance = 0x001A, |
|||
|
|||
/// <summary>
|
|||
/// GPSProcessingMethod
|
|||
/// </summary>
|
|||
GPSProcessingMethod = 0x001B, |
|||
|
|||
/// <summary>
|
|||
/// GPSAreaInformation
|
|||
/// </summary>
|
|||
GPSAreaInformation = 0x001C, |
|||
|
|||
/// <summary>
|
|||
/// GPSDateStamp
|
|||
/// </summary>
|
|||
GPSDateStamp = 0x001D, |
|||
|
|||
/// <summary>
|
|||
/// GPSDifferential
|
|||
/// </summary>
|
|||
GPSDifferential = 0x001E |
|||
} |
|||
} |
|||
@ -0,0 +1,595 @@ |
|||
// <copyright file="ExifValue.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Text; |
|||
|
|||
/// <summary>
|
|||
/// A value of the exif profile.
|
|||
/// </summary>
|
|||
public sealed class ExifValue : IEquatable<ExifValue> |
|||
{ |
|||
private object value; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExifValue"/> class
|
|||
/// by making a copy from another exif value.
|
|||
/// </summary>
|
|||
/// <param name="other">The other exif value, where the clone should be made from.</param>
|
|||
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
|
|||
public ExifValue(ExifValue other) |
|||
{ |
|||
Guard.NotNull(other, nameof(other)); |
|||
|
|||
DataType = other.DataType; |
|||
IsArray = other.IsArray; |
|||
Tag = other.Tag; |
|||
|
|||
if (!other.IsArray) |
|||
{ |
|||
value = other.value; |
|||
} |
|||
else |
|||
{ |
|||
Array array = (Array)other.value; |
|||
value = array.Clone(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The data type of the exif value.
|
|||
/// </summary>
|
|||
public ExifDataType DataType |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns true if the value is an array.
|
|||
/// </summary>
|
|||
public bool IsArray |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The tag of the exif value.
|
|||
/// </summary>
|
|||
public ExifTag Tag |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The value.
|
|||
/// </summary>
|
|||
public object Value |
|||
{ |
|||
get |
|||
{ |
|||
return this.value; |
|||
} |
|||
set |
|||
{ |
|||
CheckValue(value); |
|||
this.value = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified ExifValue instances are considered equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first ExifValue to compare.</param>
|
|||
/// <param name="right"> The second ExifValue to compare.</param>
|
|||
/// <returns></returns>
|
|||
public static bool operator ==(ExifValue left, ExifValue right) |
|||
{ |
|||
return Equals(left, right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified ExifValue instances are not considered equal.
|
|||
/// </summary>
|
|||
/// <param name="left">The first ExifValue to compare.</param>
|
|||
/// <param name="right"> The second ExifValue to compare.</param>
|
|||
/// <returns></returns>
|
|||
public static bool operator !=(ExifValue left, ExifValue right) |
|||
{ |
|||
return !Equals(left, right); |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Determines whether the specified object is equal to the current exif value.
|
|||
///</summary>
|
|||
///<param name="obj">The object to compare this exif value with.</param>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(this, obj)) |
|||
return true; |
|||
|
|||
return Equals(obj as ExifValue); |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Determines whether the specified exif value is equal to the current exif value.
|
|||
///</summary>
|
|||
///<param name="other">The exif value to compare this exif value with.</param>
|
|||
public bool Equals(ExifValue other) |
|||
{ |
|||
if (ReferenceEquals(other, null)) |
|||
return false; |
|||
|
|||
if (ReferenceEquals(this, other)) |
|||
return true; |
|||
|
|||
return |
|||
Tag == other.Tag && |
|||
DataType == other.DataType && |
|||
object.Equals(this.value, other.value); |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Serves as a hash of this type.
|
|||
///</summary>
|
|||
public override int GetHashCode() |
|||
{ |
|||
int hashCode = Tag.GetHashCode() ^ DataType.GetHashCode(); |
|||
return this.value != null ? hashCode ^ this.value.GetHashCode() : hashCode; |
|||
} |
|||
|
|||
///<summary>
|
|||
/// Returns a string that represents the current value.
|
|||
///</summary>
|
|||
public override string ToString() |
|||
{ |
|||
|
|||
if (this.value == null) |
|||
return null; |
|||
|
|||
if (DataType == ExifDataType.Ascii) |
|||
return (string)this.value; |
|||
|
|||
if (!IsArray) |
|||
return ToString(this.value); |
|||
|
|||
StringBuilder sb = new StringBuilder(); |
|||
foreach (object value in (Array)this.value) |
|||
{ |
|||
sb.Append(ToString(value)); |
|||
sb.Append(" "); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
internal bool HasValue |
|||
{ |
|||
get |
|||
{ |
|||
if (this.value == null) |
|||
return false; |
|||
|
|||
if (DataType == ExifDataType.Ascii) |
|||
return ((string)this.value).Length > 0; |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
internal int Length |
|||
{ |
|||
get |
|||
{ |
|||
if (this.value == null) |
|||
return 4; |
|||
|
|||
int size = (int)(GetSize(DataType) * NumberOfComponents); |
|||
|
|||
return size < 4 ? 4 : size; |
|||
} |
|||
} |
|||
|
|||
internal int NumberOfComponents |
|||
{ |
|||
get |
|||
{ |
|||
if (DataType == ExifDataType.Ascii) |
|||
return Encoding.UTF8.GetBytes((string)this.value).Length; |
|||
|
|||
if (IsArray) |
|||
return ((Array)this.value).Length; |
|||
|
|||
return 1; |
|||
} |
|||
} |
|||
|
|||
internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray) |
|||
{ |
|||
Tag = tag; |
|||
DataType = dataType; |
|||
IsArray = isArray; |
|||
|
|||
if (dataType == ExifDataType.Ascii) |
|||
IsArray = false; |
|||
} |
|||
|
|||
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) |
|||
: this(tag, dataType, isArray) |
|||
{ |
|||
this.value = value; |
|||
} |
|||
|
|||
internal static ExifValue Create(ExifTag tag, object value) |
|||
{ |
|||
Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag"); |
|||
|
|||
ExifValue exifValue = null; |
|||
Type type = value != null ? value.GetType() : null; |
|||
if (type != null && type.IsArray) |
|||
type = type.GetElementType(); |
|||
|
|||
switch (tag) |
|||
{ |
|||
case ExifTag.ImageDescription: |
|||
case ExifTag.Make: |
|||
case ExifTag.Model: |
|||
case ExifTag.Software: |
|||
case ExifTag.DateTime: |
|||
case ExifTag.Artist: |
|||
case ExifTag.HostComputer: |
|||
case ExifTag.Copyright: |
|||
case ExifTag.DocumentName: |
|||
case ExifTag.PageName: |
|||
case ExifTag.InkNames: |
|||
case ExifTag.TargetPrinter: |
|||
case ExifTag.ImageID: |
|||
case ExifTag.SpectralSensitivity: |
|||
case ExifTag.DateTimeOriginal: |
|||
case ExifTag.DateTimeDigitized: |
|||
case ExifTag.SubsecTime: |
|||
case ExifTag.SubsecTimeOriginal: |
|||
case ExifTag.SubsecTimeDigitized: |
|||
case ExifTag.RelatedSoundFile: |
|||
case ExifTag.ImageUniqueID: |
|||
case ExifTag.GPSLatitudeRef: |
|||
case ExifTag.GPSLongitudeRef: |
|||
case ExifTag.GPSSatellites: |
|||
case ExifTag.GPSStatus: |
|||
case ExifTag.GPSMeasureMode: |
|||
case ExifTag.GPSSpeedRef: |
|||
case ExifTag.GPSTrackRef: |
|||
case ExifTag.GPSImgDirectionRef: |
|||
case ExifTag.GPSMapDatum: |
|||
case ExifTag.GPSDestLatitudeRef: |
|||
case ExifTag.GPSDestLongitudeRef: |
|||
case ExifTag.GPSDestBearingRef: |
|||
case ExifTag.GPSDestDistanceRef: |
|||
case ExifTag.GPSDateStamp: |
|||
exifValue = new ExifValue(tag, ExifDataType.Ascii, true); |
|||
break; |
|||
|
|||
case ExifTag.ClipPath: |
|||
case ExifTag.VersionYear: |
|||
case ExifTag.XMP: |
|||
case ExifTag.GPSVersionID: |
|||
exifValue = new ExifValue(tag, ExifDataType.Byte, true); |
|||
break; |
|||
case ExifTag.FaxProfile: |
|||
case ExifTag.ModeNumber: |
|||
case ExifTag.GPSAltitudeRef: |
|||
exifValue = new ExifValue(tag, ExifDataType.Byte, false); |
|||
break; |
|||
|
|||
case ExifTag.FreeOffsets: |
|||
case ExifTag.FreeByteCounts: |
|||
case ExifTag.TileOffsets: |
|||
case ExifTag.SMinSampleValue: |
|||
case ExifTag.SMaxSampleValue: |
|||
case ExifTag.JPEGQTables: |
|||
case ExifTag.JPEGDCTables: |
|||
case ExifTag.JPEGACTables: |
|||
case ExifTag.StripRowCounts: |
|||
exifValue = new ExifValue(tag, ExifDataType.Long, true); |
|||
break; |
|||
case ExifTag.SubIFDOffset: |
|||
case ExifTag.GPSIFDOffset: |
|||
case ExifTag.T4Options: |
|||
case ExifTag.T6Options: |
|||
case ExifTag.XClipPathUnits: |
|||
case ExifTag.YClipPathUnits: |
|||
case ExifTag.ProfileType: |
|||
case ExifTag.CodingMethods: |
|||
case ExifTag.JPEGInterchangeFormat: |
|||
case ExifTag.JPEGInterchangeFormatLength: |
|||
exifValue = new ExifValue(tag, ExifDataType.Long, false); |
|||
break; |
|||
|
|||
case ExifTag.WhitePoint: |
|||
case ExifTag.PrimaryChromaticities: |
|||
case ExifTag.YCbCrCoefficients: |
|||
case ExifTag.ReferenceBlackWhite: |
|||
case ExifTag.GPSLatitude: |
|||
case ExifTag.GPSLongitude: |
|||
case ExifTag.GPSTimestamp: |
|||
case ExifTag.GPSDestLatitude: |
|||
case ExifTag.GPSDestLongitude: |
|||
exifValue = new ExifValue(tag, ExifDataType.Rational, true); |
|||
break; |
|||
case ExifTag.XPosition: |
|||
case ExifTag.YPosition: |
|||
case ExifTag.XResolution: |
|||
case ExifTag.YResolution: |
|||
case ExifTag.ExposureTime: |
|||
case ExifTag.FNumber: |
|||
case ExifTag.CompressedBitsPerPixel: |
|||
case ExifTag.ApertureValue: |
|||
case ExifTag.MaxApertureValue: |
|||
case ExifTag.SubjectDistance: |
|||
case ExifTag.FocalLength: |
|||
case ExifTag.FlashEnergy: |
|||
case ExifTag.FocalPlaneXResolution: |
|||
case ExifTag.FocalPlaneYResolution: |
|||
case ExifTag.ExposureIndex: |
|||
case ExifTag.DigitalZoomRatio: |
|||
case ExifTag.GPSAltitude: |
|||
case ExifTag.GPSDOP: |
|||
case ExifTag.GPSSpeed: |
|||
case ExifTag.GPSTrack: |
|||
case ExifTag.GPSImgDirection: |
|||
case ExifTag.GPSDestBearing: |
|||
case ExifTag.GPSDestDistance: |
|||
exifValue = new ExifValue(tag, ExifDataType.Rational, false); |
|||
break; |
|||
|
|||
case ExifTag.BitsPerSample: |
|||
case ExifTag.MinSampleValue: |
|||
case ExifTag.MaxSampleValue: |
|||
case ExifTag.GrayResponseCurve: |
|||
case ExifTag.ColorMap: |
|||
case ExifTag.ExtraSamples: |
|||
case ExifTag.PageNumber: |
|||
case ExifTag.TransferFunction: |
|||
case ExifTag.Predictor: |
|||
case ExifTag.HalftoneHints: |
|||
case ExifTag.SampleFormat: |
|||
case ExifTag.TransferRange: |
|||
case ExifTag.DefaultImageColor: |
|||
case ExifTag.JPEGLosslessPredictors: |
|||
case ExifTag.JPEGPointTransforms: |
|||
case ExifTag.YCbCrSubsampling: |
|||
case ExifTag.ISOSpeedRatings: |
|||
case ExifTag.SubjectArea: |
|||
case ExifTag.SubjectLocation: |
|||
exifValue = new ExifValue(tag, ExifDataType.Short, true); |
|||
break; |
|||
case ExifTag.Compression: |
|||
case ExifTag.PhotometricInterpretation: |
|||
case ExifTag.Threshholding: |
|||
case ExifTag.CellWidth: |
|||
case ExifTag.CellLength: |
|||
case ExifTag.FillOrder: |
|||
case ExifTag.Orientation: |
|||
case ExifTag.SamplesPerPixel: |
|||
case ExifTag.PlanarConfiguration: |
|||
case ExifTag.GrayResponseUnit: |
|||
case ExifTag.ResolutionUnit: |
|||
case ExifTag.CleanFaxData: |
|||
case ExifTag.InkSet: |
|||
case ExifTag.NumberOfInks: |
|||
case ExifTag.DotRange: |
|||
case ExifTag.Indexed: |
|||
case ExifTag.OPIProxy: |
|||
case ExifTag.JPEGProc: |
|||
case ExifTag.JPEGRestartInterval: |
|||
case ExifTag.YCbCrPositioning: |
|||
case ExifTag.ExposureProgram: |
|||
case ExifTag.MeteringMode: |
|||
case ExifTag.LightSource: |
|||
case ExifTag.Flash: |
|||
case ExifTag.ColorSpace: |
|||
case ExifTag.FocalPlaneResolutionUnit: |
|||
case ExifTag.SensingMethod: |
|||
case ExifTag.CustomRendered: |
|||
case ExifTag.ExposureMode: |
|||
case ExifTag.WhiteBalance: |
|||
case ExifTag.FocalLengthIn35mmFilm: |
|||
case ExifTag.SceneCaptureType: |
|||
case ExifTag.GainControl: |
|||
case ExifTag.Contrast: |
|||
case ExifTag.Saturation: |
|||
case ExifTag.Sharpness: |
|||
case ExifTag.SubjectDistanceRange: |
|||
case ExifTag.GPSDifferential: |
|||
exifValue = new ExifValue(tag, ExifDataType.Short, false); |
|||
break; |
|||
|
|||
case ExifTag.Decode: |
|||
exifValue = new ExifValue(tag, ExifDataType.SignedRational, true); |
|||
break; |
|||
case ExifTag.ShutterSpeedValue: |
|||
case ExifTag.BrightnessValue: |
|||
case ExifTag.ExposureBiasValue: |
|||
exifValue = new ExifValue(tag, ExifDataType.SignedRational, false); |
|||
break; |
|||
|
|||
case ExifTag.JPEGTables: |
|||
case ExifTag.OECF: |
|||
case ExifTag.ExifVersion: |
|||
case ExifTag.ComponentsConfiguration: |
|||
case ExifTag.MakerNote: |
|||
case ExifTag.UserComment: |
|||
case ExifTag.FlashpixVersion: |
|||
case ExifTag.SpatialFrequencyResponse: |
|||
case ExifTag.CFAPattern: |
|||
case ExifTag.DeviceSettingDescription: |
|||
case ExifTag.GPSProcessingMethod: |
|||
case ExifTag.GPSAreaInformation: |
|||
exifValue = new ExifValue(tag, ExifDataType.Undefined, true); |
|||
break; |
|||
case ExifTag.FileSource: |
|||
case ExifTag.SceneType: |
|||
exifValue = new ExifValue(tag, ExifDataType.Undefined, false); |
|||
break; |
|||
|
|||
case ExifTag.StripOffsets: |
|||
case ExifTag.TileByteCounts: |
|||
case ExifTag.ImageLayer: |
|||
exifValue = CreateNumber(tag, type, true); |
|||
break; |
|||
case ExifTag.ImageWidth: |
|||
case ExifTag.ImageLength: |
|||
case ExifTag.TileWidth: |
|||
case ExifTag.TileLength: |
|||
case ExifTag.BadFaxLines: |
|||
case ExifTag.ConsecutiveBadFaxLines: |
|||
case ExifTag.PixelXDimension: |
|||
case ExifTag.PixelYDimension: |
|||
exifValue = CreateNumber(tag, type, false); |
|||
break; |
|||
|
|||
default: |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
exifValue.Value = value; |
|||
return exifValue; |
|||
} |
|||
|
|||
internal static uint GetSize(ExifDataType dataType) |
|||
{ |
|||
switch (dataType) |
|||
{ |
|||
case ExifDataType.Ascii: |
|||
case ExifDataType.Byte: |
|||
case ExifDataType.SignedByte: |
|||
case ExifDataType.Undefined: |
|||
return 1; |
|||
case ExifDataType.Short: |
|||
case ExifDataType.SignedShort: |
|||
return 2; |
|||
case ExifDataType.Long: |
|||
case ExifDataType.SignedLong: |
|||
case ExifDataType.SingleFloat: |
|||
return 4; |
|||
case ExifDataType.DoubleFloat: |
|||
case ExifDataType.Rational: |
|||
case ExifDataType.SignedRational: |
|||
return 8; |
|||
default: |
|||
throw new NotImplementedException(dataType.ToString()); |
|||
} |
|||
} |
|||
|
|||
private void CheckValue(object value) |
|||
{ |
|||
if (value == null) |
|||
return; |
|||
|
|||
Type type = value.GetType(); |
|||
|
|||
if (DataType == ExifDataType.Ascii) |
|||
{ |
|||
Guard.IsTrue(type == typeof(string), nameof(value), "Value should be a string."); |
|||
return; |
|||
} |
|||
|
|||
if (type.IsArray) |
|||
{ |
|||
Guard.IsTrue(IsArray, nameof(value), "Value should not be an array."); |
|||
type = type.GetElementType(); |
|||
} |
|||
else |
|||
{ |
|||
Guard.IsFalse(IsArray, nameof(value), "Value should not be an array."); |
|||
} |
|||
|
|||
switch (DataType) |
|||
{ |
|||
case ExifDataType.Byte: |
|||
Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.DoubleFloat: |
|||
case ExifDataType.Rational: |
|||
case ExifDataType.SignedRational: |
|||
Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.Long: |
|||
Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.Short: |
|||
Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.SignedByte: |
|||
Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.SignedLong: |
|||
Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.SignedShort: |
|||
Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.SingleFloat: |
|||
Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(IsArray ? " array." : ".")}"); |
|||
break; |
|||
case ExifDataType.Undefined: |
|||
Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array."); |
|||
break; |
|||
default: |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray) |
|||
{ |
|||
if (type == null || type == typeof(ushort)) |
|||
return new ExifValue(tag, ExifDataType.Short, isArray); |
|||
else if (type == typeof(short)) |
|||
return new ExifValue(tag, ExifDataType.SignedShort, isArray); |
|||
else if (type == typeof(uint)) |
|||
return new ExifValue(tag, ExifDataType.Long, isArray); |
|||
else |
|||
return new ExifValue(tag, ExifDataType.SignedLong, isArray); |
|||
} |
|||
|
|||
private string ToString(object value) |
|||
{ |
|||
switch (DataType) |
|||
{ |
|||
case ExifDataType.Ascii: |
|||
return (string)value; |
|||
case ExifDataType.Byte: |
|||
return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); |
|||
case ExifDataType.DoubleFloat: |
|||
return ((double)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.Long: |
|||
return ((uint)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.Rational: |
|||
return ((double)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.Short: |
|||
return ((ushort)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.SignedByte: |
|||
return ((sbyte)value).ToString("X2", CultureInfo.InvariantCulture); |
|||
case ExifDataType.SignedLong: |
|||
return ((int)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.SignedRational: |
|||
return ((double)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.SignedShort: |
|||
return ((short)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.SingleFloat: |
|||
return ((float)value).ToString(CultureInfo.InvariantCulture); |
|||
case ExifDataType.Undefined: |
|||
return ((byte)value).ToString("X2", CultureInfo.InvariantCulture); |
|||
default: |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,407 @@ |
|||
// <copyright file="ExifWriter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.Text; |
|||
|
|||
internal sealed class ExifWriter |
|||
{ |
|||
private static readonly ExifTag[] IfdTags = new ExifTag[93] |
|||
{ |
|||
ExifTag.ImageWidth, ExifTag.ImageLength, ExifTag.BitsPerSample, ExifTag.Compression, |
|||
ExifTag.PhotometricInterpretation, ExifTag.Threshholding, ExifTag.CellWidth, |
|||
ExifTag.CellLength, ExifTag.FillOrder,ExifTag.ImageDescription, ExifTag.Make, |
|||
ExifTag.Model, ExifTag.StripOffsets, ExifTag.Orientation, ExifTag.SamplesPerPixel, |
|||
ExifTag.RowsPerStrip, ExifTag.StripByteCounts, ExifTag.MinSampleValue, |
|||
ExifTag.MaxSampleValue, ExifTag.XResolution, ExifTag.YResolution, |
|||
ExifTag.PlanarConfiguration, ExifTag.FreeOffsets, ExifTag.FreeByteCounts, |
|||
ExifTag.GrayResponseUnit, ExifTag.GrayResponseCurve, ExifTag.ResolutionUnit, |
|||
ExifTag.Software, ExifTag.DateTime, ExifTag.Artist, ExifTag.HostComputer, |
|||
ExifTag.ColorMap, ExifTag.ExtraSamples, ExifTag.Copyright, ExifTag.DocumentName, |
|||
ExifTag.PageName, ExifTag.XPosition, ExifTag.YPosition, ExifTag.T4Options, |
|||
ExifTag.T6Options, ExifTag.PageNumber, ExifTag.TransferFunction, ExifTag.Predictor, |
|||
ExifTag.WhitePoint, ExifTag.PrimaryChromaticities, ExifTag.HalftoneHints, |
|||
ExifTag.TileWidth, ExifTag.TileLength, ExifTag.TileOffsets, ExifTag.TileByteCounts, |
|||
ExifTag.BadFaxLines, ExifTag.CleanFaxData, ExifTag.ConsecutiveBadFaxLines, |
|||
ExifTag.InkSet, ExifTag.InkNames, ExifTag.NumberOfInks, ExifTag.DotRange, |
|||
ExifTag.TargetPrinter, ExifTag.SampleFormat, ExifTag.SMinSampleValue, |
|||
ExifTag.SMaxSampleValue, ExifTag.TransferRange, ExifTag.ClipPath, |
|||
ExifTag.XClipPathUnits, ExifTag.YClipPathUnits, ExifTag.Indexed, ExifTag.JPEGTables, |
|||
ExifTag.OPIProxy, ExifTag.ProfileType, ExifTag.FaxProfile, ExifTag.CodingMethods, |
|||
ExifTag.VersionYear, ExifTag.ModeNumber, ExifTag.Decode, ExifTag.DefaultImageColor, |
|||
ExifTag.JPEGProc, ExifTag.JPEGInterchangeFormat, ExifTag.JPEGInterchangeFormatLength, |
|||
ExifTag.JPEGRestartInterval, ExifTag.JPEGLosslessPredictors, |
|||
ExifTag.JPEGPointTransforms, ExifTag.JPEGQTables, ExifTag.JPEGDCTables, |
|||
ExifTag.JPEGACTables, ExifTag.YCbCrCoefficients, ExifTag.YCbCrSubsampling, |
|||
ExifTag.YCbCrSubsampling, ExifTag.YCbCrPositioning, ExifTag.ReferenceBlackWhite, |
|||
ExifTag.StripRowCounts, ExifTag.XMP, ExifTag.ImageID, ExifTag.ImageLayer |
|||
}; |
|||
|
|||
private static readonly ExifTag[] ExifTags = new ExifTag[56] |
|||
{ |
|||
ExifTag.ExposureTime, ExifTag.FNumber, ExifTag.ExposureProgram, |
|||
ExifTag.SpectralSensitivity, ExifTag.ISOSpeedRatings, ExifTag.OECF, |
|||
ExifTag.ExifVersion, ExifTag.DateTimeOriginal, ExifTag.DateTimeDigitized, |
|||
ExifTag.ComponentsConfiguration, ExifTag.CompressedBitsPerPixel, |
|||
ExifTag.ShutterSpeedValue, ExifTag.ApertureValue, ExifTag.BrightnessValue, |
|||
ExifTag.ExposureBiasValue, ExifTag.MaxApertureValue, ExifTag.SubjectDistance, |
|||
ExifTag.MeteringMode, ExifTag.LightSource, ExifTag.Flash, ExifTag.FocalLength, |
|||
ExifTag.SubjectArea, ExifTag.MakerNote, ExifTag.UserComment, ExifTag.SubsecTime, |
|||
ExifTag.SubsecTimeOriginal, ExifTag.SubsecTimeDigitized, ExifTag.FlashpixVersion, |
|||
ExifTag.ColorSpace, ExifTag.PixelXDimension, ExifTag.PixelYDimension, |
|||
ExifTag.RelatedSoundFile, ExifTag.FlashEnergy, ExifTag.SpatialFrequencyResponse, |
|||
ExifTag.FocalPlaneXResolution, ExifTag.FocalPlaneYResolution, |
|||
ExifTag.FocalPlaneResolutionUnit, ExifTag.SubjectLocation, ExifTag.ExposureIndex, |
|||
ExifTag.SensingMethod, ExifTag.FileSource, ExifTag.SceneType, ExifTag.CFAPattern, |
|||
ExifTag.CustomRendered, ExifTag.ExposureMode, ExifTag.WhiteBalance, |
|||
ExifTag.DigitalZoomRatio, ExifTag.FocalLengthIn35mmFilm, ExifTag.SceneCaptureType, |
|||
ExifTag.GainControl, ExifTag.Contrast, ExifTag.Saturation, ExifTag.Sharpness, |
|||
ExifTag.DeviceSettingDescription, ExifTag.SubjectDistanceRange, ExifTag.ImageUniqueID |
|||
}; |
|||
|
|||
private static readonly ExifTag[] GPSTags = new ExifTag[31] |
|||
{ |
|||
ExifTag.GPSVersionID, ExifTag.GPSLatitudeRef, ExifTag.GPSLatitude, |
|||
ExifTag.GPSLongitudeRef, ExifTag.GPSLongitude, ExifTag.GPSAltitudeRef, |
|||
ExifTag.GPSAltitude, ExifTag.GPSTimestamp, ExifTag.GPSSatellites, ExifTag.GPSStatus, |
|||
ExifTag.GPSMeasureMode, ExifTag.GPSDOP, ExifTag.GPSSpeedRef, ExifTag.GPSSpeed, |
|||
ExifTag.GPSTrackRef, ExifTag.GPSTrack, ExifTag.GPSImgDirectionRef, |
|||
ExifTag.GPSImgDirection, ExifTag.GPSMapDatum, ExifTag.GPSDestLatitudeRef, |
|||
ExifTag.GPSDestLatitude, ExifTag.GPSDestLongitudeRef, ExifTag.GPSDestLongitude, |
|||
ExifTag.GPSDestBearingRef, ExifTag.GPSDestBearing, ExifTag.GPSDestDistanceRef, |
|||
ExifTag.GPSDestDistance, ExifTag.GPSProcessingMethod, ExifTag.GPSAreaInformation, |
|||
ExifTag.GPSDateStamp, ExifTag.GPSDifferential |
|||
}; |
|||
|
|||
private const int StartIndex = 6; |
|||
|
|||
private ExifParts allowedParts; |
|||
private bool bestPrecision; |
|||
private Collection<ExifValue> values; |
|||
private Collection<int> dataOffsets; |
|||
private Collection<int> ifdIndexes; |
|||
private Collection<int> exifIndexes; |
|||
private Collection<int> gpsIndexes; |
|||
|
|||
public ExifWriter(Collection<ExifValue> values, ExifParts allowedParts, bool bestPrecision) |
|||
{ |
|||
this.values = values; |
|||
this.allowedParts = allowedParts; |
|||
this.bestPrecision = bestPrecision; |
|||
|
|||
this.ifdIndexes = GetIndexes(ExifParts.IfdTags, IfdTags); |
|||
this.exifIndexes = GetIndexes(ExifParts.ExifTags, ExifTags); |
|||
this.gpsIndexes = GetIndexes(ExifParts.GPSTags, GPSTags); |
|||
} |
|||
|
|||
public byte[] GetData() |
|||
{ |
|||
uint length = 0; |
|||
int exifIndex = -1; |
|||
int gpsIndex = -1; |
|||
|
|||
if (this.exifIndexes.Count > 0) |
|||
exifIndex = (int)GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset); |
|||
|
|||
if (this.gpsIndexes.Count > 0) |
|||
gpsIndex = (int)GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset); |
|||
|
|||
uint ifdLength = 2 + GetLength(this.ifdIndexes) + 4; |
|||
uint exifLength = GetLength(this.exifIndexes); |
|||
uint gpsLength = GetLength(this.gpsIndexes); |
|||
|
|||
if (exifLength > 0) |
|||
exifLength += 2; |
|||
|
|||
if (gpsLength > 0) |
|||
gpsLength += 2; |
|||
|
|||
length = ifdLength + exifLength + gpsLength; |
|||
|
|||
if (length == 6) |
|||
return null; |
|||
|
|||
length += 10 + 4 + 2; |
|||
|
|||
byte[] result = new byte[length]; |
|||
result[0] = (byte)'E'; |
|||
result[1] = (byte)'x'; |
|||
result[2] = (byte)'i'; |
|||
result[3] = (byte)'f'; |
|||
result[4] = 0x00; |
|||
result[5] = 0x00; |
|||
result[6] = (byte)'I'; |
|||
result[7] = (byte)'I'; |
|||
result[8] = 0x2A; |
|||
result[9] = 0x00; |
|||
|
|||
int i = 10; |
|||
uint ifdOffset = ((uint)i - StartIndex) + 4; |
|||
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; |
|||
|
|||
if (exifLength > 0) |
|||
this.values[exifIndex].Value = (ifdOffset + ifdLength); |
|||
|
|||
if (gpsLength > 0) |
|||
this.values[gpsIndex].Value = (ifdOffset + ifdLength + exifLength); |
|||
|
|||
i = Write(BitConverter.GetBytes(ifdOffset), result, i); |
|||
i = WriteHeaders(this.ifdIndexes, result, i); |
|||
i = Write(BitConverter.GetBytes(thumbnailOffset), result, i); |
|||
i = WriteData(this.ifdIndexes, result, i); |
|||
|
|||
if (exifLength > 0) |
|||
{ |
|||
i = WriteHeaders(this.exifIndexes, result, i); |
|||
i = WriteData(this.exifIndexes, result, i); |
|||
} |
|||
|
|||
if (gpsLength > 0) |
|||
{ |
|||
i = WriteHeaders(this.gpsIndexes, result, i); |
|||
i = WriteData(this.gpsIndexes, result, i); |
|||
} |
|||
|
|||
Write(BitConverter.GetBytes((ushort)0), result, i); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private int GetIndex(Collection<int> indexes, ExifTag tag) |
|||
{ |
|||
foreach (int index in indexes) |
|||
{ |
|||
if (this.values[index].Tag == tag) |
|||
return index; |
|||
} |
|||
|
|||
int newIndex = this.values.Count; |
|||
indexes.Add(newIndex); |
|||
this.values.Add(ExifValue.Create(tag, null)); |
|||
return newIndex; |
|||
} |
|||
|
|||
private Collection<int> GetIndexes(ExifParts part, ExifTag[] tags) |
|||
{ |
|||
if (((int)this.allowedParts & (int)part) == 0) |
|||
return new Collection<int>(); |
|||
|
|||
Collection<int> result = new Collection<int>(); |
|||
for (int i = 0; i < this.values.Count; i++) |
|||
{ |
|||
ExifValue value = this.values[i]; |
|||
|
|||
if (!value.HasValue) |
|||
continue; |
|||
|
|||
int index = Array.IndexOf(tags, value.Tag); |
|||
if (index > -1) |
|||
result.Add(i); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private uint GetLength(IEnumerable<int> indexes) |
|||
{ |
|||
uint length = 0; |
|||
|
|||
foreach (int index in indexes) |
|||
{ |
|||
uint valueLength = (uint)this.values[index].Length; |
|||
|
|||
if (valueLength > 4) |
|||
length += 12 + valueLength; |
|||
else |
|||
length += 12; |
|||
} |
|||
|
|||
return length; |
|||
} |
|||
|
|||
private static int Write(byte[] source, byte[] destination, int offset) |
|||
{ |
|||
Buffer.BlockCopy(source, 0, destination, offset, source.Length); |
|||
|
|||
return offset + source.Length; |
|||
} |
|||
|
|||
private int WriteArray(ExifValue value, byte[] destination, int offset) |
|||
{ |
|||
if (value.DataType == ExifDataType.Ascii) |
|||
return WriteValue(ExifDataType.Ascii, value.Value, destination, offset); |
|||
|
|||
int newOffset = offset; |
|||
foreach (object obj in (Array)value.Value) |
|||
newOffset = WriteValue(value.DataType, obj, destination, newOffset); |
|||
|
|||
return newOffset; |
|||
} |
|||
|
|||
private int WriteData(Collection<int> indexes, byte[] destination, int offset) |
|||
{ |
|||
if (this.dataOffsets.Count == 0) |
|||
return offset; |
|||
|
|||
int newOffset = offset; |
|||
|
|||
int i = 0; |
|||
foreach (int index in indexes) |
|||
{ |
|||
ExifValue value = this.values[index]; |
|||
if (value.Length > 4) |
|||
{ |
|||
Write(BitConverter.GetBytes(newOffset - StartIndex), destination, this.dataOffsets[i++]); |
|||
newOffset = WriteValue(value, destination, newOffset); |
|||
} |
|||
} |
|||
|
|||
return newOffset; |
|||
} |
|||
|
|||
private int WriteHeaders(Collection<int> indexes, byte[] destination, int offset) |
|||
{ |
|||
this.dataOffsets = new Collection<int>(); |
|||
|
|||
int newOffset = Write(BitConverter.GetBytes((ushort)indexes.Count), destination, offset); |
|||
|
|||
if (indexes.Count == 0) |
|||
return newOffset; |
|||
|
|||
foreach (int index in indexes) |
|||
{ |
|||
ExifValue value = this.values[index]; |
|||
newOffset = Write(BitConverter.GetBytes((ushort)value.Tag), destination, newOffset); |
|||
newOffset = Write(BitConverter.GetBytes((ushort)value.DataType), destination, newOffset); |
|||
newOffset = Write(BitConverter.GetBytes((uint)value.NumberOfComponents), destination, newOffset); |
|||
|
|||
if (value.Length > 4) |
|||
this.dataOffsets.Add(newOffset); |
|||
else |
|||
WriteValue(value, destination, newOffset); |
|||
|
|||
newOffset += 4; |
|||
} |
|||
|
|||
return newOffset; |
|||
} |
|||
|
|||
private int WriteRational(double value, byte[] destination, int offset) |
|||
{ |
|||
uint numerator = 1; |
|||
uint denominator = 1; |
|||
|
|||
if (double.IsPositiveInfinity(value)) |
|||
denominator = 0; |
|||
else if (double.IsNegativeInfinity(value)) |
|||
denominator = 0; |
|||
else |
|||
{ |
|||
double val = Math.Abs(value); |
|||
double df = numerator / denominator; |
|||
double epsilon = this.bestPrecision ? double.Epsilon : .000001; |
|||
|
|||
while (Math.Abs(df - val) > epsilon) |
|||
{ |
|||
if (df < val) |
|||
numerator++; |
|||
else |
|||
{ |
|||
denominator++; |
|||
numerator = (uint)(val * denominator); |
|||
} |
|||
|
|||
df = numerator / (double)denominator; |
|||
} |
|||
} |
|||
|
|||
Write(BitConverter.GetBytes(numerator), destination, offset); |
|||
Write(BitConverter.GetBytes(denominator), destination, offset + 4); |
|||
|
|||
return offset + 8; |
|||
} |
|||
|
|||
private int WriteSignedRational(double value, byte[] destination, int offset) |
|||
{ |
|||
int numerator = 1; |
|||
int denominator = 1; |
|||
|
|||
if (double.IsPositiveInfinity(value)) |
|||
denominator = 0; |
|||
else if (double.IsNegativeInfinity(value)) |
|||
denominator = 0; |
|||
else |
|||
{ |
|||
double val = Math.Abs(value); |
|||
double df = numerator / denominator; |
|||
double epsilon = this.bestPrecision ? double.Epsilon : .000001; |
|||
|
|||
while (Math.Abs(df - val) > epsilon) |
|||
{ |
|||
if (df < val) |
|||
numerator++; |
|||
else |
|||
{ |
|||
denominator++; |
|||
numerator = (int)(val * denominator); |
|||
} |
|||
|
|||
df = numerator / (double)denominator; |
|||
} |
|||
} |
|||
|
|||
Write(BitConverter.GetBytes(numerator * (value < 0.0 ? -1 : 1)), destination, offset); |
|||
Write(BitConverter.GetBytes(denominator), destination, offset + 4); |
|||
|
|||
return offset + 8; |
|||
} |
|||
|
|||
private int WriteValue(ExifDataType dataType, object value, byte[] destination, int offset) |
|||
{ |
|||
switch (dataType) |
|||
{ |
|||
case ExifDataType.Ascii: |
|||
return Write(Encoding.UTF8.GetBytes((string)value), destination, offset); |
|||
case ExifDataType.Byte: |
|||
case ExifDataType.Undefined: |
|||
destination[offset] = (byte)value; |
|||
return offset + 1; |
|||
case ExifDataType.DoubleFloat: |
|||
return Write(BitConverter.GetBytes((double)value), destination, offset); |
|||
case ExifDataType.Short: |
|||
return Write(BitConverter.GetBytes((ushort)value), destination, offset); |
|||
case ExifDataType.Long: |
|||
return Write(BitConverter.GetBytes((uint)value), destination, offset); |
|||
case ExifDataType.Rational: |
|||
return WriteRational((double)value, destination, offset); |
|||
case ExifDataType.SignedByte: |
|||
destination[offset] = unchecked((byte)((sbyte)value)); |
|||
return offset + 1; |
|||
case ExifDataType.SignedLong: |
|||
return Write(BitConverter.GetBytes((int)value), destination, offset); |
|||
case ExifDataType.SignedShort: |
|||
return Write(BitConverter.GetBytes((short)value), destination, offset); |
|||
case ExifDataType.SignedRational: |
|||
return WriteSignedRational((double)value, destination, offset); |
|||
case ExifDataType.SingleFloat: |
|||
return Write(BitConverter.GetBytes((float)value), destination, offset); |
|||
default: |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
private int WriteValue(ExifValue value, byte[] destination, int offset) |
|||
{ |
|||
if (value.IsArray && value.DataType != ExifDataType.Ascii) |
|||
return WriteArray(value, destination, offset); |
|||
else |
|||
return WriteValue(value.DataType, value.Value, destination, offset); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
Adapted from Magick.NET: |
|||
|
|||
https://github.com/dlemstra/Magick.NET/tree/784e23b1f5c824fc03d4b95d3387b3efe1ed510b/Magick.NET/Core/Profiles/Exif |
|||
@ -0,0 +1,306 @@ |
|||
// <copyright file="ExifProfileTests.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Tests |
|||
{ |
|||
using System; |
|||
using System.Collections; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using Xunit; |
|||
|
|||
public class ExifProfileTests |
|||
{ |
|||
[Fact] |
|||
public void Constructor() |
|||
{ |
|||
using (FileStream stream = File.OpenRead(TestImages.Jpg.Calliphora)) |
|||
{ |
|||
Image image = new Image(stream); |
|||
|
|||
Assert.Null(image.ExifProfile); |
|||
|
|||
image.ExifProfile = new ExifProfile(); |
|||
image.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); |
|||
|
|||
image = WriteAndRead(image); |
|||
|
|||
Assert.NotNull(image.ExifProfile); |
|||
Assert.Equal(1, image.ExifProfile.Values.Count()); |
|||
|
|||
ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); |
|||
TestValue(value, "Dirk Lemstra"); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructorEmpty() |
|||
{ |
|||
new ExifProfile((byte[])null); |
|||
new ExifProfile(new byte[] { }); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructorCopy() |
|||
{ |
|||
Assert.Throws<ArgumentNullException>(() => { new ExifProfile((ExifProfile)null); }); |
|||
|
|||
ExifProfile profile = GetExifProfile(); |
|||
|
|||
ExifProfile clone = new ExifProfile(profile); |
|||
TestProfile(clone); |
|||
|
|||
profile.SetValue(ExifTag.ColorSpace, (ushort)2); |
|||
|
|||
clone = new ExifProfile(profile); |
|||
TestProfile(clone); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteFraction() |
|||
{ |
|||
using (MemoryStream memStream = new MemoryStream()) |
|||
{ |
|||
double exposureTime = 1.0 / 1600; |
|||
|
|||
ExifProfile profile = GetExifProfile(); |
|||
|
|||
profile.SetValue(ExifTag.ExposureTime, exposureTime); |
|||
|
|||
Image image = new Image(1, 1); |
|||
image.ExifProfile = profile; |
|||
|
|||
image.SaveAsJpeg(memStream); |
|||
|
|||
memStream.Position = 0; |
|||
image = new Image(memStream); |
|||
|
|||
profile = image.ExifProfile; |
|||
Assert.NotNull(profile); |
|||
|
|||
ExifValue value = profile.GetValue(ExifTag.ExposureTime); |
|||
Assert.NotNull(value); |
|||
Assert.NotEqual(exposureTime, value.Value); |
|||
|
|||
memStream.Position = 0; |
|||
profile = GetExifProfile(); |
|||
|
|||
profile.SetValue(ExifTag.ExposureTime, exposureTime); |
|||
profile.BestPrecision = true; |
|||
image.ExifProfile = profile; |
|||
|
|||
image.SaveAsJpeg(memStream); |
|||
|
|||
memStream.Position = 0; |
|||
image = new Image(memStream); |
|||
|
|||
profile = image.ExifProfile; |
|||
Assert.NotNull(profile); |
|||
|
|||
value = profile.GetValue(ExifTag.ExposureTime); |
|||
TestValue(value, exposureTime); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReadWriteInfinity() |
|||
{ |
|||
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) |
|||
{ |
|||
Image image = new Image(stream); |
|||
image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.PositiveInfinity); |
|||
|
|||
image = WriteAndRead(image); |
|||
ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); |
|||
Assert.NotNull(value); |
|||
Assert.Equal(double.PositiveInfinity, value.Value); |
|||
|
|||
image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.NegativeInfinity); |
|||
|
|||
image = WriteAndRead(image); |
|||
value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); |
|||
Assert.NotNull(value); |
|||
Assert.Equal(double.NegativeInfinity, value.Value); |
|||
|
|||
image.ExifProfile.SetValue(ExifTag.FlashEnergy, double.NegativeInfinity); |
|||
|
|||
image = WriteAndRead(image); |
|||
value = image.ExifProfile.GetValue(ExifTag.FlashEnergy); |
|||
Assert.NotNull(value); |
|||
Assert.Equal(double.PositiveInfinity, value.Value); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void SetValue() |
|||
{ |
|||
double[] latitude = new double[] { 12.3, 4.56, 789.0 }; |
|||
|
|||
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) |
|||
{ |
|||
Image image = new Image(stream); |
|||
image.ExifProfile.SetValue(ExifTag.Software, "ImageProcessorCore"); |
|||
|
|||
ExifValue value = image.ExifProfile.GetValue(ExifTag.Software); |
|||
TestValue(value, "ImageProcessorCore"); |
|||
|
|||
Assert.Throws<ArgumentException>(() => { value.Value = 15; }); |
|||
|
|||
image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, 75.55); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); |
|||
TestValue(value, 75.55); |
|||
|
|||
Assert.Throws<ArgumentException>(() => { value.Value = 75; }); |
|||
|
|||
image.ExifProfile.SetValue(ExifTag.XResolution, 150.0); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.XResolution); |
|||
TestValue(value, 150.0); |
|||
|
|||
Assert.Throws<ArgumentException>(() => { value.Value = "ImageProcessorCore"; }); |
|||
|
|||
image.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); |
|||
TestValue(value, (string)null); |
|||
|
|||
image.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); |
|||
TestValue(value, latitude); |
|||
|
|||
image = WriteAndRead(image); |
|||
|
|||
Assert.NotNull(image.ExifProfile); |
|||
Assert.Equal(17, image.ExifProfile.Values.Count()); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.Software); |
|||
TestValue(value, "ImageProcessorCore"); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); |
|||
TestValue(value, 75.55); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.XResolution); |
|||
TestValue(value, 150.0); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); |
|||
Assert.Null(value); |
|||
|
|||
value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); |
|||
TestValue(value, latitude); |
|||
|
|||
image.ExifProfile.Parts = ExifParts.ExifTags; |
|||
|
|||
image = WriteAndRead(image); |
|||
|
|||
Assert.NotNull(image.ExifProfile); |
|||
Assert.Equal(8, image.ExifProfile.Values.Count()); |
|||
|
|||
Assert.NotNull(image.ExifProfile.GetValue(ExifTag.ColorSpace)); |
|||
Assert.True(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); |
|||
Assert.False(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); |
|||
Assert.Null(image.ExifProfile.GetValue(ExifTag.ColorSpace)); |
|||
|
|||
Assert.Equal(7, image.ExifProfile.Values.Count()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Values() |
|||
{ |
|||
ExifProfile profile = GetExifProfile(); |
|||
|
|||
TestProfile(profile); |
|||
|
|||
var thumbnail = profile.CreateThumbnail<Color, uint>(); |
|||
Assert.NotNull(thumbnail); |
|||
Assert.Equal(256, thumbnail.Width); |
|||
Assert.Equal(170, thumbnail.Height); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteTooLargeProfile() |
|||
{ |
|||
StringBuilder junk = new StringBuilder(); |
|||
for (int i = 0; i < 65500; i++) |
|||
junk.Append("I"); |
|||
|
|||
Image image = new Image(100, 100); |
|||
image.ExifProfile = new ExifProfile(); |
|||
image.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); |
|||
|
|||
using (MemoryStream memStream = new MemoryStream()) |
|||
{ |
|||
Assert.Throws<ImageFormatException>(() => image.SaveAsJpeg(memStream)); |
|||
} |
|||
} |
|||
|
|||
private static ExifProfile GetExifProfile() |
|||
{ |
|||
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) |
|||
{ |
|||
Image image = new Image(stream); |
|||
|
|||
ExifProfile profile = image.ExifProfile; |
|||
Assert.NotNull(profile); |
|||
|
|||
return profile; |
|||
} |
|||
} |
|||
|
|||
private static Image WriteAndRead(Image image) |
|||
{ |
|||
using (MemoryStream memStream = new MemoryStream()) |
|||
{ |
|||
image.SaveAsJpeg(memStream); |
|||
|
|||
memStream.Position = 0; |
|||
return new Image(memStream); |
|||
} |
|||
} |
|||
|
|||
private static void TestProfile(ExifProfile profile) |
|||
{ |
|||
Assert.NotNull(profile); |
|||
|
|||
Assert.Equal(16, profile.Values.Count()); |
|||
|
|||
foreach (ExifValue value in profile.Values) |
|||
{ |
|||
Assert.NotNull(value.Value); |
|||
|
|||
if (value.Tag == ExifTag.Software) |
|||
Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); |
|||
|
|||
if (value.Tag == ExifTag.XResolution) |
|||
Assert.Equal(300.0, value.Value); |
|||
|
|||
if (value.Tag == ExifTag.PixelXDimension) |
|||
Assert.Equal(2338U, value.Value); |
|||
} |
|||
} |
|||
|
|||
private static void TestValue(ExifValue value, string expected) |
|||
{ |
|||
Assert.NotNull(value); |
|||
Assert.Equal(expected, value.Value); |
|||
} |
|||
|
|||
private static void TestValue(ExifValue value, double expected) |
|||
{ |
|||
Assert.NotNull(value); |
|||
Assert.Equal(expected, value.Value); |
|||
} |
|||
|
|||
private static void TestValue(ExifValue value, double[] expected) |
|||
{ |
|||
Assert.NotNull(value); |
|||
|
|||
Assert.Equal(expected, (ICollection)value.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// <copyright file="ExifValueTests.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Tests |
|||
{ |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Xunit; |
|||
|
|||
public class ExifValueTests |
|||
{ |
|||
private static ExifValue GetExifValue() |
|||
{ |
|||
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) |
|||
{ |
|||
Image image = new Image(stream); |
|||
|
|||
ExifProfile profile = image.ExifProfile; |
|||
Assert.NotNull(profile); |
|||
|
|||
return profile.Values.First(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void IEquatable() |
|||
{ |
|||
ExifValue first = GetExifValue(); |
|||
ExifValue second = GetExifValue(); |
|||
|
|||
Assert.True(first == second); |
|||
Assert.True(first.Equals(second)); |
|||
Assert.True(first.Equals((object)second)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Properties() |
|||
{ |
|||
ExifValue value = GetExifValue(); |
|||
|
|||
Assert.Equal(ExifDataType.Ascii, value.DataType); |
|||
Assert.Equal(ExifTag.GPSDOP, value.Tag); |
|||
Assert.Equal(false, value.IsArray); |
|||
Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); |
|||
Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// <copyright file="FileTestBase.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Tests |
|||
{ |
|||
/// <summary>
|
|||
/// Class that contains all the test images.
|
|||
/// </summary>
|
|||
public static class TestImages |
|||
{ |
|||
public static class Png |
|||
{ |
|||
private static readonly string folder = "TestImages/Formats/Png/"; |
|||
|
|||
public static string P1 { get { return folder + "pl.png"; } } |
|||
public static string Pd { get { return folder + "pd.png"; } } |
|||
public static string Blur { get { return folder + "blur.png"; } } |
|||
public static string Indexed { get { return folder + "indexed.png"; } } |
|||
public static string Splash { get { return folder + "splash.png"; } } |
|||
} |
|||
|
|||
public static class Jpg |
|||
{ |
|||
private static readonly string folder = "TestImages/Formats/Jpg/"; |
|||
|
|||
public static string Floorplan { get { return folder + "Floorplan.jpeg"; } } |
|||
public static string Calliphora { get { return folder + "Calliphora.jpg"; } } |
|||
public static string Turtle { get { return folder + "turtle.jpg"; } } |
|||
public static string Fb { get { return folder + "fb.jpg"; } } |
|||
public static string Progress { get { return folder + "progress.jpg"; } } |
|||
public static string Gamma_dalai_lama_gray { get { return folder + "gamma_dalai_lama_gray.jpg"; } } |
|||
} |
|||
|
|||
public static class Bmp |
|||
{ |
|||
private static readonly string folder = "TestImages/Formats/Bmp/"; |
|||
|
|||
public static string Car { get { return folder + "Car.bmp"; } } |
|||
public static string Neg_height { get { return folder + "neg_height.bmp"; } } |
|||
} |
|||
|
|||
public static class Gif |
|||
{ |
|||
private static readonly string folder = "TestImages/Formats/Gif/"; |
|||
|
|||
public static string Rings { get { return folder + "rings.gif"; } } |
|||
public static string Giphy { get { return folder + "giphy.gif"; } } |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue