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