Browse Source

Merge branch 'master' into jpeg-quantization-metadata

pull/1706/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
1e6547bcca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      src/ImageSharp/Common/Helpers/ExifResolutionValues.cs
  2. 25
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  3. 61
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  4. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs
  5. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  6. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  7. 71
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  8. 72
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  9. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs
  10. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  11. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  12. 28
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs
  13. 21
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  14. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  15. 61
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  16. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs
  17. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  18. 10
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  19. 12
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  20. 61
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  21. 160
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  22. 58
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  23. 10
      src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
  24. 16
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  25. 16
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  26. 4
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  27. 3
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  28. 6
      src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
  29. 4
      src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
  30. 4
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
  31. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  32. 114
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
  33. 179
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs
  34. 100
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  35. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  36. 20
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  37. 23
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  38. 6
      tests/ImageSharp.Tests/TestImages.cs
  39. 3
      tests/Images/Input/Tiff/Issues/Issue1716.tiff
  40. 3
      tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff
  41. 3
      tests/Images/Input/Tiff/flower-miniswhite-16.tiff
  42. 3
      tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff
  43. 3
      tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff
  44. 3
      tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff

21
src/ImageSharp/Common/Helpers/ExifResolutionValues.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Common.Helpers
{
internal readonly struct ExifResolutionValues
{
public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution)
{
this.ResolutionUnit = resolutionUnit;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public ushort ResolutionUnit { get; }
public double? HorizontalResolution { get; }
public double? VerticalResolution { get; }
}
}

25
src/ImageSharp/Common/Helpers/UnitConverter.cs

@ -98,14 +98,14 @@ namespace SixLabors.ImageSharp.Common.Helpers
} }
/// <summary> /// <summary>
/// Sets the exif profile resolution values. /// Gets the exif profile resolution values.
/// </summary> /// </summary>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="unit">The resolution unit.</param> /// <param name="unit">The resolution unit.</param>
/// <param name="horizontal">The horizontal resolution value.</param> /// <param name="horizontal">The horizontal resolution value.</param>
/// <param name="vertical">The vertical resolution value.</param> /// <param name="vertical">The vertical resolution value.</param>
/// <returns><see cref="ExifResolutionValues"/></returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical) public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical)
{ {
switch (unit) switch (unit)
{ {
@ -115,9 +115,9 @@ namespace SixLabors.ImageSharp.Common.Helpers
break; break;
case PixelResolutionUnit.PixelsPerMeter: case PixelResolutionUnit.PixelsPerMeter:
{ {
unit = PixelResolutionUnit.PixelsPerCentimeter; unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = UnitConverter.MeterToCm(horizontal); horizontal = MeterToCm(horizontal);
vertical = UnitConverter.MeterToCm(vertical); vertical = MeterToCm(vertical);
} }
break; break;
@ -126,18 +126,13 @@ namespace SixLabors.ImageSharp.Common.Helpers
break; break;
} }
exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1)); ushort exifUnit = (ushort)(unit + 1);
if (unit == PixelResolutionUnit.AspectRatio) if (unit == PixelResolutionUnit.AspectRatio)
{ {
exifProfile.RemoveValue(ExifTag.XResolution); return new ExifResolutionValues(exifUnit, null, null);
exifProfile.RemoveValue(ExifTag.YResolution);
}
else
{
exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal));
exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical));
} }
return new ExifResolutionValues(exifUnit, horizontal, vertical);
} }
} }
} }

61
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images.
/// </summary>
internal class BlackIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="BlackIsZero16TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public BlackIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
}
}
}
}

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -24,14 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8); var l8 = default(L8);
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
byte intensity = data[offset++]; byte intensity = data[offset++];
pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color);
l8.PackedValue = intensity;
color.FromL8(l8);
pixels[x, y] = color;
} }
} }
} }

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs

@ -34,13 +34,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
int value = bitReader.ReadBits(this.bitsPerSample0); int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = value / this.factor; float intensity = value / this.factor;
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color; pixelRow[x] = color;
} }
bitReader.NextRow(); bitReader.NextRow();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -35,10 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
int index = bitReader.ReadBits(this.bitsPerSample0); int index = bitReader.ReadBits(this.bitsPerSample0);
pixels[x, y] = this.palette[index]; pixelRow[x] = this.palette[index];
} }
bitReader.NextRow(); bitReader.NextRow();

71
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 16 bits for each channel.
/// </summary>
internal class Rgb161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb161616TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb161616TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong g = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong b = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
offset += 2;
ulong g = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
offset += 2;
ulong b = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
}
}
}
}

72
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for all 16 bit.
/// </summary>
internal class Rgb16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb16PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToShortBigEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToShortBigEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToShortBigEndian(blueData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToShortLittleEndian(blueData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
}
}
}
}

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs

