Browse Source

Change TiffEncoder to use TiffPhotometricInterpretation instead of EncodingMode

pull/1553/head
Brian Popow 5 years ago
parent
commit
d22692ee8f
  1. 4
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  2. 20
      src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
  3. 12
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  4. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  5. 8
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  6. 170
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  7. 29
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  8. 36
      src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs
  9. 3
      src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs
  10. 15
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs
  11. 17
      src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
  12. 14
      tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs
  13. 218
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  14. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

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

@ -344,8 +344,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
while (codeLength > 0)
{
var bitNumber = (int)codeLength;
var bit = (code & (1 << (bitNumber - 1))) != 0;
int bitNumber = (int)codeLength;
bool bit = (code & (1 << (bitNumber - 1))) != 0;
if (bit)
{
BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition);

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

@ -10,6 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
///
/// Not supported by the TiffEncoder.
/// </summary>
WhiteIsZero = 0,
@ -29,42 +31,58 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
PaletteColor = 3,
/// <summary>
/// A transparency mask
/// A transparency mask.
///
/// Not supported by the TiffEncoder.
/// </summary>
TransparencyMask = 4,
/// <summary>
/// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
Separated = 5,
/// <summary>
/// YCbCr (see Section 21 of the TIFF 6.0 specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
YCbCr = 6,
/// <summary>
/// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
CieLab = 8,
/// <summary>
/// ICC L*a*b* (see TIFF Specification, supplement 1).
///
/// Not supported by the TiffEncoder.
/// </summary>
IccLab = 9,
/// <summary>
/// ITU L*a*b* (see RFC2301).
///
/// Not supported by the TiffEncoder.
/// </summary>
ItuLab = 10,
/// <summary>
/// Color Filter Array (see the DNG specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
ColorFilterArray = 32803,
/// <summary>
/// Linear Raw (see the DNG specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
LinearRaw = 34892
}

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

@ -20,24 +20,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Gets the compression type to use.
/// </summary>
TiffCompression Compression { get; }
TiffCompression? Compression { get; }
/// <summary>
/// Gets the compression level 1-9 for the deflate compression mode.
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression"/>.</remarks>
/// </summary>
DeflateCompressionLevel CompressionLevel { get; }
DeflateCompressionLevel? CompressionLevel { get; }
/// <summary>
/// Gets the encoding mode to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
/// If no mode is specified in the options, RGB will be used.
/// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
/// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
/// </summary>
TiffEncodingMode Mode { get; }
TiffPhotometricInterpretation? PhotometricInterpretation { get; }
/// <summary>
/// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
/// </summary>
TiffPredictor HorizontalPredictor { get; }
TiffPredictor? HorizontalPredictor { get; }
/// <summary>
/// Gets the quantizer for creating a color palette image.

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

@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration;
options.Predictor = predictor;
options.PhotometricInterpretation = exifProfile.GetValue(ExifTag.PhotometricInterpretation) != null ?
(TiffPhotometricInterpretation)exifProfile.GetValue(ExifTag.PhotometricInterpretation).Value : TiffPhotometricInterpretation.WhiteIsZero;
(TiffPhotometricInterpretation)exifProfile.GetValue(ExifTag.PhotometricInterpretation).Value : TiffPhotometricInterpretation.BlackIsZero;
options.BitsPerPixel = entries.BitsPerPixel != null ? (int)entries.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = GetBitsPerSample(entries.BitsPerPixel);

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

@ -22,16 +22,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffBitsPerPixel? BitsPerPixel { get; set; }
/// <inheritdoc/>
public TiffCompression Compression { get; set; } = TiffCompression.None;
public TiffCompression? Compression { get; set; }
/// <inheritdoc/>
public DeflateCompressionLevel CompressionLevel { get; set; } = DeflateCompressionLevel.DefaultCompression;
public DeflateCompressionLevel? CompressionLevel { get; set; }
/// <inheritdoc/>
public TiffEncodingMode Mode { get; set; }
public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; }
/// <inheritdoc/>
public TiffPredictor HorizontalPredictor { get; set; }
public TiffPredictor? HorizontalPredictor { get; set; }
/// <inheritdoc/>
public IQuantizer Quantizer { get; set; }

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

