Browse Source

Merge pull request #1731 from SixLabors/bp/ycbcr

Add support for decoding tiff's with YCbCr PhotometricInterpretation
pull/1742/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
d105ab4273
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      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. 80
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
  5. 102
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  6. 8
      src/ImageSharp/Formats/Tiff/README.md
  7. 35
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  8. 33
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  9. 17
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  10. 45
      src/ImageSharp/Primitives/Rational.cs
  11. 19
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  12. 16
      tests/ImageSharp.Tests/TestImages.cs
  13. 3
      tests/Images/Input/Tiff/flower-rgb-contig-08.tiff
  14. 3
      tests/Images/Input/Tiff/flower-ycbcr-contig-08_h1v1.tiff
  15. 3
      tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v1.tiff
  16. 3
      tests/Images/Input/Tiff/flower-ycbcr-contig-08_h2v2.tiff
  17. 3
      tests/Images/Input/Tiff/flower-ycbcr-contig-08_h4v4.tiff
  18. 3
      tests/Images/Input/Tiff/flower-ycbcr-planar-08_h1v1.tiff
  19. 3
      tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h1v1.tiff
  20. 3
      tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v1.tiff
  21. 3
      tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h2v2.tiff
  22. 3
      tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v2.tiff
  23. 3
      tests/Images/Input/Tiff/rgb-ycbcr-contig-08_h4v4.tiff

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
@ -8,7 +9,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal static class TiffColorDecoderFactory<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
public static TiffBaseColorDecoder<TPixel> Create(
Configuration configuration,
MemoryAllocator memoryAllocator,
TiffColorType colorType,
TiffBitsPerSample bitsPerSample,
ushort[] colorMap,
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
ushort[] ycbcrSubSampling,
ByteOrder byteOrder)
{
switch (colorType)
{
@ -200,12 +210,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.NotNull(colorMap, "colorMap");
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
case TiffColorType.YCbCr:
return new YCbCrTiffColor<TPixel>(memoryAllocator, referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}
}
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,
ushort[] ycbcrSubSampling,
ByteOrder byteOrder)
{
switch (colorType)
{
@ -213,6 +233,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
case TiffColorType.Rgb161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);

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

@ -167,5 +167,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// RGB Full Color. Planar configuration of data. 32 Bit per color channel.
/// </summary>
Rgb323232Planar,
/// <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)Numerics.Clamp(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;
}
}
}
}

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

@ -0,0 +1,80 @@
// 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
{
internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly YCbCrConverter converter;
private readonly ushort[] ycbcrSubSampling;
public YCbCrPlanarTiffColor(Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling)
{
this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients);
this.ycbcrSubSampling = ycbcrSubSampling;
}
/// <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();
if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1))
{
ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], cbData, crData);
}
var color = default(TPixel);
int offset = 0;
int widthPadding = 0;
if (this.ycbcrSubSampling != null)
{
// Round to the next integer multiple of horizontalSubSampling.
widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[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++;
}
offset += widthPadding;
}
}
private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, Span<byte> planarCb, Span<byte> planarCr)
{
// If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively,
// then the source data will be padded.
width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling);
height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling);
for (int row = height - 1; row >= 0; row--)
{
for (int col = width - 1; col >= 0; col--)
{
int offset = (row * width) + col;
int subSampleOffset = (row / verticalSubSampling * (width / horizontalSubSampling)) + (col / horizontalSubSampling);
planarCb[offset] = planarCb[subSampleOffset];
planarCr[offset] = planarCr[subSampleOffset];
}
}
}
}
}

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

@ -0,0 +1,102 @@
// 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
{
internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly MemoryAllocator memoryAllocator;
private readonly YCbCrConverter converter;
private readonly ushort[] ycbcrSubSampling;
public YCbCrTiffColor(MemoryAllocator memoryAllocator, Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling)
{
this.memoryAllocator = memoryAllocator;
this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients);
this.ycbcrSubSampling = ycbcrSubSampling;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
ReadOnlySpan<byte> ycbcrData = data;
if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1))
{
// 4 extra rows and columns for possible padding.
int paddedWidth = width + 4;
int paddedHeight = height + 4;
int requiredBytes = paddedWidth * paddedHeight * 3;
using IMemoryOwner<byte> tmpBuffer = this.memoryAllocator.Allocate<byte>(requiredBytes);
Span<byte> tmpBufferSpan = tmpBuffer.GetSpan();
ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan);
ycbcrData = tmpBufferSpan;
}
var color = default(TPixel);
int offset = 0;
int widthPadding = 0;
if (this.ycbcrSubSampling != null)
{
// Round to the next integer multiple of horizontalSubSampling.
widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[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(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]);
color.FromRgba32(rgba);
pixelRow[x] = color;
offset += 3;
}
offset += widthPadding * 3;
}
}
private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, ReadOnlySpan<byte> source, Span<byte> destination)
{
// If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively,
// then the source data will be padded.
width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling);
height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling);
int blockWidth = width / horizontalSubSampling;
int blockHeight = height / verticalSubSampling;
int cbCrOffsetInBlock = horizontalSubSampling * verticalSubSampling;
int blockByteCount = cbCrOffsetInBlock + 2;
for (int blockRow = blockHeight - 1; blockRow >= 0; blockRow--)
{
for (int blockCol = blockWidth - 1; blockCol >= 0; blockCol--)
{
int blockOffset = (blockRow * blockWidth) + blockCol;
ReadOnlySpan<byte> blockData = source.Slice(blockOffset * blockByteCount, blockByteCount);
byte cr = blockData[cbCrOffsetInBlock + 1];
byte cb = blockData[cbCrOffsetInBlock];
for (int row = verticalSubSampling - 1; row >= 0; row--)
{
for (int col = horizontalSubSampling - 1; col >= 0; col--)
{
int offset = 3 * ((((blockRow * verticalSubSampling) + row) * width) + (blockCol * horizontalSubSampling) + col);
destination[offset + 2] = cr;
destination[offset + 1] = cb;
destination[offset] = blockData[(row * horizontalSubSampling) + col];
}
}
}
}
}
}
}

