Browse Source

Fix issue with encoding paletted tiff when quantized palette is smaller than 256

pull/1570/head
Brian Popow 5 years ago
parent
commit
edcdc08efd
  1. 4
      src/ImageSharp/Formats/Tiff/README.md
  2. 6
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  3. 68
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  4. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

4
src/ImageSharp/Formats/Tiff/README.md

@ -48,7 +48,7 @@
|Lzw | | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
|Old Jpeg | | | We should not even try to support this |
|Jpeg (Technote 2) | | | |
|Deflate (Technote 2) | | Y | |
|Deflate (Technote 2) | (Y) | Y | Based on PNG Deflate. Deflate encoding only for RGB now, should we allow this for the gray and palette too? |
|Old Deflate (Technote 2) | | Y | |
### Photometric Interpretation Formats
@ -59,7 +59,7 @@
|BlackIsZero | | Y | General + 1/4/8-bit optimised implementations |
|Rgb (Chunky) | | Y | General + Rgb888 optimised implementation |
|Rgb (Planar) | Y | Y | General implementation only |
|PaletteColor | | Y | General implementation only |
|PaletteColor | Y | Y | General implementation only |
|TransparencyMask | | | |
|Separated (TIFF Extension) | | | |
|YCbCr (TIFF Extension) | | | |

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

@ -153,10 +153,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (this.Mode)
{
case TiffEncodingMode.ColorPalette:
imageDataBytes = writer.WritePalettedRgbImageData(image, this.quantizer, this.padding, out colorMap);
imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, out colorMap);
break;
case TiffEncodingMode.Gray:
imageDataBytes = writer.WriteGrayImageData(image, this.padding);
imageDataBytes = writer.WriteGray(image, this.padding);
break;
default:
imageDataBytes = writer.WriteRgbImageData(image, this.padding, this.CompressionType);
@ -350,9 +350,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
switch (this.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.PaletteColor:
case TiffPhotometricInterpretation.Rgb:
return 3;
case TiffPhotometricInterpretation.PaletteColor:
case TiffPhotometricInterpretation.BlackIsZero:
return 1;
default:

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

@ -143,30 +143,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
using IManagedByteBuffer row = this.AllocateRow(image.Width, 3, padding);
Span<byte> rowSpan = row.GetSpan();
int bytesWritten = 0;
if (compression == TiffEncoderCompression.Deflate)
{
using var memoryStream = new MemoryStream();
// TODO: move zlib compression from png to a common place?
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length);
deflateStream.Write(rowSpan);
}
deflateStream.Flush();
byte[] buffer = memoryStream.ToArray();
this.output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
return this.WriteDeflateCompressedRgb(image, rowSpan);
}
// No compression.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
@ -178,6 +161,37 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bytesWritten;
}
/// <summary>
/// Writes the image data as RGB compressed with zlib to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A Span for a pixel row.</param>
/// <returns>The number of bytes written.</returns>
private int WriteDeflateCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
// TODO: move zlib compression from png to a common place?
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length);
deflateStream.Write(rowSpan);
}
deflateStream.Flush();
byte[] buffer = memoryStream.ToArray();
this.output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as indices into a color map to the stream.
/// </summary>
@ -187,13 +201,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <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)
public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, int padding, out IExifValue colorMap)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorPaletteSize = 256 * 3 * 2;
using IManagedByteBuffer row = this.AllocateRow(image.Width, 1, padding);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(256 * 2 * 3);
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(colorPaletteSize);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
@ -203,20 +218,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Span<Rgb48> quantizedColorRgb48 = MemoryMarshal.Cast<byte, Rgb48>(colorPalette.Slice(0, quantizedColorBytes));
PixelOperations<TPixel>.Instance.ToRgb48(this.configuration, quantizedColors, quantizedColorRgb48);
// It can happen that the quantized colors are less than the expected 256.
var diffToMaxColors = 256 - quantizedColors.Length;
// 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.
var palette = new ushort[quantizedColorBytes];
var palette = new ushort[colorPaletteSize];
int paletteIdx = 0;
for (int i = 0; i < quantizedColors.Length; i++)
{
palette[paletteIdx++] = quantizedColorRgb48[i].R;
}
paletteIdx += diffToMaxColors;
for (int i = 0; i < quantizedColors.Length; i++)
{
palette[paletteIdx++] = quantizedColorRgb48[i].G;
}
paletteIdx += diffToMaxColors;
for (int i = 0; i < quantizedColors.Length; i++)
{
palette[paletteIdx++] = quantizedColorRgb48[i].B;
@ -251,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <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>
public int WriteGrayImageData<TPixel>(Image<TPixel> image, int padding)
public int WriteGray<TPixel>(Image<TPixel> image, int padding)
where TPixel : unmanaged, IPixel<TPixel>
{
using IManagedByteBuffer row = this.AllocateRow(image.Width, 1, padding);

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

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray);
[Theory]
[WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
[WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette);

Loading…
Cancel
Save