@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.Writers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -61,34 +62,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
this.Mode = options.Mode;
this.PhotometricInterpretation = options.PhotometricInterpretation;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.BitsPerPixel = options.BitsPerPixel;
this.HorizontalPredictor = options.HorizontalPredictor;
this.CompressionType = options.Compression != TiffCompression.Invalid ? options.Compression : TiffCompression.None;
this.compressionLevel = options.CompressionLevel;
this.CompressionType = options.Compression;
this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression;
}
/// <summary>
/// Gets the photometric interpretation implementation to use when encoding the image.
/// </summary>
internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; }
internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; }
/// <summary>
/// Gets or sets the compression implementation to use when encoding the image.
/// </summary>
internal TiffCompression CompressionType { get; set; }
internal TiffCompression? CompressionType { get; set; }
/// <summary>
/// Gets the encoding mode to use. RGB, RGB with color palette or gray.
/// If no mode is specified in the options, RGB will be used.
/// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression.
/// </summary>
internal TiffEncodingMode Mode { get; private set; }
/// <summary>
/// Gets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression.
/// </summary>
internal TiffPredictor HorizontalPredictor { get; }
internal TiffPredictor? HorizontalPredictor { get; set; }
/// <summary>
/// Gets the bits per pixel.
@ -111,18 +106,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.configuration = image.GetConfiguration();
TiffPhotometricInterpretation rootFramePhotometricInterpretation = GetRootFramePhotometricInterpretation(image);
TiffPhotometricInterpretation photometricInterpretation = this.Mode == TiffEncodingMode.ColorPalette
TiffPhotometricInterpretation photometricInterpretation = this.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor
? TiffPhotometricInterpretation.PaletteColor
: rootFramePhotometricInterpretation;
TiffBitsPerPixel? rootFrameBitsPerPixel = image.Frames.RootFrame.Metadata.GetTiffMetadata().BitsPerPixel;
ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata;
ExifProfile rootFrameExifProfile = image.Frames.RootFrame.Metadata.ExifProfile;
TiffBitsPerPixel? rootFrameBitsPerPixel = rootFrameMetaData.GetTiffMetadata().BitsPerPixel;
// If the user has not chosen a predictor or compression, set the values from the decoded image, if present.
if (!this.HorizontalPredictor.HasValue && rootFrameExifProfile?.GetValue(ExifTag.Predictor) != null)
{
this.HorizontalPredictor = (TiffPredictor)rootFrameExifProfile?.GetValue(ExifTag.Predictor).Value;
}
if (!this.CompressionType.HasValue && rootFrameExifProfile?.GetValue(ExifTag.Compression) != null)
{
this.CompressionType = (TiffCompression)rootFrameExifProfile?.GetValue(ExifTag.Compression).Value;
}
// TODO: This isn't correct.
// We're overwriting explicit BPP based upon the Mode. It should be the other way around.
// BPP should also be nullable and based upon the current TPixel if not set.
this.SetBitsPerPixel(rootFrameBitsPerPixel, photometricInterpretation);
this.SetMode(photometricInterpretation);
this.SetPhotometricInterpretation();
this.SetBitsPerPixel(rootFrameBitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation);
this.SetPhotometricInterpretation(photometricInterpretation);
using (var writer = new TiffStreamWriter(stream))
{
@ -159,20 +163,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
var entriesCollector = new TiffEncoderEntriesCollector();
// Write the image bytes to the steam.
uint imageDataStart = (uint)writer.Position;
using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
this.CompressionType,
this.CompressionType ?? TiffCompression.None,
writer.BaseStream,
this.memoryAllocator,
image.Width,
(int)this.BitsPerPixel,
this.compressionLevel,
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor : TiffPredictor.None);
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(
this.Mode,
this.PhotometricInterpretation,
image.Frames.RootFrame,
this.quantizer,
this.memoryAllocator,
@ -227,8 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
if (entries.Count == 0)
{
// TODO: Perf. Throwhelper
throw new ArgumentException("There must be at least one entry per IFD.", nameof(entries));
TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD.");
}
uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12));
@ -277,52 +277,73 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return nextIfdMarker;
}
private void SetMode(TiffPhotometricInterpretation photometricInterpretation)
private void SetPhotometricInterpretation(TiffPhotometricInterpretation? photometricInterpretation)
{
// Make sure, that the fax compressions are only used together with the BiColor mode.
// Make sure, that the fax compressions are only used together with the WhiteIsZero.
if (this.CompressionType == TiffCompression.CcittGroup3Fax || this.CompressionType == TiffCompression.Ccitt1D)
{
// Default means the user has not specified a preferred encoding mode.
if (this.Mode == TiffEncodingMode.Default)
// The user has not specified a preferred photometric interpretation.
if (this.PhotometricInterpretation == null)
{
this.Mode = TiffEncodingMode.BiColor;
this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
this.BitsPerPixel = TiffBitsPerPixel.Bit1;
return;
}
if (this.Mode != TiffEncodingMode.BiColor)
if (this.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && this.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero)
{
TiffThrowHelper.ThrowImageFormatException($"The {this.CompressionType} compression and {this.Mode} aren't compatible. Please use {this.CompressionType} only with {TiffEncodingMode.BiColor} or {TiffEncodingMode.Default} mode.");
TiffThrowHelper.ThrowImageFormatException(
$"The {this.CompressionType} compression and {this.PhotometricInterpretation} aren't compatible. Please use {this.CompressionType} only with {TiffPhotometricInterpretation.BlackIsZero} or {TiffPhotometricInterpretation.WhiteIsZero}.");
}
else
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
}
return;
}
switch (this.PhotometricInterpretation)
{
// The currently supported values by the encoder for photometric interpretation:
case TiffPhotometricInterpretation.PaletteColor:
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
break;
default:
this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
break;
}
// Use the bits per pixel to determine the encoding mode.
this.SetModeWithBitsPerPixel(this.BitsPerPixel, photometricInterpretation);
// Use the bits per pixel to determine the photometric interpretation.
this.SetPhotometricInterpretationWithBitsPerPixel(this.BitsPerPixel, photometricInterpretation);
}
private void SetModeWithBitsPerPixel(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
private void SetPhotometricInterpretationWithBitsPerPixel(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation)
{
switch (bitsPerPixel)
{
case TiffBitsPerPixel.Bit1:
this.Mode = TiffEncodingMode.BiColor;
this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero;
break;
case TiffBitsPerPixel.Bit4:
this.Mode = TiffEncodingMode.ColorPalette;
this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor;
break;
case TiffBitsPerPixel.Bit8:
this.Mode = photometricInterpretation == TiffPhotometricInterpretation.PaletteColor
? TiffEncodingMode.ColorPalette
: TiffEncodingMode.Gray;
this.PhotometricInterpretation = photometricInterpretation == TiffPhotometricInterpretation.PaletteColor
? TiffPhotometricInterpretation.PaletteColor
: TiffPhotometricInterpretation.BlackIsZero;
break;
default:
this.Mode = TiffEncodingMode.Rgb;
this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
break;
}
}
private void SetBitsPerPixel(TiffBitsPerPixel? rootFrameBitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
private void SetBitsPerPixel(TiffBitsPerPixel? rootFrameBitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
{
this.BitsPerPixel ??= rootFrameBitsPerPixel;
@ -341,56 +362,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return;
}
switch (this.Mode)
if (this.PhotometricInterpretation == null && inputBitsPerPixel == 8)
{
case TiffEncodingMode.BiColor:
this.BitsPerPixel = TiffBitsPerPixel.Bit1;
break;
case TiffEncodingMode.ColorPalette:
if (this.BitsPerPixel != TiffBitsPerPixel.Bit8 && this.BitsPerPixel != TiffBitsPerPixel.Bit4)
{
this.BitsPerPixel = TiffBitsPerPixel.Bit8;
}
break;
case TiffEncodingMode.Gray:
this.BitsPerPixel = TiffBitsPerPixel.Bit8;
break;
case TiffEncodingMode.Rgb:
this.BitsPerPixel = TiffBitsPerPixel.Bit24;
break;
default:
this.Mode = TiffEncodingMode.Rgb;
this.BitsPerPixel = TiffBitsPerPixel.Bit24;
break;
this.BitsPerPixel = TiffBitsPerPixel.Bit8;
return;
}
}
private void SetPhotometricInterpretation()
{
switch (this.Mode)
switch (this.PhotometricInterpretation)
{
case TiffEncodingMode.ColorPalette:
this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor;
break;
case TiffEncodingMode.BiColor:
if (this.CompressionType == TiffCompression.CcittGroup3Fax || this.CompressionType == TiffCompression.Ccitt1D)
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (this.CompressionType == TiffCompression.Ccitt1D ||
this.CompressionType == TiffCompression.CcittGroup3Fax ||
this.CompressionType == TiffCompression.CcittGroup4Fax)
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
this.BitsPerPixel = TiffBitsPerPixel.Bit1;
}
else
{
this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero;
this.BitsPerPixel = TiffBitsPerPixel.Bit8;
}
break;
case TiffPhotometricInterpretation.PaletteColor:
if (this.BitsPerPixel != TiffBitsPerPixel.Bit8 && this.BitsPerPixel != TiffBitsPerPixel.Bit4)
{
this.BitsPerPixel = TiffBitsPerPixel.Bit8;
}
case TiffEncodingMode.Gray:
this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero;
break;
case TiffPhotometricInterpretation.Rgb:
this.BitsPerPixel = TiffBitsPerPixel.Bit24;
break;
default:
this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
this.BitsPerPixel = TiffBitsPerPixel.Bit24;
break;
}
}
@ -400,7 +406,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
ExifProfile exifProfile = image.Frames.RootFrame.Metadata.ExifProfile;
return exifProfile?.GetValue(ExifTag.PhotometricInterpretation) != null
? (TiffPhotometricInterpretation)exifProfile?.GetValue(ExifTag.PhotometricInterpretation).Value
: TiffPhotometricInterpretation.WhiteIsZero;
: TiffPhotometricInterpretation.BlackIsZero;
}
}
}

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

