Browse Source

Add default WhiteIsZero implementation

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
5e4c4f392d
  1. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  2. 53
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
  3. 33
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 65
      src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
  5. 12
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs
  6. 52
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

@ -10,6 +10,11 @@ namespace ImageSharp.Formats.Tiff
/// </summary>
internal enum TiffColorType
{
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
/// </summary>
WhiteIsZero,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for bilevel images.
/// </summary>

53
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs

@ -0,0 +1,53 @@
// <copyright file="WhiteIsZeroTiffColor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Tiff
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp;
using ImageSharp.PixelFormats;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal static class WhiteIsZeroTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, PixelAccessor<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default(TPixel);
BitReader bitReader = new BitReader(data);
float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(bitsPerSample[0]);
float intensity = 1.0f - (((float)value) / factor);
color.PackFromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

33
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -52,6 +52,11 @@ namespace ImageSharp.Formats
this.IsLittleEndian = isLittleEndian;
}
/// <summary>
/// Gets or sets the number of bits for each sample of the pixel format used to encode the image.
/// </summary>
public uint[] BitsPerSample { get; set; }
/// <summary>
/// Gets or sets the photometric interpretation implementation to use when decoding the image.
/// </summary>
@ -288,11 +293,11 @@ namespace ImageSharp.Formats
{
if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry))
{
uint[] bitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry);
this.BitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry);
if (bitsPerSample.Length == 1)
if (this.BitsPerSample.Length == 1)
{
switch (bitsPerSample[0])
switch (this.BitsPerSample[0])
{
case 8:
{
@ -314,7 +319,8 @@ namespace ImageSharp.Formats
default:
{
throw new NotSupportedException("The specified TIFF bit-depth is not supported.");
this.ColorType = TiffColorType.WhiteIsZero;
break;
}
}
}
@ -322,6 +328,7 @@ namespace ImageSharp.Formats
else
{
this.ColorType = TiffColorType.WhiteIsZero1;
this.BitsPerSample = new[] { 1u };
}
break;
@ -340,17 +347,14 @@ namespace ImageSharp.Formats
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
public int CalculateImageBufferSize(int width, int height)
{
switch (this.ColorType)
uint bitsPerPixel = 0;
for (int i = 0; i < this.BitsPerSample.Length; i++)
{
case TiffColorType.WhiteIsZero1:
return ((width + 7) / 8) * height;
case TiffColorType.WhiteIsZero4:
return ((width + 1) / 2) * height;
case TiffColorType.WhiteIsZero8:
return width * height;
default:
throw new InvalidOperationException();
bitsPerPixel += this.BitsPerSample[i];
}
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
return bytesPerRow * height;
}
/// <summary>
@ -391,6 +395,9 @@ namespace ImageSharp.Formats
{
switch (this.ColorType)
{
case TiffColorType.WhiteIsZero:
WhiteIsZeroTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
break;
case TiffColorType.WhiteIsZero1:
WhiteIsZero1TiffColor.Decode(data, pixels, left, top, width, height);
break;

65
src/ImageSharp/Formats/Tiff/Utils/BitReader.cs

@ -0,0 +1,65 @@
// <copyright file="BitReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Tiff
{
using System.IO;
/// <summary>
/// Utility class to read a sequence of bits from an array
/// </summary>
internal class BitReader
{
private readonly byte[] array;
private int offset;
private int bitOffset;
/// <summary>
/// Initializes a new instance of the <see cref="BitReader" /> class.
/// </summary>
/// <param name="array">The array to read data from.</param>
public BitReader(byte[] array)
{
this.array = array;
}
/// <summary>
/// Reads the specified number of bits from the array.
/// </summary>
/// <param name="bits">The number of bits to read.</param>
/// <returns>The value read from the array.</returns>
public int ReadBits(uint bits)
{
int value = 0;
for (uint i = 0; i < bits; i++)
{
int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01;
value = (value << 1) | bit;
this.bitOffset++;
if (this.bitOffset == 8)
{
this.bitOffset = 0;
this.offset++;
}
}
return value;
}
/// <summary>
/// Moves the reader to the next row of byte-aligned data.
/// </summary>
public void NextRow()
{
if (this.bitOffset > 0)
{
this.bitOffset = 0;
this.offset++;
}
}
}
}

12
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs

@ -119,6 +119,18 @@ namespace ImageSharp.Tests
}
}
[Theory]
[MemberData(nameof(Bilevel_Data))]
[MemberData(nameof(Grayscale4_Data))]
[MemberData(nameof(Grayscale8_Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Bilevel_Data))]
public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)

52
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

@ -176,6 +176,8 @@ namespace ImageSharp.Tests
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 }, TiffColorType.WhiteIsZero)]
[InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 }, TiffColorType.WhiteIsZero)]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, TiffColorType.WhiteIsZero8)]
[InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, TiffColorType.WhiteIsZero8)]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 4 }, TiffColorType.WhiteIsZero4)]
@ -287,33 +289,57 @@ namespace ImageSharp.Tests
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 })]
[InlineData(true, TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 })]
public void ReadImageFormat_ThrowsExceptionForUnsupportedBitDepth(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample)
[InlineData(false, new[] { 8u })]
[InlineData(true, new[] { 8u })]
[InlineData(false, new[] { 4u })]
[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 })]
public void ReadImageFormat_ReadsBitsPerSample(bool isLittleEndian, uint[] 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);
decoder.ReadImageFormat(ifd);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal(bitsPerSample, decoder.BitsPerSample);
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.WhiteIsZero)]
[InlineData(true, TiffPhotometricInterpretation.WhiteIsZero)]
public void ReadImageFormat_ReadsBitsPerSample_DefaultsToBilevel(bool isLittleEndian, ushort photometricInterpretation)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithoutEntry(TiffTags.BitsPerSample)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal("The specified TIFF bit-depth is not supported.", e.Message);
Assert.Equal(new[] { 1u }, decoder.BitsPerSample);
}
[Theory]
[InlineData(TiffColorType.WhiteIsZero8, 100, 80, 100 * 80)]
[InlineData(TiffColorType.WhiteIsZero4, 100, 80, 50 * 80)]
[InlineData(TiffColorType.WhiteIsZero4, 99, 80, 50 * 80)]
[InlineData(TiffColorType.WhiteIsZero1, 160, 80, 20 * 80)]
[InlineData(TiffColorType.WhiteIsZero1, 153, 80, 20 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize(ushort colorType, int width, int height, int expectedResult)
[InlineData(new uint[] { 1 }, 160, 80, 20 * 80)]
[InlineData(new uint[] { 1 }, 153, 80, 20 * 80)]
[InlineData(new uint[] { 3 }, 100, 80, 38 * 80)]
[InlineData(new uint[] { 4 }, 100, 80, 50 * 80)]
[InlineData(new uint[] { 4 }, 99, 80, 50 * 80)]
[InlineData(new uint[] { 8 }, 100, 80, 100 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize(uint[] bitsPerSample, int width, int height, int expectedResult)
{
TiffDecoderCore decoder = new TiffDecoderCore(null, null);
decoder.ColorType = (TiffColorType)colorType;
decoder.BitsPerSample = bitsPerSample;
int bufferSize = decoder.CalculateImageBufferSize(width, height);

Loading…
Cancel
Save