@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var rgba = default(Rgba32); var rgba = default(Rgba32);
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.GetRowSpan(y); Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = left; x < left + width; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
byte r = data[offset++]; byte r = data[offset++];
byte g = data[offset++]; byte g = data[offset++];

10
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
@ -12,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <summary> /// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). /// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary> /// </summary>
internal class RgbPlanarTiffColor<TPixel> internal class RgbPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly float rFactor; private readonly float rFactor;
@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <param name="top">The y-coordinate of the top of the image block.</param> /// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param> /// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param> /// <param name="height">The height of the image block.</param>
public void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{ {
var color = default(TPixel); var color = default(TPixel);
@ -57,14 +58,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f)); color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color; pixelRow[x] = color;
} }
rBitReader.NextRow(); rBitReader.NextRow();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs

@ -47,14 +47,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f)); color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color; pixelRow[x] = color;
} }
bitReader.NextRow(); bitReader.NextRow();

28
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// The base class for planar color decoders.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Decodes source raw pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffers to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public abstract void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height);
}
}

21
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal static class TiffColorDecoderFactory<TPixel> internal static class TiffColorDecoderFactory<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
{ {
switch (colorType) switch (colorType)
{ {
@ -32,6 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero8TiffColor<TPixel>(); return new WhiteIsZero8TiffColor<TPixel>();
case TiffColorType.WhiteIsZero16:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero16TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.BlackIsZero: case TiffColorType.BlackIsZero:
DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -52,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero8TiffColor<TPixel>(); return new BlackIsZero8TiffColor<TPixel>();
case TiffColorType.BlackIsZero16:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero16TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb: case TiffColorType.Rgb:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample); return new RgbTiffColor<TPixel>(bitsPerSample);
@ -124,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 16, && bitsPerSample.Channel0 == 16,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample); return new Rgb161616TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.PaletteColor: case TiffColorType.PaletteColor:
DebugGuard.NotNull(colorMap, "colorMap"); DebugGuard.NotNull(colorMap, "colorMap");
@ -135,12 +145,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
} }
} }
public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
{ {
switch (colorType) switch (colorType)
{ {
case TiffColorType.RgbPlanar: case TiffColorType.RgbPlanar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
if (bitsPerSample.Channel0 == 16 && bitsPerSample.Channel1 == 16 && bitsPerSample.Channel2 == 16)
{
return new Rgb16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
}
return new RgbPlanarTiffColor<TPixel>(bitsPerSample); return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
default: default:

10
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

@ -28,6 +28,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary> /// </summary>
BlackIsZero8, BlackIsZero8,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images.
/// </summary>
BlackIsZero16,
/// <summary> /// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. /// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
/// </summary> /// </summary>
@ -48,6 +53,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary> /// </summary>
WhiteIsZero8, WhiteIsZero8,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 16-bit images.
/// </summary>
WhiteIsZero16,
/// <summary> /// <summary>
/// Palette-color. /// Palette-color.
/// </summary> /// </summary>

61
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images.
/// </summary>
internal class WhiteIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="WhiteIsZero16TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public WhiteIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
}
}
}
}

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -24,14 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8); var l8 = default(L8);
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
byte intensity = (byte)(255 - data[offset++]); byte intensity = (byte)(255 - data[offset++]);
pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color);
l8.PackedValue = intensity;
color.FromL8(l8);
pixels[x, y] = color;
} }
} }
} }

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs

@ -34,13 +34,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
for (int x = left; x < left + width; x++) Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{ {
int value = bitReader.ReadBits(this.bitsPerSample0); int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (value / this.factor); float intensity = 1.0f - (value / this.factor);
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color; pixelRow[x] = color;
} }
bitReader.NextRow(); bitReader.NextRow();

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