@ -281,7 +281,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (encoder.HorizontalPredictor == TiffPredictor.Horizontal)
{
if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette)
if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb ||
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ||
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
{
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
@ -320,20 +322,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.Rgb:
return TiffConstants.BitsPerSampleRgb8Bit;
case TiffPhotometricInterpretation.WhiteIsZero:
if (encoder.Mode == TiffEncodingMode.BiColor)
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1)
{
return TiffConstants.BitsPerSample1Bit;
}
return TiffConstants.BitsPerSample8Bit;
case TiffPhotometricInterpretation.BlackIsZero:
if (encoder.Mode == TiffEncodingMode.BiColor)
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1)
{
return TiffConstants.BitsPerSample1Bit;
}
return TiffConstants.BitsPerSample8Bit;
default:
return TiffConstants.BitsPerSampleRgb8Bit;
}
@ -350,7 +355,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
// PackBits is allowed for all modes.
return (ushort)TiffCompression.PackBits;
case TiffCompression.Lzw:
if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette)
if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb ||
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ||
encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
{
return (ushort)TiffCompression.Lzw;
}
@ -358,20 +365,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
case TiffCompression.CcittGroup3Fax:
if (encoder.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.CcittGroup3Fax;
}
break;
return (ushort)TiffCompression.CcittGroup3Fax;
case TiffCompression.Ccitt1D:
if (encoder.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.Ccitt1D;
}
break;
return (ushort)TiffCompression.Ccitt1D;
}
return (ushort)TiffCompression.None;