8
src/ImageSharp/Formats/Tiff/README.md

@ -63,7 +63,7 @@
|PaletteColor | Y | Y | General implementation only |
|TransparencyMask | | | |
|Separated (TIFF Extension) | | | |
|YCbCr (TIFF Extension) | | | |
|YCbCr (TIFF Extension) | | Y | |
|CieLab (TIFF Extension) | | | |
|IccLab (TechNote 1) | | | |
@ -165,10 +165,10 @@
|JPEGQTables | | | |
|JPEGDCTables | | | |
|JPEGACTables | | | |
|YCbCrCoefficients | | | |
|YCbCrSubSampling | | | |
|YCbCrCoefficients | | Y | |
|YCbCrSubSampling | | Y | |
|YCbCrPositioning | | | |
|ReferenceBlackWhite | | | |
|ReferenceBlackWhite | | Y | |
|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). |
|XMP | Y | Y | |
|ImageID | | | |

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

@ -75,6 +75,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public TiffColorType ColorType { get; set; }
/// <summary>
/// Gets or sets the reference black and white for decoding YCbCr pixel data.
/// </summary>
public Rational[] ReferenceBlackAndWhite { get; set; }
/// <summary>
/// Gets or sets the YCbCr coefficients.
/// </summary>
public Rational[] YcbcrCoefficients { get; set; }
/// <summary>
/// Gets or sets the YCbCr sub sampling.
/// </summary>
public ushort[] YcbcrSubSampling { get; set; }
/// <summary>
/// Gets or sets the compression used, when the image was encoded.
/// </summary>
@ -286,7 +301,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.FillOrder,
this.byteOrder);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(
this.ColorType,
this.BitsPerSample,
this.ColorMap,
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
this.YcbcrSubSampling,
this.byteOrder);
for (int i = 0; i < stripsPerPlane; i++)
{
@ -339,7 +361,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.FillOrder,
this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(
this.Configuration,
this.memoryAllocator,
this.ColorType,
this.BitsPerSample,
this.ColorMap,
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
this.YcbcrSubSampling,
this.byteOrder);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{

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

@ -59,6 +59,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
ushort[] ycbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2)
{
TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values.");
}
if (ycbcrSubSampling != null && ycbcrSubSampling[1] > ycbcrSubSampling[0])
{
TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz.");
}
if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null)
{
TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported.");
@ -72,6 +83,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
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.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
options.FillOrder = fillOrder;
options.ParseColorType(exifProfile);
@ -339,6 +353,25 @@ 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.");
}
ushort bitsPerChannel = options.BitsPerSample.Channel0;
if (bitsPerChannel != 8)
{
TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for YCbCr images.");
}
options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.YCbCr : TiffColorType.YCbCrPlanar;
break;
}
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}");

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

@ -98,5 +98,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
color.FromVector4(colorVector);
return color;
}
/// <summary>
/// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value.
/// </summary>
/// <param name="valueToRoundUp">The width or height to round up.</param>
/// <param name="subSampling">The sub sampling.</param>
/// <returns>The padding.</returns>
public static int PaddingToNextInteger(int valueToRoundUp, int subSampling)
{
if (valueToRoundUp % subSampling == 0)
{
return 0;
}
int padding = subSampling - (valueToRoundUp % subSampling);
return padding;
}
}
}

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

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

@ -178,6 +178,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSave(provider);
}
[Theory]
[WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush1v1, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)]
[WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Contiguoush2v1, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Contiguoush2v2, PixelTypes.Rgba32)]
[WithFile(FlowerYCbCr888Contiguoush4v4, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_YCbCr_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong
// converting the pixel data from Magick.Net to our format with YCbCr?
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]

16
tests/ImageSharp.Tests/TestImages.cs

@ -585,15 +585,25 @@ 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_h1v1.tiff";
public const string FlowerYCbCr888Planar = "Tiff/flower-ycbcr-planar-08_h1v1.tiff";
public const string FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff";
public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.tiff";
public const string FlowerYCbCr888Contiguoush4v4 = "Tiff/flower-ycbcr-contig-08_h4v4.tiff";
public const string RgbYCbCr888Contiguoush1v1 = "Tiff/rgb-ycbcr-contig-08_h1v1.tiff";
public const string RgbYCbCr888Contiguoush2v1 = "Tiff/rgb-ycbcr-contig-08_h2v1.tiff";
public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff";
public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.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_h1v1.tiff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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