@ -36,6 +36,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
private BufferedReadStream inputStream; private BufferedReadStream inputStream;
/// <summary>
/// Indicates the byte order of the stream.
/// </summary>
private ByteOrder byteOrder;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class. /// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary> /// </summary>
@ -109,6 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var reader = new DirectoryReader(stream); var reader = new DirectoryReader(stream);
IEnumerable<ExifProfile> directories = reader.Read(); IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
var frames = new List<ImageFrame<TPixel>>(); var frames = new List<ImageFrame<TPixel>>();
foreach (ExifProfile ifd in directories) foreach (ExifProfile ifd in directories)
@ -260,7 +266,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap); TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
for (int i = 0; i < stripsPerPlane; i++) for (int i = 0; i < stripsPerPlane; i++)
{ {
@ -310,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.Predictor, this.Predictor,
this.FaxCompressionOptions); this.FaxCompressionOptions);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap); TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{ {

12
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -111,6 +111,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerChannel) switch (bitsPerChannel)
{ {
case 16:
{
options.ColorType = TiffColorType.WhiteIsZero16;
break;
}
case 8: case 8:
{ {
options.ColorType = TiffColorType.WhiteIsZero8; options.ColorType = TiffColorType.WhiteIsZero8;
@ -154,6 +160,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerChannel) switch (bitsPerChannel)
{ {
case 16:
{
options.ColorType = TiffColorType.BlackIsZero16;
break;
}
case 8: case 8:
{ {
options.ColorType = TiffColorType.BlackIsZero8; options.ColorType = TiffColorType.BlackIsZero8;

61
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -74,6 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
private readonly List<(long, uint)> frameMarkers = new List<(long, uint)>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class. /// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary> /// </summary>
@ -147,13 +149,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff
// Make sure, the Encoder options makes sense in combination with each other. // Make sure, the Encoder options makes sense in combination with each other.
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor); this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
using (var writer = new TiffStreamWriter(stream)) using var writer = new TiffStreamWriter(stream);
long ifdMarker = this.WriteHeader(writer);
Image<TPixel> metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
long firstIfdMarker = this.WriteHeader(writer); var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage);
// TODO: multiframing is not supported ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
this.WriteImage(writer, image, firstIfdMarker); metadataImage = null;
}
long currentOffset = writer.BaseStream.Position;
foreach ((long, uint) marker in this.frameMarkers)
{
writer.WriteMarkerFast(marker.Item1, marker.Item2);
} }
writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
} }
/// <summary> /// <summary>
@ -174,41 +188,56 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Writes all data required to define an image. /// Writes all data required to define an image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param> /// <param name="writer">The <see cref="BinaryWriter" /> to write data to.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="frame">The tiff frame.</param>
/// <param name="imageMetadata">The image metadata (resolution values for each frame).</param>
/// <param name="image">The image (common metadata for root frame).</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param> /// <param name="ifdOffset">The marker to write this IFD offset.</param>
private void WriteImage<TPixel>(TiffStreamWriter writer, Image<TPixel> image, long ifdOffset) /// <returns>
/// The next IFD offset value.
/// </returns>
private long WriteFrame<TPixel>(
TiffStreamWriter writer,
ImageFrame<TPixel> frame,
ImageMetadata imageMetadata,
Image<TPixel> image,
long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var entriesCollector = new TiffEncoderEntriesCollector();
using TiffBaseCompressor compressor = TiffCompressorFactory.Create( using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
this.CompressionType ?? TiffCompression.None, this.CompressionType ?? TiffCompression.None,
writer.BaseStream, writer.BaseStream,
this.memoryAllocator, this.memoryAllocator,
image.Width, frame.Width,
(int)this.BitsPerPixel, (int)this.BitsPerPixel,
this.compressionLevel, this.compressionLevel,
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
var entriesCollector = new TiffEncoderEntriesCollector();
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create( using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(
this.PhotometricInterpretation, this.PhotometricInterpretation,
image.Frames.RootFrame, frame,
this.quantizer, this.quantizer,
this.memoryAllocator, this.memoryAllocator,
this.configuration, this.configuration,
entriesCollector, entriesCollector,
(int)this.BitsPerPixel); (int)this.BitsPerPixel);
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow); int rowsPerStrip = this.CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow);
colorWriter.Write(compressor, rowsPerStrip); colorWriter.Write(compressor, rowsPerStrip);
if (image != null)
{
entriesCollector.ProcessMetadata(image);
}
entriesCollector.ProcessFrameInfo(frame, imageMetadata);
entriesCollector.ProcessImageFormat(this); entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image);
writer.WriteMarker(ifdOffset, (uint)writer.Position); this.frameMarkers.Add((ifdOffset, (uint)writer.Position));
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
return this.WriteIfd(writer, entriesCollector.Entries);
} }
/// <summary> /// <summary>
@ -272,7 +301,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
else else
{ {
var raw = new byte[length]; byte[] raw = new byte[length];
int sz = ExifWriter.WriteValue(entry, raw, 0); int sz = ExifWriter.WriteValue(entry, raw, 0);
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
largeDataBlocks.Add(raw); largeDataBlocks.Add(raw);

160
src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs

@ -6,7 +6,6 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff namespace SixLabors.ImageSharp.Formats.Tiff
{ {
@ -16,9 +15,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public List<IExifValue> Entries { get; } = new List<IExifValue>(); public List<IExifValue> Entries { get; } = new List<IExifValue>();
public void ProcessGeneral<TPixel>(Image<TPixel> image) public void ProcessMetadata(Image image)
where TPixel : unmanaged, IPixel<TPixel> => new MetadataProcessor(this).Process(image);
=> new GeneralProcessor(this).Process(image);
public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata)
=> new FrameInfoProcessor(this).Process(frame, imageMetadata);
public void ProcessImageFormat(TiffEncoderCore encoder) public void ProcessImageFormat(TiffEncoderCore encoder)
=> new ImageFormatProcessor(this).Process(encoder); => new ImageFormatProcessor(this).Process(encoder);
@ -38,44 +39,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private void Add(IExifValue entry) => this.Entries.Add(entry); private void Add(IExifValue entry) => this.Entries.Add(entry);
private class GeneralProcessor private abstract class BaseProcessor
{ {
private readonly TiffEncoderEntriesCollector collector; public BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector;
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; protected TiffEncoderEntriesCollector Collector { get; }
}
public void Process<TPixel>(Image<TPixel> image) private class MetadataProcessor : BaseProcessor
where TPixel : unmanaged, IPixel<TPixel> {
public MetadataProcessor(TiffEncoderEntriesCollector collector)
: base(collector)
{ {
ImageFrame<TPixel> rootFrame = image.Frames.RootFrame; }
ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile();
byte[] rootFrameXmpBytes = rootFrame.Metadata.XmpProfile;
var width = new ExifLong(ExifTagValue.ImageWidth)
{
Value = (uint)image.Width
};
var height = new ExifLong(ExifTagValue.ImageLength)
{
Value = (uint)image.Height
};
var software = new ExifString(ExifTagValue.Software)
{
Value = SoftwareValue
};
this.collector.AddOrReplace(width); public void Process(Image image)
this.collector.AddOrReplace(height); {
ImageFrame rootFrame = image.Frames.RootFrame;
ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile();
byte[] foorFrameXmpBytes = rootFrame.Metadata.XmpProfile;
this.ProcessResolution(image.Metadata, rootFrameExifProfile); this.ProcessProfiles(image.Metadata, rootFrameExifProfile, foorFrameXmpBytes);
this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpBytes);
this.ProcessMetadata(rootFrameExifProfile); this.ProcessMetadata(rootFrameExifProfile);
if (!this.collector.Entries.Exists(t => t.Tag == ExifTag.Software)) if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software))
{ {
this.collector.Add(software); this.Collector.Add(new ExifString(ExifTagValue.Software)
{
Value = SoftwareValue
});
} }
} }
@ -114,26 +106,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
} }
private void ProcessResolution(ImageMetadata imageMetadata, ExifProfile exifProfile)
{
UnitConverter.SetResolutionValues(
exifProfile,
imageMetadata.ResolutionUnits,
imageMetadata.HorizontalResolution,
imageMetadata.VerticalResolution);
this.collector.Add(exifProfile.GetValue(ExifTag.ResolutionUnit).DeepClone());
IExifValue xResolution = exifProfile.GetValue(ExifTag.XResolution)?.DeepClone();
IExifValue yResolution = exifProfile.GetValue(ExifTag.YResolution)?.DeepClone();
if (xResolution != null && yResolution != null)
{
this.collector.Add(xResolution);
this.collector.Add(yResolution);
}
}
private void ProcessMetadata(ExifProfile exifProfile) private void ProcessMetadata(ExifProfile exifProfile)
{ {
foreach (IExifValue entry in exifProfile.Values) foreach (IExifValue entry in exifProfile.Values)
@ -170,9 +142,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break; break;
} }
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag)) if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag))
{ {
this.collector.AddOrReplace(entry.DeepClone()); this.Collector.AddOrReplace(entry.DeepClone());
} }
} }
} }
@ -183,12 +155,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
foreach (IExifValue entry in exifProfile.Values) foreach (IExifValue entry in exifProfile.Values)
{ {
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null) if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null)
{ {
ExifParts entryPart = ExifTags.GetPart(entry.Tag); ExifParts entryPart = ExifTags.GetPart(entry.Tag);
if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart)) if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart))
{ {
this.collector.AddOrReplace(entry.DeepClone()); this.Collector.AddOrReplace(entry.DeepClone());
} }
} }
} }
@ -206,7 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = imageMetadata.IptcProfile.Data Value = imageMetadata.IptcProfile.Data
}; };
this.collector.Add(iptc); this.Collector.Add(iptc);
} }
else else
{ {
@ -220,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = imageMetadata.IccProfile.ToByteArray() Value = imageMetadata.IccProfile.ToByteArray()
}; };
this.collector.Add(icc); this.Collector.Add(icc);
} }
else else
{ {
@ -234,7 +206,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = xmpProfile Value = xmpProfile
}; };
this.collector.Add(xmp); this.Collector.Add(xmp);
} }
else else
{ {
@ -243,11 +215,61 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
} }
private class ImageFormatProcessor private class FrameInfoProcessor : BaseProcessor
{ {
private readonly TiffEncoderEntriesCollector collector; public FrameInfoProcessor(TiffEncoderEntriesCollector collector)
: base(collector)
{
}
public void Process(ImageFrame frame, ImageMetadata imageMetadata)
{
this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth)
{
Value = (uint)frame.Width
});
this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength)
{
Value = (uint)frame.Height
});
this.ProcessResolution(imageMetadata);
}
private void ProcessResolution(ImageMetadata imageMetadata)
{
ExifResolutionValues resolution = UnitConverter.GetExifResolutionValues(
imageMetadata.ResolutionUnits,
imageMetadata.HorizontalResolution,
imageMetadata.VerticalResolution);
public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit)
{
Value = resolution.ResolutionUnit
});
if (resolution.VerticalResolution.HasValue && resolution.HorizontalResolution.HasValue)
{
this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution)
{
Value = new Rational(resolution.HorizontalResolution.Value)
});
this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution)
{
Value = new Rational(resolution.VerticalResolution.Value)
});
}
}
}
private class ImageFormatProcessor : BaseProcessor
{
public ImageFormatProcessor(TiffEncoderEntriesCollector collector)
: base(collector)
{
}
public void Process(TiffEncoderCore encoder) public void Process(TiffEncoderCore encoder)
{ {
@ -278,11 +300,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = (ushort)encoder.PhotometricInterpretation Value = (ushort)encoder.PhotometricInterpretation
}; };
this.collector.AddOrReplace(planarConfig); this.Collector.AddOrReplace(planarConfig);
this.collector.AddOrReplace(samplesPerPixel); this.Collector.AddOrReplace(samplesPerPixel);
this.collector.AddOrReplace(bitPerSample); this.Collector.AddOrReplace(bitPerSample);
this.collector.AddOrReplace(compression); this.Collector.AddOrReplace(compression);
this.collector.AddOrReplace(photometricInterpretation); this.Collector.AddOrReplace(photometricInterpretation);
if (encoder.HorizontalPredictor == TiffPredictor.Horizontal) if (encoder.HorizontalPredictor == TiffPredictor.Horizontal)
{ {
@ -292,7 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
this.collector.AddOrReplace(predictor); this.Collector.AddOrReplace(predictor);
} }
} }
} }

