Browse Source

Support long8 types for exif reader

pull/1760/head
Ildar Khayrutdinov 4 years ago
parent
commit
fdfb547936
  1. 32
      src/ImageSharp/Common/Helpers/Numerics.cs
  2. 17
      src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs
  3. 5
      src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs
  4. 147
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  5. 53
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs
  6. 27
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs
  7. 17
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs
  8. 37
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs
  9. 26
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs
  10. 22
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs
  11. 4
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  12. 75
      src/ImageSharp/Primitives/Number.cs

32
src/ImageSharp/Common/Helpers/Numerics.cs

@ -226,6 +226,38 @@ namespace SixLabors.ImageSharp
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Clamp(ulong value, ulong min, ulong max)
{
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long Clamp(long value, long min, long max)
{
if (value > max)
{
return max;
}
if (value < min)
{
return min;
}
return value;
}
/// <summary>
/// Returns the value clamped to the inclusive range of min and max.
/// </summary>

17
src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs

@ -80,6 +80,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <summary>
/// Reference to an IFD (32-bit (4-byte) unsigned integer).
/// </summary>
Ifd = 13
Ifd = 13,
/// <summary>
/// A 64-bit (8-byte) unsigned integer.
/// </summary>
Long8 = 16,
/// <summary>
/// A 64-bit (8-byte) signed integer (2's complement notation).
/// </summary>
SignedLong8 = 17,
/// <summary>
/// Reference to an IFD (64-bit (8-byte) unsigned integer).
/// </summary>
Ifd8 = 18,
}
}

5
src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs

@ -32,10 +32,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
case ExifDataType.Long:
case ExifDataType.SignedLong:
case ExifDataType.SingleFloat:
case ExifDataType.Ifd:
return 4;
case ExifDataType.DoubleFloat:
case ExifDataType.Rational:
case ExifDataType.SignedRational:
case ExifDataType.Long8:
case ExifDataType.SignedLong8:
case ExifDataType.Ifd8:
return 8;
default:
throw new NotSupportedException(dataType.ToString());

