From 0e1e6c6fd73dff2e02a9273246d8e039a5be7cf2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 4 Mar 2022 19:33:56 +0100 Subject: [PATCH 1/6] Add support for decoding Tiff images with UnassociatedAlphaData --- .../Rgba16161616TiffColor{TPixel}.cs | 77 ++++++++ .../Rgba24242424TiffColor{TPixel}.cs | 90 +++++++++ .../Rgba32323232TiffColor{TPixel}.cs | 79 ++++++++ .../Rgba8888TiffColor{TPixel}.cs | 39 ++++ .../RgbaFloat32323232TiffColor{TPixel}.cs | 97 ++++++++++ .../RgbaTiffColor{TPixel}.cs | 72 ++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 173 ++++++++++++++++++ .../TiffColorType.cs | 80 ++++++++ src/ImageSharp/Formats/Tiff/README.md | 2 +- .../Formats/Tiff/TiffBitsPerSample.cs | 38 +++- .../Formats/Tiff/TiffDecoderCore.cs | 7 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 52 ++++-- .../Formats/Tiff/TiffExtraSampleType.cs | 27 +++ .../Formats/Tiff/Utils/TiffUtils.cs | 18 ++ .../Formats/Tiff/TiffDecoderTests.cs | 121 ++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 23 +++ .../Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha10bit_msb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha12bit_msb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha14bit_msb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha16bit_msb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha24bit_msb.tiff | 3 + .../Input/Tiff/RgbaUnassociatedAlpha2bit.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff | 3 + .../Tiff/RgbaUnassociatedAlpha32bit_msb.tiff | 3 + .../Input/Tiff/RgbaUnassociatedAlpha3bit.tiff | 3 + .../Input/Tiff/RgbaUnassociatedAlpha4bit.tiff | 3 + .../Input/Tiff/RgbaUnassociatedAlpha5bit.tiff | 3 + .../Input/Tiff/RgbaUnassociatedAlpha6bit.tiff | 3 + .../Input/Tiff/RgbaUnassociatedAlpha8bit.tiff | 3 + tests/Images/Input/Tiff/flower-rgb-3bit.tiff | 3 + tests/Images/Input/Tiff/flower-rgb-5bit.tiff | 3 + tests/Images/Input/Tiff/flower-rgb-6bit.tiff | 3 + 37 files changed, 1034 insertions(+), 24 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-3bit.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-5bit.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-6bit.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs new file mode 100644 index 0000000000..7fd8d9879b --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel. + /// + internal class Rgba16161616TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + private readonly Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian) + { + this.configuration = configuration; + this.isBigEndian = isBigEndian; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D 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 + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + + int offset = 0; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + } + } + else + { + int byteCount = pixelRow.Length * 8; + PixelOperations.Instance.FromRgba64Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + offset += byteCount; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs new file mode 100644 index 0000000000..3d88d9fb5a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel. + /// + internal class Rgba24242424TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D 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; + byte[] buffer = new byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + Span bufferSpan = buffer.AsSpan(bufferStartIdx); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs new file mode 100644 index 0000000000..fbd18ca3e0 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. + /// + internal class Rgba32323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D 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; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs new file mode 100644 index 0000000000..491a42fb75 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel. + /// + internal class Rgba8888TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly Configuration configuration; + + public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + int offset = 0; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + int byteCount = pixelRow.Length * 4; + PixelOperations.Instance.FromRgba32Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + offset += byteCount; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs new file mode 100644 index 0000000000..4fb0797dc5 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs @@ -0,0 +1,97 @@ +// 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 +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. + /// + internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D 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; + byte[] buffer = new byte[4]; + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float a = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(r, g, b, a); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 4).CopyTo(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float a = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(r, g, b, a); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs new file mode 100644 index 0000000000..8bec7da89d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs @@ -0,0 +1,72 @@ +// 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 +{ + /// + /// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths). + /// + internal class RgbaTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly float rFactor; + + private readonly float gFactor; + + private readonly float bFactor; + + private readonly float aFactor; + + private readonly ushort bitsPerSampleR; + + private readonly ushort bitsPerSampleG; + + private readonly ushort bitsPerSampleB; + + private readonly ushort bitsPerSampleA; + + public RgbaTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + this.bitsPerSampleA = bitsPerSample.Channel3; + + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var bitReader = new BitReader(data); + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) + { + float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; + + color.FromVector4(new Vector4(r, g, b, a)); + pixelRow[x] = color; + } + + bitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index a8c4cef4ed..25ca534315 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -116,6 +116,38 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba2222: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 2 + && bitsPerSample.Channel2 == 2 + && bitsPerSample.Channel1 == 2 + && bitsPerSample.Channel0 == 2, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + + case TiffColorType.Rgb333: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 3 + && bitsPerSample.Channel1 == 3 + && bitsPerSample.Channel0 == 3, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba3333: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 3 + && bitsPerSample.Channel2 == 3 + && bitsPerSample.Channel1 == 3 + && bitsPerSample.Channel0 == 3, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb444: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -126,6 +158,59 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb444TiffColor(); + case TiffColorType.Rgba4444: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 4 + && bitsPerSample.Channel2 == 4 + && bitsPerSample.Channel1 == 4 + && bitsPerSample.Channel0 == 4, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + + case TiffColorType.Rgb555: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 5 + && bitsPerSample.Channel1 == 5 + && bitsPerSample.Channel0 == 5, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba5555: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 5 + && bitsPerSample.Channel2 == 5 + && bitsPerSample.Channel1 == 5 + && bitsPerSample.Channel0 == 5, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + + case TiffColorType.Rgb666: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 6 + && bitsPerSample.Channel1 == 6 + && bitsPerSample.Channel0 == 6, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba6666: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 6 + && bitsPerSample.Channel2 == 6 + && bitsPerSample.Channel1 == 6 + && bitsPerSample.Channel0 == 6, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb888: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -136,6 +221,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb888TiffColor(configuration); + case TiffColorType.Rgba8888: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 8 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba8888TiffColor(configuration); + case TiffColorType.Rgb101010: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -146,6 +242,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba10101010: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 10 + && bitsPerSample.Channel2 == 10 + && bitsPerSample.Channel1 == 10 + && bitsPerSample.Channel0 == 10, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb121212: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -156,6 +263,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba12121212: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 12 + && bitsPerSample.Channel2 == 12 + && bitsPerSample.Channel1 == 12 + && bitsPerSample.Channel0 == 12, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb141414: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -166,6 +284,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgba14141414: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 14 + && bitsPerSample.Channel2 == 14 + && bitsPerSample.Channel1 == 14 + && bitsPerSample.Channel0 == 14, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(bitsPerSample); + case TiffColorType.Rgb161616: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -176,6 +305,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba16161616: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 16 + && bitsPerSample.Channel2 == 16 + && bitsPerSample.Channel1 == 16 + && bitsPerSample.Channel0 == 16, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba16161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb242424: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -186,6 +326,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba24242424: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 24 + && bitsPerSample.Channel2 == 24 + && bitsPerSample.Channel1 == 24 + && bitsPerSample.Channel0 == 24, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba24242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb323232: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -196,6 +347,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba32323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 32 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.RgbFloat323232: DebugGuard.IsTrue( bitsPerSample.Channels == 3 @@ -206,6 +368,17 @@ public static TiffBaseColorDecoder Create( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbFloat323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.RgbaFloat32323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 32 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaFloat32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); return new PaletteTiffColor(bitsPerSample, colorMap); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 8caefaed56..a0a48c2b93 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -103,51 +103,131 @@ internal enum TiffColorType /// Rgb222, + /// + /// RGBA color image with 2 bits for each channel. + /// + Rgba2222, + + /// + /// RGB color image with 3 bits for each channel. + /// + Rgb333, + + /// + /// RGBA color image with 3 bits for each channel. + /// + Rgba3333, + /// /// RGB color image with 4 bits for each channel. /// Rgb444, + /// + /// RGBA color image with 4 bits for each channel. + /// + Rgba4444, + + /// + /// RGB color image with 5 bits for each channel. + /// + Rgb555, + + /// + /// RGBA color image with 5 bits for each channel. + /// + Rgba5555, + + /// + /// RGB color image with 6 bits for each channel. + /// + Rgb666, + + /// + /// RGBA color image with 6 bits for each channel. + /// + Rgba6666, + /// /// RGB Full Color. Optimized implementation for 8-bit images. /// Rgb888, + /// + /// RGBA Full Color with 8-bit for each channel. + /// + Rgba8888, + /// /// RGB color image with 10 bits for each channel. /// Rgb101010, + /// + /// RGBA color image with 10 bits for each channel. + /// + Rgba10101010, + /// /// RGB color image with 12 bits for each channel. /// Rgb121212, + /// + /// RGBA color image with 12 bits for each channel. + /// + Rgba12121212, + /// /// RGB color image with 14 bits for each channel. /// Rgb141414, + /// + /// RGBA color image with 14 bits for each channel. + /// + Rgba14141414, + /// /// RGB color image with 16 bits for each channel. /// Rgb161616, + /// + /// RGBA color image with 16 bits for each channel. + /// + Rgba16161616, + /// /// RGB color image with 24 bits for each channel. /// Rgb242424, + /// + /// RGBA color image with 24 bits for each channel. + /// + Rgba24242424, + /// /// RGB color image with 32 bits for each channel. /// Rgb323232, + /// + /// RGBA color image with 32 bits for each channel. + /// + Rgba32323232, + /// /// RGB color image with 32 bits floats for each channel. /// RgbFloat323232, + /// + /// RGBA color image with 32 bits floats for each channel. + /// + RgbaFloat32323232, + /// /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. /// diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 51e84ef558..aa960b373c 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -105,7 +105,7 @@ |Artist | Y | Y | | |HostComputer | Y | Y | | |ColorMap | Y | Y | | -|ExtraSamples | | - | | +|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far | |Copyright | Y | Y | | ### Extension TIFF Tags diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs index 8fd26ac13d..9348a68839 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -25,6 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public readonly ushort Channel2; + /// + /// The bits for the alpha channel. + /// + public readonly ushort Channel3; + /// /// The number of channels. /// @@ -36,16 +41,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The bits for the channel 0. /// The bits for the channel 1. /// The bits for the channel 2. - public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2) + /// The bits for the channel 3. + public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2, ushort channel3 = 0) { this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); + this.Channel3 = (ushort)Numerics.Clamp(channel3, 0, 32); this.Channels = 0; this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel3 != 0 ? 1 : 0); } /// @@ -62,11 +70,19 @@ public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) return false; } + ushort c3 = 0; ushort c2; ushort c1; ushort c0; switch (value.Length) { + case 4: + c3 = value[3]; + c2 = value[2]; + c1 = value[1]; + c0 = value[0]; + break; + case 3: c2 = value[2]; c1 = value[1]; @@ -84,7 +100,7 @@ public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) break; } - sample = new TiffBitsPerSample(c0, c1, c2); + sample = new TiffBitsPerSample(c0, c1, c2, c3); return true; } @@ -96,11 +112,12 @@ public override bool Equals(object obj) public bool Equals(TiffBitsPerSample other) => this.Channel0 == other.Channel0 && this.Channel1 == other.Channel1 - && this.Channel2 == other.Channel2; + && this.Channel2 == other.Channel2 + && this.Channel3 == other.Channel3; /// public override int GetHashCode() - => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2); + => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2, this.Channel3); /// /// Converts the bits per sample struct to an ushort array. @@ -118,7 +135,12 @@ public ushort[] ToArray() return new[] { this.Channel0, this.Channel1 }; } - return new[] { this.Channel0, this.Channel1, this.Channel2 }; + if (this.Channel3 == 0) + { + return new[] { this.Channel0, this.Channel1, this.Channel2 }; + } + + return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 }; } /// @@ -127,12 +149,14 @@ public ushort[] ToArray() /// Bits per pixel. public TiffBitsPerPixel BitsPerPixel() { - int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2; + int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2 + this.Channel3; return (TiffBitsPerPixel)bitsPerPixel; } /// public override string ToString() - => $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})"; + => this.Channel3 is 0 ? + $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})" + : $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2}, {this.Channel3})"; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index cd06282f18..aaaef0b8a7 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -35,7 +35,7 @@ internal class TiffDecoderCore : IImageDecoderInternals /// /// Gets the decoding mode for multi-frame images /// - private FrameDecodingMode decodingMode; + private readonly FrameDecodingMode decodingMode; /// /// The stream to decode from. @@ -117,6 +117,11 @@ public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) /// public TiffFillOrder FillOrder { get; set; } + /// + /// Gets or sets the extra samples, which can contain the alpha channel data. + /// + public TiffExtraSampleType? ExtraSamples { get; set; } + /// /// Gets or sets the JPEG tables when jpeg compression is used. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index a187d444ac..0f1ffc79ff 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -29,9 +29,21 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); } - if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null) + IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples); + if (extraSamplesExifValue is not null) { - TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); + short[] extraSamples = (short[])extraSamplesExifValue.GetValue(); + if (extraSamples.Length != 1) + { + TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data."); + } + + var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; + options.ExtraSamples = extraSamplesType; + if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData) + { + TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is only supported with UnassociatedAlphaData."); + } } TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; @@ -52,7 +64,7 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif sampleFormat = sampleFormats[0]; foreach (TiffSampleFormat format in sampleFormats) { - if (format != TiffSampleFormat.UnsignedInteger && format != TiffSampleFormat.Float) + if (format is not TiffSampleFormat.UnsignedInteger and not TiffSampleFormat.Float) { TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat."); } @@ -252,12 +264,13 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi case TiffPhotometricInterpretation.Rgb: { TiffBitsPerSample bitsPerSample = options.BitsPerSample; - if (bitsPerSample.Channels != 3) + if (bitsPerSample.Channels is not (3 or 4)) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - if (!(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) + if ((bitsPerSample.Channels == 3 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) || + (bitsPerSample.Channels == 4 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2 && bitsPerSample.Channel2 == bitsPerSample.Channel3))) { TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); } @@ -270,41 +283,50 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi case 32: if (options.SampleFormat == TiffSampleFormat.Float) { - options.ColorType = TiffColorType.RgbFloat323232; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.RgbFloat323232 : TiffColorType.RgbaFloat32323232; return; } - options.ColorType = TiffColorType.Rgb323232; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232 : TiffColorType.Rgba32323232; break; case 24: - options.ColorType = TiffColorType.Rgb242424; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424 : TiffColorType.Rgba24242424; break; case 16: - options.ColorType = TiffColorType.Rgb161616; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616 : TiffColorType.Rgba16161616; break; case 14: - options.ColorType = TiffColorType.Rgb141414; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb141414 : TiffColorType.Rgba14141414; break; case 12: - options.ColorType = TiffColorType.Rgb121212; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb121212 : TiffColorType.Rgba12121212; break; case 10: - options.ColorType = TiffColorType.Rgb101010; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb101010 : TiffColorType.Rgba10101010; break; case 8: - options.ColorType = TiffColorType.Rgb888; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888 : TiffColorType.Rgba8888; + break; + case 6: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb666 : TiffColorType.Rgba6666; + break; + case 5: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb555 : TiffColorType.Rgba5555; break; case 4: - options.ColorType = TiffColorType.Rgb444; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb444 : TiffColorType.Rgba4444; + break; + case 3: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb333 : TiffColorType.Rgba3333; break; case 2: - options.ColorType = TiffColorType.Rgb222; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb222 : TiffColorType.Rgba2222; break; default: TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); diff --git a/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs new file mode 100644 index 0000000000..5fbc29177d --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Description of extra components. + /// + internal enum TiffExtraSampleType + { + /// + /// The data is unspecified, not supported. + /// + UnspecifiedData = 0, + + /// + /// The extra data is associated alpha data (with pre-multiplied color). + /// + AssociatedAlphaData = 1, + + /// + /// The extra data is unassociated alpha data is transparency information that logically exists independent of an image; + /// it is commonly called a soft matte. + /// + UnassociatedAlphaData = 2 + } +} diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 4f71fa35c9..6ec905929f 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -54,6 +54,15 @@ public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulon return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48); + color.FromRgba64(rgba); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel @@ -72,6 +81,15 @@ public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a); + color.FromVector4(colorVector); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) where TPixel : unmanaged, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index e4f4d26b85..de8ae8e729 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -123,6 +123,21 @@ public void TiffDecoder_CanDecode_6Bit_Gray(TestImageProvider pr public void TiffDecoder_CanDecode_8Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba2BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Flower10BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_10Bit_Gray(TestImageProvider provider) @@ -139,11 +154,31 @@ public void TiffDecoder_CanDecode_12Bit(TestImageProvider provid public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Flower14BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower16BitGray, PixelTypes.Rgba32)] @@ -158,6 +193,36 @@ public void TiffDecoder_CanDecode_16Bit_Gray(TestImageProvider p public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba4BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb6Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_18Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + + [Theory] + [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba6BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] @@ -207,6 +272,16 @@ public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider p image.DebugSave(provider); } + [Theory] + [WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] @@ -223,6 +298,12 @@ public void TiffDecoder_CanDecode_32Bit_Gray_WithPredictor(TestImageProv public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba10BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] @@ -238,12 +319,24 @@ public void TiffDecoder_CanDecode_42Bit(TestImageProvider provid public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba12BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_48Bit_WithPredictor(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba14BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba14BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] @@ -270,6 +363,17 @@ public void TiffDecoder_CanDecode_96Bit(TestImageProvider provid image.DebugSave(provider); } + [Theory] + [WithFile(Rgba24BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba24BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)] [WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)] @@ -305,6 +409,23 @@ public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index aa4314b8e0..476d54e83f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -831,6 +831,9 @@ public static class Tiff public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; + public const string FLowerRgb3Bit = "Tiff/flower-rgb-3bit.tiff"; + public const string FLowerRgb5Bit = "Tiff/flower-rgb-5bit.tiff"; + public const string FLowerRgb6Bit = "Tiff/flower-rgb-6bit.tiff"; public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; @@ -855,6 +858,26 @@ public static class Tiff public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff"; public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; + // Images with alpha channel. + public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; + public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; + public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; + public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; + public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; + public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; + public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; + public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; + public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; + public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; + public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; + public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; + public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; + public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; + public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff new file mode 100644 index 0000000000..f4a1a3f6bc --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f78ef11e4044d13ea3bf699e33472a708df3a5cc817dc41edb4df184f127f2b +size 294278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff new file mode 100644 index 0000000000..06beb72f3a --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha10bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9c2d6f4e16677d9fdfb38cc2bfb7df05eedbb8dc0e3c26a6dba9b427c2c698a +size 294278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff new file mode 100644 index 0000000000..94a9447050 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53e9ff25da2a2a7a613328cfaf33799df51fe150586fb8de52070e8cc8830d97 +size 353078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff new file mode 100644 index 0000000000..26d911272e --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha12bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caff76e01bc39b7a295f01a11e3787a6487ac002af5586dd956166a9c91eb048 +size 353078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff new file mode 100644 index 0000000000..f1bd4c405f --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9193b6a194be970b2cfb26369fa487fd6ec2f1656af11df2e48f1d6b0971bbf8 +size 411878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff new file mode 100644 index 0000000000..b098877e0c --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha14bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:888bc84af8dffc4565b215412a8a2bb56f0c78211a082b893d87595cd9f555c1 +size 411878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff new file mode 100644 index 0000000000..cfb5082f14 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eae92c012ad56c084929e0a2aff7c93091224d9f8ab7f52f71b845792d6b763 +size 470678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff new file mode 100644 index 0000000000..5cd4d9c457 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha16bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbab54f221956215266c35bfd26fdfb123e092e3836e2401b9f24e1c5b23516e +size 470678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff new file mode 100644 index 0000000000..c0f54d865b --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd9fa514619604275cede0b4747291db2f8e5ad02095565c891ace2b537d6336 +size 705878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff new file mode 100644 index 0000000000..33f7bee0fa --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha24bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:915ca9bbda952fc9ac78b44be07dab603948d51fb1a274935905e73cfe5bb0b9 +size 705878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff new file mode 100644 index 0000000000..cfbc058325 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha2bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f650c49faed4fd19b5527a0771489110090948e4ed33daa53b42c1776e288d89 +size 59078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff new file mode 100644 index 0000000000..0e11ef26cb --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cad4c5f42d77539ce1f67efa7e0ed1fa4f5dd32b3269e5862453d878c6b18d7 +size 941078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff new file mode 100644 index 0000000000..cfb8beb2bb --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha32bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8874322776b8620573c26a3c84b8c7c9bf0aeaa7d68a7fef009f8838d14dca5b +size 941078 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff new file mode 100644 index 0000000000..aed59c3317 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha3bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0d89ddcda8525799b90c1cb4a15f3ea1cf399c2259017f219b1d09876161587 +size 88478 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff new file mode 100644 index 0000000000..7176c382f1 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha4bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be006e56c2c2f34686293e8a5f4397a7bf873ff927d4dd0808cac90310569254 +size 117878 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff new file mode 100644 index 0000000000..aad945b8e7 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha5bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2928f06ef146625f5c696272901a63644699e3410dc155b7e4e470009a7f6dfc +size 147278 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff new file mode 100644 index 0000000000..9909afe71e --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha6bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:514ce84d3506aab7360b24f63aa1da1ea66abd9b1e534a12487d03486a7e593b +size 176678 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff new file mode 100644 index 0000000000..47270d98de --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlpha8bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:234401d70156cc748a67992919f8780bb855bc5e87e404b573f61b5eb4817dcf +size 266816 diff --git a/tests/Images/Input/Tiff/flower-rgb-3bit.tiff b/tests/Images/Input/Tiff/flower-rgb-3bit.tiff new file mode 100644 index 0000000000..db5f7916c7 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-3bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b778a97467b1475c47c71b5f029c23b0962309095b8702cfc81c8fbaf4b8edb +size 3886 diff --git a/tests/Images/Input/Tiff/flower-rgb-5bit.tiff b/tests/Images/Input/Tiff/flower-rgb-5bit.tiff new file mode 100644 index 0000000000..af1bc3921d --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-5bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44a53ffce2bfd3f1010a1fe232c8010708458880230f11b75ea3ef16b5164ce1 +size 6208 diff --git a/tests/Images/Input/Tiff/flower-rgb-6bit.tiff b/tests/Images/Input/Tiff/flower-rgb-6bit.tiff new file mode 100644 index 0000000000..b0399487d5 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-6bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d1db5b448aa9d61dd38dfb86e91e04fd0779a9c68cc2073913bcee3c635b5fe +size 7412 From e2bc1af8f40d6219efb31e44836812cd042454e1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 7 Mar 2022 13:00:41 +0100 Subject: [PATCH 2/6] Add support for decoding Tiff images with UnassociatedAlphaData and planar configuration --- .../Rgba16PlanarTiffColor{TPixel}.cs | 75 ++++++++++++++++ .../Rgba24PlanarTiffColor{TPixel}.cs | 85 ++++++++++++++++++ .../Rgba32PlanarTiffColor{TPixel}.cs | 74 ++++++++++++++++ .../RgbaPlanarTiffColor{TPixel}.cs | 87 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 16 ++++ .../TiffColorType.cs | 20 +++++ .../Formats/Tiff/TiffDecoderCore.cs | 15 ++-- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 8 +- .../Formats/Tiff/TiffDecoderTests.cs | 35 +++++++- tests/ImageSharp.Tests/TestImages.cs | 19 ++-- .../RgbaUnassociatedAlphaPlanar16bit_lsb.tiff | 3 + .../RgbaUnassociatedAlphaPlanar16bit_msb.tiff | 3 + .../RgbaUnassociatedAlphaPlanar24bit_lsb.tiff | 3 + .../RgbaUnassociatedAlphaPlanar24bit_msb.tiff | 3 + .../RgbaUnassociatedAlphaPlanar32bit_lsb.tiff | 3 + .../RgbaUnassociatedAlphaPlanar32bit_msb.tiff | 3 + .../Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff | 3 + 17 files changed, 437 insertions(+), 18 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff create mode 100644 tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..705010ce90 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit. + /// + internal class Rgba16PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D 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 + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); + ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); + + offset += 2; + + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); + ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); + + offset += 2; + + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..654074b1b1 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit. + /// + internal class Rgba24PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D 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); + byte[] buffer = new byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + Span bufferSpan = buffer.AsSpan(bufferStartIdx); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + alphaData.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); + + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + alphaData.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); + + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..e231111590 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit. + /// + internal class Rgba32PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D 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); + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); + ulong a = TiffUtils.ConvertToUIntBigEndian(alphaData.Slice(offset, 4)); + + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); + ulong a = TiffUtils.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4)); + + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..f2ccf2ec81 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths). + /// + internal class RgbaPlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly float rFactor; + + private readonly float gFactor; + + private readonly float bFactor; + + private readonly float aFactor; + + private readonly ushort bitsPerSampleR; + + private readonly ushort bitsPerSampleG; + + private readonly ushort bitsPerSampleB; + + private readonly ushort bitsPerSampleA; + + public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + this.bitsPerSampleA = bitsPerSample.Channel3; + + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + } + + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The buffers 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. + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + var rBitReader = new BitReader(data[0].GetSpan()); + var gBitReader = new BitReader(data[1].GetSpan()); + var bBitReader = new BitReader(data[2].GetSpan()); + var aBitReader = new BitReader(data[3].GetSpan()); + + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) + { + float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; + + color.FromVector4(new Vector4(r, g, b, a)); + pixelRow[x] = color; + } + + rBitReader.NextRow(); + gBitReader.NextRow(); + bBitReader.NextRow(); + aBitReader.NextRow(); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 25ca534315..1c608e3033 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -406,6 +406,10 @@ public static TiffBasePlanarColorDecoder CreatePlanar( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbPlanarTiffColor(bitsPerSample); + case TiffColorType.Rgba8888Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaPlanarTiffColor(bitsPerSample); + case TiffColorType.YCbCrPlanar: return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); @@ -413,14 +417,26 @@ public static TiffBasePlanarColorDecoder CreatePlanar( DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba16161616Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb242424Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba24242424Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb323232Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba32323232Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + default: throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index a0a48c2b93..6b39224fbd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -233,21 +233,41 @@ internal enum TiffColorType /// Rgb888Planar, + /// + /// RGBA color image with an alpha channel. Planar configuration of data. 8 Bit per color channel. + /// + Rgba8888Planar, + /// /// RGB Full Color. Planar configuration of data. 16 Bit per color channel. /// Rgb161616Planar, + /// + /// RGB Color with an alpha channel. Planar configuration of data. 16 Bit per color channel. + /// + Rgba16161616Planar, + /// /// RGB Full Color. Planar configuration of data. 24 Bit per color channel. /// Rgb242424Planar, + /// + /// RGB Color with an alpha channel. Planar configuration of data. 24 Bit per color channel. + /// + Rgba24242424Planar, + /// /// RGB Full Color. Planar configuration of data. 32 Bit per color channel. /// Rgb323232Planar, + /// + /// RGB Color with an alpha channel. Planar configuration of data. 32 Bit per color channel. + /// + Rgba32323232Planar, + /// /// The pixels are stored in YCbCr format. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index aaaef0b8a7..e5b810738a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -280,12 +280,10 @@ private IMemoryOwner ConvertNumbers(Array array, out Span span) return memory; } - else - { - DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); - span = (ulong[])array; - return null; - } + + DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); + span = (ulong[])array; + return null; } /// @@ -319,8 +317,11 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1) case 2: bitsPerPixel = this.BitsPerSample.Channel2; break; + case 3: + bitsPerPixel = this.BitsPerSample.Channel2; + break; default: - TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported"); + TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); break; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 0f1ffc79ff..23bc5f15f2 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -339,16 +339,16 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi switch (bitsPerChannel) { case 32: - options.ColorType = TiffColorType.Rgb323232Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232Planar : TiffColorType.Rgba32323232Planar; break; case 24: - options.ColorType = TiffColorType.Rgb242424Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424Planar : TiffColorType.Rgba24242424Planar; break; case 16: - options.ColorType = TiffColorType.Rgb161616Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616Planar : TiffColorType.Rgba16161616Planar; break; default: - options.ColorType = TiffColorType.Rgb888Planar; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888Planar : TiffColorType.Rgba8888Planar; break; } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index de8ae8e729..18e39d44a5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -78,9 +78,42 @@ public void TiffDecoder_CanDecode_Uncompressed(TestImageProvider [Theory] [WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)] [WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)] - public void TiffDecoder_Planar(TestImageProvider provider) + public void TiffDecoder_CanDecode_Planar(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Rgba8BitPlanarUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba16BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_64Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba24BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba24BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_96Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + + [Theory] + [WithFile(Rgba32BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_128Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 476d54e83f..9d37a0de47 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -865,18 +865,25 @@ public static class Tiff public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; + public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff"; public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; - public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; + public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; - public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; + public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; - public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; + public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; - public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; + public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff"; + public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff"; public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; - public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; + public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff"; + public const string Rgba24BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff"; + public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff"; public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; - public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff"; + public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff"; + public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff"; public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff new file mode 100644 index 0000000000..a24d74c19d --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bc4325ce7be8a16d23c559300c79c3228c2f5a4c266844ba49763a32d29f10e +size 470710 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff new file mode 100644 index 0000000000..b7393cb227 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08f8c284f7a9a6f362c09a418d85a94a1fe09bfc3f4cfe6859a82d6814718091 +size 470710 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff new file mode 100644 index 0000000000..fd4171158f --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ba4da7d63931f4462113e00bdee9e66e333ca42a47a33f62057c248bf4696ef +size 705910 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff new file mode 100644 index 0000000000..7aa6beeaaf --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:862ec9153cc755aa3ece8965a9d001a630ff756dfb018a9474661730482964cb +size 705910 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff new file mode 100644 index 0000000000..fb5dd1dccb --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc4f67dacd3262418769831aeabf99c9a88a9674fabf9a08c8b3d3e47ac6d07a +size 941110 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff new file mode 100644 index 0000000000..6c45e96fa6 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a98a0176f5db7af5b22c74f4a518c2df1055b5ca7e732f63426b3df8090fc313 +size 941110 diff --git a/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff new file mode 100644 index 0000000000..f853cbc623 --- /dev/null +++ b/tests/Images/Input/Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2185ef9a84701bcff104d4f2fe40171ed853e5d02a2049b209ee2a4c65105ea9 +size 235502 From c08545ef82a9560d32ff60e0a8b91f0e7d527f93 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 7 Mar 2022 13:16:30 +0100 Subject: [PATCH 3/6] Use stackalloc --- .../PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs | 4 ++-- .../Rgb24PlanarTiffColor{TPixel}.cs | 4 ++-- .../Rgba24242424TiffColor{TPixel}.cs | 4 ++-- .../Rgba24PlanarTiffColor{TPixel}.cs | 4 ++-- src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 985ffeb182..4130ee1a29 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -30,10 +30,10 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); int offset = 0; - byte[] buffer = new byte[4]; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; - Span bufferSpan = buffer.AsSpan(bufferStartIdx); + Span bufferSpan = buffer.Slice(bufferStartIdx); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index ac4435db63..e37fff1e74 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -30,13 +30,13 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); - byte[] buffer = new byte[4]; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); Span blueData = data[2].GetSpan(); - Span bufferSpan = buffer.AsSpan(bufferStartIdx); + Span bufferSpan = buffer.Slice(bufferStartIdx); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs index 3d88d9fb5a..30f1d0f75e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs @@ -30,10 +30,10 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); int offset = 0; - byte[] buffer = new byte[4]; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; - Span bufferSpan = buffer.AsSpan(bufferStartIdx); + Span bufferSpan = buffer.Slice(bufferStartIdx); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs index 654074b1b1..43b677b77e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs @@ -30,14 +30,14 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); - byte[] buffer = new byte[4]; + Span buffer = stackalloc byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); Span blueData = data[2].GetSpan(); Span alphaData = data[3].GetSpan(); - Span bufferSpan = buffer.AsSpan(bufferStartIdx); + Span bufferSpan = buffer.Slice(bufferStartIdx); int offset = 0; for (int y = top; y < top + height; y++) diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 6ec905929f..fef4bbe2e4 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -18,11 +18,11 @@ internal static class TiffUtils private const float Scale32Bit = 1.0f / 0xFFFFFFFF; - public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); + public static Vector4 Vector4Default { get; } = new(0.0f, 0.0f, 0.0f, 0.0f); - public static Rgba64 Rgba64Default { get; } = new Rgba64(0, 0, 0, 0); + public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0); - public static L16 L16Default { get; } = new L16(0); + public static L16 L16Default { get; } = new(0); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ConvertToUShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); From 198c1de6febaf4d5df66d3b35f2b6498dc4cf777 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 7 Mar 2022 17:33:19 +0100 Subject: [PATCH 4/6] Fix issue with 24 bits per channel --- .../Rgba24242424TiffColor{TPixel}.cs | 4 ++-- .../Rgba24PlanarTiffColor{TPixel}.cs | 4 ++-- src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs | 11 ++++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs index 30f1d0f75e..71e1f7abd6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs @@ -58,7 +58,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } else @@ -81,7 +81,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs index 43b677b77e..03b78c3f87 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs @@ -58,7 +58,7 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } else @@ -76,7 +76,7 @@ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index fef4bbe2e4..b7d4b6e7cc 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -72,6 +72,15 @@ public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, a * Scale24Bit); + color.FromVector4(colorVector); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel @@ -85,7 +94,7 @@ public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) where TPixel : unmanaged, IPixel { - var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a); + var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a * Scale32Bit); color.FromVector4(colorVector); return color; } From 53db07a323badef653ba4eec6f3d45af1fdc07a9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 7 Mar 2022 17:40:10 +0100 Subject: [PATCH 5/6] Use pattern matching for magicFrame.Depth --- .../TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index f0834dc001..5d1b904c0a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -84,13 +84,13 @@ public Image Decode(Configuration configuration, Stream stream) MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth == 8 || magicFrame.Depth == 6 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10 || magicFrame.Depth == 12) + if (magicFrame.Depth is 8 or 6 or 4 or 2 or 1 or 10 or 12) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); FromRgba32Bytes(configuration, data, framePixels); } - else if (magicFrame.Depth == 16 || magicFrame.Depth == 14) + else if (magicFrame.Depth is 16 or 14) { ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); From bd703954dca8534ea5d6e126fcd2b9bfd4df8eb2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 7 Mar 2022 17:51:22 +0100 Subject: [PATCH 6/6] Add 5 and 3 to valid magicFrame.Depth --- .../Formats/Tiff/TiffDecoderTests.cs | 28 +++---------------- .../ReferenceCodecs/MagickReferenceDecoder.cs | 2 +- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 18e39d44a5..5e5e1b7781 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -164,12 +164,7 @@ public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha(TestImagePr [Theory] [WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(Flower10BitGray, PixelTypes.Rgba32)] @@ -190,12 +185,7 @@ public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider p [Theory] [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(Flower14BitGray, PixelTypes.Rgba32)] @@ -205,12 +195,7 @@ public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider p [Theory] [WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] @@ -239,12 +224,7 @@ public void TiffDecoder_CanDecode_18Bit(TestImageProvider provid [Theory] [WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 5d1b904c0a..a612612934 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -84,7 +84,7 @@ public Image Decode(Configuration configuration, Stream stream) MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth is 8 or 6 or 4 or 2 or 1 or 10 or 12) + if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA);