Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for decoding Tiff images with UnassociatedAlphaData #2037

Merged
merged 9 commits into from Mar 8, 2022
@@ -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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel.
/// </summary>
internal class Rgba16161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;

private readonly Configuration configuration;

/// <summary>
/// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian)
{
this.configuration = configuration;
this.isBigEndian = isBigEndian;
}

/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);

int offset = 0;

for (int y = top; y < top + height; y++)
{
Span<TPixel> 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<TPixel>.Instance.FromRgba64Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);

offset += byteCount;
}
}
}
}
}
@@ -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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel.
/// </summary>
internal class Rgba24242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;

/// <summary>
/// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;

/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this allocation be avoided?
I.e. turn it into stackalloc and use it as Span<byte>. Maybe TiffUtils need to be updated also.

int bufferStartIdx = this.isBigEndian ? 1 : 0;

Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
for (int y = top; y < top + height; y++)
{
Span<TPixel> 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);
}
}
}
}
}
}
@@ -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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel.
/// </summary>
internal class Rgba32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;

/// <summary>
/// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;

/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;

for (int y = top; y < top + height; y++)
{
Span<TPixel> 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);
}
}
}
}
}
}
@@ -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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel.
/// </summary>
internal class Rgba8888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;

public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration;

/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
int offset = 0;

for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
int byteCount = pixelRow.Length * 4;
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);

offset += byteCount;
}
}
}
}
@@ -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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel.
/// </summary>
internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;

/// <summary>
/// Initializes a new instance of the <see cref="RgbaFloat32323232TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;

/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.


for (int y = top; y < top + height; y++)
{
Span<TPixel> 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be nice when we can use BinaryPrimitives.ReadSingleBigEndian here.

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;
}
}
}
}
}
}