147
src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return values;
}
protected override void RegisterExtLoader(uint offset, Action loader) => this.loaders.Add(loader);
protected override void RegisterExtLoader(ulong offset, Action loader) => this.loaders.Add(loader);
private void GetThumbnail(uint offset)
{
@ -87,6 +87,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
internal abstract class BaseExifReader
{
private readonly byte[] offsetBuffer = new byte[4];
private readonly byte[] offsetBuffer8 = new byte[8];
private readonly byte[] buf8 = new byte[8];
private readonly byte[] buf4 = new byte[4];
private readonly byte[] buf2 = new byte[2];
@ -119,7 +121,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
public bool IsBigEndian { get; protected set; }
protected abstract void RegisterExtLoader(uint offset, Action loader);
protected abstract void RegisterExtLoader(ulong offset, Action loader);
/// <summary>
/// Reads the values to the values collection.
@ -155,6 +157,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
}
protected void ReadValues64(List<IExifValue> values, ulong offset)
{
if (offset > (ulong)this.data.Length)
{
return;
}
this.Seek(offset);
ulong count = this.ReadUInt64();
for (ulong i = 0; i < count; i++)
{
this.ReadValue64(values);
}
}
protected void ReadSubIfd64(List<IExifValue> values)
{
if (this.exifOffset != 0)
{
this.ReadValues64(values, this.exifOffset);
}
if (this.gpsOffset != 0)
{
this.ReadValues64(values, this.gpsOffset);
}
}
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, ReadOnlySpan<byte> data, ConverterMethod<TDataType> converter)
{
int dataTypeSize = (int)ExifDataTypes.GetSize(dataType);
@ -186,7 +217,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return Encoding.UTF8.GetString(buffer);
}
private object ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, uint numberOfComponents)
private object ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, ulong numberOfComponents)
{
if (buffer.Length == 0)
{
@ -269,6 +300,20 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
return ToArray(dataType, buffer, this.ConvertToSingle);
case ExifDataType.Long8:
if (numberOfComponents == 1)
{
return this.ConvertToUInt64(buffer);
}
return ToArray(dataType, buffer, this.ConvertToUInt64);
case ExifDataType.SignedLong8:
if (numberOfComponents == 1)
{
return this.ConvertToInt64(buffer);
}
return ToArray(dataType, buffer, this.ConvertToUInt64);
case ExifDataType.Undefined:
if (numberOfComponents == 1)
{
@ -349,6 +394,69 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
}
private void ReadValue64(List<IExifValue> values)
{
if ((this.data.Length - this.data.Position) < 20)
{
return;
}
var tag = (ExifTagValue)this.ReadUInt16();
ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown);
ulong numberOfComponents = this.ReadUInt64();
this.TryReadSpan(this.offsetBuffer8);
if (dataType == ExifDataType.Unknown)
{
return;
}
if (dataType == ExifDataType.Undefined && numberOfComponents == 0)
{
numberOfComponents = 8;
}
// The StripOffsets, StripByteCounts, TileOffsets, and TileByteCounts tags are allowed to have the datatype TIFF_LONG8 in BigTIFF.
// Old datatypes TIFF_LONG, and TIFF_SHORT where allowed in the TIFF 6.0 specification, are still valid in BigTIFF, too.
// https://www.awaresystems.be/imaging/tiff/bigtiff.html
ExifValue exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents);
if (exifValue is null)
{
this.AddInvalidTag(new UnkownExifTag(tag));
return;
}
ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType);
if (size > 8)
{
ulong newOffset = this.ConvertToUInt64(this.offsetBuffer8);
if (newOffset > ulong.MaxValue || (newOffset + size) > (ulong)this.data.Length)
{
this.AddInvalidTag(new UnkownExifTag(tag));
return;
}
this.RegisterExtLoader(newOffset, () =>
{
byte[] dataBuffer = new byte[size];
this.Seek(newOffset);
if (this.TryReadSpan(dataBuffer))
{
object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents);
this.Add(values, exifValue, value);
}
});
}
else
{
object value = this.ConvertValue(dataType, ((Span<byte>)this.offsetBuffer8).Slice(0, (int)size), numberOfComponents);
this.Add(values, exifValue, value);
}
}
private void Add(IList<IExifValue> values, IExifValue exif, object value)
{
if (!exif.TrySetValue(value))
@ -383,8 +491,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
private void AddInvalidTag(ExifTag tag)
=> (this.invalidTags ??= new List<ExifTag>()).Add(tag);
private void Seek(long pos)
=> this.data.Seek(pos, SeekOrigin.Begin);
private void Seek(ulong pos)
=> this.data.Seek((long)pos, SeekOrigin.Begin);
private bool TryReadSpan(Span<byte> span)
{
@ -398,6 +506,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return read == length;
}
protected ulong ReadUInt64() =>
this.TryReadSpan(this.buf8)
? this.ConvertToUInt64(this.buf8)
: default;
// Known as Long in Exif Specification.
protected uint ReadUInt32() =>
this.TryReadSpan(this.buf4)
@ -408,6 +521,30 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
? this.ConvertToShort(this.buf2)
: default;
private long ConvertToInt64(ReadOnlySpan<byte> buffer)
{
if (buffer.Length < 8)
{
return default;
}
return this.IsBigEndian
? BinaryPrimitives.ReadInt64BigEndian(buffer)
: BinaryPrimitives.ReadInt64LittleEndian(buffer);
}
private ulong ConvertToUInt64(ReadOnlySpan<byte> buffer)
{
if (buffer.Length < 8)
{
return default;
}
return this.IsBigEndian
? BinaryPrimitives.ReadUInt64BigEndian(buffer)
: BinaryPrimitives.ReadUInt64LittleEndian(buffer);
}
private double ConvertToDouble(ReadOnlySpan<byte> buffer)
{
if (buffer.Length < 8)

53
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs

@ -0,0 +1,53 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Globalization;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
internal sealed class ExifLong8 : ExifValue<ulong>
{
public ExifLong8(ExifTag<ulong> tag)
: base(tag)
{
}
public ExifLong8(ExifTagValue tag)
: base(tag)
{
}
private ExifLong8(ExifLong8 value)
: base(value)
{
}
public override ExifDataType DataType => ExifDataType.Long8;
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override bool TrySetValue(object value)
{
if (base.TrySetValue(value))
{
return true;
}
switch (value)
{
case long intValue:
if (intValue >= 0)
{
this.Value = (ulong)intValue;
return true;
}
return false;
default:
return false;
}
}
public override IExifValue DeepClone() => new ExifLong8(this);
}
}

27
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs

@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
internal sealed class ExifLong8Array : ExifArrayValue<ulong>
{
public ExifLong8Array(ExifTag<ulong[]> tag)
: base(tag)
{
}
public ExifLong8Array(ExifTagValue tag)
: base(tag)
{
}
private ExifLong8Array(ExifLong8Array value)
: base(value)
{
}
public override ExifDataType DataType => ExifDataType.Long8;
public override IExifValue DeepClone() => new ExifLong8Array(this);
}
}

