mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 11d6858f985ac5f9e1c17f19d1dcc418a05bbadd Former-commit-id: bfed05308fafbf01141966edb8fadc991c78b437 Former-commit-id: f97b6f7e891f5158021033ad3fb4344d1d5632d8af/merge-core
9 changed files with 2715 additions and 0 deletions
@ -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 |
||||
Loading…
Reference in new issue