Browse Source

Add support for RGB full color images

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
3f4041b590
  1. 56
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
  2. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  3. 24
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 151
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs
  5. 55
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

56
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs

@ -0,0 +1,56 @@
// <copyright file="RgbTiffColor.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 (for all bit depths).
/// </summary>
internal static class RgbTiffColor
{
/// <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 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)bitReader.ReadBits(bitsPerSample[0])) / rFactor;
float g = ((float)bitReader.ReadBits(bitsPerSample[1])) / gFactor;
float b = ((float)bitReader.ReadBits(bitsPerSample[2])) / bFactor;
color.PackFromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

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

@ -53,6 +53,11 @@ namespace ImageSharp.Formats.Tiff
/// <summary>
/// Palette-color.
/// </summary>
PaletteColor
PaletteColor,
/// <summary>
/// RGB Full Color.
/// </summary>
Rgb,
}
}

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

@ -389,6 +389,27 @@ namespace ImageSharp.Formats
break;
}
case TiffPhotometricInterpretation.Rgb:
{
if (this.BitsPerSample.Length == 3)
{
if (this.BitsPerSample[0] == 8 && this.BitsPerSample[1] == 8 && this.BitsPerSample[2] == 8)
{
this.ColorType = TiffColorType.Rgb;
}
else
{
this.ColorType = TiffColorType.Rgb;
}
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
}
case TiffPhotometricInterpretation.PaletteColor:
{
if (ifd.TryGetIfdEntry(TiffTags.ColorMap, out TiffIfdEntry colorMapEntry))
@ -505,6 +526,9 @@ namespace ImageSharp.Formats
case TiffColorType.BlackIsZero8:
BlackIsZero8TiffColor.Decode(data, pixels, left, top, width, height);
break;
case TiffColorType.Rgb:
RgbTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
break;
case TiffColorType.PaletteColor:
PaletteTiffColor.Decode(data, this.BitsPerSample, this.ColorMap, pixels, left, top, width, height);
break;

151
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs

@ -0,0 +1,151 @@
// <copyright file="RgbTiffColorTests.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 RgbTiffColorTests : 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 = new byte[] { 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF,
0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F,
0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C,
0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC };
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 = new byte[] { 0x00, 0x0F, 0xFF, 0x00, 0x00,
0xF0, 0x00, 0xF0, 0x00, 0xF0,
0x40, 0x08, 0x00, 0xC0, 0x00,
0x00, 0x04, 0x44, 0x88, 0x80 };
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 = new byte[] { 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255,
255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255,
064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192,
000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 };
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 = new byte[] { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F,
0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C,
0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C };
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 }};
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 =>
{
RgbTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height);
});
}
}
}

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

@ -200,6 +200,10 @@ namespace ImageSharp.Tests
[InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, TiffColorType.PaletteColor)]
[InlineData(false, TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, TiffColorType.PaletteColor)]
[InlineData(true, TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, TiffColorType.PaletteColor)]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, TiffColorType.Rgb)]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, TiffColorType.Rgb)]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, TiffColorType.Rgb)]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, TiffColorType.Rgb)]
public void ReadImageFormat_DeterminesCorrectColorImplementation(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
{
Stream stream = CreateTiffGenIfd()
@ -273,7 +277,6 @@ namespace ImageSharp.Tests
[InlineData(false, TiffPhotometricInterpretation.IccLab)]
[InlineData(false, TiffPhotometricInterpretation.ItuLab)]
[InlineData(false, TiffPhotometricInterpretation.LinearRaw)]
[InlineData(false, TiffPhotometricInterpretation.Rgb)]
[InlineData(false, TiffPhotometricInterpretation.Separated)]
[InlineData(false, TiffPhotometricInterpretation.TransparencyMask)]
[InlineData(false, TiffPhotometricInterpretation.YCbCr)]
@ -283,7 +286,6 @@ namespace ImageSharp.Tests
[InlineData(true, TiffPhotometricInterpretation.IccLab)]
[InlineData(true, TiffPhotometricInterpretation.ItuLab)]
[InlineData(true, TiffPhotometricInterpretation.LinearRaw)]
[InlineData(true, TiffPhotometricInterpretation.Rgb)]
[InlineData(true, TiffPhotometricInterpretation.Separated)]
[InlineData(true, TiffPhotometricInterpretation.TransparencyMask)]
[InlineData(true, TiffPhotometricInterpretation.YCbCr)]
@ -369,12 +371,18 @@ namespace ImageSharp.Tests
[InlineData(true, TiffPhotometricInterpretation.BlackIsZero, new int[] { })]
[InlineData(false, TiffPhotometricInterpretation.PaletteColor, new int[] { })]
[InlineData(true, TiffPhotometricInterpretation.PaletteColor, new int[] { })]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new int[] { })]
[InlineData(true, TiffPhotometricInterpretation.Rgb, 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 })]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 8 })]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 8 })]
[InlineData(false, TiffPhotometricInterpretation.Rgb, new[] { 8, 8 })]
[InlineData(true, TiffPhotometricInterpretation.Rgb, new[] { 8, 8 })]
public void ReadImageFormat_ThrowsExceptionForUnsupportedNumberOfSamples(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample)
{
Stream stream = CreateTiffGenIfd()
@ -424,34 +432,25 @@ namespace ImageSharp.Tests
}
[Theory]
[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)
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 1 }, 160, 80, 20 * 80)]
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 1 }, 153, 80, 20 * 80)]
[InlineData(TiffColorType.WhiteIsZero, new uint[] { 3 }, 100, 80, 38 * 80)]
[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.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)
{
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.ColorType = (TiffColorType)colorType;
decoder.BitsPerSample = bitsPerSample;
int bufferSize = decoder.CalculateImageBufferSize(width, height);

Loading…
Cancel
Save