Browse Source

Allow encoding 4bit color palette images

pull/1570/head
Brian Popow 5 years ago
parent
commit
1dbe583824
  1. 5
      src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
  2. 2
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  3. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  4. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  5. 7
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  6. 10
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  7. 8
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  8. 9
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  9. 4
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  10. 2
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  11. 10
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  12. 20
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  13. 5
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  14. 27
      src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs
  15. 4
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  16. 3
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  17. 107
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  18. 22
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  19. 8
      src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs
  20. 5
      src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
  21. 76
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs
  22. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  23. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  24. 38
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  25. 1
      tests/ImageSharp.Tests/TestImages.cs
  26. 3
      tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff

5
src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs

@ -3,13 +3,14 @@
using System; using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{ {
internal class NoCompressor : TiffBaseCompressor internal class NoCompressor : TiffBaseCompressor
{ {
public NoCompressor(Stream output) public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
: base(output, default, default, default) : base(output, memoryAllocator, width, bitsPerPixel)
{ {
} }

2
src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs

@ -201,8 +201,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
this.useModifiedHuffman = useModifiedHuffman; this.useModifiedHuffman = useModifiedHuffman;
} }
/// <inheritdoc/>
public override TiffEncoderCompression Method => this.useModifiedHuffman ? TiffEncoderCompression.ModifiedHuffman : TiffEncoderCompression.CcittGroup3Fax; public override TiffEncoderCompression Method => this.useModifiedHuffman ? TiffEncoderCompression.ModifiedHuffman : TiffEncoderCompression.CcittGroup3Fax;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip) public override void Initialize(int rowsPerStrip)
{ {
// This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good.

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

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <remarks> /// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks> /// </remarks>
internal class DeflateTiffCompression : TiffBaseDecompresor internal class DeflateTiffCompression : TiffBaseDecompressor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class. /// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression. /// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary> /// </summary>
internal class LzwTiffCompression : TiffBaseDecompresor internal class LzwTiffCompression : TiffBaseDecompressor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class. /// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.

7
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

@ -22,10 +22,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class. /// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="allocator">The memory allocator.</param> /// <param name="allocator">The memory allocator.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width) /// <param name="bitsPerPixel">The number of bits per pixel.</param>
: base(allocator, FaxCompressionOptions.None, photometricInterpretation, width) /// <param name="photometricInterpretation">The photometric interpretation.</param>
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation)
{ {
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.whiteValue = (byte)(isWhiteZero ? 0 : 1);

10
src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs

@ -4,19 +4,23 @@
using System; using System;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors
{ {
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is not compressed. /// Class to handle cases where TIFF image data is not compressed.
/// </summary> /// </summary>
internal class NoneTiffCompression : TiffBaseDecompresor internal class NoneTiffCompression : TiffBaseDecompressor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="NoneTiffCompression" /> class. /// Initializes a new instance of the <see cref="NoneTiffCompression" /> class.
/// </summary> /// </summary>
public NoneTiffCompression() /// <param name="memoryAllocator">The memory allocator.</param>
: base(default, default, default) /// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
: base(memoryAllocator, width, bitsPerPixel)
{ {
} }

8
src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression. /// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary> /// </summary>
internal class PackBitsTiffCompression : TiffBaseDecompresor internal class PackBitsTiffCompression : TiffBaseDecompressor
{ {
private IMemoryOwner<byte> compressedDataMemory; private IMemoryOwner<byte> compressedDataMemory;
@ -20,8 +20,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// Initializes a new instance of the <see cref="PackBitsTiffCompression" /> class. /// Initializes a new instance of the <see cref="PackBitsTiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param> /// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
public PackBitsTiffCompression(MemoryAllocator memoryAllocator) /// <param name="width">The width of the image.</param>
: base(memoryAllocator, default, default) /// <param name="bitsPerPixel">The number of bits per pixel.</param>
public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
: base(memoryAllocator, width, bitsPerPixel)
{ {
} }

9
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression.
/// </summary> /// </summary>
internal class T4TiffCompression : TiffBaseDecompresor internal class T4TiffCompression : TiffBaseDecompressor
{ {
private readonly FaxCompressionOptions faxCompressionOptions; private readonly FaxCompressionOptions faxCompressionOptions;
@ -24,11 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// Initializes a new instance of the <see cref="T4TiffCompression" /> class. /// Initializes a new instance of the <see cref="T4TiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="allocator">The memory allocator.</param> /// <param name="allocator">The memory allocator.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="faxOptions">Fax compression options.</param> /// <param name="faxOptions">Fax compression options.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param> /// <param name="photometricInterpretation">The photometric interpretation.</param>
/// <param name="width">The image width.</param> public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation)
public T4TiffCompression(MemoryAllocator allocator, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation, int width) : base(allocator, width, bitsPerPixel)
: base(allocator, width, default)
{ {
this.faxCompressionOptions = faxOptions; this.faxCompressionOptions = faxOptions;

4
src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs → src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs

@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// <summary> /// <summary>
/// The base tiff decompressor class. /// The base tiff decompressor class.
/// </summary> /// </summary>
internal abstract class TiffBaseDecompresor : TiffBaseCompression internal abstract class TiffBaseDecompressor : TiffBaseCompression
{ {
protected TiffBaseDecompresor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor) : base(allocator, width, bitsPerPixel, predictor)
{ {
} }

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

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new NoCompressor(output); return new NoCompressor(output, allocator, width, bitsPerPixel);
case TiffEncoderCompression.PackBits: case TiffEncoderCompression.PackBits:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");

10
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{ {
internal static class TiffDecompressorsFactory internal static class TiffDecompressorsFactory
{ {
public static TiffBaseDecompresor Create( public static TiffBaseDecompressor Create(
TiffDecoderCompressionType method, TiffDecoderCompressionType method,
MemoryAllocator allocator, MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation, TiffPhotometricInterpretation photometricInterpretation,
@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
case TiffDecoderCompressionType.None: case TiffDecoderCompressionType.None:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new NoneTiffCompression(); return new NoneTiffCompression(allocator, width, bitsPerPixel);
case TiffDecoderCompressionType.PackBits: case TiffDecoderCompressionType.PackBits:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new PackBitsTiffCompression(allocator); return new PackBitsTiffCompression(allocator, width, bitsPerPixel);
case TiffDecoderCompressionType.Deflate: case TiffDecoderCompressionType.Deflate:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
@ -40,11 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
case TiffDecoderCompressionType.T4: case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4TiffCompression(allocator, faxOptions, photometricInterpretation, width); return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation);
case TiffDecoderCompressionType.HuffmanRle: case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, photometricInterpretation, width); return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation);
default: default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));

20
src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs

@ -75,6 +75,26 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants
/// </summary> /// </summary>
public const int SizeOfDouble = 8; public const int SizeOfDouble = 8;
/// <summary>
/// The bits per sample for 1 bit bicolor images.
/// </summary>
public static readonly ushort[] BitsPerSample1Bit = { 1 };
/// <summary>
/// The bits per sample for images with a 4 color palette.
/// </summary>
public static readonly ushort[] BitsPerSample4Bit = { 4 };
/// <summary>
/// The bits per sample for 8 bit images.
/// </summary>
public static readonly ushort[] BitsPerSample8Bit = { 8 };
/// <summary>
/// The bits per sample for images with 8 bits for each color channel.
/// </summary>
public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 };
/// <summary> /// <summary>
/// The list of mimetypes that equate to a tiff. /// The list of mimetypes that equate to a tiff.
/// </summary> /// </summary>

5
src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs

@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
internal interface ITiffEncoderOptions internal interface ITiffEncoderOptions
{ {
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
TiffBitsPerPixel? BitsPerPixel { get; set; }
/// <summary> /// <summary>
/// Gets the compression type to use. /// Gets the compression type to use.
/// </summary> /// </summary>

27
src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs

@ -3,19 +3,12 @@
using System; using System;
using SixLabors.ImageSharp.Formats.Experimental.Tiff; using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
namespace SixLabors.ImageSharp.Formats.Tiff namespace SixLabors.ImageSharp.Formats.Tiff
{ {
internal static class TiffBitsPerSampleExtensions internal static class TiffBitsPerSampleExtensions
{ {
private static readonly ushort[] One = { 1 };
private static readonly ushort[] Four = { 4 };
private static readonly ushort[] Eight = { 8 };
private static readonly ushort[] Rgb888 = { 8, 8, 8 };
/// <summary> /// <summary>
/// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8] /// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8]
/// </summary> /// </summary>
@ -26,13 +19,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (tiffBitsPerSample) switch (tiffBitsPerSample)
{ {
case TiffBitsPerSample.One: case TiffBitsPerSample.One:
return One; return TiffConstants.BitsPerSample1Bit;
case TiffBitsPerSample.Four: case TiffBitsPerSample.Four:
return Four; return TiffConstants.BitsPerSample4Bit;
case TiffBitsPerSample.Eight: case TiffBitsPerSample.Eight:
return Eight; return TiffConstants.BitsPerSample8Bit;
case TiffBitsPerSample.Rgb888: case TiffBitsPerSample.Rgb888:
return Rgb888; return TiffConstants.BitsPerSampleRgb8Bit;
default: default:
TiffThrowHelper.ThrowNotSupported("The bits per pixels are not supported"); TiffThrowHelper.ThrowNotSupported("The bits per pixels are not supported");
@ -50,7 +43,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerSample.Length) switch (bitsPerSample.Length)
{ {
case 3: case 3:
if (bitsPerSample[0] == Rgb888[0] && bitsPerSample[1] == Rgb888[1] && bitsPerSample[2] == Rgb888[2]) if (bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0] &&
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] &&
bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2])
{ {
return TiffBitsPerSample.Rgb888; return TiffBitsPerSample.Rgb888;
} }
@ -58,17 +53,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break; break;
case 1: case 1:
if (bitsPerSample[0] == One[0]) if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0])
{ {
return TiffBitsPerSample.One; return TiffBitsPerSample.One;
} }
if (bitsPerSample[0] == Four[0]) if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0])
{ {
return TiffBitsPerSample.Four; return TiffBitsPerSample.Four;
} }
if (bitsPerSample[0] == Eight[0]) if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0])
{ {
return TiffBitsPerSample.Eight; return TiffBitsPerSample.Eight;
} }

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

@ -250,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
} }
using TiffBaseDecompresor 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.Bits(), this.ColorMap); RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap);
@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer; Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompresor 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);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap);

3
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -17,6 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{ {
/// <inheritdoc/>
public TiffBitsPerPixel? BitsPerPixel { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None; public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None;

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

@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -40,11 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
private Configuration configuration; private Configuration configuration;
/// <summary>
/// The color depth, in number of bits per pixel.
/// </summary>
private TiffBitsPerPixel bitsPerPixel;
/// <summary> /// <summary>
/// The quantizer for creating color palette image. /// The quantizer for creating color palette image.
/// </summary> /// </summary>
@ -70,6 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.Mode = options.Mode; this.Mode = options.Mode;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.BitsPerPixel = options.BitsPerPixel;
this.UseHorizontalPredictor = options.UseHorizontalPredictor; this.UseHorizontalPredictor = options.UseHorizontalPredictor;
this.CompressionType = options.Compression; this.CompressionType = options.Compression;
this.compressionLevel = options.CompressionLevel; this.compressionLevel = options.CompressionLevel;
@ -82,9 +79,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; } internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; }
/// <summary> /// <summary>
/// Gets the compression implementation to use when encoding the image. /// Gets or sets the compression implementation to use when encoding the image.
/// </summary> /// </summary>
internal TiffEncoderCompression CompressionType { get; } internal TiffEncoderCompression CompressionType { get; set; }
/// <summary> /// <summary>
/// Gets the encoding mode to use. RGB, RGB with color palette or gray. /// Gets the encoding mode to use. RGB, RGB with color palette or gray.
@ -97,6 +94,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
internal bool UseHorizontalPredictor { get; } internal bool UseHorizontalPredictor { get; }
/// <summary>
/// Gets the bits per pixel.
/// </summary>
internal TiffBitsPerPixel? BitsPerPixel { get; private set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
@ -111,8 +113,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration(); this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
TiffMetadata tiffMetadata = metadata.GetTiffMetadata();
this.BitsPerPixel ??= tiffMetadata.BitsPerPixel;
this.SetMode(image); this.SetMode(tiffMetadata);
this.SetBitsPerPixel();
this.SetPhotometricInterpretation(); this.SetPhotometricInterpretation();
using (var writer = new TiffStreamWriter(stream)) using (var writer = new TiffStreamWriter(stream))
@ -155,20 +161,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
// Write the image bytes to the steam. // Write the image bytes to the steam.
var imageDataStart = (uint)writer.Position; var imageDataStart = (uint)writer.Position;
using TiffBaseCompressor compressor = TiffCompressorFactory.Create( TiffBitsPerPixel? tiffBitsPerPixel = this.BitsPerPixel;
this.CompressionType, if (tiffBitsPerPixel != null)
writer.BaseStream, {
this.memoryAllocator, using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
image.Width, this.CompressionType,
(int)this.bitsPerPixel, writer.BaseStream,
this.compressionLevel, this.memoryAllocator,
this.UseHorizontalPredictor ? TiffPredictor.Horizontal : TiffPredictor.None); image.Width,
(int)tiffBitsPerPixel,
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(this.Mode, image.Frames.RootFrame, this.quantizer, this.memoryAllocator, this.configuration, entriesCollector); this.compressionLevel,
this.UseHorizontalPredictor ? TiffPredictor.Horizontal : TiffPredictor.None);
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame, colorWriter.BytesPerRow);
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(
colorWriter.Write(compressor, rowsPerStrip); this.Mode,
image.Frames.RootFrame,
this.quantizer,
this.memoryAllocator,
this.configuration,
entriesCollector,
(int)tiffBitsPerPixel);
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow);
colorWriter.Write(compressor, rowsPerStrip);
}
entriesCollector.ProcessImageFormat(this); entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image); entriesCollector.ProcessGeneral(image);
@ -177,12 +194,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries); long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
} }
private int CalcRowsPerStrip(ImageFrame image, int bytesPerRow) /// <summary>
/// Calculates the number of rows written per strip.
/// </summary>
/// <param name="height">The height of the image.</param>
/// <param name="bytesPerRow">The number of bytes per row.</param>
/// <returns>Number of rows per strip.</returns>
private int CalcRowsPerStrip(int height, int bytesPerRow)
{ {
int sz = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize; DebugGuard.MustBeGreaterThan(height, 0, nameof(height));
int height = sz / bytesPerRow; DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow));
int stripBytes = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize;
int rowsPerStrip = stripBytes / bytesPerRow;
return height > 0 ? (height < image.Height ? height : image.Height) : 1; return rowsPerStrip > 0 ? (rowsPerStrip < height ? rowsPerStrip : height) : 1;
} }
/// <summary> /// <summary>
@ -242,14 +268,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return nextIfdMarker; return nextIfdMarker;
} }
private void SetMode(Image image) private void SetMode(TiffMetadata tiffMetadata)
{ {
// Make sure, that the fax compressions are only used together with the BiColor mode.
if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax || this.CompressionType == TiffEncoderCompression.ModifiedHuffman) if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax || this.CompressionType == TiffEncoderCompression.ModifiedHuffman)
{ {
// Default means the user has not specified a preferred encoding mode.
if (this.Mode == TiffEncodingMode.Default) if (this.Mode == TiffEncodingMode.Default)
{ {
this.Mode = TiffEncodingMode.BiColor; this.Mode = TiffEncodingMode.BiColor;
this.bitsPerPixel = TiffBitsPerPixel.Pixel1;
return; return;
} }
@ -262,36 +289,48 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
if (this.Mode == TiffEncodingMode.Default) if (this.Mode == TiffEncodingMode.Default)
{ {
// Preserve input bits per pixel, if no mode was specified. // Preserve input bits per pixel, if no mode was specified.
TiffMetadata tiffMetadata = image.Metadata.GetTiffMetadata();
switch (tiffMetadata.BitsPerPixel) switch (tiffMetadata.BitsPerPixel)
{ {
case TiffBitsPerPixel.Pixel1: case TiffBitsPerPixel.Pixel1:
this.Mode = TiffEncodingMode.BiColor; this.Mode = TiffEncodingMode.BiColor;
break; break;
case TiffBitsPerPixel.Pixel4:
this.Mode = TiffEncodingMode.ColorPalette;
break;
case TiffBitsPerPixel.Pixel8: case TiffBitsPerPixel.Pixel8:
this.Mode = tiffMetadata.PhotometricInterpretation != TiffPhotometricInterpretation.PaletteColor ? TiffEncodingMode.Gray : TiffEncodingMode.Rgb; this.Mode = tiffMetadata.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ? TiffEncodingMode.ColorPalette : TiffEncodingMode.Gray;
break; break;
default: default:
this.Mode = TiffEncodingMode.Rgb; this.Mode = TiffEncodingMode.Rgb;
break; break;
} }
} }
}
private void SetBitsPerPixel()
{
switch (this.Mode) switch (this.Mode)
{ {
case TiffEncodingMode.BiColor: case TiffEncodingMode.BiColor:
this.bitsPerPixel = TiffBitsPerPixel.Pixel1; this.BitsPerPixel = TiffBitsPerPixel.Pixel1;
break; break;
case TiffEncodingMode.ColorPalette: case TiffEncodingMode.ColorPalette:
if (this.BitsPerPixel != TiffBitsPerPixel.Pixel8 && this.BitsPerPixel != TiffBitsPerPixel.Pixel4)
{
this.BitsPerPixel = TiffBitsPerPixel.Pixel8;
}
break;
case TiffEncodingMode.Gray: case TiffEncodingMode.Gray:
this.bitsPerPixel = TiffBitsPerPixel.Pixel8; this.BitsPerPixel = TiffBitsPerPixel.Pixel8;
break; break;
case TiffEncodingMode.Rgb: case TiffEncodingMode.Rgb:
this.bitsPerPixel = TiffBitsPerPixel.Pixel24; this.BitsPerPixel = TiffBitsPerPixel.Pixel24;
break; break;
default: default:
this.Mode = TiffEncodingMode.Rgb; this.Mode = TiffEncodingMode.Rgb;
this.bitsPerPixel = TiffBitsPerPixel.Pixel24; this.BitsPerPixel = TiffBitsPerPixel.Pixel24;
break; break;
} }
} }

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

@ -298,25 +298,33 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
switch (encoder.PhotometricInterpretation) switch (encoder.PhotometricInterpretation)
{ {
case TiffPhotometricInterpretation.PaletteColor: case TiffPhotometricInterpretation.PaletteColor:
return new ushort[] { 8 }; if (encoder.BitsPerPixel == TiffBitsPerPixel.Pixel4)
{
return TiffConstants.BitsPerSample4Bit;
}
else
{
return TiffConstants.BitsPerSample8Bit;
}
case TiffPhotometricInterpretation.Rgb: case TiffPhotometricInterpretation.Rgb:
return new ushort[] { 8, 8, 8 }; return TiffConstants.BitsPerSampleRgb8Bit;
case TiffPhotometricInterpretation.WhiteIsZero: case TiffPhotometricInterpretation.WhiteIsZero:
if (encoder.Mode == TiffEncodingMode.BiColor) if (encoder.Mode == TiffEncodingMode.BiColor)
{ {
return new ushort[] { 1 }; return TiffConstants.BitsPerSample1Bit;
} }
return new ushort[] { 8 }; return TiffConstants.BitsPerSample8Bit;
case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.BlackIsZero:
if (encoder.Mode == TiffEncodingMode.BiColor) if (encoder.Mode == TiffEncodingMode.BiColor)
{ {
return new ushort[] { 1 }; return TiffConstants.BitsPerSample1Bit;
} }
return new ushort[] { 8 }; return TiffConstants.BitsPerSample8Bit;
default: default:
return new ushort[] { 8, 8, 8 }; return TiffConstants.BitsPerSampleRgb8Bit;
} }
} }

