Browse Source

Add tiff encoding mode enum

pull/1570/head
Brian Popow 6 years ago
parent
commit
a0e406bec8
  1. 9
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  2. 9
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  3. 68
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  4. 31
      src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs
  5. 13
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  6. 27
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  7. 63
      tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs

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

@ -10,20 +10,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
internal interface ITiffEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
TiffBitsPerPixel? BitsPerPixel { get; }
/// <summary>
/// Gets the compression type to use.
/// </summary>
TiffEncoderCompression Compression { get; }
/// <summary>
/// Gets a value indicating whether to use a color palette.
/// Gets the encoding mode to use. RGB, RGB with color palette or gray.
/// </summary>
bool UseColorPalette { get; }
TiffEncodingMode Mode { get; }
/// <summary>
/// Gets the quantizer for creating a color palette image.

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

@ -15,20 +15,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
/// <summary>
/// Gets or sets the number of bits per pixel. 8 bit implies a grayscale image.
/// </summary>
public TiffBitsPerPixel? BitsPerPixel { get; set; }
/// <summary>
/// Gets or sets a value indicating which compression to use.
/// </summary>
public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None;
/// <summary>
/// Gets or sets a value indicating whether to use a color palette.
/// Gets or sets the encoding mode to use. RGB, RGB with a color palette or gray.
/// </summary>
public bool UseColorPalette { get; set; }
public TiffEncodingMode Mode { get; set; }
/// <summary>
/// Gets or sets the quantizer for color images with a palette.

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

@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
this.memoryAllocator = memoryAllocator;
this.CompressionType = options.Compression;
this.UseColorMap = options.UseColorPalette;
this.Mode = options.Mode;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
}
@ -70,9 +70,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private TiffEncoderCompression CompressionType { get; }
/// <summary>
/// Gets a value indicating whether to use a colormap.
/// Gets or sets the encoding mode to use. RGB, RGB with color palette or gray.
/// </summary>
private bool UseColorMap { get; }
private TiffEncodingMode Mode { get; set; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -91,6 +91,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
ImageMetadata metadata = image.Metadata;
TiffMetadata tiffMetadata = metadata.GetTiffMetadata();
this.bitsPerPixel ??= tiffMetadata.BitsPerPixel;
if (this.Mode == TiffEncodingMode.Default)
{
// Preserve input bits per pixel, if no mode was specified.
if (this.bitsPerPixel == TiffBitsPerPixel.Pixel8)
{
this.Mode = TiffEncodingMode.Gray;
}
}
this.SetPhotometricInterpretation();
short bpp = (short)this.bitsPerPixel;
@ -141,17 +150,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
// Write the image bytes to the steam.
var imageDataStart = (uint)writer.Position;
int imageDataBytes;
if (this.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb)
{
imageDataBytes = writer.WriteRgbImageData(image, this.padding);
}
else if (this.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor)
{
imageDataBytes = writer.WritePalettedRgbImageData(image, this.quantizer, this.padding, out colorMap);
}
else
switch (this.PhotometricInterpretation)
{
imageDataBytes = writer.WriteGrayImageData(image, this.padding);
case TiffPhotometricInterpretation.PaletteColor:
imageDataBytes = writer.WritePalettedRgbImageData(image, this.quantizer, this.padding, out colorMap);
break;
case TiffPhotometricInterpretation.BlackIsZero:
imageDataBytes = writer.WriteGrayImageData(image, this.padding);
break;
default:
imageDataBytes = writer.WriteRgbImageData(image, this.padding);
break;
}
// Write info's about the image to the stream.
@ -270,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel)
{
Value = 3
Value = this.GetSamplesPerPixel()
};
var rowsPerStrip = new ExifLong(ExifTagValue.RowsPerStrip)
@ -323,19 +332,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private void SetPhotometricInterpretation()
{
if (this.UseColorMap)
switch (this.Mode)
{
this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor;
return;
case TiffEncodingMode.ColorPalette:
this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor;
break;
case TiffEncodingMode.Gray:
this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero;
break;
default:
this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
break;
}
}
if (this.bitsPerPixel == TiffBitsPerPixel.Pixel8)
{
this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero;
}
else
private uint GetSamplesPerPixel()
{
switch (this.PhotometricInterpretation)
{
this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
case TiffPhotometricInterpretation.PaletteColor:
case TiffPhotometricInterpretation.Rgb:
return 3;
case TiffPhotometricInterpretation.BlackIsZero:
return 1;
default:
return 3;
}
}
@ -344,6 +365,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (this.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.PaletteColor:
return new ushort[] { 8 };
case TiffPhotometricInterpretation.Rgb:
return new ushort[] { 8, 8, 8 };
case TiffPhotometricInterpretation.BlackIsZero:

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

@ -0,0 +1,31 @@
// 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,
}
}

