Browse Source

Add support for decoding ycbcr tiff's

pull/1731/head
Brian Popow 5 years ago
parent
commit
8b469b4368
  1. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  2. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  3. 138
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  4. 12
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  5. 14
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  6. 45
      src/ImageSharp/Primitives/Rational.cs
  7. 10
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  8. 8
      tests/ImageSharp.Tests/TestImages.cs
  9. 3
      tests/Images/Input/Tiff/flower-rgb-contig-08.tiff
  10. 3
      tests/Images/Input/Tiff/flower_ycbcr_contig-08.tiff

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

@ -8,7 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal static class TiffColorDecoderFactory<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
public static TiffBaseColorDecoder<TPixel> Create(
TiffColorType colorType,
TiffBitsPerSample bitsPerSample,
ushort[] colorMap,
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
ByteOrder byteOrder)
{
switch (colorType)
{
@ -140,6 +146,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.NotNull(colorMap, "colorMap");
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
case TiffColorType.YCbCr:
return new YCbCrTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}

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

@ -107,5 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// RGB Full Color. Planar configuration of data.
/// </summary>
RgbPlanar,
YCbCr
}
}

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

@ -0,0 +1,138 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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 YCbCrTiffColor(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]);
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
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.ConvertToRgba32(data[offset], data[offset + 1], data[offset + 2]);
color.FromRgba32(rgba);
pixelRow[x] = color;
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;
}
}
}
}

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

@ -75,6 +75,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public TiffColorType ColorType { get; set; }
public Rational[] ReferenceBlackAndWhite { get; set; }
public Rational[] YcbcrCoefficients { get; set; }
/// <summary>
/// Gets or sets the compression used, when the image was encoded.
/// </summary>
@ -316,7 +320,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.Predictor,
this.FaxCompressionOptions);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(
this.ColorType,
this.BitsPerSample,
this.ColorMap,
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
this.byteOrder);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{

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

@ -69,6 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.ReferenceBlackAndWhite = exifProfile.GetValue(ExifTag.ReferenceBlackWhite)?.Value;
options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -264,6 +266,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
case TiffPhotometricInterpretation.YCbCr:
{
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
if (options.BitsPerSample.Channels != 3)
{
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
options.ColorType = TiffColorType.YCbCr;
break;
}
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}");

45
src/ImageSharp/Primitives/Rational.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp
{
if (simplify)
{
var rational = new LongRational(numerator, denominator).Simplify();
LongRational rational = new LongRational(numerator, denominator).Simplify();
this.Numerator = (uint)rational.Numerator;
this.Denominator = (uint)rational.Denominator;
@ -93,10 +93,7 @@ namespace SixLabors.ImageSharp
/// <param name="left">The first <see cref="Rational"/> to compare.</param>
/// <param name="right"> The second <see cref="Rational"/> to compare.</param>
/// <returns>The <see cref="bool"/></returns>
public static bool operator ==(Rational left, Rational right)
{
return left.Equals(right);
}
public static bool operator ==(Rational left, Rational right) => left.Equals(right);
/// <summary>
/// Determines whether the specified <see cref="Rational"/> instances are not considered equal.
@ -104,10 +101,7 @@ namespace SixLabors.ImageSharp
/// <param name="left">The first <see cref="Rational"/> to compare.</param>
/// <param name="right"> The second <see cref="Rational"/> to compare.</param>
/// <returns>The <see cref="bool"/></returns>
public static bool operator !=(Rational left, Rational right)
{
return !left.Equals(right);
}
public static bool operator !=(Rational left, Rational right) => !left.Equals(right);
/// <summary>
/// Converts the specified <see cref="double"/> to an instance of this type.
@ -116,10 +110,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="Rational"/>.
/// </returns>
public static Rational FromDouble(double value)
{
return new Rational(value, false);
}
public static Rational FromDouble(double value) => new Rational(value, false);
/// <summary>
/// Converts the specified <see cref="double"/> to an instance of this type.
@ -129,16 +120,10 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="Rational"/>.
/// </returns>
public static Rational FromDouble(double value, bool bestPrecision)
{
return new Rational(value, bestPrecision);
}
public static Rational FromDouble(double value, bool bestPrecision) => new Rational(value, bestPrecision);
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is Rational other && this.Equals(other);
}
public override bool Equals(object obj) => obj is Rational other && this.Equals(other);
/// <inheritdoc/>
public bool Equals(Rational other)
@ -162,16 +147,18 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="double"/>.
/// </returns>
public double ToDouble()
{
return this.Numerator / (double)this.Denominator;
}
public double ToDouble() => this.Numerator / (double)this.Denominator;
/// <summary>
/// Converts a rational number to the nearest <see cref="float"/>.
/// </summary>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
public float ToSingle() => this.Numerator / (float)this.Denominator;
/// <inheritdoc/>
public override string ToString()
{
return this.ToString(CultureInfo.InvariantCulture);
}
public override string ToString() => this.ToString(CultureInfo.InvariantCulture);
/// <summary>
/// Converts the numeric value of this instance to its equivalent string representation using

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

@ -153,6 +153,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_YCbCr_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]

8
tests/ImageSharp.Tests/TestImages.cs

@ -569,15 +569,17 @@ namespace SixLabors.ImageSharp.Tests
public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff";
public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff";
public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff";
public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff";
public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff";
public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff";
public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff";
public const string FlowerRgb888Contiguous = "Tiff/flower-rgb-contig-08.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 FlowerYCbCr888Contiguous = "Tiff/flower_ycbcr_contig-08.tiff";
public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-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 FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.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 Flower2BitGray = "Tiff/flower-minisblack-02.tiff";
public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff";
public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff";

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

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

3
tests/Images/Input/Tiff/flower_ycbcr_contig-08.tiff

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