8
src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs

@ -20,13 +20,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
this.MemoryAllocator = memoryAllocator; this.MemoryAllocator = memoryAllocator;
this.Configuration = configuration; this.Configuration = configuration;
this.EntriesCollector = entriesCollector; this.EntriesCollector = entriesCollector;
this.BytesPerRow = ((image.Width * this.BitsPerPixel) + 7) / 8;
} }
public abstract int BitsPerPixel { get; } public abstract int BitsPerPixel { get; }
public int BytesPerRow { get; } public int BytesPerRow => ((this.Image.Width * this.BitsPerPixel) + 7) / 8;
protected ImageFrame<TPixel> Image { get; } protected ImageFrame<TPixel> Image { get; }
@ -38,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip)
{ {
DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow || compressor.BytesPerRow == 0, "Values must be equals"); DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer");
int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip;
uint[] stripOffsets = new uint[stripsCount]; uint[] stripOffsets = new uint[stripsCount];
@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
stripIndex++; stripIndex++;
} }
DebugGuard.IsTrue(stripIndex == stripsCount, "Values must be equals"); DebugGuard.IsTrue(stripIndex == stripsCount, "stripIndex and stripsCount should match");
this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts);
} }

5
src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs

@ -15,13 +15,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
IQuantizer quantizer, IQuantizer quantizer,
MemoryAllocator memoryAllocator, MemoryAllocator memoryAllocator,
Configuration configuration, Configuration configuration,
TiffEncoderEntriesCollector entriesCollector) TiffEncoderEntriesCollector entriesCollector,
int bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
switch (mode) switch (mode)
{ {
case TiffEncodingMode.ColorPalette: case TiffEncodingMode.ColorPalette:
return new TiffPaletteWriter<TPixel>(image, quantizer, memoryAllocator, configuration, entriesCollector); return new TiffPaletteWriter<TPixel>(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel);
case TiffEncodingMode.Gray: case TiffEncodingMode.Gray:
return new TiffGrayWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector); return new TiffGrayWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
case TiffEncodingMode.BiColor: case TiffEncodingMode.BiColor:

76
src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -16,52 +17,85 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel> internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private const int ColorsPerChannel = 256; private readonly int maxColors;
private const int ColorPaletteSize = ColorsPerChannel * 3; private readonly int colorPaletteSize;
private const int ColorPaletteBytes = ColorPaletteSize * 2; private readonly int colorPaletteBytes;
private readonly IndexedImageFrame<TPixel> quantizedImage;
private readonly IndexedImageFrame<TPixel> quantized;
public TiffPaletteWriter(
public TiffPaletteWriter(ImageFrame<TPixel> image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) ImageFrame<TPixel> image,
IQuantizer quantizer,
MemoryAllocator memoryAllocator,
Configuration configuration,
TiffEncoderEntriesCollector entriesCollector,
int bitsPerPixel)
: base(image, memoryAllocator, configuration, entriesCollector) : base(image, memoryAllocator, configuration, entriesCollector)
{ {
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.Configuration); DebugGuard.NotNull(quantizer, nameof(quantizer));
this.quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); DebugGuard.NotNull(configuration, nameof(configuration));
DebugGuard.NotNull(entriesCollector, nameof(entriesCollector));
DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel));
this.BitsPerPixel = bitsPerPixel;
this.maxColors = this.BitsPerPixel == 4 ? 16 : 256;
this.colorPaletteSize = this.maxColors * 3;
this.colorPaletteBytes = this.colorPaletteSize * 2;
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.Configuration, new QuantizerOptions()
{
MaxColors = this.maxColors
});
this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
this.AddTag(this.quantized); this.AddColorMapTag();
} }
/// <inheritdoc /> /// <inheritdoc />
public override int BitsPerPixel => 8; public override int BitsPerPixel { get; }
/// <inheritdoc /> /// <inheritdoc />
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{ {
Span<byte> pixels = GetStripPixels(((IPixelSource)this.quantized).PixelBuffer, y, height); Span<byte> pixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height);
compressor.CompressStrip(pixels, height); if (this.BitsPerPixel == 4)
{
using IMemoryOwner<byte> rows4bitBuffer = this.MemoryAllocator.Allocate<byte>(pixels.Length / 2);
Span<byte> rows4bit = rows4bitBuffer.GetSpan();
int idx = 0;
for (int i = 0; i < rows4bit.Length; i++)
{
rows4bit[i] = (byte)((pixels[idx] << 4) | (pixels[idx + 1] & 0xF));
idx += 2;
}
compressor.CompressStrip(rows4bit, height);
}
else
{
compressor.CompressStrip(pixels, height);
}
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) => this.quantized?.Dispose(); protected override void Dispose(bool disposing) => this.quantizedImage?.Dispose();
private void AddTag(IndexedImageFrame<TPixel> quantized) private void AddColorMapTag()
{ {
using IMemoryOwner<byte> colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(ColorPaletteBytes); using IMemoryOwner<byte> colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(this.colorPaletteBytes);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColors = this.quantizedImage.Palette.Span;
int quantizedColorBytes = quantizedColors.Length * 3 * 2; int quantizedColorBytes = quantizedColors.Length * 3 * 2;
// In the ColorMap, black is represented by 0,0,0 and white is represented by 65535, 65535, 65535. // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535.
Span<Rgb48> quantizedColorRgb48 = MemoryMarshal.Cast<byte, Rgb48>(colorPalette.Slice(0, quantizedColorBytes)); Span<Rgb48> quantizedColorRgb48 = MemoryMarshal.Cast<byte, Rgb48>(colorPalette.Slice(0, quantizedColorBytes));
PixelOperations<TPixel>.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); PixelOperations<TPixel>.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48);
// It can happen that the quantized colors are less than the expected 256 per channel. // It can happen that the quantized colors are less than the expected maximum per channel.
var diffToMaxColors = ColorsPerChannel - quantizedColors.Length; var diffToMaxColors = this.maxColors - quantizedColors.Length;
// In a TIFF ColorMap, all the Red values come first, followed by the Green values, // In a TIFF ColorMap, all the Red values come first, followed by the Green values,
// then the Blue values. Convert the quantized palette to this format. // then the Blue values. Convert the quantized palette to this format.
var palette = new ushort[ColorPaletteSize]; var palette = new ushort[this.colorPaletteSize];
int paletteIdx = 0; int paletteIdx = 0;
for (int i = 0; i < quantizedColors.Length; i++) for (int i = 0; i < quantizedColors.Length; i++)
{ {

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length]; var buffer = new byte[expectedResult.Length];
new NoneTiffCompression().Decompress(stream, 0, byteCount, buffer); new NoneTiffCompression(default, default, default).Decompress(stream, 0, byteCount, buffer);
Assert.Equal(expectedResult, buffer); Assert.Equal(expectedResult, buffer);
} }

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length]; var buffer = new byte[expectedResult.Length];
using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()); using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default);
decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer); decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer);
Assert.Equal(expectedResult, buffer); Assert.Equal(expectedResult, buffer);

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

