Browse Source

Small improve tiff decoder. Add TiffThrowHelper.

pull/1570/head
Ildar Khayrutdinov 5 years ago
parent
commit
2da3c6aed7
  1. 2
      src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
  2. 2
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
  3. 8
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  4. 4
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  5. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs
  6. 53
      src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs
  7. 8
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  8. 85
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  9. 33
      src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
  10. 8
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  11. 50
      src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs

2
src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if ((cmf & 0x0f) != 8)
{
throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}");
TiffThrowHelper.ThrowBadZlibHeader(cmf);
}
// If the 'fdict' flag is set then we should skip the next four bytes

2
src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffCompressionType.Lzw:
return new LzwTiffCompression(allocator);
default:
throw new InvalidOperationException();
throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType));
}
}
}

8
src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs

@ -1,9 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
@ -40,13 +38,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
ushort magic = this.stream.ReadUInt16();
if (magic != TiffConstants.HeaderMagicNumber)
{
throw new ImageFormatException("Invalid TIFF header magic number: " + magic);
TiffThrowHelper.ThrowInvalidHeader();
}
uint firstIfdOffset = this.stream.ReadUInt32();
if (firstIfdOffset == 0)
{
throw new ImageFormatException("Invalid TIFF file header.");
TiffThrowHelper.ThrowInvalidHeader();
}
this.nextIfdOffset = firstIfdOffset;
@ -95,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else if (leftBytes < 0)
{
throw new InvalidDataException("Out of range of IFD structure.");
TiffThrowHelper.ThrowOutOfRange("IFD");
}
return entries.ToArray();

4
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else if (leftBytes < 0)
{
throw new InvalidDataException("Out of range of IFD entry structure.");
TiffThrowHelper.ThrowOutOfRange("IFD entry");
}
}
@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (buf[buf.Length - 1] != 0)
{
throw new ImageFormatException("The retrieved string is not null terminated.");
TiffThrowHelper.ThrowBadStringEntry();
}
return Encoding.UTF8.GetString(buf, 0, buf.Length - 1);

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs

@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
default:
throw new InvalidOperationException();
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}
}
@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
default:
throw new InvalidOperationException();
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}
}
}

53
src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The tiff data stream factory class.
/// </summary>
internal static class TiffStreamFactory
{
/// <summary>
/// Creates the specified byte order.
/// </summary>
/// <param name="byteOrder">The byte order.</param>
/// <param name="stream">The stream.</param>
public static TiffStream Create(TiffByteOrder byteOrder, Stream stream)
{
if (byteOrder == TiffByteOrder.BigEndian)
{
return new TiffBigEndianStream(stream);
}
else if (byteOrder == TiffByteOrder.LittleEndian)
{
return new TiffLittleEndianStream(stream);
}
throw new ArgumentOutOfRangeException(nameof(byteOrder));
}
/// <summary>
/// Reads the byte order of stream.
/// </summary>
/// <param name="stream">The stream.</param>
public static TiffByteOrder ReadByteOrder(Stream stream)
{
byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
return TiffByteOrder.LittleEndian;
}
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
{
return TiffByteOrder.BigEndian;
}
throw new ImageFormatException("Invalid TIFF file header.");
}
}
}

8
src/ImageSharp/Formats/Tiff/TiffDecoder.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
Guard.NotNull(stream, "stream");
using var decoder = new TiffDecoderCore(stream, configuration, this);
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
var decoder = new TiffDecoderCore(configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
var decoder = new TiffDecoderCore(configuration, this);
return decoder.Identify(configuration, stream);
}
@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
var decoder = new TiffDecoderCore(configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}

85
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Performs the tiff decoding operation.
/// </summary>
internal class TiffDecoderCore : IImageDecoderInternals, IDisposable
internal class TiffDecoderCore : IImageDecoderInternals
{
/// <summary>
/// The global configuration
@ -35,42 +35,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
private readonly bool ignoreMetadata;
private BufferedReadStream inputStream;
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
private TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
{
options = options ?? new TiffDecoder();
options ??= new TiffDecoder();
this.configuration = configuration ?? Configuration.Default;
this.ignoreMetadata = options.IgnoreMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public TiffDecoderCore(Stream stream, Configuration configuration, ITiffDecoderOptions options)
: this(configuration, options)
{
this.ByteOrder = TiffStreamFactory.ReadByteOrder(stream);
}
/// <summary>
/// Gets the byte order.
/// </summary>
public TiffByteOrder ByteOrder { get; }
/// <summary>
/// Gets the input stream.
/// </summary>
public TiffStream Stream { get; private set; }
/// <summary>
/// Gets or sets the number of bits for each sample of the pixel format used to encode the image.
/// </summary>
@ -111,8 +91,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream);
var reader = new DirectoryReader(this.Stream);
this.inputStream = stream;
TiffStream tiffStream = CreateStream(stream);
var reader = new DirectoryReader(tiffStream);
IEnumerable<IExifValue[]> directories = reader.Read();
@ -125,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
framesMetadata.Add(frameMetadata);
}
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder);
// todo: tiff frames can have different sizes
{
@ -135,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
if (frame.Size() != root.Size())
{
throw new NotSupportedException("Images with different sizes are not supported");
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported");
}
}
}
@ -148,8 +129,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream);
var reader = new DirectoryReader(this.Stream);
this.inputStream = stream;
TiffStream tiffStream = CreateStream(stream);
var reader = new DirectoryReader(tiffStream);
IEnumerable<IExifValue[]> directories = reader.Read();
@ -159,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
framesMetadata.Add(new TiffFrameMetadata() { Tags = ifd });
}
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder);
TiffFrameMetadata root = framesMetadata.First();
int bitsPerPixel = 0;
@ -171,10 +153,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, metadata);
}
/// <inheritdoc/>
public void Dispose()
private static TiffStream CreateStream(Stream stream)
{
TiffByteOrder byteOrder = ReadByteOrder(stream);
if (byteOrder == TiffByteOrder.BigEndian)
{
return new TiffBigEndianStream(stream);
}
else if (byteOrder == TiffByteOrder.LittleEndian)
{
return new TiffLittleEndianStream(stream);
}
throw TiffThrowHelper.InvalidHeader();
}
private static TiffByteOrder ReadByteOrder(Stream stream)
{
// nothing
byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
return TiffByteOrder.LittleEndian;
}
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
{
return TiffByteOrder.BigEndian;
}
throw TiffThrowHelper.InvalidHeader();
}
/// <summary>
@ -283,8 +290,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
int stripIndex = (i * stripsPerPixel) + planeIndex;
this.Stream.Seek(stripOffsets[stripIndex]);
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan());
this.inputStream.Seek(stripOffsets[stripIndex], SeekOrigin.Begin);
decompressor.Decompress(this.inputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan());
}
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
@ -316,8 +323,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
this.Stream.Seek(stripOffsets[stripIndex]);
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan());
this.inputStream.Seek(stripOffsets[stripIndex], SeekOrigin.Begin);
decompressor.Decompress(this.inputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan());
colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, rowsPerStrip * stripIndex, frame.Width, stripHeight);
}

