diff --git a/src/ImageProcessorCore/Common/Helpers/Guard.cs b/src/ImageProcessorCore/Common/Helpers/Guard.cs
index 96c7023d4..d11addf2a 100644
--- a/src/ImageProcessorCore/Common/Helpers/Guard.cs
+++ b/src/ImageProcessorCore/Common/Helpers/Guard.cs
@@ -188,5 +188,63 @@ namespace ImageProcessorCore
$"Value must be greater than or equal to {min} and less than or equal to {max}.");
}
}
+
+ ///
+ /// Verifies, that the method parameter with specified target value is true
+ /// and throws an exception if it is found to be so.
+ ///
+ ///
+ /// The target value, which cannot be false.
+ ///
+ ///
+ /// The name of the parameter that is to be checked.
+ ///
+ ///
+ /// The error message, if any to add to the exception.
+ ///
+ ///
+ /// is null
+ ///
+ public static void IsTrue(bool target, string parameterName, string message = "")
+ {
+ if (!target)
+ {
+ if (string.IsNullOrWhiteSpace(message))
+ {
+ throw new ArgumentException(parameterName, message);
+ }
+
+ throw new ArgumentException(parameterName);
+ }
+ }
+
+ ///
+ /// Verifies, that the method parameter with specified target value is false
+ /// and throws an exception if it is found to be so.
+ ///
+ ///
+ /// The target value, which cannot be true.
+ ///
+ ///
+ /// The name of the parameter that is to be checked.
+ ///
+ ///
+ /// The error message, if any to add to the exception.
+ ///
+ ///
+ /// is null
+ ///
+ public static void IsFalse(bool target, string parameterName, string message = "")
+ {
+ if (target)
+ {
+ if (string.IsNullOrWhiteSpace(message))
+ {
+ throw new ArgumentException(parameterName, message);
+ }
+
+ throw new ArgumentException(parameterName);
+ }
+ }
}
}
diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id
index d5d55bcd2..ae3ffc69d 100644
--- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id
+++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id
@@ -1 +1 @@
-25b2ab2a0acfb874ca8ac8ae979fc6b1bc9bf466
\ No newline at end of file
+09f4618eaade2900f7174b9ab400deb0a25bd813
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
index 48c65caad..2368a498d 100644
--- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
+++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
@@ -488,10 +488,9 @@ namespace ImageProcessorCore.Formats
int componentCount = 3;
// Write the Start Of Image marker.
- double densityX = image.HorizontalResolution;
- double densityY = image.VerticalResolution;
+ WriteApplicationHeader((short)image.HorizontalResolution, (short)image.VerticalResolution);
- WriteApplicationHeader((short)densityX, (short)densityY);
+ WriteProfiles(image);
// Write the quantization tables.
this.WriteDQT();
@@ -571,6 +570,40 @@ namespace ImageProcessorCore.Formats
this.outputStream.Write(this.buffer, 0, 4);
}
+ private void WriteProfiles(Image image)
+ where T : IPackedVector
+ where TP : struct
+ {
+ WriteProfile(image.ExifProfile);
+ }
+
+ private void WriteProfile(ExifProfile exifProfile)
+ {
+ if (exifProfile == null)
+ {
+ return;
+ }
+
+ byte[] data = exifProfile.ToByteArray();
+ if (data == null || data.Length == 0)
+ {
+ return;
+ }
+
+ if (data.Length > 65533)
+ {
+ throw new ImageFormatException("Exif profile size exceeds limit.");
+ }
+
+ this.buffer[0] = JpegConstants.Markers.XFF;
+ this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker
+ this.buffer[2] = (byte)((data.Length >> 8) & 0xFF);
+ this.buffer[3] = (byte)(data.Length & 0xFF);
+
+ this.outputStream.Write(this.buffer, 0, 4);
+ this.outputStream.Write(data, 0, data.Length);
+ }
+
///
/// Writes the Define Quantization Marker and tables.
///
diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs
index a53c87c5f..ff91e2372 100644
--- a/src/ImageProcessorCore/Image/Image.cs
+++ b/src/ImageProcessorCore/Image/Image.cs
@@ -91,6 +91,11 @@ namespace ImageProcessorCore
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.CurrentImageFormat = other.CurrentImageFormat;
+
+ if (other.ExifProfile != null)
+ {
+ this.ExifProfile = new ExifProfile(other.ExifProfile);
+ }
}
///
@@ -185,6 +190,11 @@ namespace ImageProcessorCore
///
public IImageFormat CurrentImageFormat { get; internal set; }
+ ///
+ /// Gets or sets the Exif profile.
+ ///
+ public ExifProfile ExifProfile { get; set; }
+
///
public override IPixelAccessor Lock()
{
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifDataType.cs b/src/ImageProcessorCore/Profiles/Exif/ExifDataType.cs
new file mode 100644
index 000000000..517b76011
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifDataType.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ ///
+ /// Specifies exif data types.
+ ///
+ public enum ExifDataType
+ {
+ ///
+ /// Unknown
+ ///
+ Unknown,
+
+ ///
+ /// Byte
+ ///
+ Byte,
+
+ ///
+ /// Ascii
+ ///
+ Ascii,
+
+ ///
+ /// Short
+ ///
+ Short,
+
+ ///
+ /// Long
+ ///
+ Long,
+
+ ///
+ /// Rational
+ ///
+ Rational,
+
+ ///
+ /// SignedByte
+ ///
+ SignedByte,
+
+ ///
+ /// Undefined
+ ///
+ Undefined,
+
+ ///
+ /// SignedShort
+ ///
+ SignedShort,
+
+ ///
+ /// SignedLong
+ ///
+ SignedLong,
+
+ ///
+ /// SignedRational
+ ///
+ SignedRational,
+
+ ///
+ /// SingleFloat
+ ///
+ SingleFloat,
+
+ ///
+ /// DoubleFloat
+ ///
+ DoubleFloat
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifParts.cs b/src/ImageProcessorCore/Profiles/Exif/ExifParts.cs
new file mode 100644
index 000000000..3ad53430b
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifParts.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+
+ ///
+ /// Specifies which parts will be written when the profile is added to an image.
+ ///
+ [Flags]
+ public enum ExifParts
+ {
+
+ ///
+ /// None
+ ///
+ None = 0,
+
+ ///
+ /// IfdTags
+ ///
+ IfdTags = 1,
+
+ ///
+ /// ExifTags
+ ///
+ ExifTags = 4,
+
+ ///
+ /// GPSTags
+ ///
+ GPSTags = 8,
+
+ ///
+ /// All
+ ///
+ All = IfdTags | ExifTags | GPSTags
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs b/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs
new file mode 100644
index 000000000..2caa22859
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs
@@ -0,0 +1,227 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.IO;
+
+ ///
+ /// Class that can be used to access an Exif profile.
+ ///
+ public sealed class ExifProfile
+ {
+ private byte[] data;
+ private Collection values;
+ private List invalidTags;
+ private int thumbnailOffset;
+ private int thumbnailLength;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///The byte array to read the exif profile from.
+ public ExifProfile()
+ : this((byte[])null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///The byte array to read the exif profile from.
+ public ExifProfile(byte[] data)
+ {
+ Parts = ExifParts.All;
+ BestPrecision = false;
+ this.data = data;
+ this.invalidTags = new List();
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// by making a copy from another exif profile.
+ ///
+ /// The other exif profile, where the clone should be made from.
+ /// is null.
+ 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(other.invalidTags);
+ if (other.values != null)
+ {
+ this.values = new Collection();
+ foreach(ExifValue value in other.values)
+ {
+ this.values.Add(new ExifValue(value));
+ }
+ }
+ else
+ {
+ this.data = other.data;
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ public bool BestPrecision
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Specifies which parts will be written when the profile is added to an image.
+ ///
+ public ExifParts Parts
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Returns the tags that where found but contained an invalid value.
+ ///
+ public IEnumerable InvalidTags
+ {
+ get
+ {
+ return this.invalidTags;
+ }
+ }
+
+ ///
+ /// Returns the values of this exif profile.
+ ///
+ public IEnumerable Values
+ {
+ get
+ {
+ InitializeValues();
+ return this.values;
+ }
+ }
+
+ ///
+ /// Returns the thumbnail in the exif profile when available.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public Image CreateThumbnail()
+ where T : IPackedVector
+ 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(memStream);
+ }
+ }
+
+ ///
+ /// Returns the value with the specified tag.
+ ///
+ ///The tag of the exif value.
+ public ExifValue GetValue(ExifTag tag)
+ {
+ foreach (ExifValue exifValue in Values)
+ {
+ if (exifValue.Tag == tag)
+ return exifValue;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Removes the value with the specified tag.
+ ///
+ ///The tag of the exif value.
+ 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;
+ }
+
+ ///
+ /// Sets the value of the specified tag.
+ ///
+ ///The tag of the exif value.
+ ///The value.
+ 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);
+ }
+
+ ///
+ /// Converts this instance to a byte array.
+ ///
+ 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();
+ return;
+ }
+
+ ExifReader reader = new ExifReader();
+ this.values = reader.Read(this.data);
+ this.invalidTags = new List(reader.InvalidTags);
+ this.thumbnailOffset = (int)reader.ThumbnailOffset;
+ this.thumbnailLength = (int)reader.ThumbnailLength;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
new file mode 100644
index 000000000..c37d15388
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
@@ -0,0 +1,426 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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(byte[] data);
+
+ private byte[] data;
+ private Collection invalidTags = new Collection();
+ 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 Read(byte[] data)
+ {
+ Collection result = new Collection();
+
+ 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 InvalidTags
+ {
+ get
+ {
+ return this.invalidTags;
+ }
+ }
+
+ private void AddValues(Collection 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(int value, TEnum defaultValue)
+ where TEnum : struct
+ {
+ TEnum enumValue = (TEnum)(object)value;
+ if (Enum.GetValues(typeof(TEnum)).Cast().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 values = new Collection();
+ 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(ExifDataType dataType, Byte[] data,
+ ConverterMethod 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifTag.cs b/src/ImageProcessorCore/Profiles/Exif/ExifTag.cs
new file mode 100644
index 000000000..944a2a15a
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifTag.cs
@@ -0,0 +1,927 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ ///
+ /// All exif tags from the Exif standard 2.2
+ ///
+ public enum ExifTag
+ {
+ ///
+ /// Unknown
+ ///
+ Unknown = 0xFFFF,
+
+ ///
+ /// SubIFDOffset
+ ///
+ SubIFDOffset = 0x8769,
+
+ ///
+ /// GPSIFDOffset
+ ///
+ GPSIFDOffset = 0x8825,
+
+
+ ///
+ /// ImageWidth
+ ///
+ ImageWidth = 0x0100,
+
+ ///
+ /// ImageLength
+ ///
+ ImageLength = 0x0101,
+
+ ///
+ /// BitsPerSample
+ ///
+ BitsPerSample = 0x0102,
+
+ ///
+ /// Compression
+ ///
+ Compression = 0x0103,
+
+ ///
+ /// PhotometricInterpretation
+ ///
+ PhotometricInterpretation = 0x0106,
+
+ ///
+ /// Threshholding
+ ///
+ Threshholding = 0x0107,
+
+ ///
+ /// CellWidth
+ ///
+ CellWidth = 0x0108,
+
+ ///
+ /// CellLength
+ ///
+ CellLength = 0x0109,
+
+ ///
+ /// FillOrder
+ ///
+ FillOrder = 0x010A,
+
+ ///
+ /// ImageDescription
+ ///
+ ImageDescription = 0x010E,
+
+ ///
+ /// Make
+ ///
+ Make = 0x010F,
+
+ ///
+ /// Model
+ ///
+ Model = 0x0110,
+
+ ///
+ /// StripOffsets
+ ///
+ StripOffsets = 0x0111,
+
+ ///
+ /// Orientation
+ ///
+ Orientation = 0x0112,
+
+ ///
+ /// SamplesPerPixel
+ ///
+ SamplesPerPixel = 0x0115,
+
+ ///
+ /// RowsPerStrip
+ ///
+ RowsPerStrip = 0x0116,
+
+ ///
+ /// StripByteCounts
+ ///
+ StripByteCounts = 0x0117,
+
+ ///
+ /// MinSampleValue
+ ///
+ MinSampleValue = 0x0118,
+
+ ///
+ /// MaxSampleValue
+ ///
+ MaxSampleValue = 0x0119,
+
+ ///
+ /// XResolution
+ ///
+ XResolution = 0x011A,
+
+ ///
+ /// YResolution
+ ///
+ YResolution = 0x011B,
+
+ ///
+ /// PlanarConfiguration
+ ///
+ PlanarConfiguration = 0x011C,
+
+ ///
+ /// FreeOffsets
+ ///
+ FreeOffsets = 0x0120,
+
+ ///
+ /// FreeByteCounts
+ ///
+ FreeByteCounts = 0x0121,
+
+ ///
+ /// GrayResponseUnit
+ ///
+ GrayResponseUnit = 0x0122,
+
+ ///
+ /// GrayResponseCurve
+ ///
+ GrayResponseCurve = 0x0123,
+
+ ///
+ /// ResolutionUnit
+ ///
+ ResolutionUnit = 0x0128,
+
+ ///
+ /// Software
+ ///
+ Software = 0x0131,
+
+ ///
+ /// DateTime
+ ///
+ DateTime = 0x0132,
+
+ ///
+ /// Artist
+ ///
+ Artist = 0x013B,
+
+ ///
+ /// HostComputer
+ ///
+ HostComputer = 0x013C,
+
+ ///
+ /// ColorMap
+ ///
+ ColorMap = 0x0140,
+
+ ///
+ /// ExtraSamples
+ ///
+ ExtraSamples = 0x0152,
+
+ ///
+ /// Copyright
+ ///
+ Copyright = 0x8298,
+
+
+ ///
+ /// DocumentName
+ ///
+ DocumentName = 0x010D,
+
+ ///
+ /// PageName
+ ///
+ PageName = 0x011D,
+
+ ///
+ /// XPosition
+ ///
+ XPosition = 0x011E,
+
+ ///
+ /// YPosition
+ ///
+ YPosition = 0x011F,
+
+ ///
+ /// T4Options
+ ///
+ T4Options = 0x0124,
+
+ ///
+ /// T6Options
+ ///
+ T6Options = 0x0125,
+
+ ///
+ /// PageNumber
+ ///
+ PageNumber = 0x0129,
+
+ ///
+ /// TransferFunction
+ ///
+ TransferFunction = 0x012D,
+
+ ///
+ /// Predictor
+ ///
+ Predictor = 0x013D,
+
+ ///
+ /// WhitePoint
+ ///
+ WhitePoint = 0x013E,
+
+ ///
+ /// PrimaryChromaticities
+ ///
+ PrimaryChromaticities = 0x013F,
+
+ ///
+ /// HalftoneHints
+ ///
+ HalftoneHints = 0x0141,
+
+ ///
+ /// TileWidth
+ ///
+ TileWidth = 0x0142,
+
+ ///
+ /// TileLength
+ ///
+ TileLength = 0x0143,
+
+ ///
+ /// TileOffsets
+ ///
+ TileOffsets = 0x0144,
+
+ ///
+ /// TileByteCounts
+ ///
+ TileByteCounts = 0x0145,
+
+ ///
+ /// BadFaxLines
+ ///
+ BadFaxLines = 0x0146,
+
+ ///
+ /// CleanFaxData
+ ///
+ CleanFaxData = 0x0147,
+
+ ///
+ /// ConsecutiveBadFaxLines
+ ///
+ ConsecutiveBadFaxLines = 0x0148,
+
+ ///
+ /// InkSet
+ ///
+ InkSet = 0x014C,
+
+ ///
+ /// InkNames
+ ///
+ InkNames = 0x014D,
+
+ ///
+ /// NumberOfInks
+ ///
+ NumberOfInks = 0x014E,
+
+ ///
+ /// DotRange
+ ///
+ DotRange = 0x0150,
+
+ ///
+ /// TargetPrinter
+ ///
+ TargetPrinter = 0x0151,
+
+ ///
+ /// SampleFormat
+ ///
+ SampleFormat = 0x0153,
+
+ ///
+ /// SMinSampleValue
+ ///
+ SMinSampleValue = 0x0154,
+
+ ///
+ /// SMaxSampleValue
+ ///
+ SMaxSampleValue = 0x0155,
+
+ ///
+ /// TransferRange
+ ///
+ TransferRange = 0x0156,
+
+ ///
+ /// ClipPath
+ ///
+ ClipPath = 0x0157,
+
+ ///
+ /// XClipPathUnits
+ ///
+ XClipPathUnits = 0x0158,
+
+ ///
+ /// YClipPathUnits
+ ///
+ YClipPathUnits = 0x0159,
+
+ ///
+ /// Indexed
+ ///
+ Indexed = 0x015A,
+
+ ///
+ /// JPEGTables
+ ///
+ JPEGTables = 0x015B,
+
+ ///
+ /// OPIProxy
+ ///
+ OPIProxy = 0x015F,
+
+ ///
+ /// ProfileType
+ ///
+ ProfileType = 0x0191,
+
+ ///
+ /// FaxProfile
+ ///
+ FaxProfile = 0x0192,
+
+ ///
+ /// CodingMethods
+ ///
+ CodingMethods = 0x0193,
+
+ ///
+ /// VersionYear
+ ///
+ VersionYear = 0x0194,
+
+ ///
+ /// ModeNumber
+ ///
+ ModeNumber = 0x0195,
+
+ ///
+ /// Decode
+ ///
+ Decode = 0x01B1,
+
+ ///
+ /// DefaultImageColor
+ ///
+ DefaultImageColor = 0x01B2,
+
+ ///
+ /// JPEGProc
+ ///
+ JPEGProc = 0x0200,
+
+ ///
+ /// JPEGInterchangeFormat
+ ///
+ JPEGInterchangeFormat = 0x0201,
+
+ ///
+ /// JPEGInterchangeFormatLength
+ ///
+ JPEGInterchangeFormatLength = 0x0202,
+
+ ///
+ /// JPEGRestartInterval
+ ///
+ JPEGRestartInterval = 0x0203,
+
+ ///
+ /// JPEGLosslessPredictors
+ ///
+ JPEGLosslessPredictors = 0x0205,
+
+ ///
+ /// JPEGPointTransforms
+ ///
+ JPEGPointTransforms = 0x0206,
+
+ ///
+ /// JPEGQTables
+ ///
+ JPEGQTables = 0x0207,
+
+ ///
+ /// JPEGDCTables
+ ///
+ JPEGDCTables = 0x0208,
+
+ ///
+ /// JPEGACTables
+ ///
+ JPEGACTables = 0x0209,
+
+ ///
+ /// YCbCrCoefficients
+ ///
+ YCbCrCoefficients = 0x0211,
+
+ ///
+ /// YCbCrSubsampling
+ ///
+ YCbCrSubsampling = 0x0212,
+
+ ///
+ /// YCbCrPositioning
+ ///
+ YCbCrPositioning = 0x0213,
+
+ ///
+ /// ReferenceBlackWhite
+ ///
+ ReferenceBlackWhite = 0x0214,
+
+ ///
+ /// StripRowCounts
+ ///
+ StripRowCounts = 0x022F,
+
+ ///
+ /// XMP
+ ///
+ XMP = 0x02BC,
+
+ ///
+ /// ImageID
+ ///
+ ImageID = 0x800D,
+
+ ///
+ /// ImageLayer
+ ///
+ ImageLayer = 0x87AC,
+
+
+ ///
+ /// ExposureTime
+ ///
+ ExposureTime = 0x829A,
+
+ ///
+ /// FNumber
+ ///
+ FNumber = 0x829D,
+
+ ///
+ /// ExposureProgram
+ ///
+ ExposureProgram = 0x8822,
+
+ ///
+ /// SpectralSensitivity
+ ///
+ SpectralSensitivity = 0x8824,
+
+ ///
+ /// ISOSpeedRatings
+ ///
+ ISOSpeedRatings = 0x8827,
+
+ ///
+ /// OECF
+ ///
+ OECF = 0x8828,
+
+ ///
+ /// ExifVersion
+ ///
+ ExifVersion = 0x9000,
+
+ ///
+ /// DateTimeOriginal
+ ///
+ DateTimeOriginal = 0x9003,
+
+ ///
+ /// DateTimeDigitized
+ ///
+ DateTimeDigitized = 0x9004,
+
+ ///
+ /// ComponentsConfiguration
+ ///
+ ComponentsConfiguration = 0x9101,
+
+ ///
+ /// CompressedBitsPerPixel
+ ///
+ CompressedBitsPerPixel = 0x9102,
+
+ ///
+ /// ShutterSpeedValue
+ ///
+ ShutterSpeedValue = 0x9201,
+
+ ///
+ /// ApertureValue
+ ///
+ ApertureValue = 0x9202,
+
+ ///
+ /// BrightnessValue
+ ///
+ BrightnessValue = 0x9203,
+
+ ///
+ /// ExposureBiasValue
+ ///
+ ExposureBiasValue = 0x9204,
+
+ ///
+ /// MaxApertureValue
+ ///
+ MaxApertureValue = 0x9205,
+
+ ///
+ /// SubjectDistance
+ ///
+ SubjectDistance = 0x9206,
+
+ ///
+ /// MeteringMode
+ ///
+ MeteringMode = 0x9207,
+
+ ///
+ /// LightSource
+ ///
+ LightSource = 0x9208,
+
+ ///
+ /// Flash
+ ///
+ Flash = 0x9209,
+
+ ///
+ /// FocalLength
+ ///
+ FocalLength = 0x920A,
+
+ ///
+ /// SubjectArea
+ ///
+ SubjectArea = 0x9214,
+
+ ///
+ /// MakerNote
+ ///
+ MakerNote = 0x927C,
+
+ ///
+ /// UserComment
+ ///
+ UserComment = 0x9286,
+
+ ///
+ /// SubsecTime
+ ///
+ SubsecTime = 0x9290,
+
+ ///
+ /// SubsecTimeOriginal
+ ///
+ SubsecTimeOriginal = 0x9291,
+
+ ///
+ /// SubsecTimeDigitized
+ ///
+ SubsecTimeDigitized = 0x9292,
+
+ ///
+ /// FlashpixVersion
+ ///
+ FlashpixVersion = 0xA000,
+
+ ///
+ /// ColorSpace
+ ///
+ ColorSpace = 0xA001,
+
+ ///
+ /// PixelXDimension
+ ///
+ PixelXDimension = 0xA002,
+
+ ///
+ /// PixelYDimension
+ ///
+ PixelYDimension = 0xA003,
+
+ ///
+ /// RelatedSoundFile
+ ///
+ RelatedSoundFile = 0xA004,
+
+ ///
+ /// FlashEnergy
+ ///
+ FlashEnergy = 0xA20B,
+
+ ///
+ /// SpatialFrequencyResponse
+ ///
+ SpatialFrequencyResponse = 0xA20C,
+
+ ///
+ /// FocalPlaneXResolution
+ ///
+ FocalPlaneXResolution = 0xA20E,
+
+ ///
+ /// FocalPlaneYResolution
+ ///
+ FocalPlaneYResolution = 0xA20F,
+
+ ///
+ /// FocalPlaneResolutionUnit
+ ///
+ FocalPlaneResolutionUnit = 0xA210,
+
+ ///
+ /// SubjectLocation
+ ///
+ SubjectLocation = 0xA214,
+
+ ///
+ /// ExposureIndex
+ ///
+ ExposureIndex = 0xA215,
+
+ ///
+ /// SensingMethod
+ ///
+ SensingMethod = 0xA217,
+
+ ///
+ /// FileSource
+ ///
+ FileSource = 0xA300,
+
+ ///
+ /// SceneType
+ ///
+ SceneType = 0xA301,
+
+ ///
+ /// CFAPattern
+ ///
+ CFAPattern = 0xA302,
+
+ ///
+ /// CustomRendered
+ ///
+ CustomRendered = 0xA401,
+
+ ///
+ /// ExposureMode
+ ///
+ ExposureMode = 0xA402,
+
+ ///
+ /// WhiteBalance
+ ///
+ WhiteBalance = 0xA403,
+
+ ///
+ /// DigitalZoomRatio
+ ///
+ DigitalZoomRatio = 0xA404,
+
+ ///
+ /// FocalLengthIn35mmFilm
+ ///
+ FocalLengthIn35mmFilm = 0xA405,
+
+ ///
+ /// SceneCaptureType
+ ///
+ SceneCaptureType = 0xA406,
+
+ ///
+ /// GainControl
+ ///
+ GainControl = 0xA407,
+
+ ///
+ /// Contrast
+ ///
+ Contrast = 0xA408,
+
+ ///
+ /// Saturation
+ ///
+ Saturation = 0xA409,
+
+ ///
+ /// Sharpness
+ ///
+ Sharpness = 0xA40A,
+
+ ///
+ /// DeviceSettingDescription
+ ///
+ DeviceSettingDescription = 0xA40B,
+
+ ///
+ /// SubjectDistanceRange
+ ///
+ SubjectDistanceRange = 0xA40C,
+
+ ///
+ /// ImageUniqueID
+ ///
+ ImageUniqueID = 0xA420,
+
+
+ ///
+ /// GPSVersionID
+ ///
+ GPSVersionID = 0x0000,
+
+ ///
+ /// GPSLatitudeRef
+ ///
+ GPSLatitudeRef = 0x0001,
+
+ ///
+ /// GPSLatitude
+ ///
+ GPSLatitude = 0x0002,
+
+ ///
+ /// GPSLongitudeRef
+ ///
+ GPSLongitudeRef = 0x0003,
+
+ ///
+ /// GPSLongitude
+ ///
+ GPSLongitude = 0x0004,
+
+ ///
+ /// GPSAltitudeRef
+ ///
+ GPSAltitudeRef = 0x0005,
+
+ ///
+ /// GPSAltitude
+ ///
+ GPSAltitude = 0x0006,
+
+ ///
+ /// GPSTimestamp
+ ///
+ GPSTimestamp = 0x0007,
+
+ ///
+ /// GPSSatellites
+ ///
+ GPSSatellites = 0x0008,
+
+ ///
+ /// GPSStatus
+ ///
+ GPSStatus = 0x0009,
+
+ ///
+ /// GPSMeasureMode
+ ///
+ GPSMeasureMode = 0x000A,
+
+ ///
+ /// GPSDOP
+ ///
+ GPSDOP = 0x000B,
+
+ ///
+ /// GPSSpeedRef
+ ///
+ GPSSpeedRef = 0x000C,
+
+ ///
+ /// GPSSpeed
+ ///
+ GPSSpeed = 0x000D,
+
+ ///
+ /// GPSTrackRef
+ ///
+ GPSTrackRef = 0x000E,
+
+ ///
+ /// GPSTrack
+ ///
+ GPSTrack = 0x000F,
+
+ ///
+ /// GPSImgDirectionRef
+ ///
+ GPSImgDirectionRef = 0x0010,
+
+ ///
+ /// GPSImgDirection
+ ///
+ GPSImgDirection = 0x0011,
+
+ ///
+ /// GPSMapDatum
+ ///
+ GPSMapDatum = 0x0012,
+
+ ///
+ /// GPSDestLatitudeRef
+ ///
+ GPSDestLatitudeRef = 0x0013,
+
+ ///
+ /// GPSDestLatitude
+ ///
+ GPSDestLatitude = 0x0014,
+
+ ///
+ /// GPSDestLongitudeRef
+ ///
+ GPSDestLongitudeRef = 0x0015,
+
+ ///
+ /// GPSDestLongitude
+ ///
+ GPSDestLongitude = 0x0016,
+
+ ///
+ /// GPSDestBearingRef
+ ///
+ GPSDestBearingRef = 0x0017,
+
+ ///
+ /// GPSDestBearing
+ ///
+ GPSDestBearing = 0x0018,
+
+ ///
+ /// GPSDestDistanceRef
+ ///
+ GPSDestDistanceRef = 0x0019,
+
+ ///
+ /// GPSDestDistance
+ ///
+ GPSDestDistance = 0x001A,
+
+ ///
+ /// GPSProcessingMethod
+ ///
+ GPSProcessingMethod = 0x001B,
+
+ ///
+ /// GPSAreaInformation
+ ///
+ GPSAreaInformation = 0x001C,
+
+ ///
+ /// GPSDateStamp
+ ///
+ GPSDateStamp = 0x001D,
+
+ ///
+ /// GPSDifferential
+ ///
+ GPSDifferential = 0x001E
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs b/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
new file mode 100644
index 000000000..860ff30dc
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
@@ -0,0 +1,595 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Globalization;
+ using System.Text;
+
+ ///
+ /// A value of the exif profile.
+ ///
+ public sealed class ExifValue : IEquatable
+ {
+ private object value;
+
+ ///
+ /// Initializes a new instance of the class
+ /// by making a copy from another exif value.
+ ///
+ /// The other exif value, where the clone should be made from.
+ /// is null.
+ 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();
+ }
+ }
+
+ ///
+ /// The data type of the exif value.
+ ///
+ public ExifDataType DataType
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Returns true if the value is an array.
+ ///
+ public bool IsArray
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The tag of the exif value.
+ ///
+ public ExifTag Tag
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The value.
+ ///
+ public object Value
+ {
+ get
+ {
+ return this.value;
+ }
+ set
+ {
+ CheckValue(value);
+ this.value = value;
+ }
+ }
+
+ ///
+ /// Determines whether the specified ExifValue instances are considered equal.
+ ///
+ /// The first ExifValue to compare.
+ /// The second ExifValue to compare.
+ ///
+ public static bool operator ==(ExifValue left, ExifValue right)
+ {
+ return Equals(left, right);
+ }
+
+ ///
+ /// Determines whether the specified ExifValue instances are not considered equal.
+ ///
+ /// The first ExifValue to compare.
+ /// The second ExifValue to compare.
+ ///
+ public static bool operator !=(ExifValue left, ExifValue right)
+ {
+ return !Equals(left, right);
+ }
+
+ ///
+ /// Determines whether the specified object is equal to the current exif value.
+ ///
+ ///The object to compare this exif value with.
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(this, obj))
+ return true;
+
+ return Equals(obj as ExifValue);
+ }
+
+ ///
+ /// Determines whether the specified exif value is equal to the current exif value.
+ ///
+ ///The exif value to compare this exif value with.
+ 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);
+ }
+
+ ///
+ /// Serves as a hash of this type.
+ ///
+ public override int GetHashCode()
+ {
+ int hashCode = Tag.GetHashCode() ^ DataType.GetHashCode();
+ return this.value != null ? hashCode ^ this.value.GetHashCode() : hashCode;
+ }
+
+ ///
+ /// Returns a string that represents the current value.
+ ///
+ 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();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs b/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
new file mode 100644
index 000000000..cee38cda5
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
@@ -0,0 +1,407 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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 values;
+ private Collection dataOffsets;
+ private Collection ifdIndexes;
+ private Collection exifIndexes;
+ private Collection gpsIndexes;
+
+ public ExifWriter(Collection 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 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 GetIndexes(ExifParts part, ExifTag[] tags)
+ {
+ if (((int)this.allowedParts & (int)part) == 0)
+ return new Collection();
+
+ Collection result = new Collection();
+ 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 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 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 indexes, byte[] destination, int offset)
+ {
+ this.dataOffsets = new Collection();
+
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/README.md b/src/ImageProcessorCore/Profiles/Exif/README.md
new file mode 100644
index 000000000..b6e27b70c
--- /dev/null
+++ b/src/ImageProcessorCore/Profiles/Exif/README.md
@@ -0,0 +1,3 @@
+Adapted from Magick.NET:
+
+https://github.com/dlemstra/Magick.NET/tree/784e23b1f5c824fc03d4b95d3387b3efe1ed510b/Magick.NET/Core/Profiles/Exif
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs
index aff63e8d2..a0f447510 100644
--- a/tests/ImageProcessorCore.Tests/FileTestBase.cs
+++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs
@@ -19,21 +19,21 @@ namespace ImageProcessorCore.Tests
///
protected static readonly List Files = new List
{
- //"TestImages/Formats/Png/pl.png",
- //"TestImages/Formats/Png/pd.png",
- //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only
- "TestImages/Formats/Jpg/Calliphora.jpg",
- //"TestImages/Formats/Jpg/turtle.jpg",
- //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only
- //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only
- //"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", // Perf: Enable for local testing only
- "TestImages/Formats/Bmp/Car.bmp",
- // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only
- //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only
- //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only
- "TestImages/Formats/Png/splash.png",
- "TestImages/Formats/Gif/rings.gif",
- //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only
+ //TestImages.Png.P1,
+ //TestImages.Png.Pd,
+ //TestImages.Jpg.Floorplan, // Perf: Enable for local testing only
+ TestImages.Jpg.Calliphora,
+ //TestImages.Jpg.Turtle,
+ //TestImages.Jpg.Fb, // Perf: Enable for local testing only
+ //TestImages.Jpg.Progress, // Perf: Enable for local testing only
+ //TestImages.Jpg.Gamma_dalai_lama_gray. // Perf: Enable for local testing only
+ TestImages.Bmp.Car,
+ //TestImages.Bmp.Neg_height, // Perf: Enable for local testing only
+ //TestImages.Png.Blur, // Perf: Enable for local testing only
+ //TestImages.Png.Indexed, // Perf: Enable for local testing only
+ TestImages.Png.Splash,
+ TestImages.Gif.Rings,
+ //TestImages.Gif.Giphy // Perf: Enable for local testing only
};
protected void ProgressUpdate(object sender, ProgressEventArgs e)
diff --git a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs
index 14b81708f..7c33470c3 100644
--- a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs
+++ b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs
@@ -200,5 +200,43 @@ namespace ImageProcessorCore.Tests.Helpers
Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo"));
Assert.Null(ex);
}
+
+ ///
+ /// Tests that the method throws when the argument is false.
+ ///
+ [Fact]
+ public void IsTrueThrowsWhenArgIsFalse()
+ {
+ Assert.Throws(() => Guard.IsTrue(false, "foo"));
+ }
+
+ ///
+ /// Tests that the method does not throw when the argument is true.
+ ///
+ [Fact]
+ public void IsTrueDoesThrowsWhenArgIsTrue()
+ {
+ Exception ex = Record.Exception(() => Guard.IsTrue(true, "foo"));
+ Assert.Null(ex);
+ }
+
+ ///
+ /// Tests that the method throws when the argument is true.
+ ///
+ [Fact]
+ public void IsFalseThrowsWhenArgIsFalse()
+ {
+ Assert.Throws(() => Guard.IsFalse(true, "foo"));
+ }
+
+ ///
+ /// Tests that the method does not throw when the argument is false.
+ ///
+ [Fact]
+ public void IsFalseDoesThrowsWhenArgIsTrue()
+ {
+ Exception ex = Record.Exception(() => Guard.IsFalse(false, "foo"));
+ Assert.Null(ex);
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
new file mode 100644
index 000000000..908d49b59
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
@@ -0,0 +1,306 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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(() => { 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(() => { value.Value = 15; });
+
+ image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, 75.55);
+
+ value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
+ TestValue(value, 75.55);
+
+ Assert.Throws(() => { value.Value = 75; });
+
+ image.ExifProfile.SetValue(ExifTag.XResolution, 150.0);
+
+ value = image.ExifProfile.GetValue(ExifTag.XResolution);
+ TestValue(value, 150.0);
+
+ Assert.Throws(() => { 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();
+ 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(() => 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);
+ }
+ }
+}
diff --git a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifValueTests.cs b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifValueTests.cs
new file mode 100644
index 000000000..3ee09c23d
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifValueTests.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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);
+ }
+ }
+}
diff --git a/tests/ImageProcessorCore.Tests/TestImages.cs b/tests/ImageProcessorCore.Tests/TestImages.cs
new file mode 100644
index 000000000..f9c5bb243
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/TestImages.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ ///
+ /// Class that contains all the test images.
+ ///
+ 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"; } }
+ }
+ }
+}