mirror of https://github.com/SixLabors/ImageSharp
20 changed files with 309 additions and 815 deletions
@ -1,319 +1,59 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; |
|||
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff |
|||
{ |
|||
internal class EntryReader |
|||
internal class EntryReader : ExifReader |
|||
{ |
|||
private readonly TiffStream stream; |
|||
private readonly uint startOffset; |
|||
|
|||
private readonly SortedDictionary<uint, Action> extValueLoaders = new SortedDictionary<uint, Action>(); |
|||
public EntryReader(ByteOrder byteOrder, Stream stream, uint ifdOffset) |
|||
: base(byteOrder == ByteOrder.BigEndian, stream) => |
|||
this.startOffset = ifdOffset; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EntryReader" /> class.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
public EntryReader(TiffStream stream) |
|||
{ |
|||
this.stream = stream; |
|||
} |
|||
|
|||
public IExifValue ReadNext() |
|||
{ |
|||
var tagId = (ExifTagValue)this.stream.ReadUInt16(); |
|||
ExifDataType dataType = EnumUtils.Parse(this.stream.ReadUInt16(), ExifDataType.Unknown); |
|||
uint count = this.stream.ReadUInt32(); |
|||
|
|||
ExifDataType rawDataType = dataType; |
|||
dataType = LongOrShortFiltering(tagId, dataType); |
|||
bool isArray = GetIsArray(tagId, count); |
|||
public uint? BigValuesOffset => this.LazyStartOffset; |
|||
|
|||
ExifValue entry = ExifValues.Create(tagId, dataType, isArray); |
|||
if (rawDataType == ExifDataType.Undefined && count == 0) |
|||
{ |
|||
// todo: investgate
|
|||
count = 4; |
|||
} |
|||
|
|||
if (this.ReadValueOrOffset(entry, rawDataType, count)) |
|||
{ |
|||
return entry; |
|||
} |
|||
public uint NextIfdOffset { get; private set; } |
|||
|
|||
return null; // new UnkownExifTag(tagId);
|
|||
} |
|||
|
|||
public void LoadExtendedData() |
|||
public override List<IExifValue> ReadValues() |
|||
{ |
|||
foreach (Action action in this.extValueLoaders.Values) |
|||
{ |
|||
action(); |
|||
} |
|||
} |
|||
var values = new List<IExifValue>(); |
|||
this.AddValues(values, this.startOffset); |
|||
|
|||
private static bool HasExtData(ExifValue tag, uint count) => ExifDataTypes.GetSize(tag.DataType) * count > 4; |
|||
this.NextIfdOffset = this.ReadUInt32(); |
|||
|
|||
private static bool SetValue(ExifValue entry, object value) |
|||
{ |
|||
if (!entry.IsArray && entry.DataType != ExifDataType.Ascii) |
|||
{ |
|||
DebugGuard.IsTrue(((Array)value).Length == 1, "Expected a length is 1"); |
|||
|
|||
var single = ((Array)value).GetValue(0); |
|||
return entry.TrySetValue(single); |
|||
} |
|||
|
|||
return entry.TrySetValue(value); |
|||
this.AddSubIfdValues(values); |
|||
return values; |
|||
} |
|||
|
|||
private static ExifDataType LongOrShortFiltering(ExifTagValue tagId, ExifDataType dataType) |
|||
{ |
|||
switch (tagId) |
|||
{ |
|||
case ExifTagValue.ImageWidth: |
|||
case ExifTagValue.ImageLength: |
|||
case ExifTagValue.StripOffsets: |
|||
case ExifTagValue.RowsPerStrip: |
|||
case ExifTagValue.StripByteCounts: |
|||
case ExifTagValue.TileWidth: |
|||
case ExifTagValue.TileLength: |
|||
case ExifTagValue.TileOffsets: |
|||
case ExifTagValue.TileByteCounts: |
|||
case ExifTagValue.OldSubfileType: // by spec SHORT, but can be LONG
|
|||
return ExifDataType.Long; |
|||
|
|||
default: |
|||
return dataType; |
|||
} |
|||
} |
|||
|
|||
private static bool GetIsArray(ExifTagValue tagId, uint count) |
|||
{ |
|||
switch (tagId) |
|||
{ |
|||
case ExifTagValue.BitsPerSample: |
|||
case ExifTagValue.StripOffsets: |
|||
case ExifTagValue.StripByteCounts: |
|||
case ExifTagValue.TileOffsets: |
|||
case ExifTagValue.TileByteCounts: |
|||
case ExifTagValue.ColorMap: |
|||
case ExifTagValue.ExtraSamples: |
|||
case ExifTagValue.SampleFormat: |
|||
return true; |
|||
|
|||
default: |
|||
return count > 1; |
|||
} |
|||
} |
|||
public void LoadBigValues() => this.LazyLoad(); |
|||
} |
|||
|
|||
private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count) |
|||
internal class HeaderReader : ExifReader |
|||
{ |
|||
public HeaderReader(ByteOrder byteOrder, Stream stream) |
|||
: base(byteOrder == ByteOrder.BigEndian, stream) |
|||
{ |
|||
if (entry.Tag == ExifTag.SubIFDOffset || entry.Tag == ExifTag.GPSIFDOffset /*|| entry.Tag == ExifTagValue.SubIFDs*/) |
|||
{ |
|||
// todo: ignore subIfds (exif, gps)
|
|||
this.stream.Skip(4); |
|||
return false; |
|||
} |
|||
|
|||
if (HasExtData(entry, count)) |
|||
{ |
|||
uint offset = this.stream.ReadUInt32(); |
|||
this.extValueLoaders.Add(offset, () => |
|||
{ |
|||
this.ReadExtValue(entry, rawDataType, offset, count); |
|||
}); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
long pos = this.stream.Position; |
|||
object value = this.ReadData(entry.DataType, rawDataType, count); |
|||
if (value == null) |
|||
{ |
|||
// read unknown type value
|
|||
value = this.stream.ReadBytes(4); |
|||
} |
|||
else |
|||
{ |
|||
int leftBytes = 4 - (int)(this.stream.Position - pos); |
|||
if (leftBytes > 0) |
|||
{ |
|||
this.stream.Skip(leftBytes); |
|||
} |
|||
else if (leftBytes < 0) |
|||
{ |
|||
TiffThrowHelper.ThrowOutOfRange("IFD entry"); |
|||
} |
|||
} |
|||
|
|||
return SetValue(entry, value); |
|||
} |
|||
|
|||
private void ReadExtValue(ExifValue entry, ExifDataType rawDataType, uint offset, uint count) |
|||
{ |
|||
DebugGuard.IsTrue(HasExtData(entry, count), "Excepted extended data"); |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(offset, (uint)TiffConstants.SizeOfTiffHeader, nameof(offset)); |
|||
|
|||
this.stream.Seek(offset); |
|||
var value = this.ReadData(entry.DataType, rawDataType, count); |
|||
public uint FirstIfdOffset { get; private set; } |
|||
|
|||
SetValue(entry, value); |
|||
|
|||
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii || count > 1 ^ !entry.IsArray, "Invalid tag"); |
|||
DebugGuard.IsTrue(entry.GetValue() != null, "Invalid tag"); |
|||
} |
|||
|
|||
private object ReadData(ExifDataType entryDataType, ExifDataType rawDataType, uint count) |
|||
public uint ReadFileHeader() |
|||
{ |
|||
switch (rawDataType) |
|||
ushort magic = this.ReadUInt16(); |
|||
if (magic != TiffConstants.HeaderMagicNumber) |
|||
{ |
|||
case ExifDataType.Byte: |
|||
case ExifDataType.Undefined: |
|||
{ |
|||
return this.stream.ReadBytes(count); |
|||
} |
|||
|
|||
case ExifDataType.SignedByte: |
|||
{ |
|||
sbyte[] res = new sbyte[count]; |
|||
byte[] buf = this.stream.ReadBytes(count); |
|||
Array.Copy(buf, res, buf.Length); |
|||
return res; |
|||
} |
|||
|
|||
case ExifDataType.Short: |
|||
{ |
|||
if (entryDataType == ExifDataType.Long) |
|||
{ |
|||
uint[] buf = new uint[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadUInt16(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
else |
|||
{ |
|||
ushort[] buf = new ushort[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadUInt16(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
} |
|||
|
|||
case ExifDataType.SignedShort: |
|||
{ |
|||
short[] buf = new short[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadInt16(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Long: |
|||
{ |
|||
uint[] buf = new uint[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadUInt32(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.SignedLong: |
|||
{ |
|||
int[] buf = new int[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadInt32(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Ascii: |
|||
{ |
|||
byte[] buf = this.stream.ReadBytes(count); |
|||
|
|||
if (buf[buf.Length - 1] != 0) |
|||
{ |
|||
TiffThrowHelper.ThrowBadStringEntry(); |
|||
} |
|||
|
|||
return Encoding.UTF8.GetString(buf, 0, buf.Length - 1); |
|||
} |
|||
|
|||
case ExifDataType.SingleFloat: |
|||
{ |
|||
float[] buf = new float[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadSingle(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.DoubleFloat: |
|||
{ |
|||
double[] buf = new double[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
buf[i] = this.stream.ReadDouble(); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Rational: |
|||
{ |
|||
var buf = new Rational[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
uint numerator = this.stream.ReadUInt32(); |
|||
uint denominator = this.stream.ReadUInt32(); |
|||
buf[i] = new Rational(numerator, denominator); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.SignedRational: |
|||
{ |
|||
var buf = new SignedRational[count]; |
|||
for (int i = 0; i < buf.Length; i++) |
|||
{ |
|||
int numerator = this.stream.ReadInt32(); |
|||
int denominator = this.stream.ReadInt32(); |
|||
buf[i] = new SignedRational(numerator, denominator); |
|||
} |
|||
|
|||
return buf; |
|||
} |
|||
|
|||
case ExifDataType.Ifd: |
|||
{ |
|||
return this.stream.ReadUInt32(); |
|||
} |
|||
|
|||
default: |
|||
return null; |
|||
TiffThrowHelper.ThrowInvalidHeader(); |
|||
} |
|||
|
|||
this.FirstIfdOffset = this.ReadUInt32(); |
|||
return this.FirstIfdOffset; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,88 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams |
|||
{ |
|||
internal class TiffBigEndianStream : TiffStream |
|||
{ |
|||
public TiffBigEndianStream(Stream stream) |
|||
: base(stream) |
|||
{ |
|||
} |
|||
|
|||
public override ByteOrder ByteOrder => ByteOrder.BigEndian; |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override short ReadInt16() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(2); |
|||
return (short)((bytes[0] << 8) | bytes[1]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override int ReadInt32() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override uint ReadUInt32() |
|||
{ |
|||
return (uint)this.ReadInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override ushort ReadUInt16() |
|||
{ |
|||
return (ushort)this.ReadInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override float ReadSingle() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
|
|||
if (BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToSingle(bytes, 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override double ReadDouble() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(8); |
|||
|
|||
if (BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToDouble(bytes, 0); |
|||
} |
|||
} |
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams |
|||
{ |
|||
internal class TiffLittleEndianStream : TiffStream |
|||
{ |
|||
public TiffLittleEndianStream(Stream stream) |
|||
: base(stream) |
|||
{ |
|||
} |
|||
|
|||
public override ByteOrder ByteOrder => ByteOrder.LittleEndian; |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override short ReadInt16() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(2); |
|||
return (short)(bytes[0] | (bytes[1] << 8)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override int ReadInt32() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override uint ReadUInt32() |
|||
{ |
|||
return (uint)this.ReadInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override ushort ReadUInt16() |
|||
{ |
|||
return (ushort)this.ReadInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override float ReadSingle() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(4); |
|||
|
|||
if (!BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToSingle(bytes, 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public override double ReadDouble() |
|||
{ |
|||
byte[] bytes = this.ReadBytes(8); |
|||
|
|||
if (!BitConverter.IsLittleEndian) |
|||
{ |
|||
Array.Reverse(bytes); |
|||
} |
|||
|
|||
return BitConverter.ToDouble(bytes, 0); |
|||
} |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Streams |
|||
{ |
|||
/// <summary>
|
|||
/// The tiff data stream base class.
|
|||
/// </summary>
|
|||
internal abstract class TiffStream |
|||
{ |
|||
/// <summary>
|
|||
/// The input stream.
|
|||
/// </summary>
|
|||
private readonly Stream stream; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffStream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
protected TiffStream(Stream stream) |
|||
{ |
|||
this.stream = stream; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the file is encoded in little-endian or big-endian format.
|
|||
/// </summary>
|
|||
public abstract ByteOrder ByteOrder { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream.
|
|||
/// </summary>
|
|||
public Stream InputStream => this.stream; |
|||
|
|||
/// <summary>
|
|||
/// Gets the stream position.
|
|||
/// </summary>
|
|||
public long Position => this.stream.Position; |
|||
|
|||
public void Seek(uint offset) |
|||
{ |
|||
this.stream.Seek(offset, SeekOrigin.Begin); |
|||
} |
|||
|
|||
public void Skip(uint offset) |
|||
{ |
|||
this.stream.Seek(offset, SeekOrigin.Current); |
|||
} |
|||
|
|||
public void Skip(int offset) |
|||
{ |
|||
this.stream.Seek(offset, SeekOrigin.Current); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into a <see cref="byte"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public byte ReadByte() |
|||
{ |
|||
return (byte)this.stream.ReadByte(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts buffer data into an <see cref="sbyte"/> using the correct endianness.
|
|||
/// </summary>
|
|||
/// <returns>The converted value.</returns>
|
|||
public sbyte ReadSByte() |
|||
{ |
|||
return (sbyte)this.stream.ReadByte(); |
|||
} |
|||
|
|||
public byte[] ReadBytes(uint count) |
|||
{ |
|||
byte[] buf = new byte[count]; |
|||
this.stream.Read(buf, 0, buf.Length); |
|||
return buf; |
|||
} |
|||
|
|||
public abstract short ReadInt16(); |
|||
|
|||
public abstract int ReadInt32(); |
|||
|
|||
public abstract uint ReadUInt32(); |
|||
|
|||
public abstract ushort ReadUInt16(); |
|||
|
|||
public abstract float ReadSingle(); |
|||
|
|||
public abstract double ReadDouble(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue