Browse Source

Add support for RGB (planar) pixel formats

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
240d86b070
  1. 60
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
  2. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  3. 108
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 11
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
  5. 199
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs
  6. 103
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
  7. 1
      tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj

60
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs

@ -0,0 +1,60 @@
// <copyright file="RgbTiffPlanarColor.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 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary>
internal static class RgbPlanarTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffers 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 rBitReader = new BitReader(data[0]);
BitReader gBitReader = new BitReader(data[1]);
BitReader bBitReader = new BitReader(data[2]);
float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f;
float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = ((float)rBitReader.ReadBits(bitsPerSample[0])) / rFactor;
float g = ((float)gBitReader.ReadBits(bitsPerSample[1])) / gFactor;
float b = ((float)bBitReader.ReadBits(bitsPerSample[2])) / bFactor;
color.PackFromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
}
rBitReader.NextRow();
gBitReader.NextRow();
bBitReader.NextRow();
}
}
}
}

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

@ -63,6 +63,11 @@ namespace ImageSharp.Formats.Tiff
/// <summary>
/// RGB Full Color. Optimised implementation for 8-bit images.
/// </summary>
Rgb888
Rgb888,
/// <summary>
/// RGB Full Color. Planar configuration of data.
/// </summary>
RgbPlanar,
}
}

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

@ -82,6 +82,11 @@ namespace ImageSharp.Formats
/// </summary>
public bool IsLittleEndian { get; private set; }
/// <summary>
/// Gets or sets the planar configuration type to use when decoding the image.
/// </summary>
public TiffPlanarConfiguration PlanarConfiguration { get; set; }
/// <summary>
/// Calculates the size (in bytes) of the data contained within an IFD entry.
/// </summary>
@ -281,6 +286,15 @@ namespace ImageSharp.Formats
}
}
if (ifd.TryGetIfdEntry(TiffTags.PlanarConfiguration, out TiffIfdEntry planarConfigurationEntry))
{
this.PlanarConfiguration = (TiffPlanarConfiguration)this.ReadUnsignedInteger(ref planarConfigurationEntry);
}
else
{
this.PlanarConfiguration = TiffPlanarConfiguration.Chunky;
}
TiffPhotometricInterpretation photometricInterpretation;
if (ifd.TryGetIfdEntry(TiffTags.PhotometricInterpretation, out TiffIfdEntry photometricInterpretationEntry))
@ -400,13 +414,20 @@ namespace ImageSharp.Formats
{
if (this.BitsPerSample.Length == 3)
{
if (this.BitsPerSample[0] == 8 && this.BitsPerSample[1] == 8 && this.BitsPerSample[2] == 8)
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
this.ColorType = TiffColorType.Rgb888;
if (this.BitsPerSample[0] == 8 && this.BitsPerSample[1] == 8 && this.BitsPerSample[2] == 8)
{
this.ColorType = TiffColorType.Rgb888;
}
else
{
this.ColorType = TiffColorType.Rgb;
}
}
else
{
this.ColorType = TiffColorType.Rgb;
this.ColorType = TiffColorType.RgbPlanar;
}
}
else
@ -457,17 +478,25 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="width">The width for the desired pixel buffer.</param>
/// <param name="height">The height for the desired pixel buffer.</param>
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
public int CalculateImageBufferSize(int width, int height)
public int CalculateImageBufferSize(int width, int height, int plane)
{
uint bitsPerPixel = 0;
for (int i = 0; i < this.BitsPerSample.Length; i++)
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
bitsPerPixel += this.BitsPerSample[i];
for (int i = 0; i < this.BitsPerSample.Length; i++)
{
bitsPerPixel += this.BitsPerSample[i];
}
}
else
{
bitsPerPixel = this.BitsPerSample[plane];
}
int sampleMultiplier = this.ColorType == TiffColorType.PaletteColor ? 3 : 1;
int bytesPerRow = ((width * (int)bitsPerPixel * sampleMultiplier) + 7) / 8;
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
return bytesPerRow * height;
}
@ -498,7 +527,7 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// Decodes pixel data using the current photometric interpretation (chunky configuration).
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
@ -507,7 +536,7 @@ namespace ImageSharp.Formats
/// <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>
public void ProcessImageBlock<TPixel>(byte[] data, PixelAccessor<TPixel> pixels, int left, int top, int width, int height)
public void ProcessImageBlockChunky<TPixel>(byte[] data, PixelAccessor<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
switch (this.ColorType)
@ -550,6 +579,29 @@ namespace ImageSharp.Formats
}
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation (planar configuration).
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</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>
public void ProcessImageBlockPlanar<TPixel>(byte[][] data, PixelAccessor<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
switch (this.ColorType)
{
case TiffColorType.RgbPlanar:
RgbPlanarTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
break;
default:
throw new InvalidOperationException();
}
}
/// <summary>
/// Reads the data from a <see cref="TiffIfdEntry"/> as an array of bytes.
/// </summary>
@ -1108,25 +1160,47 @@ namespace ImageSharp.Formats
private void DecodeImageStrips<TPixel>(Image<TPixel> image, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
where TPixel : struct, IPixel<TPixel>
{
int uncompressedStripSize = this.CalculateImageBufferSize(image.Width, rowsPerStrip);
int stripsPerPixel = this.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? 1 : this.BitsPerSample.Length;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
using (PixelAccessor<TPixel> pixels = image.Lock())
{
byte[] stripBytes = ArrayPool<byte>.Shared.Rent(uncompressedStripSize);
byte[][] stripBytes = new byte[stripsPerPixel][];
for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++)
{
int uncompressedStripSize = this.CalculateImageBufferSize(image.Width, rowsPerStrip, stripIndex);
stripBytes[stripIndex] = ArrayPool<byte>.Shared.Rent(uncompressedStripSize);
}
try
{
for (int i = 0; i < stripOffsets.Length; i++)
for (int i = 0; i < stripsPerPlane; i++)
{
int stripHeight = i < stripOffsets.Length - 1 || image.Height % rowsPerStrip == 0 ? rowsPerStrip : image.Height % rowsPerStrip;
int stripHeight = i < stripsPerPlane - 1 || image.Height % rowsPerStrip == 0 ? rowsPerStrip : image.Height % rowsPerStrip;
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
{
int stripIndex = i * stripsPerPixel + planeIndex;
this.DecompressImageBlock(stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBytes[planeIndex]);
}
this.DecompressImageBlock(stripOffsets[i], stripByteCounts[i], stripBytes);
this.ProcessImageBlock(stripBytes, pixels, 0, rowsPerStrip * i, image.Width, stripHeight);
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
this.ProcessImageBlockChunky(stripBytes[0], pixels, 0, rowsPerStrip * i, image.Width, stripHeight);
}
else
{
this.ProcessImageBlockPlanar(stripBytes, pixels, 0, rowsPerStrip * i, image.Width, stripHeight);
}
}
}
finally
{
ArrayPool<byte>.Shared.Return(stripBytes);
for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++)
{
ArrayPool<byte>.Shared.Return(stripBytes[stripIndex]);
}
}
}
}

