mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
23 changed files with 506 additions and 40 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:dd1333eb93d8e7ea614b755ca1c8909c67b4b44fc03a8cab6be5491bf4d15841 |
|||
size 9753 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:95f7b71c3a333734f799d73076032e31a6dfff1802bb3b454ba1eada7be50b0d |
|||
size 10058 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:270e0331818a755f5fac600172eacbcbebda86f93f521bfc8d75f4b8bc530177 |
|||
size 6944 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:7ef6ebc9dfe72fbe6ed65ebfc2465ebb18f326119a640faf3301aa4cfa31990f |
|||
size 5464 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:c5ea966cc7b823a5d228b49cdc55a261353f73b1eb94a218f1c68321d757e25f |
|||
size 4342 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:cdc4d8033214a6737f41c4e32d9314db77b3b1ae14515496f10468047390f6c5 |
|||
size 10042 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:389ee18596cd3d9f1f7f04b4db8fd21edce2900837c17ebb57cc4b64a6820f3e |
|||
size 120354 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:95b1ba4ff48ea2263041eca4ada44d009277297bb3b3a185d48580bdf3f7caaf |
|||
size 81382 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:fbd835c2406700523b239b80299b2b02c36d41182ac338f7ed7164979a787c60 |
|||
size 63438 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:399d5bc062baa00c2054a138489709379032f8683fbcb292bb2125b62e715b5f |
|||
size 50336 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:58c4914b32b27df1ef303bb127fe9211c2aeda23e17bb5f4b349543c96d845b7 |
|||
size 45152 |
|||
Loading…
Reference in new issue