17
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs

@ -21,6 +21,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
get
{
if (this.Value > uint.MaxValue)
{
return ExifDataType.Long8;
}
if (this.Value > ushort.MaxValue)
{
return ExifDataType.Long;
@ -41,6 +46,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
switch (value)
{
case long longValue:
if (longValue >= 0)
{
this.Value = (ulong)longValue;
return true;
}
return false;
case ulong ulongValue:
this.Value = ulongValue;
return true;
case int intValue:
if (intValue >= uint.MinValue)
{

37
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs

@ -26,6 +26,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
for (int i = 0; i < this.Value.Length; i++)
{
if (this.Value[i] > uint.MaxValue)
{
return ExifDataType.Long8;
}
if (this.Value[i] > ushort.MaxValue)
{
return ExifDataType.Long;
@ -45,6 +50,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
switch (value)
{
case long val:
return this.SetSingle(val);
case ulong val:
return this.SetSingle(val);
case int val:
return this.SetSingle(val);
case uint val:
@ -53,6 +62,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return this.SetSingle(val);
case ushort val:
return this.SetSingle(val);
case long[] array:
return this.SetArray(array);
case ulong[] array:
return this.SetArray(array);
case int[] array:
return this.SetArray(array);
case uint[] array:
@ -74,6 +87,30 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return true;
}
private bool SetArray(long[] values)
{
var numbers = new Number[values.Length];
for (int i = 0; i < values.Length; i++)
{
numbers[i] = values[i];
}
this.Value = numbers;
return true;
}
private bool SetArray(ulong[] values)
{
var numbers = new Number[values.Length];
for (int i = 0; i < values.Length; i++)
{
numbers[i] = values[i];
}
this.Value = numbers;
return true;
}
private bool SetArray(int[] values)
{
var numbers = new Number[values.Length];

26
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Globalization;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
internal sealed class ExifSignedLong8 : ExifValue<long>
{
public ExifSignedLong8(ExifTagValue tag)
: base(tag)
{
}
private ExifSignedLong8(ExifSignedLong8 value)
: base(value)
{
}
public override ExifDataType DataType => ExifDataType.SignedLong8;
protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture);
public override IExifValue DeepClone() => new ExifSignedLong8(this);
}
}

22
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
internal sealed class ExifSignedLong8Array : ExifArrayValue<long>
{
public ExifSignedLong8Array(ExifTagValue tag)
: base(tag)
{
}
private ExifSignedLong8Array(ExifSignedLong8Array value)
: base(value)
{
}
public override ExifDataType DataType => ExifDataType.SignedLong8;
public override IExifValue DeepClone() => new ExifSignedLong8Array(this);
}
}

4
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents) => Create(tag, dataType, numberOfComponents != 1);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray)
{
@ -19,10 +19,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
case ExifDataType.DoubleFloat: return isArray ? (ExifValue)new ExifDoubleArray(tag) : new ExifDouble(tag);
case ExifDataType.SingleFloat: return isArray ? (ExifValue)new ExifFloatArray(tag) : new ExifFloat(tag);
case ExifDataType.Long: return isArray ? (ExifValue)new ExifLongArray(tag) : new ExifLong(tag);
case ExifDataType.Long8: return isArray ? (ExifValue)new ExifLong8Array(tag) : new ExifLong8(tag);
case ExifDataType.Rational: return isArray ? (ExifValue)new ExifRationalArray(tag) : new ExifRational(tag);
case ExifDataType.Short: return isArray ? (ExifValue)new ExifShortArray(tag) : new ExifShort(tag);
case ExifDataType.SignedByte: return isArray ? (ExifValue)new ExifSignedByteArray(tag) : new ExifSignedByte(tag);
case ExifDataType.SignedLong: return isArray ? (ExifValue)new ExifSignedLongArray(tag) : new ExifSignedLong(tag);
case ExifDataType.SignedLong8: return isArray ? (ExifValue)new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag);
case ExifDataType.SignedRational: return isArray ? (ExifValue)new ExifSignedRationalArray(tag) : new ExifSignedRational(tag);
case ExifDataType.SignedShort: return isArray ? (ExifValue)new ExifSignedShortArray(tag) : new ExifSignedShort(tag);
case ExifDataType.Ascii: return new ExifString(tag);

75
src/ImageSharp/Primitives/Number.cs

@ -14,19 +14,19 @@ namespace SixLabors.ImageSharp
public struct Number : IEquatable<Number>, IComparable<Number>
{
[FieldOffset(0)]
private readonly int signedValue;
private readonly long signedValue;
[FieldOffset(0)]
private readonly uint unsignedValue;
private readonly ulong unsignedValue;
[FieldOffset(4)]
[FieldOffset(8)]
private readonly bool isSigned;
/// <summary>
/// Initializes a new instance of the <see cref="Number"/> struct.
/// </summary>
/// <param name="value">The value of the number.</param>
public Number(int value)
public Number(long value)
: this()
{
this.signedValue = value;
@ -37,30 +37,70 @@ namespace SixLabors.ImageSharp
/// Initializes a new instance of the <see cref="Number"/> struct.
/// </summary>
/// <param name="value">The value of the number.</param>
public Number(uint value)
public Number(ulong value)
: this()
{
this.unsignedValue = value;
this.isSigned = false;
}
/// <summary>
/// Converts the specified <see cref="long"/> to an instance of this type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Number(long value) => new Number(value);
/// <summary>
/// Converts the specified <see cref="ulong"/> to an instance of this type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Number(ulong value) => new Number(value);
/// <summary>
/// Converts the specified <see cref="int"/> to an instance of this type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Number(int value) => new Number(value);
public static implicit operator Number(int value) => new Number((long)value);
/// <summary>
/// Converts the specified <see cref="uint"/> to an instance of this type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Number(uint value) => new Number(value);
public static implicit operator Number(uint value) => new Number((ulong)value);
/// <summary>
/// Converts the specified <see cref="short"/> to an instance of this type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Number(short value) => new Number((long)value);
/// <summary>
/// Converts the specified <see cref="ushort"/> to an instance of this type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Number(ushort value) => new Number((uint)value);
public static implicit operator Number(ushort value) => new Number((ulong)value);
/// <summary>
/// Converts the specified <see cref="long"/> to an instance of this type.
/// </summary>
/// <param name="number">The <see cref="Number"/> to convert.</param>
public static explicit operator long(Number number)
{
return number.isSigned
? number.signedValue
: (long)Numerics.Clamp(number.unsignedValue, 0, long.MaxValue);
}
/// <summary>
/// Converts the specified <see cref="ulong"/> to an instance of this type.
/// </summary>
/// <param name="number">The <see cref="Number"/> to convert.</param>
public static explicit operator ulong(Number number)
{
return number.isSigned
? (ulong)Numerics.Clamp(number.signedValue, 0, long.MaxValue)
: number.unsignedValue;
}
/// <summary>
/// Converts the specified <see cref="Number"/> to a <see cref="int"/>.
@ -69,8 +109,8 @@ namespace SixLabors.ImageSharp
public static explicit operator int(Number number)
{
return number.isSigned
? number.signedValue
: (int)Numerics.Clamp(number.unsignedValue, 0, int.MaxValue);
? (int)Numerics.Clamp(number.signedValue, int.MinValue, int.MaxValue)
: (int)Numerics.Clamp(number.unsignedValue, 0, (uint)int.MaxValue);
}
/// <summary>
@ -80,8 +120,19 @@ namespace SixLabors.ImageSharp
public static explicit operator uint(Number number)
{
return number.isSigned
? (uint)Numerics.Clamp(number.signedValue, 0, int.MaxValue)
: number.unsignedValue;
? (uint)Numerics.Clamp(number.signedValue, uint.MinValue, uint.MaxValue)
: (uint)Numerics.Clamp(number.unsignedValue, uint.MinValue, uint.MaxValue);
}
/// <summary>
/// Converts the specified <see cref="Number"/> to a <see cref="short"/>.
/// </summary>
/// <param name="number">The <see cref="Number"/> to convert.</param>
public static explicit operator short(Number number)
{
return number.isSigned
? (short)Numerics.Clamp(number.signedValue, short.MinValue, short.MaxValue)
: (short)Numerics.Clamp(number.unsignedValue, 0, (ushort)short.MaxValue);
}
/// <summary>

Loading…
Cancel
Save