33
src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs

@ -92,22 +92,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
if (entries.ExtraSamples != null)
{
throw new NotSupportedException("ExtraSamples is not supported.");
TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported.");
}
if (entries.FillOrder != TiffFillOrder.MostSignificantBitFirst)
{
throw new NotSupportedException("The lower-order bits of the byte FillOrder is not supported.");
TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported.");
}
if (entries.GetArray<uint>(ExifTag.TileOffsets, true) != null)
{
throw new NotSupportedException("The Tile images is not supported.");
TiffThrowHelper.ThrowNotSupported("The Tile images is not supported.");
}
if (entries.Predictor != TiffPredictor.None)
{
throw new NotSupportedException("At the moment support only None Predictor.");
TiffThrowHelper.ThrowNotSupported("At the moment support only None Predictor.");
}
if (entries.SampleFormat != null)
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
if (format != TiffSampleFormat.UnsignedInteger)
{
throw new NotSupportedException("At the moment support only UnsignedInteger SampleFormat.");
TiffThrowHelper.ThrowNotSupported("At the moment support only UnsignedInteger SampleFormat.");
}
}
}
@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
@ -236,7 +236,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
@ -260,19 +260,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
}
else
{
throw new ImageFormatException("The TIFF ColorMap entry is missing for a palette color image.");
TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image.");
}
break;
}
default:
throw new NotSupportedException("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation);
{
TiffThrowHelper.ThrowNotSupported("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation);
}
break;
}
}
@ -288,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
throw new ImageFormatException("The TIFF BitsPerSample entry is missing.");
TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing.");
}
}
}
@ -304,7 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
throw new ImageFormatException("The TIFF photometric interpretation entry is missing.");
TiffThrowHelper.ThrowNotSupported("The TIFF photometric interpretation entry is missing.");
}
}
@ -346,7 +350,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
default:
{
throw new NotSupportedException("The specified TIFF compression format is not supported: " + compression);
TiffThrowHelper.ThrowNotSupported("The specified TIFF compression format is not supported: " + compression);
break;
}
}
}

8
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -209,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (!optional)
{
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
TiffThrowHelper.ThrowTagNotFound(nameof(tag));
}
return null;
@ -245,7 +245,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (!optional)
{
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
TiffThrowHelper.ThrowTagNotFound(nameof(tag));
}
return null;
@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private TEnum GetSingleEnum<TEnum, TTagValue>(ExifTag tag, TEnum? defaultValue = null)
where TEnum : struct
where TTagValue : struct
=> this.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)));
=> this.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag)));
private T GetSingle<T>(ExifTag tag)
where T : struct
@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return result;
}
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
throw TiffThrowHelper.TagNotFound(nameof(tag));
}
private bool TryGetSingle<T>(ExifTag tag, out T result)

50
src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Cold path optimizations for throwing tiff format based exceptions.
/// </summary>
internal static class TiffThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static Exception TagNotFound(string tagName)
=> new ArgumentException("Required tag is not found.", tagName);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowTagNotFound(string tagName)
=> throw TagNotFound(tagName);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadZlibHeader(int cmf) => throw new ImageFormatException($"Bad compression method for ZLIB header: cmf={cmf}");
[MethodImpl(InliningOptions.ColdPath)]
public static Exception NotSupportedCompression(string compressionType) => new NotSupportedException("Not supported compression: " + compressionType);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedCompression(string compressionType) => throw NotSupportedCompression(compressionType);
[MethodImpl(InliningOptions.ColdPath)]
public static Exception InvalidColorType(string colorType) => new NotSupportedException("Invalid color type: " + colorType);
[MethodImpl(InliningOptions.ColdPath)]
public static Exception InvalidHeader() => new ImageFormatException("Invalid TIFF file header.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidHeader() => throw InvalidHeader();
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowOutOfRange(string structure) => throw new InvalidDataException($"Out of range of {structure} structure.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadStringEntry() => throw new ImageFormatException("The retrieved string is not null terminated.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupported(string message) => throw new NotSupportedException(message);
}
}
Loading…
Cancel
Save