diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
index da7d6af265..be9e14df40 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
@@ -10,6 +10,16 @@ namespace ImageSharp.Formats.Tiff
///
internal enum TiffColorType
{
+ ///
+ /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for bilevel images.
+ ///
+ WhiteIsZero1,
+
+ ///
+ /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 4-bit images.
+ ///
+ WhiteIsZero4,
+
///
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images.
///
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
new file mode 100644
index 0000000000..5e486c7fef
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.Runtime.CompilerServices;
+ using ImageSharp;
+
+ ///
+ /// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images).
+ ///
+ internal static class WhiteIsZero1TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// 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, PixelAccessor pixels, int left, int top, int width, int height)
+ where TColor : struct, IPixel
+ {
+ TColor color = default(TColor);
+
+ uint offset = 0;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x += 8)
+ {
+ byte b = data[offset++];
+ int maxShift = Math.Min(left + width - x, 8);
+
+ for (int shift = 0; shift < maxShift; shift++)
+ {
+ int bit = (b >> (7 - shift)) & 1;
+ byte intensity = (bit == 1) ? (byte)0 : (byte)255;
+ color.PackFromBytes(intensity, intensity, intensity, 255);
+ pixels[x + shift, y] = color;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
new file mode 100644
index 0000000000..98f74dca0e
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
@@ -0,0 +1,61 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System.Runtime.CompilerServices;
+ using ImageSharp;
+
+ ///
+ /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images).
+ ///
+ internal static class WhiteIsZero4TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// 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, PixelAccessor pixels, int left, int top, int width, int height)
+ where TColor : struct, IPixel
+ {
+ TColor color = default(TColor);
+
+ uint offset = 0;
+ bool isOddWidth = (width & 1) == 1;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width - 1; x += 2)
+ {
+ byte byteData = data[offset++];
+
+ byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
+ color.PackFromBytes(intensity1, intensity1, intensity1, 255);
+ pixels[x, y] = color;
+
+ byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
+ color.PackFromBytes(intensity2, intensity2, intensity2, 255);
+ pixels[x + 1, y] = color;
+ }
+
+ if (isOddWidth)
+ {
+ byte byteData = data[offset++];
+
+ byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
+ color.PackFromBytes(intensity1, intensity1, intensity1, 255);
+ pixels[left + width - 1, y] = color;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
index 97d7edaca6..8ddafd9833 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
@@ -9,7 +9,7 @@ namespace ImageSharp.Formats.Tiff
using ImageSharp;
///
- /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
+ /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
///
internal static class WhiteIsZero8TiffColor
{
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index d9086c95a7..cc2d0f8b7d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -299,18 +299,38 @@ namespace ImageSharp.Formats
{
uint[] bitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry);
- if (bitsPerSample.Length == 1 && bitsPerSample[0] == 8)
+ if (bitsPerSample.Length == 1)
{
- this.ColorType = TiffColorType.WhiteIsZero8;
- }
- else
- {
- throw new NotSupportedException("The specified TIFF bit-depth is not supported.");
+ switch (bitsPerSample[0])
+ {
+ case 8:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero8;
+ break;
+ }
+
+ case 4:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero4;
+ break;
+ }
+
+ case 1:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero1;
+ break;
+ }
+
+ default:
+ {
+ throw new NotSupportedException("The specified TIFF bit-depth is not supported.");
+ }
+ }
}
}
else
{
- throw new NotSupportedException("TIFF bilevel images are not supported.");
+ this.ColorType = TiffColorType.WhiteIsZero1;
}
break;
@@ -331,6 +351,10 @@ namespace ImageSharp.Formats
{
switch (this.ColorType)
{
+ case TiffColorType.WhiteIsZero1:
+ return ((width + 7) / 8) * height;
+ case TiffColorType.WhiteIsZero4:
+ return ((width + 1) / 2) * height;
case TiffColorType.WhiteIsZero8:
return width * height;
default:
@@ -373,6 +397,12 @@ namespace ImageSharp.Formats
{
switch (this.ColorType)
{
+ case TiffColorType.WhiteIsZero1:
+ WhiteIsZero1TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.WhiteIsZero4:
+ WhiteIsZero4TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
case TiffColorType.WhiteIsZero8:
WhiteIsZero8TiffColor.Decode(data, pixels, left, top, width, height);
break;
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
index 7fdb121772..3c245855d3 100644
--- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
@@ -50,7 +50,8 @@ namespace ImageSharp.Tests
{
for (int x = 0; x < resultWidth; x++)
{
- Assert.Equal(expectedResult[y][x], pixels[x, y]);
+ Assert.True(expectedResult[y][x] == pixels[x, y],
+ $"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x,y]}");
}
}
}
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColorTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColorTests.cs
deleted file mode 100644
index 7b2513ce5c..0000000000
--- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColorTests.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// 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;
-
- public class WhiteIsZero8TiffColorTests : PhotometricInterpretationTestBase
- {
- private static Color Gray000 = new Color(255, 255, 255, 255);
- private static Color Gray128 = new Color(127, 127, 127, 255);
- private static Color Gray255 = new Color(0, 0, 0, 255);
-
- private static byte[] GrayscaleBytes4x4 = new byte[] { 128, 255, 000, 255,
- 255, 255, 255, 255,
- 000, 128, 128, 255,
- 255, 000, 255, 128 };
-
- private static Color[][] GrayscaleResult4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 },
- new[] { Gray255, Gray255, Gray255, Gray255 },
- new[] { Gray000, Gray128, Gray128, Gray255 },
- new[] { Gray255, Gray000, Gray255, Gray128 }};
-
- public static IEnumerable