58
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
/// <summary>
/// Helper methods for TIFF decoding.
/// </summary>
internal static class TiffUtils
{
public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f);
public static Rgba64 Rgba64Default { get; } = new Rgba64(0, 0, 0, 0);
public static L16 L16Default { get; } = new L16(0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToShortBigEndian(ReadOnlySpan<byte> buffer) =>
BinaryPrimitives.ReadUInt16BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToShortLittleEndian(ReadOnlySpan<byte> buffer) =>
BinaryPrimitives.ReadUInt16LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL8<TPixel>(L8 l8, byte intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
l8.PackedValue = intensity;
color.FromL8(l8);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
l16.PackedValue = intensity;
color.FromL16(l16);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
color.FromRgba64(rgba);
return color;
}
}
}

10
src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs

@ -126,10 +126,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
/// <param name="value">The four-byte unsigned integer to write.</param> /// <param name="value">The four-byte unsigned integer to write.</param>
public void WriteMarker(long offset, uint value) public void WriteMarker(long offset, uint value)
{ {
long currentOffset = this.BaseStream.Position; long back = this.BaseStream.Position;
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.BaseStream.Seek(back, SeekOrigin.Begin);
}
public void WriteMarkerFast(long offset, uint value)
{
this.BaseStream.Seek(offset, SeekOrigin.Begin); this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value); this.Write(value);
this.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
} }
/// <summary> /// <summary>