@ -1,7 +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 System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -38,7 +37,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.None, TiffBitsPerPixel.Pixel1, TiffCompression.None)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.None, TiffBitsPerPixel.Pixel1, TiffCompression.None)]
[InlineData(TiffEncodingMode.Default, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel24, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.Default, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel24, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel24, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel24, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel8, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel8, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel8, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel1, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel1, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.Default, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel24, TiffCompression.PackBits)] [InlineData(TiffEncodingMode.Default, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel24, TiffCompression.PackBits)]
@ -47,7 +45,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel8, TiffCompression.PackBits)] [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel8, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel1, TiffCompression.PackBits)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel1, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel24, TiffCompression.Lzw)] [InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel24, TiffCompression.Lzw)]
[InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel8, TiffCompression.Lzw)]
[InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel8, TiffCompression.Lzw)] [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel8, TiffCompression.Lzw)]
[InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax, TiffBitsPerPixel.Pixel1, TiffCompression.CcittGroup3Fax)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax, TiffBitsPerPixel.Pixel1, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.ModifiedHuffman, TiffBitsPerPixel.Pixel1, TiffCompression.Ccitt1D)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.ModifiedHuffman, TiffBitsPerPixel.Pixel1, TiffCompression.Ccitt1D)]
@ -73,7 +70,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel1)] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel1)]
[WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)] [WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel24)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel24)]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel24)] [WithFile(Rgb4BitPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel4)]
[WithFile(RgbPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)]
public void TiffEncoder_PreserveBitsPerPixel<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel expectedBitsPerPixel) public void TiffEncoder_PreserveBitsPerPixel<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel expectedBitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -97,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)] [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)]
public void TiffEncoder_CorrectBiMode<TPixel>(TestImageProvider<TPixel> provider, TiffEncoderCompression compression, TiffCompression expectedCompression) public void TiffEncoder_EncodesWithCorrectBiColorModeCompression<TPixel>(TestImageProvider<TPixel> provider, TiffEncoderCompression compression, TiffCompression expectedCompression)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// arrange // arrange
@ -197,37 +196,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f); TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f);
[Theory] [Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeColorPalette_With4Bit_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, usePredictor: true, useExactComparer: false, compareTolerance: 0.001f);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, usePredictor: true, useExactComparer: false, compareTolerance: 0.001f); TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f);
[Theory] [Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f);
[Theory] [Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
@ -335,6 +316,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
var encoder = new TiffEncoder var encoder = new TiffEncoder
{ {
Mode = mode, Mode = mode,
BitsPerPixel = bitsPerPixel,
Compression = compression, Compression = compression,
UseHorizontalPredictor = usePredictor, UseHorizontalPredictor = usePredictor,
MaxStripBytes = maxStripSize MaxStripBytes = maxStripSize

1
tests/ImageSharp.Tests/TestImages.cs

@ -556,6 +556,7 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff";
public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff"; public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff";
public const string RgbPalette = "Tiff/rgb_palette.tiff"; public const string RgbPalette = "Tiff/rgb_palette.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 SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";

3
tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff

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