36
src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs

@ -1,36 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Enum for the different tiff encoding options.
/// </summary>
public enum TiffEncodingMode
{
/// <summary>
/// No mode specified. Will preserve the bits per pixels of the input image.
/// </summary>
Default = 0,
/// <summary>
/// The image will be encoded as RGB, 8 bit per channel.
/// </summary>
Rgb = 1,
/// <summary>
/// The image will be encoded as RGB with a color palette.
/// </summary>
ColorPalette = 2,
/// <summary>
/// The image will be encoded as 8 bit gray.
/// </summary>
Gray = 3,
/// <summary>
/// The image will be written as a white and black image.
/// </summary>
BiColor = 4,
}
}

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

@ -32,5 +32,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupported(string message) => throw new NotSupportedException(message);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowArgumentException(string message) => throw new ArgumentException(message);
}
}

15
src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs

@ -36,16 +36,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
/// <inheritdoc/>
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{
if (this.pixelsAsGray == null)
{
this.pixelsAsGray = this.MemoryAllocator.Allocate<byte>(height * this.Image.Width);
}
this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(height * this.Image.Width);
Span<byte> pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width);
Span<TPixel> pixels = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height);
Span<TPixel> pixelsBlackWhite = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixels, pixelAsGraySpan, pixels.Length);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhite, pixelAsGraySpan, pixelsBlackWhite.Length);
if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D)
{
@ -54,11 +51,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
}
else
{
// Write uncompressed image.
int bytesPerStrip = this.BytesPerRow * height;
if (this.bitStrip == null)
{
this.bitStrip = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip);
}
this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip);
Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip);
rows.Clear();

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
internal static class TiffColorWriterFactory
{
public static TiffBaseColorWriter<TPixel> Create<TPixel>(
TiffEncodingMode mode,
TiffPhotometricInterpretation? photometricInterpretation,
ImageFrame<TPixel> image,
IQuantizer quantizer,
MemoryAllocator memoryAllocator,
@ -19,14 +20,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
int bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
switch (mode)
switch (photometricInterpretation)
{
case TiffEncodingMode.ColorPalette:
case TiffPhotometricInterpretation.PaletteColor:
return new TiffPaletteWriter<TPixel>(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel);
case TiffEncodingMode.Gray:
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (bitsPerPixel == 1)
{
return new TiffBiColorWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
}
return new TiffGrayWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
case TiffEncodingMode.BiColor:
return new TiffBiColorWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
default:
return new TiffRgbWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
}

14
tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs

@ -61,8 +61,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void SystemDrawing()
{
ImageCodecInfo codec = FindCodecForType("image/tiff");
using var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression));
using var parameters = new EncoderParameters(1)
{
Param = {[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression))}
};
using var memoryStream = new MemoryStream();
this.drawing.Save(memoryStream, codec, parameters);
@ -71,15 +73,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tiff")]
public void TiffCore()
{
TiffEncodingMode mode = TiffEncodingMode.Default;
TiffPhotometricInterpretation photometricInterpretation = TiffPhotometricInterpretation.Rgb;
// workaround for 1-bit bug
// Workaround for 1-bit bug
if (this.Compression == TiffCompression.CcittGroup3Fax || this.Compression == TiffCompression.Ccitt1D)
{
mode = TiffEncodingMode.BiColor;
photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
}
var encoder = new TiffEncoder() { Compression = this.Compression, Mode = mode };
var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using var memoryStream = new MemoryStream();
this.core.SaveAsTiff(memoryStream, encoder);
}

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

