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