11
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs

@ -7,9 +7,12 @@ namespace ImageSharp.Tests
{
using System;
using Xunit;
using ImageSharp;
public abstract class PhotometricInterpretationTestBase
{
public static Rgba32 DefaultColor = new Rgba32(42, 96, 18, 128);
public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height)
{
int inputHeight = input.Length;
@ -20,6 +23,11 @@ namespace ImageSharp.Tests
for (int y = 0; y < output.Length; y++)
{
output[y] = new Rgba32[width];
for (int x = 0; x < width; x++)
{
output[y][x] = DefaultColor;
}
}
for (int y = 0; y < inputHeight; y++)
@ -38,6 +46,7 @@ namespace ImageSharp.Tests
int resultWidth = expectedResult[0].Length;
int resultHeight = expectedResult.Length;
Image<Rgba32> image = new Image<Rgba32>(resultWidth, resultHeight);
image.Fill(DefaultColor);
using (PixelAccessor<Rgba32> pixels = image.Lock())
{
@ -51,7 +60,7 @@ namespace ImageSharp.Tests
for (int x = 0; x < resultWidth; x++)
{
Assert.True(expectedResult[y][x] == pixels[x, y],
$"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x,y]}");
$"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}");
}
}
}

199
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs

@ -0,0 +1,199 @@
// <copyright file="RgbPlanarTiffColorTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats.Tiff;
public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase
{
private static Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255);
private static Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255);
private static Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255);
private static Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255);
private static Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255);
private static Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255);
private static Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255);
private static byte[] Rgb4_Bytes4x4_R = new byte[] { 0x0F, 0x0F,
0xF0, 0x0F,
0x48, 0xC4,
0x04, 0x8C };
private static byte[] Rgb4_Bytes4x4_G = new byte[] { 0x0F, 0x0F,
0x0F, 0x00,
0x00, 0x08,
0x04, 0x8C };
private static byte[] Rgb4_Bytes4x4_B = new byte[] { 0x0F, 0x0F,
0x00, 0xFF,
0x00, 0x0C,
0x04, 0x8C };
private static byte[][] Rgb4_Bytes4x4 = new[] { Rgb4_Bytes4x4_R, Rgb4_Bytes4x4_G, Rgb4_Bytes4x4_B };
private static Rgba32[][] Rgb4_Result4x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF },
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F },
new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C },
new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC }};
private static byte[] Rgb4_Bytes3x4_R = new byte[] { 0x0F, 0x00,
0xF0, 0x00,
0x48, 0xC0,
0x04, 0x80 };
private static byte[] Rgb4_Bytes3x4_G = new byte[] { 0x0F, 0x00,
0x0F, 0x00,
0x00, 0x00,
0x04, 0x80 };
private static byte[] Rgb4_Bytes3x4_B = new byte[] { 0x0F, 0x00,
0x00, 0xF0,
0x00, 0x00,
0x04, 0x80 };
private static byte[][] Rgb4_Bytes3x4 = new[] { Rgb4_Bytes3x4_R, Rgb4_Bytes3x4_G, Rgb4_Bytes3x4_B };
private static Rgba32[][] Rgb4_Result3x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 },
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F },
new[] { Rgb4_400, Rgb4_800, Rgb4_C00 },
new[] { Rgb4_000, Rgb4_444, Rgb4_888 }};
public static IEnumerable<object[]> Rgb4_Data
{
get
{
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) };
}
}
private static Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255);
private static Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255);
private static Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255);
private static Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255);
private static Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255);
private static Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255);
private static Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255);
private static byte[] Rgb8_Bytes4x4_R = new byte[] { 000, 255, 000, 255,
255, 000, 000, 255,
064, 128, 192, 064,
000, 064, 128, 192 };
private static byte[] Rgb8_Bytes4x4_G = new byte[] { 000, 255, 000, 255,
000, 255, 000, 000,
000, 000, 000, 128,
000, 064, 128, 192 };
private static byte[] Rgb8_Bytes4x4_B = new byte[] { 000, 255, 000, 255,
000, 000, 255, 255,
000, 000, 000, 192,
000, 064, 128, 192 };
private static byte[][] Rgb8_Bytes4x4 = new[] { Rgb8_Bytes4x4_R, Rgb8_Bytes4x4_G, Rgb8_Bytes4x4_B };
private static Rgba32[][] Rgb8_Result4x4 = new[] { new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF },
new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F },
new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C },
new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC }};
public static IEnumerable<object[]> Rgb8_Data
{
get
{
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) };
}
}
private static Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255);
private static Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255);
private static Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255);
private static Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255);
private static Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255);
private static Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255);
private static Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255);
private static byte[] Rgb484_Bytes4x4_R = new byte[] { 0x0F, 0x0F,
0xF0, 0x0F,
0x48, 0xC4,
0x04, 0x8C };
private static byte[] Rgb484_Bytes4x4_G = new byte[] { 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80,
0x00, 0x40, 0x80, 0xC0 };
private static byte[] Rgb484_Bytes4x4_B = new byte[] { 0x0F, 0x0F,
0x00, 0xFF,
0x00, 0x0C,
0x04, 0x8C };
private static Rgba32[][] Rgb484_Result4x4 = new[] { new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF },
new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F },
new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C },
new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC }};
private static byte[][] Rgb484_Bytes4x4 = new[] { Rgb484_Bytes4x4_R, Rgb484_Bytes4x4_G, Rgb484_Bytes4x4_B };
public static IEnumerable<object[]> Rgb484_Data
{
get
{
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(Rgb4_Data))]
[MemberData(nameof(Rgb8_Data))]
[MemberData(nameof(Rgb484_Data))]
public void Decode_WritesPixelData(byte[][] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
RgbPlanarTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height);
});
}
}
}

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