@ -31,15 +31,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
[InlineData(TiffEncodingMode.Default, TiffBitsPerPixel.Bit24)]
[InlineData(TiffEncodingMode.Rgb, TiffBitsPerPixel.Bit24)]
[InlineData(TiffEncodingMode.ColorPalette, TiffBitsPerPixel.Bit8)]
[InlineData(TiffEncodingMode.Gray, TiffBitsPerPixel.Bit8)]
[InlineData(TiffEncodingMode.BiColor, TiffBitsPerPixel.Bit1)]
public void EncoderOptions_SetEncodingMode_Works(TiffEncodingMode mode, TiffBitsPerPixel expectedBitsPerPixel)
[InlineData(null, TiffBitsPerPixel.Bit24)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)]
[InlineData(TiffPhotometricInterpretation.PaletteColor, TiffBitsPerPixel.Bit8)]
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit8)]
public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel)
{
// arrange
var tiffEncoder = new TiffEncoder { Mode = mode };
var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation };
using Image input = new Image<Rgb24>(10, 10);
using var memStream = new MemoryStream();
@ -81,30 +80,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
[InlineData(TiffEncodingMode.Default, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.Gray, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.BiColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit1, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.ColorPalette, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)]
[InlineData(TiffEncodingMode.Default, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.ColorPalette, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.Gray, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.BiColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit1, TiffCompression.PackBits)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)]
[InlineData(TiffEncodingMode.Gray, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)]
[InlineData(TiffEncodingMode.ColorPalette, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)]
[InlineData(TiffEncodingMode.BiColor, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffEncodingMode.BiColor, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)]
public void EncoderOptions_SetEncodingModeAndCompression_Works(TiffEncodingMode mode, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression)
[InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)]
[InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)]
[InlineData(null, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)]
[InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)]
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)]
[InlineData(null, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)]
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)]
[InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)]
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)]
public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression)
{
// arrange
var tiffEncoder = new TiffEncoder { Mode = mode, Compression = compression };
var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image input = new Image<Rgb24>(10, 10);
using var memStream = new MemoryStream();
@ -115,8 +113,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
memStream.Position = 0;
using var output = Image.Load<Rgba32>(Configuration, memStream);
ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile;
var frameMetaData = TiffFrameMetadata.Parse(exifProfile);
Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
TiffFrameMetadata rootFrameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(expectedBitsPerPixel, rootFrameMetaData.BitsPerPixel);
Assert.Equal(expectedCompression, (TiffCompression)exifProfile.GetValue(ExifTag.Compression).Value);
}
@ -146,6 +144,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
}
[Fact]
public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8()
{
// arrange
var tiffEncoder = new TiffEncoder();
using Image input = new Image<L8>(10, 10);
using var memStream = new MemoryStream();
var expectedBitsPerPixel = TiffBitsPerPixel.Bit8;
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(Configuration, memStream);
ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile;
var frameMetaData = TiffFrameMetadata.Parse(exifProfile);
Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
}
[Theory]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)]
[WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)]
[WithFile(RgbDeflate, PixelTypes.Rgba32, TiffCompression.Deflate)]
[WithFile(RgbPackbits, PixelTypes.Rgba32, TiffCompression.PackBits)]
public void TiffEncoder_PreservesCompression<TPixel>(TestImageProvider<TPixel> provider, TiffCompression expectedCompression)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder();
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(Configuration, memStream);
ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile;
Assert.Equal(expectedCompression, (TiffCompression)exifProfile.GetValue(ExifTag.Compression).Value);
}
[Theory]
[WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffPredictor.None)]
[WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)]
[WithFile(RgbDeflate, PixelTypes.Rgba32, TiffPredictor.None)]
[WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)]
public void TiffEncoder_PreservesPredictor<TPixel>(TestImageProvider<TPixel> provider, TiffPredictor expectedPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder();
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(Configuration, memStream);
ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile;
Assert.Equal(expectedPredictor, (TiffPredictor)exifProfile.GetValue(ExifTag.Predictor).Value);
}
[Theory]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)]
@ -172,15 +236,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
[InlineData(TiffEncodingMode.ColorPalette, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffEncodingMode.ColorPalette, TiffCompression.Ccitt1D)]
[InlineData(TiffEncodingMode.Gray, TiffCompression.Ccitt1D)]
[InlineData(TiffEncodingMode.Rgb, TiffCompression.Ccitt1D)]
public void TiffEncoder_IncompatibilityOptions(TiffEncodingMode mode, TiffCompression compression)
[InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Ccitt1D)]
public void TiffEncoder_IncompatibilityOptions_ThrowsImageFormatException(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
{
// arrange
using var input = new Image<Rgb24>(10, 10);
var encoder = new TiffEncoder() { Mode = mode, Compression = compression };
var encoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using var memStream = new MemoryStream();
// act
@ -190,154 +252,154 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Deflate);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate);
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal);
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithLzwCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Lzw);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw);
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal);
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.PackBits);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits);
[Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero);
[Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Deflate);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate);
[Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Deflate, TiffPredictor.Horizontal);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal);
[Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithLzwCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Lzw);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw);
[Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Lzw, TiffPredictor.Horizontal);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal);
[Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.PackBits);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f);
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f);
[Theory]
[WithFile(Rgb4BitPalette, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_With4Bit_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
//// Note: The magick reference decoder does not support 4 bit tiff's, so we use our TIFF decoder instead.
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f, imageDecoder: new TiffDecoder());
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f, imageDecoder: new TiffDecoder());
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f);
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f);
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.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.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f);
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffPredictor.Horizontal, 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.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f);
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.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> =>
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f);
TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.BiColor);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.BlackIsZero);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.Deflate);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.PackBits);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.CcittGroup3Fax);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.Ccitt1D);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D);
[Theory]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncodingMode.Gray, TiffCompression.PackBits)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncodingMode.ColorPalette, TiffCompression.Lzw)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffCompression.Deflate)]
[WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffEncodingMode.Rgb, TiffCompression.None)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffCompression.None)]
[WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffEncodingMode.Rgb, TiffCompression.None)]
public void TiffEncoder_StripLength<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffCompression compression)
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)]
[WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
[WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
public void TiffEncoder_StripLength<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
where TPixel : unmanaged, IPixel<TPixel> =>
TestStripLength(provider, mode, compression);
TestStripLength(provider, photometricInterpretation, compression);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffEncodingMode.BiColor, TiffCompression.CcittGroup3Fax)]
public void TiffEncoder_StripLength_OutOfBounds<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffCompression compression)
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)]
public void TiffEncoder_StripLength_OutOfBounds<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
where TPixel : unmanaged, IPixel<TPixel> =>
//// CcittGroup3Fax compressed data length can be larger than the original length
Assert.Throws<Xunit.Sdk.TrueException>(() => TestStripLength(provider, mode, compression));
//// CcittGroup3Fax compressed data length can be larger than the original length.
Assert.Throws<Xunit.Sdk.TrueException>(() => TestStripLength(provider, photometricInterpretation, compression));
private static void TestStripLength<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffCompression compression)
private static void TestStripLength<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { Mode = mode, Compression = compression };
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
ExifProfile exifProfileInput = input.Frames.RootFrame.Metadata.ExifProfile;
@ -384,14 +446,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TestTiffEncoderCore(
provider,
inputMeta.BitsPerPixel,
mode,
photometricInterpretation,
inputCompression);
}
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel? bitsPerPixel,
TiffEncodingMode mode,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression = TiffCompression.None,
TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true,
@ -402,7 +464,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder
{
Mode = mode,
PhotometricInterpretation = photometricInterpretation,
BitsPerPixel = bitsPerPixel,
Compression = compression,
HorizontalPredictor = predictor

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

@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel);
// Save to Tiff
var tiffEncoder = new TiffEncoder() { Mode = TiffEncodingMode.Rgb };
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb };
using var ms = new MemoryStream();
image.Save(ms, tiffEncoder);
@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
byte[] encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile;
Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel);
Assert.Equal(TiffCompression.None, (TiffCompression)encodedImageExifProfile.GetValue(ExifTag.Compression).Value);
Assert.Equal(TiffCompression.Lzw, (TiffCompression)encodedImageExifProfile.GetValue(ExifTag.Compression).Value);
Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution);
Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution);

Loading…
Cancel
Save