Browse Source

Add support for decoding ycbcr tiff's with planar configuration

pull/1731/head
Brian Popow 5 years ago
parent
commit
da8f14d97f
  1. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  2. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  3. 121
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs
  4. 40
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
  5. 79
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  6. 19
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  7. 3
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  8. 1
      tests/ImageSharp.Tests/TestImages.cs
  9. 3
      tests/Images/Input/Tiff/flower-ycbcr-planar-08.tiff

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

@ -154,7 +154,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
} }
} }
public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder) public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(
TiffColorType colorType,
TiffBitsPerSample bitsPerSample,
ushort[] colorMap,
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
ByteOrder byteOrder)
{ {
switch (colorType) switch (colorType)
{ {
@ -167,6 +173,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
return new RgbPlanarTiffColor<TPixel>(bitsPerSample); return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients);
default: default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString()); throw TiffThrowHelper.InvalidColorType(colorType.ToString());
} }

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

@ -108,6 +108,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary> /// </summary>
RgbPlanar, RgbPlanar,
YCbCr /// <summary>
/// The pixels are stored in YCbCr format.
/// </summary>
YCbCr,
/// <summary>
/// The pixels are stored in YCbCr format as planar.
/// </summary>
YCbCrPlanar
} }
} }

121
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs

@ -0,0 +1,121 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Converts YCbCr data to rgb data.
/// </summary>
internal class YCbCrConverter
{
private readonly CodingRangeExpander yExpander;
private readonly CodingRangeExpander cbExpander;
private readonly CodingRangeExpander crExpander;
private readonly YCbCrToRgbConverter converter;
private static readonly Rational[] DefaultLuma =
{
new Rational(299, 1000),
new Rational(587, 1000),
new Rational(114, 1000)
};
private static readonly Rational[] DefaultReferenceBlackWhite =
{
new Rational(0, 1), new Rational(255, 1),
new Rational(128, 1), new Rational(255, 1),
new Rational(128, 1), new Rational(255, 1)
};
public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients)
{
referenceBlackAndWhite ??= DefaultReferenceBlackWhite;
coefficients ??= DefaultLuma;
if (referenceBlackAndWhite.Length != 6)
{
TiffThrowHelper.ThrowImageFormatException("reference black and white array should have 6 entry's");
}
if (coefficients.Length != 3)
{
TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's");
}
this.yExpander = new CodingRangeExpander(referenceBlackAndWhite[0], referenceBlackAndWhite[1], 255);
this.cbExpander = new CodingRangeExpander(referenceBlackAndWhite[2], referenceBlackAndWhite[3], 127);
this.crExpander = new CodingRangeExpander(referenceBlackAndWhite[4], referenceBlackAndWhite[5], 127);
this.converter = new YCbCrToRgbConverter(coefficients[0], coefficients[1], coefficients[2]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgba32 ConvertToRgba32(byte y, byte cb, byte cr)
{
float yExpanded = this.yExpander.Expand(y);
float cbExpanded = this.cbExpander.Expand(cb);
float crExpanded = this.crExpander.Expand(cr);
Rgba32 rgba = this.converter.Convert(yExpanded, cbExpanded, crExpanded);
return rgba;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte RoundAndClampTo8Bit(float value)
{
int input = (int)MathF.Round(value);
return (byte)Math.Min(Math.Max(input, 0), 255);
}
private readonly struct CodingRangeExpander
{
private readonly float f1;
private readonly float f2;
public CodingRangeExpander(Rational referenceBlack, Rational referenceWhite, int codingRange)
{
float black = referenceBlack.ToSingle();
float white = referenceWhite.ToSingle();
this.f1 = codingRange / (white - black);
this.f2 = this.f1 * black;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float code) => (code * this.f1) - this.f2;
}
private readonly struct YCbCrToRgbConverter
{
private readonly float cr2R;
private readonly float cb2B;
private readonly float y2G;
private readonly float cr2G;
private readonly float cb2G;
public YCbCrToRgbConverter(Rational lumaRed, Rational lumaGreen, Rational lumaBlue)
{
this.cr2R = 2 - (2 * lumaRed.ToSingle());
this.cb2B = 2 - (2 * lumaBlue.ToSingle());
this.y2G = (1 - lumaBlue.ToSingle() - lumaRed.ToSingle()) / lumaGreen.ToSingle();
this.cr2G = 2 * lumaRed.ToSingle() * (lumaRed.ToSingle() - 1) / lumaGreen.ToSingle();
this.cb2G = 2 * lumaBlue.ToSingle() * (lumaBlue.ToSingle() - 1) / lumaGreen.ToSingle();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgba32 Convert(float y, float cb, float cr)
{
var pixel = default(Rgba32);
pixel.R = RoundAndClampTo8Bit((cr * this.cr2R) + y);
pixel.G = RoundAndClampTo8Bit((this.y2G * y) + (this.cr2G * cr) + (this.cb2G * cb));
pixel.B = RoundAndClampTo8Bit((cb * this.cb2B) + y);
pixel.A = byte.MaxValue;
return pixel;
}
}
}
}

40
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly YCbCrConverter converter;
public YCbCrPlanarTiffColor(Rational[] referenceBlackAndWhite, Rational[] coefficients) => this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients);
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
Span<byte> yData = data[0].GetSpan();
Span<byte> cbData = data[1].GetSpan();
Span<byte> crData = data[2].GetSpan();
var color = default(TPixel);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]);
color.FromRgba32(rgba);
pixelRow[x] = color;
offset++;
}
}
}
}
}