13
src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs

@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <returns>The number of bytes written</returns>
/// <returns>The number of bytes written.</returns>
public int WriteRgbImageData<TPixel>(Image<TPixel> image, int padding)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -151,6 +151,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bytesWritten;
}
/// <summary>
/// Writes the image data as indices into a color map to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantizer">The quantizer to use.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <param name="colorMap">The color map.</param>
/// <returns>The number of bytes written.</returns>
public int WritePalettedRgbImageData<TPixel>(Image<TPixel> image, IQuantizer quantizer, int padding, out IExifValue colorMap)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -214,7 +223,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <returns>The number of bytes written</returns>
/// <returns>The number of bytes written.</returns>
public int WriteGrayImageData<TPixel>(Image<TPixel> image, int padding)
where TPixel : unmanaged, IPixel<TPixel>
{

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

@ -5,13 +5,17 @@ using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff")]
public class TiffEncoderTests
{
private static TiffDecoder referenceDecoder = new TiffDecoder();
public static readonly TheoryData<string, TiffBitsPerPixel> TiffBitsPerPixelFiles =
new TheoryData<string, TiffBitsPerPixel>
{
@ -41,36 +45,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_Works<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel24)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, bitsPerPixel);
public void TiffEncoder_EncodeRgb_Works<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel24, TiffEncodingMode mode = TiffEncodingMode.Rgb)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, bitsPerPixel, mode);
[Theory]
[WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_Works<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel8)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, bitsPerPixel);
public void TiffEncoder_EncodeGray_Works<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel8, TiffEncodingMode mode = TiffEncodingMode.Gray)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, bitsPerPixel, mode);
[Theory]
[WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel8, bool useColorPalette = true)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, bitsPerPixel, useColorPalette);
public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixel> provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel24, TiffEncodingMode mode = TiffEncodingMode.ColorPalette)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, bitsPerPixel, mode);
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel bitsPerPixel,
bool useColorPalette = false,
TiffEncodingMode mode,
TiffEncoderCompression compression = TiffEncoderCompression.None,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder { BitsPerPixel = bitsPerPixel, Compression = compression };
var encoder = new TiffEncoder { Mode = mode, Compression = compression };
using var memStream = new MemoryStream();
image.Save(memStream, encoder);
memStream.Position = 0;
using var encodedImage = (Image<TPixel>)Image.Load(memStream);
TiffTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "tiff", bitsPerPixel, encoder, useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), referenceDecoder: referenceDecoder);
}
}
}

63
tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs

@ -1,63 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using ImageMagick;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
public class TiffTestUtils
{
public static void CompareWithReferenceDecoder<TPixel>(
TestImageProvider<TPixel> provider,
Image<TPixel> image,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider);
if (path == null)
{
throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
}
var testFile = TestFile.Create(path);
Image<Rgba32> magickImage = DecodeWithMagick<Rgba32>(Configuration.Default, new FileInfo(testFile.FullPath));
if (useExactComparer)
{
ImageComparer.Exact.VerifySimilarity(magickImage, image);
}
else
{
ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image);
}
}
public static Image<TPixel> DecodeWithMagick<TPixel>(Configuration configuration, FileInfo fileInfo)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
using var magickImage = new MagickImage(fileInfo);
magickImage.AutoOrient();
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
Assert.True(result.TryGetSinglePixelSpan(out Span<TPixel> resultPixels));
using IUnsafePixelCollection<ushort> pixels = magickImage.GetPixelsUnsafe();
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
configuration,
data,
resultPixels,
resultPixels.Length);
return result;
}
}
}
Loading…
Cancel
Save