16
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ErrorDither(in DenseMatrix<float> matrix, int offset) public ErrorDither(in DenseMatrix<float> matrix, int offset)
{ {
Guard.MustBeGreaterThan(offset, 0, nameof(offset));
this.matrix = matrix; this.matrix = matrix;
this.offset = offset; this.offset = offset;
} }
@ -95,6 +97,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (this == default)
{
ThrowDefaultInstance();
}
int offsetY = bounds.Top; int offsetY = bounds.Top;
int offsetX = bounds.Left; int offsetX = bounds.Left;
float scale = quantizer.Options.DitherScale; float scale = quantizer.Options.DitherScale;
@ -122,6 +129,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel> where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (this == default)
{
ThrowDefaultInstance();
}
float scale = processor.DitherScale; float scale = processor.DitherScale;
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
@ -210,5 +222,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine(this.offset, this.matrix); => HashCode.Combine(this.offset, this.matrix);
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowDefaultInstance()
=> throw new ImageProcessingException("Cannot use the default value type instance to dither.");
} }
} }

16
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -24,6 +24,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public OrderedDither(uint length) public OrderedDither(uint length)
{ {
Guard.MustBeGreaterThan(length, 0, nameof(length));
DenseMatrix<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); DenseMatrix<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length);
// Create a new matrix to run against, that pre-thresholds the values. // Create a new matrix to run against, that pre-thresholds the values.
@ -109,6 +111,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (this == default)
{
ThrowDefaultInstance();
}
int spread = CalculatePaletteSpread(destination.Palette.Length); int spread = CalculatePaletteSpread(destination.Palette.Length);
float scale = quantizer.Options.DitherScale; float scale = quantizer.Options.DitherScale;
@ -134,6 +141,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel> where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (this == default)
{
ThrowDefaultInstance();
}
int spread = CalculatePaletteSpread(processor.Palette.Length); int spread = CalculatePaletteSpread(processor.Palette.Length);
float scale = processor.DitherScale; float scale = processor.DitherScale;
@ -201,5 +213,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowDefaultInstance()
=> throw new ImageProcessingException("Cannot use the default value type instance to dither.");
} }
} }

4
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

@ -11,14 +11,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class OctreeQuantizer : IQuantizer public class OctreeQuantizer : IQuantizer
{ {
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>. /// using the default <see cref="QuantizerOptions"/>.
/// </summary> /// </summary>
public OctreeQuantizer() public OctreeQuantizer()
: this(DefaultOptions) : this(new QuantizerOptions())
{ {
} }

3
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -11,7 +11,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class PaletteQuantizer : IQuantizer public class PaletteQuantizer : IQuantizer
{ {
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
private readonly ReadOnlyMemory<Color> colorPalette; private readonly ReadOnlyMemory<Color> colorPalette;
/// <summary> /// <summary>
@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="palette">The color palette.</param> /// <param name="palette">The color palette.</param>
public PaletteQuantizer(ReadOnlyMemory<Color> palette) public PaletteQuantizer(ReadOnlyMemory<Color> palette)
: this(palette, DefaultOptions) : this(palette, new QuantizerOptions())
{ {
} }

6
src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
@ -10,13 +8,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class WebSafePaletteQuantizer : PaletteQuantizer public class WebSafePaletteQuantizer : PaletteQuantizer
{ {
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class. /// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary> /// </summary>
public WebSafePaletteQuantizer() public WebSafePaletteQuantizer()
: this(DefaultOptions) : this(new QuantizerOptions())
{ {
} }

4
src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs

@ -9,13 +9,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class WernerPaletteQuantizer : PaletteQuantizer public class WernerPaletteQuantizer : PaletteQuantizer
{ {
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class. /// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary> /// </summary>
public WernerPaletteQuantizer() public WernerPaletteQuantizer()
: this(DefaultOptions) : this(new QuantizerOptions())
{ {
} }

4
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs

@ -10,14 +10,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class WuQuantizer : IQuantizer public class WuQuantizer : IQuantizer
{ {
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class /// Initializes a new instance of the <see cref="WuQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>. /// using the default <see cref="QuantizerOptions"/>.
/// </summary> /// </summary>
public WuQuantizer() public WuQuantizer()
: this(DefaultOptions) : this(new QuantizerOptions())
{ {
} }

6
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -140,7 +140,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)]
[WithFile(Flower16BitGray, PixelTypes.Rgba32)] [WithFile(Flower16BitGray, PixelTypes.Rgba32)]
[WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)]
[WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
@ -163,7 +166,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory] [Theory]
[WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616ContiguousLittleEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616PlanarLittleEndian, PixelTypes.Rgba32)]
[WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);

114
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Format", "Tiff")]
public abstract class TiffEncoderBaseTester
{
protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
protected static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None;
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile;
TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata();
ImageFrame<Rgba32> rootFrame = output.Frames.RootFrame;
Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity;
Assert.True(output.Height > (int)rowsPerStrip);
Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1);
Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value;
Assert.NotNull(stripByteCounts);
Assert.True(stripByteCounts.Length > 1);
Assert.NotNull(outputMeta.BitsPerPixel);
foreach (Number sz in stripByteCounts)
{
Assert.True((uint)sz <= TiffConstants.DefaultStripSize);
}
// For uncompressed more accurate test.
if (compression == TiffCompression.None)
{
for (int i = 0; i < stripByteCounts.Length - 1; i++)
{
// The difference must be less than one row.
int stripBytes = (int)stripByteCounts[i];
int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width;
Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes);
}
}
// Compare with reference.
TestTiffEncoderCore(
provider,
inputMeta.BitsPerPixel,
photometricInterpretation,
inputCompression,
useExactComparer: useExactComparer,
compareTolerance: compareTolerance);
}
protected static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel? bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression = TiffCompression.None,
TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true,
float compareTolerance = 0.001f,
IImageDecoder imageDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder
{
PhotometricInterpretation = photometricInterpretation,
BitsPerPixel = bitsPerPixel,
Compression = compression,
HorizontalPredictor = predictor
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance),
referenceDecoder: imageDecoder ?? ReferenceDecoder);
}
}
}

179
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs

@ -0,0 +1,179 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
using static SixLabors.ImageSharp.Tests.TestImages.Tiff;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Format", "Tiff")]
public class TiffEncoderMultiframeTests : TiffEncoderBaseTester
{
[Theory]
[WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)]
[WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_NotSupport<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb));
[Theory]
[WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_WithPreview<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(TestImages.Gif.Receipt, PixelTypes.Rgb24)]
[WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_Convert<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit48, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_RemoveFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
Assert.True(image.Frames.Count > 1);
image.Frames.RemoveFrame(0);
TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24;
var encoder = new TiffEncoder
{
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
BitsPerPixel = bitsPerPixel,
Compression = TiffCompression.Deflate
};
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
ImageComparer.Exact);
}
[Theory]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_AddFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
Assert.Equal(1, image.Frames.Count);
using var image1 = new Image<Rgba32>(image.Width, image.Height, Color.Green.ToRgba32());
using var image2 = new Image<Rgba32>(image.Width, image.Height, Color.Yellow.ToRgba32());
image.Frames.AddFrame(image1.Frames.RootFrame);
image.Frames.AddFrame(image2.Frames.RootFrame);
TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24;
var encoder = new TiffEncoder
{
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
BitsPerPixel = bitsPerPixel,
Compression = TiffCompression.Deflate
};
using (var ms = new System.IO.MemoryStream())
{
image.Save(ms, encoder);
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
Assert.Equal(3, output.Frames.Count);
ImageFrame<Rgba32> frame1 = output.Frames[1];
ImageFrame<Rgba32> frame2 = output.Frames[2];
Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]);
Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]);
Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffPhotometricInterpretation.Rgb, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.Rgb, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation);
}
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
ImageComparer.Exact);
}
[Theory]
[WithBlankImages(100, 100, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_Create<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
using var image0 = new Image<Rgba32>(image.Width, image.Height, Color.Red.ToRgba32());
using var image1 = new Image<Rgba32>(image.Width, image.Height, Color.Green.ToRgba32());
using var image2 = new Image<Rgba32>(image.Width, image.Height, Color.Yellow.ToRgba32());
image.Frames.AddFrame(image0.Frames.RootFrame);
image.Frames.AddFrame(image1.Frames.RootFrame);
image.Frames.AddFrame(image2.Frames.RootFrame);
image.Frames.RemoveFrame(0);
TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit8;
var encoder = new TiffEncoder
{
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
BitsPerPixel = bitsPerPixel,
Compression = TiffCompression.Lzw
};
using (var ms = new System.IO.MemoryStream())
{
image.Save(ms, encoder);
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
Assert.Equal(3, output.Frames.Count);
ImageFrame<Rgba32> frame0 = output.Frames[0];
ImageFrame<Rgba32> frame1 = output.Frames[1];
ImageFrame<Rgba32> frame2 = output.Frames[2];
Assert.Equal(Color.Red.ToRgba32(), frame0[10, 10]);
Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]);
Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]);
Assert.Equal(TiffCompression.Lzw, frame0.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame0.Metadata.GetTiffMetadata().PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation);
}
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
ImageComparer.Exact);
}
}
}

100
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -2,14 +2,9 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit; using Xunit;
@ -19,10 +14,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
[Collection("RunSerial")] [Collection("RunSerial")]
[Trait("Format", "Tiff")] [Trait("Format", "Tiff")]
public class TiffEncoderTests public class TiffEncoderTests : TiffEncoderBaseTester
{ {
private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
[Theory] [Theory]
[InlineData(null, TiffBitsPerPixel.Bit24)] [InlineData(null, TiffBitsPerPixel.Bit24)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)]
@ -451,96 +444,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation };
image.DebugSave(provider, encoder); image.DebugSave(provider, encoder);
} }
private static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None;
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile;
TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata();
ImageFrame<Rgba32> rootFrame = output.Frames.RootFrame;
Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity;
Assert.True(output.Height > (int)rowsPerStrip);
Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1);
Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value;
Assert.NotNull(stripByteCounts);
Assert.True(stripByteCounts.Length > 1);
Assert.NotNull(outputMeta.BitsPerPixel);
foreach (Number sz in stripByteCounts)
{
Assert.True((uint)sz <= TiffConstants.DefaultStripSize);
}
// For uncompressed more accurate test.
if (compression == TiffCompression.None)
{
for (int i = 0; i < stripByteCounts.Length - 1; i++)
{
// The difference must be less than one row.
int stripBytes = (int)stripByteCounts[i];
int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width;
Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes);
}
}
// Compare with reference.
TestTiffEncoderCore(
provider,
inputMeta.BitsPerPixel,
photometricInterpretation,
inputCompression,
useExactComparer: useExactComparer,
compareTolerance: compareTolerance);
}
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel? bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression = TiffCompression.None,
TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true,
float compareTolerance = 0.001f,
IImageDecoder imageDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder
{
PhotometricInterpretation = photometricInterpretation,
BitsPerPixel = bitsPerPixel,
Compression = compression,
HorizontalPredictor = predictor
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance),
referenceDecoder: imageDecoder ?? ReferenceDecoder);
}
} }
} }

4
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -278,8 +278,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput); PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput);
PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile); PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile);
Assert.Equal(resolutionUnitInput, resolutionUnitEncoded); Assert.Equal(resolutionUnitInput, resolutionUnitEncoded);
Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution), encodedImageExifProfile.GetValue(ExifTag.XResolution)); Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble());
Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution), encodedImageExifProfile.GetValue(ExifTag.YResolution)); Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble());
Assert.Equal(xmpProfileInput, encodedImageXmpProfile); Assert.Equal(xmpProfileInput, encodedImageXmpProfile);

20
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -43,6 +43,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering
{ KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) }
}; };
public static readonly TheoryData<IDither> DefaultInstanceDitherers
= new TheoryData<IDither>
{
default(ErrorDither),
default(OrderedDither)
};
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f);
private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; private static IDither DefaultDitherer => KnownDitherings.Bayer4x4;
@ -175,5 +182,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering
c => c.Dither(dither), c => c.Dither(dither),
name); name);
} }
[Theory]
[MemberData(nameof(DefaultInstanceDitherers))]
public void ShouldThrowForDefaultDitherInstance(IDither dither)
{
void Command()
{
using var image = new Image<Rgba32>(10, 10);
image.Mutate(x => x.Dither(dither));
}
Assert.Throws<ImageProcessingException>(Command);
}
} }
} }

23
tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs

@ -5,6 +5,7 @@ using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit; using Xunit;
@ -152,6 +153,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
new WuQuantizer(OrderedDitherOptions), new WuQuantizer(OrderedDitherOptions),
}; };
public static readonly TheoryData<IDither> DefaultInstanceDitherers
= new TheoryData<IDither>
{
default(ErrorDither),
default(OrderedDither)
};
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F);
[Theory] [Theory]
@ -217,5 +225,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
testOutputDetails: testOutputDetails, testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: false); appendPixelTypeToFileName: false);
} }
[Theory]
[MemberData(nameof(DefaultInstanceDitherers))]
public void ShouldThrowForDefaultDitherInstance(IDither dither)
{
void Command()
{
using var image = new Image<Rgba32>(10, 10);
var quantizer = new WebSafePaletteQuantizer();
quantizer.Options.Dither = dither;
image.Mutate(x => x.Quantize(quantizer));
}
Assert.Throws<ImageProcessingException>(Command);
}
} }
} }

6
tests/ImageSharp.Tests/TestImages.cs

@ -564,7 +564,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff";
public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff";
public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff";
public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff";
public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff";
public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff";
public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff";
public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff";
public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff";
@ -584,6 +586,10 @@ namespace SixLabors.ImageSharp.Tests
public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff";
public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff";
public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff";
public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff";
public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff";
public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff";
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";

3
tests/Images/Input/Tiff/Issues/Issue1716.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c734dd489c65fb77bd7a35cd663aa16ce986df2c2ab8c7ca43d8b65db9d47c03
size 6666162

3
tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3806304a5453a6ec8a6795bc77b967b9aa8593288af36bbf9802f22ee27869e
size 6588

3
tests/Images/Input/Tiff/flower-miniswhite-16.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8f2c2afd8f1645717087bd2edbc3e8a46b88a54a4996c0e9350fdd652b5c382
size 6588

3
tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:435c92b453587e1943940111b66afabf70307beb0e1d65e9701fd9bb753eead2
size 6588

3
tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0951a9c2207eb6864b6a19ec8513a28a874adddb37c3c06b9fd07831372924e3
size 19150

3
tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:46a60552a7ff37f2c16c43e030e7180872af712f5d9c9c7673e2547049af3da9
size 19168
Loading…
Cancel
Save