From 80fb83d76060d3c926248408bfc6e3c02245faa7 Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Tue, 16 May 2017 11:24:28 +0100 Subject: [PATCH] Add support for pallete color images --- .../PaletteTiffColor.cs | 73 +++++++++ .../TiffColorType.cs | 7 +- .../Formats/Tiff/TiffDecoderCore.cs | 154 +++++++++++------- .../PaletteTiffColorTests.cs | 144 ++++++++++++++++ .../Formats/Tiff/TiffDecoderImageTests.cs | 118 +++++++++++++- 5 files changed, 433 insertions(+), 63 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs new file mode 100644 index 0000000000..4f4536331e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Tiff +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + using ImageSharp; + using ImageSharp.PixelFormats; + + /// + /// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths). + /// + internal static class PaletteTiffColor + { + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The pixel format. + /// The buffer to read image data from. + /// The number of bits per sample for each pixel. + /// The RGB color lookup table to use for decoding the image. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(byte[] data, uint[] bitsPerSample, uint[] colorMap, PixelAccessor pixels, int left, int top, int width, int height) + where TPixel : struct, IPixel + { + int colorCount = (int)Math.Pow(2, bitsPerSample[0]); + TPixel[] palette = GeneratePalette(colorMap, colorCount); + + BitReader bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + int index = bitReader.ReadBits(bitsPerSample[0]); + pixels[x, y] = palette[index]; + } + + bitReader.NextRow(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TPixel[] GeneratePalette(uint[] colorMap, int colorCount) + where TPixel : struct, IPixel + { + TPixel[] palette = new TPixel[colorCount]; + + int rOffset = 0; + int gOffset = colorCount; + int bOffset = colorCount * 2; + + for (int i = 0; i < palette.Length; i++) + { + float r = colorMap[rOffset + i] / 65535F; + float g = colorMap[gOffset + i] / 65535F; + float b = colorMap[bOffset + i] / 65535F; + palette[i].PackFromVector4(new Vector4(r, g, b, 1.0f)); + } + + return palette; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index f4a15aec26..c63d6febd1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -48,6 +48,11 @@ namespace ImageSharp.Formats.Tiff /// /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images. /// - WhiteIsZero8 + WhiteIsZero8, + + /// + /// Palette-color. + /// + PaletteColor } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 9a25fa9b99..5ebce1f046 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -57,6 +57,11 @@ namespace ImageSharp.Formats /// public uint[] BitsPerSample { get; set; } + /// + /// Gets or sets the lookup table for RGB palette colored images. + /// + public uint[] ColorMap { get; set; } + /// /// Gets or sets the photometric interpretation implementation to use when decoding the image. /// @@ -287,93 +292,128 @@ namespace ImageSharp.Formats } } + if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry)) + { + this.BitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry); + } + else + { + if (photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero || + photometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) + { + this.BitsPerSample = new[] { 1u }; + } + else + { + throw new ImageFormatException("The TIFF BitsPerSample entry is missing."); + } + } + switch (photometricInterpretation) { case TiffPhotometricInterpretation.WhiteIsZero: { - if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry)) + if (this.BitsPerSample.Length == 1) { - this.BitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry); - - if (this.BitsPerSample.Length == 1) + switch (this.BitsPerSample[0]) { - switch (this.BitsPerSample[0]) - { - case 8: - { - this.ColorType = TiffColorType.WhiteIsZero8; - break; - } - - case 4: - { - this.ColorType = TiffColorType.WhiteIsZero4; - break; - } + case 8: + { + this.ColorType = TiffColorType.WhiteIsZero8; + break; + } + + case 4: + { + this.ColorType = TiffColorType.WhiteIsZero4; + break; + } + + case 1: + { + this.ColorType = TiffColorType.WhiteIsZero1; + break; + } + + default: + { + this.ColorType = TiffColorType.WhiteIsZero; + break; + } + } + } + else + { + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + } - case 1: - { - this.ColorType = TiffColorType.WhiteIsZero1; - break; - } + break; + } - default: - { - this.ColorType = TiffColorType.WhiteIsZero; - break; - } - } + case TiffPhotometricInterpretation.BlackIsZero: + { + if (this.BitsPerSample.Length == 1) + { + switch (this.BitsPerSample[0]) + { + case 8: + { + this.ColorType = TiffColorType.BlackIsZero8; + break; + } + + case 4: + { + this.ColorType = TiffColorType.BlackIsZero4; + break; + } + + case 1: + { + this.ColorType = TiffColorType.BlackIsZero1; + break; + } + + default: + { + this.ColorType = TiffColorType.BlackIsZero; + break; + } } } else { - this.ColorType = TiffColorType.WhiteIsZero1; - this.BitsPerSample = new[] { 1u }; + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); } break; } - case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.PaletteColor: { - if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry)) + if (ifd.TryGetIfdEntry(TiffTags.ColorMap, out TiffIfdEntry colorMapEntry)) { - this.BitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry); + this.ColorMap = this.ReadUnsignedIntegerArray(ref colorMapEntry); if (this.BitsPerSample.Length == 1) { switch (this.BitsPerSample[0]) { - case 8: - { - this.ColorType = TiffColorType.BlackIsZero8; - break; - } - - case 4: - { - this.ColorType = TiffColorType.BlackIsZero4; - break; - } - - case 1: - { - this.ColorType = TiffColorType.BlackIsZero1; - break; - } - default: { - this.ColorType = TiffColorType.BlackIsZero; + this.ColorType = TiffColorType.PaletteColor; break; } } } + else + { + throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported."); + } } else { - this.ColorType = TiffColorType.BlackIsZero1; - this.BitsPerSample = new[] { 1u }; + throw new ImageFormatException("The TIFF ColorMap entry is missing for a pallete color image."); } break; @@ -398,7 +438,8 @@ namespace ImageSharp.Formats bitsPerPixel += this.BitsPerSample[i]; } - int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8; + int sampleMultiplier = this.ColorType == TiffColorType.PaletteColor ? 3 : 1; + int bytesPerRow = ((width * (int)bitsPerPixel * sampleMultiplier) + 7) / 8; return bytesPerRow * height; } @@ -464,6 +505,9 @@ namespace ImageSharp.Formats case TiffColorType.BlackIsZero8: BlackIsZero8TiffColor.Decode(data, pixels, left, top, width, height); break; + case TiffColorType.PaletteColor: + PaletteTiffColor.Decode(data, this.BitsPerSample, this.ColorMap, pixels, left, top, width, height); + break; default: throw new InvalidOperationException(); } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs new file mode 100644 index 0000000000..8a77c67f09 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Collections.Generic; + using Xunit; + + using ImageSharp.Formats.Tiff; + using ImageSharp.PixelFormats; + + public class PaletteTiffColorTests : PhotometricInterpretationTestBase + { + public static uint[][] Palette4_ColorPalette { get => GeneratePalette(16); } + + public static uint[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); } + + private static byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23, + 0x4A, 0xD2, + 0x12, 0x34, + 0xAB, 0xEF }; + + private static Rgba32[][] Palette4_Result4x4 = GenerateResult(Palette4_ColorPalette, + new[] { new[] { 0x00, 0x01, 0x02, 0x03 }, + new[] { 0x04, 0x0A, 0x0D, 0x02 }, + new[] { 0x01, 0x02, 0x03, 0x04 }, + new[] { 0x0A, 0x0B, 0x0E, 0x0F }}); + + private static byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20, + 0x4A, 0xD0, + 0x12, 0x30, + 0xAB, 0xE0 }; + + private static Rgba32[][] Palette4_Result3x4 = GenerateResult(Palette4_ColorPalette, + new[] { new[] { 0x00, 0x01, 0x02 }, + new[] { 0x04, 0x0A, 0x0D }, + new[] { 0x01, 0x02, 0x03 }, + new[] { 0x0A, 0x0B, 0x0E }}); + + public static IEnumerable Palette4_Data + { + get + { + yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 0, 4, 4, Palette4_Result4x4 }; + yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 0, 4, 4, Offset(Palette4_Result4x4, 0, 0, 6, 6) }; + yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 1, 0, 4, 4, Offset(Palette4_Result4x4, 1, 0, 6, 6) }; + yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 1, 4, 4, Offset(Palette4_Result4x4, 0, 1, 6, 6) }; + yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 1, 1, 4, 4, Offset(Palette4_Result4x4, 1, 1, 6, 6) }; + + yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 0, 3, 4, Palette4_Result3x4 }; + yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 0, 3, 4, Offset(Palette4_Result3x4, 0, 0, 6, 6) }; + yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 1, 0, 3, 4, Offset(Palette4_Result3x4, 1, 0, 6, 6) }; + yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 1, 3, 4, Offset(Palette4_Result3x4, 0, 1, 6, 6) }; + yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 1, 1, 3, 4, Offset(Palette4_Result3x4, 1, 1, 6, 6) }; + + } + } + + public static uint[][] Palette8_ColorPalette { get => GeneratePalette(256); } + + public static uint[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); } + + private static byte[] Palette8_Bytes4x4 = new byte[] { 000, 001, 002, 003, + 100, 110, 120, 130, + 000, 255, 128, 255, + 050, 100, 150, 200 }; + + private static Rgba32[][] Palette8_Result4x4 = GenerateResult(Palette8_ColorPalette, + new[] { new[] { 000, 001, 002, 003 }, + new[] { 100, 110, 120, 130 }, + new[] { 000, 255, 128, 255 }, + new[] { 050, 100, 150, 200 }}); + + public static IEnumerable Palette8_Data + { + get + { + yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 0, 4, 4, Palette8_Result4x4 }; + yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 0, 4, 4, Offset(Palette8_Result4x4, 0, 0, 6, 6) }; + yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 1, 0, 4, 4, Offset(Palette8_Result4x4, 1, 0, 6, 6) }; + yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 1, 4, 4, Offset(Palette8_Result4x4, 0, 1, 6, 6) }; + yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 1, 1, 4, 4, Offset(Palette8_Result4x4, 1, 1, 6, 6) }; + } + } + + [Theory] + [MemberData(nameof(Palette4_Data))] + [MemberData(nameof(Palette8_Data))] + public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, uint[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + PaletteTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, colorMap, pixels, left, top, width, height); + }); + } + + private static uint[][] GeneratePalette(int count) + { + uint[][] palette = new uint[count][]; + + for (uint i = 0; i < count; i++) + { + palette[i] = new uint[] { (i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u }; + } + + return palette; + } + + private static uint[] GenerateColorMap(uint[][] colorPalette) + { + int colorCount = colorPalette.Length; + uint[] colorMap = new uint[colorCount * 3]; + + for (int i = 0; i < colorCount; i++) + { + colorMap[colorCount * 0 + i] = colorPalette[i][0]; + colorMap[colorCount * 1 + i] = colorPalette[i][1]; + colorMap[colorCount * 2 + i] = colorPalette[i][2]; + } + + return colorMap; + } + + private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup) + { + Rgba32[][] result = new Rgba32[pixelLookup.Length][]; + + for (int y = 0; y < pixelLookup.Length; y++) + { + result[y] = new Rgba32[pixelLookup[y].Length]; + + for (int x = 0; x < pixelLookup[y].Length; x++) + { + uint[] sourceColor = colorPalette[pixelLookup[y][x]]; + result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs index b1760f0838..39c5dc8c4a 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs @@ -192,6 +192,14 @@ namespace ImageSharp.Tests [InlineData(true, TiffPhotometricInterpretation.BlackIsZero, new[] { 4 }, TiffColorType.BlackIsZero4)] [InlineData(false, TiffPhotometricInterpretation.BlackIsZero, new[] { 1 }, TiffColorType.BlackIsZero1)] [InlineData(true, TiffPhotometricInterpretation.BlackIsZero, new[] { 1 }, TiffColorType.BlackIsZero1)] + [InlineData(false, TiffPhotometricInterpretation.PaletteColor, new[] { 3 }, TiffColorType.PaletteColor)] + [InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 3 }, TiffColorType.PaletteColor)] + [InlineData(false, TiffPhotometricInterpretation.PaletteColor, new[] { 8 }, TiffColorType.PaletteColor)] + [InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 8 }, TiffColorType.PaletteColor)] + [InlineData(false, TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, TiffColorType.PaletteColor)] + [InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, TiffColorType.PaletteColor)] + [InlineData(false, TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, TiffColorType.PaletteColor)] + [InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, TiffColorType.PaletteColor)] public void ReadImageFormat_DeterminesCorrectColorImplementation(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType) { Stream stream = CreateTiffGenIfd() @@ -265,7 +273,6 @@ namespace ImageSharp.Tests [InlineData(false, TiffPhotometricInterpretation.IccLab)] [InlineData(false, TiffPhotometricInterpretation.ItuLab)] [InlineData(false, TiffPhotometricInterpretation.LinearRaw)] - [InlineData(false, TiffPhotometricInterpretation.PaletteColor)] [InlineData(false, TiffPhotometricInterpretation.Rgb)] [InlineData(false, TiffPhotometricInterpretation.Separated)] [InlineData(false, TiffPhotometricInterpretation.TransparencyMask)] @@ -276,7 +283,6 @@ namespace ImageSharp.Tests [InlineData(true, TiffPhotometricInterpretation.IccLab)] [InlineData(true, TiffPhotometricInterpretation.ItuLab)] [InlineData(true, TiffPhotometricInterpretation.LinearRaw)] - [InlineData(true, TiffPhotometricInterpretation.PaletteColor)] [InlineData(true, TiffPhotometricInterpretation.Rgb)] [InlineData(true, TiffPhotometricInterpretation.Separated)] [InlineData(true, TiffPhotometricInterpretation.TransparencyMask)] @@ -303,10 +309,10 @@ namespace ImageSharp.Tests [InlineData(true, new[] { 4u })] [InlineData(false, new[] { 1u })] [InlineData(true, new[] { 1u })] - [InlineData(false, new[] { 1u, 2u, 3u })] - [InlineData(true, new[] { 1u, 2u, 3u })] - [InlineData(false, new[] { 8u, 8u, 8u })] - [InlineData(true, new[] { 8u, 8u, 8u })] + // [InlineData(false, new[] { 1u, 2u, 3u })] + // [InlineData(true, new[] { 1u, 2u, 3u })] + // [InlineData(false, new[] { 8u, 8u, 8u })] + // [InlineData(true, new[] { 8u, 8u, 8u })] public void ReadImageFormat_ReadsBitsPerSample(bool isLittleEndian, uint[] bitsPerSample) { Stream stream = CreateTiffGenIfd() @@ -339,6 +345,84 @@ namespace ImageSharp.Tests Assert.Equal(new[] { 1u }, decoder.BitsPerSample); } + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadImageFormat_ThrowsExceptionForMissingBitsPerSample(bool isLittleEndian) + { + Stream stream = CreateTiffGenIfd() + .WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor)) + .WithoutEntry(TiffTags.BitsPerSample) + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); + TiffIfd ifd = decoder.ReadIfd(0); + + var e = Assert.Throws(() => decoder.ReadImageFormat(ifd)); + + Assert.Equal("The TIFF BitsPerSample entry is missing.", e.Message); + } + + [Theory] + [InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new int[] { })] + [InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new int[] { })] + [InlineData(false, TiffPhotometricInterpretation.BlackIsZero, new int[] { })] + [InlineData(true, TiffPhotometricInterpretation.BlackIsZero, new int[] { })] + [InlineData(false, TiffPhotometricInterpretation.PaletteColor, new int[] { })] + [InlineData(true, TiffPhotometricInterpretation.PaletteColor, new int[] { })] + [InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 8, 8 })] + [InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new[] { 8, 8 })] + [InlineData(false, TiffPhotometricInterpretation.BlackIsZero, new[] { 8, 8 })] + [InlineData(true, TiffPhotometricInterpretation.BlackIsZero, new[] { 8, 8 })] + [InlineData(false, TiffPhotometricInterpretation.PaletteColor, new[] { 8, 8 })] + [InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 8, 8 })] + public void ReadImageFormat_ThrowsExceptionForUnsupportedNumberOfSamples(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample) + { + Stream stream = CreateTiffGenIfd() + .WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation)) + .WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample)) + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); + TiffIfd ifd = decoder.ReadIfd(0); + + var e = Assert.Throws(() => decoder.ReadImageFormat(ifd)); + + Assert.Equal("The number of samples in the TIFF BitsPerSample entry is not supported.", e.Message); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadImageFormat_ReadsColorMap(bool isLittleEndian) + { + Stream stream = CreateTiffGenIfd() + .WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor)) + .WithEntry(TiffGenEntry.Integer(TiffTags.ColorMap, TiffType.Short, new int[] { 10, 20, 30, 40, 50, 60 })) + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); + TiffIfd ifd = decoder.ReadIfd(0); + decoder.ReadImageFormat(ifd); + + Assert.Equal(new uint[] { 10, 20, 30, 40, 50, 60 }, decoder.ColorMap); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void ReadImageFormat_ThrowsExceptionForMissingColorMap(bool isLittleEndian) + { + Stream stream = CreateTiffGenIfd() + .WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor)) + .WithoutEntry(TiffTags.ColorMap) + .ToStream(isLittleEndian); + + TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null); + TiffIfd ifd = decoder.ReadIfd(0); + + var e = Assert.Throws(() => decoder.ReadImageFormat(ifd)); + + Assert.Equal("The TIFF ColorMap entry is missing for a pallete color image.", e.Message); + } + [Theory] [InlineData(new uint[] { 1 }, 160, 80, 20 * 80)] [InlineData(new uint[] { 1 }, 153, 80, 20 * 80)] @@ -349,6 +433,25 @@ namespace ImageSharp.Tests public void CalculateImageBufferSize_ReturnsCorrectSize(uint[] bitsPerSample, int width, int height, int expectedResult) { TiffDecoderCore decoder = new TiffDecoderCore(null, null); + decoder.ColorType = TiffColorType.WhiteIsZero; + decoder.BitsPerSample = bitsPerSample; + + int bufferSize = decoder.CalculateImageBufferSize(width, height); + + Assert.Equal(expectedResult, bufferSize); + } + + [Theory] + [InlineData(new uint[] { 1 }, 160, 80, 60 * 80)] + [InlineData(new uint[] { 1 }, 153, 80, 58 * 80)] + [InlineData(new uint[] { 3 }, 100, 80, 113 * 80)] + [InlineData(new uint[] { 4 }, 100, 80, 150 * 80)] + [InlineData(new uint[] { 4 }, 99, 80, 149 * 80)] + [InlineData(new uint[] { 8 }, 100, 80, 300 * 80)] + public void CalculateImageBufferSize_ReturnsCorrectSize_ForPaletteColor(uint[] bitsPerSample, int width, int height, int expectedResult) + { + TiffDecoderCore decoder = new TiffDecoderCore(null, null); + decoder.ColorType = TiffColorType.PaletteColor; decoder.BitsPerSample = bitsPerSample; int bufferSize = decoder.CalculateImageBufferSize(width, height); @@ -369,7 +472,8 @@ namespace ImageSharp.Tests TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, 2), TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.WhiteIsZero), TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8 }), - TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.None) + TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.None), + TiffGenEntry.Integer(TiffTags.ColorMap, TiffType.Short, new int[256]) } }; }