Browse Source

Add support for decoding 24bit per channel color tiff with contiguous pixel data

pull/1724/head
Brian Popow 5 years ago
parent
commit
6e9cff93f4
  1. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  2. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  3. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  4. 89
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  5. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  6. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  7. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  8. 4
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  9. 12
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  10. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  11. 2
      tests/ImageSharp.Tests/TestImages.cs
  12. 3
      tests/Images/Input/Tiff/flower-rgb-contig-24.tiff
  13. 3
      tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); ushort intensity = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
ulong g = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
ulong b = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
@ -55,11 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); ulong r = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
ulong g = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); ulong g = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
ulong b = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -44,9 +44,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ulong r = TiffUtils.ConvertToShortBigEndian(redData.Slice(offset, 2)); ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToShortBigEndian(greenData.Slice(offset, 2)); ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToShortBigEndian(blueData.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
offset += 2; offset += 2;
@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ulong r = TiffUtils.ConvertToShortLittleEndian(redData.Slice(offset, 2)); ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToShortLittleEndian(greenData.Slice(offset, 2)); ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToShortLittleEndian(blueData.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
offset += 2; offset += 2;

89
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -0,0 +1,89 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 16 bits for each channel.
/// </summary>
internal class Rgb242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb242424TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
float scale = 1.0f / 0xFFFFFF;
byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx));
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx));
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx));
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
var colorVector = new Vector4(r * scale, g * scale, b * scale, 1.0f);
color.FromVector4(colorVector);
pixelRow[x] = color;
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx));
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx));
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx));
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
var colorVector = new Vector4(r * scale, g * scale, b * scale, 1.0f);
color.FromVector4(colorVector);
pixelRow[x] = color;
}
}
}
}
}
}

10
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

@ -136,6 +136,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb161616TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgb161616TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 24
&& bitsPerSample.Channel1 == 24
&& bitsPerSample.Channel0 == 24,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb242424TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.PaletteColor: case TiffColorType.PaletteColor:
DebugGuard.NotNull(colorMap, "colorMap"); DebugGuard.NotNull(colorMap, "colorMap");
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap); return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);

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

@ -103,6 +103,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary> /// </summary>
Rgb161616, Rgb161616,
/// <summary>
/// RGB color image with 24 bits for each channel.
/// </summary>
Rgb242424,
/// <summary> /// <summary>
/// RGB Full Color. Planar configuration of data. /// RGB Full Color. Planar configuration of data.
/// </summary> /// </summary>

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2))); ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2))); ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);

4
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -206,6 +206,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
ushort bitsPerChannel = options.BitsPerSample.Channel0; ushort bitsPerChannel = options.BitsPerSample.Channel0;
switch (bitsPerChannel) switch (bitsPerChannel)
{ {
case 24:
options.ColorType = TiffColorType.Rgb242424;
break;
case 16: case 16:
options.ColorType = TiffColorType.Rgb161616; options.ColorType = TiffColorType.Rgb161616;
break; break;

12
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -21,12 +21,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
public static L16 L16Default { get; } = new L16(0); public static L16 L16Default { get; } = new L16(0);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToShortBigEndian(ReadOnlySpan<byte> buffer) => public static ushort ConvertToUShortBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer);
BinaryPrimitives.ReadUInt16BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToShortLittleEndian(ReadOnlySpan<byte> buffer) => public static ushort ConvertToUShortLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer);
BinaryPrimitives.ReadUInt16LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL8<TPixel>(L8 l8, byte intensity, TPixel color) public static TPixel ColorFromL8<TPixel>(L8 l8, byte intensity, TPixel color)

6
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -173,6 +173,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_72Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]

2
tests/ImageSharp.Tests/TestImages.cs

@ -561,6 +561,8 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbPalette = "Tiff/rgb_palette.tiff"; public const string RgbPalette = "Tiff/rgb_palette.tiff";
public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff";
public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff";
public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff";
public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff";
public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff";
public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff"; public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff";
public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff";

3
tests/Images/Input/Tiff/flower-rgb-contig-24.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6368a704b0a629239024f6fbfb30723fa317593ef36ddba05d76302530bd974
size 28568

3
tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bbb2b4ca6d7eeee4737c6963c99ef68fb6971cf6ccee463427a8246574bc6440
size 28632
Loading…
Cancel
Save