79
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -11,10 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly CodingRangeExpander yExpander; private readonly YCbCrConverter converter;
private readonly CodingRangeExpander cbExpander;
private readonly CodingRangeExpander crExpander;
private readonly YCbCrToRgbConverter converter;
private static readonly Rational[] DefaultLuma = private static readonly Rational[] DefaultLuma =
{ {
@ -45,10 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's"); TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's");
} }
this.yExpander = new CodingRangeExpander(referenceBlackAndWhite[0], referenceBlackAndWhite[1], 255); this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients);
this.cbExpander = new CodingRangeExpander(referenceBlackAndWhite[2], referenceBlackAndWhite[3], 127);
this.crExpander = new CodingRangeExpander(referenceBlackAndWhite[4], referenceBlackAndWhite[5], 127);
this.converter = new YCbCrToRgbConverter(coefficients[0], coefficients[1], coefficients[2]);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -61,78 +54,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++) for (int x = 0; x < pixelRow.Length; x++)
{ {
Rgba32 rgba = this.ConvertToRgba32(data[offset], data[offset + 1], data[offset + 2]); Rgba32 rgba = this.converter.ConvertToRgba32(data[offset], data[offset + 1], data[offset + 2]);
color.FromRgba32(rgba); color.FromRgba32(rgba);
pixelRow[x] = color; pixelRow[x] = color;
offset += 3; offset += 3;
} }
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Rgba32 ConvertToRgba32(byte y, byte cb, byte cr)
{
float yExpanded = this.yExpander.Expand(y);
float cbExpanded = this.cbExpander.Expand(cb);
float crExpanded = this.crExpander.Expand(cr);
Rgba32 rgba = this.converter.Convert(yExpanded, cbExpanded, crExpanded);
return rgba;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte RoundAndClampTo8Bit(float value)
{
int input = (int)MathF.Round(value);
return (byte)Math.Min(Math.Max(input, 0), 255);
}
private readonly struct CodingRangeExpander
{
private readonly float f1;
private readonly float f2;
public CodingRangeExpander(Rational referenceBlack, Rational referenceWhite, int codingRange)
{
float black = referenceBlack.ToSingle();
float white = referenceWhite.ToSingle();
this.f1 = codingRange / (white - black);
this.f2 = this.f1 * black;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float code) => (code * this.f1) - this.f2;
}
private readonly struct YCbCrToRgbConverter
{
private readonly float cr2R;
private readonly float cb2B;
private readonly float y2G;
private readonly float cr2G;
private readonly float cb2G;
public YCbCrToRgbConverter(Rational lumaRed, Rational lumaGreen, Rational lumaBlue)
{
this.cr2R = 2 - (2 * lumaRed.ToSingle());
this.cb2B = 2 - (2 * lumaBlue.ToSingle());
this.y2G = (1 - lumaBlue.ToSingle() - lumaRed.ToSingle()) / lumaGreen.ToSingle();
this.cr2G = 2 * lumaRed.ToSingle() * (lumaRed.ToSingle() - 1) / lumaGreen.ToSingle();
this.cb2G = 2 * lumaBlue.ToSingle() * (lumaBlue.ToSingle() - 1) / lumaGreen.ToSingle();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgba32 Convert(float y, float cb, float cr)
{
var pixel = default(Rgba32);
pixel.R = RoundAndClampTo8Bit((cr * this.cr2R) + y);
pixel.G = RoundAndClampTo8Bit((this.y2G * y) + (this.cr2G * cr) + (this.cb2G * cb));
pixel.B = RoundAndClampTo8Bit((cb * this.cb2B) + y);
pixel.A = byte.MaxValue;
return pixel;
}
}
} }
} }

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

@ -268,9 +268,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize); stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
} }
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.CompressionType,
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); this.memoryAllocator,
this.PhotometricInterpretation,
frame.Width,
bitsPerPixel,
this.Predictor,
this.FaxCompressionOptions);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(
this.ColorType,
this.BitsPerSample,
this.ColorMap,
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
this.byteOrder);
for (int i = 0; i < stripsPerPlane; i++) for (int i = 0; i < stripsPerPlane; i++)
{ {

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

@ -274,7 +274,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
} }
options.ColorType = TiffColorType.YCbCr; options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.YCbCr : TiffColorType.YCbCrPlanar;
break; break;
} }

1
tests/ImageSharp.Tests/TestImages.cs

@ -576,6 +576,7 @@ namespace SixLabors.ImageSharp.Tests
public const string FlowerRgb888Planar6Strips = "Tiff/flower-rgb-planar-08-6strips.tiff"; public const string FlowerRgb888Planar6Strips = "Tiff/flower-rgb-planar-08-6strips.tiff";
public const string FlowerRgb888Planar15Strips = "Tiff/flower-rgb-planar-08-15strips.tiff"; public const string FlowerRgb888Planar15Strips = "Tiff/flower-rgb-planar-08-15strips.tiff";
public const string FlowerYCbCr888Contiguous = "Tiff/flower_ycbcr_contig-08.tiff"; public const string FlowerYCbCr888Contiguous = "Tiff/flower_ycbcr_contig-08.tiff";
public const string FlowerYCbCr888Planar = "Tiff/flower_ycbcr_planar-08.tiff";
public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff";
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";

3
tests/Images/Input/Tiff/flower-ycbcr-planar-08.tiff

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