@ -204,10 +204,31 @@ namespace ImageSharp.Tests
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, TiffColorType.Rgb)]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, TiffColorType.Rgb888)]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, TiffColorType.Rgb888)]
public void ReadImageFormat_DeterminesCorrectColorImplementation(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
public void ReadImageFormat_DeterminesCorrectColorImplementation_Chunky(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)TiffPlanarConfiguration.Chunky))
.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);
Assert.Equal((TiffColorType)colorType, decoder.ColorType);
}
[Theory]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, TiffColorType.RgbPlanar)]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, TiffColorType.RgbPlanar)]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, TiffColorType.RgbPlanar)]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, TiffColorType.RgbPlanar)]
public void ReadImageFormat_DeterminesCorrectColorImplementation_Planar(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)TiffPlanarConfiguration.Planar))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
@ -431,6 +452,43 @@ namespace ImageSharp.Tests
Assert.Equal("The TIFF ColorMap entry is missing for a pallete color image.", e.Message);
}
[Theory]
[InlineData(false, TiffPlanarConfiguration.Chunky)]
[InlineData(true, TiffPlanarConfiguration.Chunky)]
[InlineData(false, TiffPlanarConfiguration.Planar)]
[InlineData(true, TiffPlanarConfiguration.Planar)]
public void ReadImageFormat_ReadsPlanarConfiguration(bool isLittleEndian, int planarConfiguration)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.Rgb))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8, 8, 8 }))
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)planarConfiguration))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffPlanarConfiguration)planarConfiguration, decoder.PlanarConfiguration);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_DefaultsPlanarConfigurationToChunky(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.Rgb))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8, 8, 8 }))
.WithoutEntry(TiffTags.PlanarConfiguration)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal(TiffPlanarConfiguration.Chunky, decoder.PlanarConfiguration);
}
[Theory]
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 1 }, 160, 80, 20 * 80)]
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 1 }, 153, 80, 20 * 80)]
@ -438,22 +496,49 @@ namespace ImageSharp.Tests
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 4 }, 100, 80, 50 * 80)]
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 4 }, 99, 80, 50 * 80)]
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 8 }, 100, 80, 100 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 1 }, 160, 80, 60 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 1 }, 153, 80, 58 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 3 }, 100, 80, 113 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 4 }, 100, 80, 150 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 4 }, 99, 80, 149 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 8 }, 100, 80, 300 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 1 }, 160, 80, 20 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 1 }, 153, 80, 20 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 3 }, 100, 80, 38 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 4 }, 100, 80, 50 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 4 }, 99, 80, 50 * 80)]
[InlineData(TiffColorType.PaletteColor, new uint[] { 8 }, 100, 80, 100 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 300 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 150 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 200 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize(ushort colorType, uint[] bitsPerSample, int width, int height, int expectedResult)
public void CalculateImageBufferSize_ReturnsCorrectSize_Chunky(ushort colorType, uint[] bitsPerSample, int width, int height, int expectedResult)
{
TiffDecoderCore decoder = new TiffDecoderCore(null, null);
decoder.ColorType = (TiffColorType)colorType;
decoder.PlanarConfiguration = TiffPlanarConfiguration.Chunky;
decoder.BitsPerSample = bitsPerSample;
int bufferSize = decoder.CalculateImageBufferSize(width, height, 0);
Assert.Equal(expectedResult, bufferSize);
}
[Theory]
[InlineData(TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 0, 100 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 1, 100 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 2, 100 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 0, 50 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 1, 50 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 2, 50 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 0, 50 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 1, 100 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 2, 50 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 0, 50 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 1, 99 * 80)]
[InlineData(TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 2, 50 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize_Planar(ushort colorType, uint[] bitsPerSample, int width, int height, int plane, int expectedResult)
{
TiffDecoderCore decoder = new TiffDecoderCore(null, null);
decoder.ColorType = (TiffColorType)colorType;
decoder.PlanarConfiguration = TiffPlanarConfiguration.Planar;
decoder.BitsPerSample = bitsPerSample;
int bufferSize = decoder.CalculateImageBufferSize(width, height);
int bufferSize = decoder.CalculateImageBufferSize(width, height, plane);
Assert.Equal(expectedResult, bufferSize);
}

1
tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj

@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

Loading…
Cancel
Save