Browse Source

simplified YCbCrImage

pull/62/head
Anton Firszov 10 years ago
parent
commit
d9d8ee6445
  1. 117
      src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
  2. 30
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  3. 65
      tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs

117
src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs

@ -12,6 +12,24 @@ namespace ImageSharp.Formats.Jpg
/// </summary> /// </summary>
internal class YCbCrImage : IDisposable internal class YCbCrImage : IDisposable
{ {
// Complex value type field + mutable + available to other classes = the field MUST NOT be private :P
#pragma warning disable SA1401 // FieldsMustBePrivate
/// <summary>
/// Gets the luminance components channel as <see cref="JpegPixelArea" />.
/// </summary>
public JpegPixelArea YChannel;
/// <summary>
/// Gets the blue chroma components channel as <see cref="JpegPixelArea" />.
/// </summary>
public JpegPixelArea CbChannel;
/// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Cr channel
/// </summary>
public JpegPixelArea CrChannel;
#pragma warning restore SA1401
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="YCbCrImage" /> class. /// Initializes a new instance of the <see cref="YCbCrImage" /> class.
/// </summary> /// </summary>
@ -20,16 +38,15 @@ namespace ImageSharp.Formats.Jpg
/// <param name="ratio">The ratio.</param> /// <param name="ratio">The ratio.</param>
public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio) public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio)
{ {
int cw, ch; Size cSize = CalculateChrominanceSize(width, height, ratio);
YCbCrSize(width, height, ratio, out cw, out ch);
this.YPixels = BytePool.Rent(width * height);
this.CbPixels = BytePool.Rent(cw * ch);
this.CrPixels = BytePool.Rent(cw * ch);
this.Ratio = ratio; this.Ratio = ratio;
this.YOffset = 0;
this.COffset = 0;
this.YStride = width; this.YStride = width;
this.CStride = cw; this.CStride = cSize.Width;
this.YChannel = JpegPixelArea.CreatePooled(width, height);
this.CbChannel = JpegPixelArea.CreatePooled(cSize.Width, cSize.Height);
this.CrChannel = JpegPixelArea.CreatePooled(cSize.Width, cSize.Height);
} }
/// <summary> /// <summary>
@ -71,29 +88,9 @@ namespace ImageSharp.Formats.Jpg
private static ArrayPool<byte> BytePool => ArrayPool<byte>.Shared; private static ArrayPool<byte> BytePool => ArrayPool<byte>.Shared;
/// <summary> /// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Cb channel /// Gets the Y slice index delta between vertically adjacent pixels.
/// </summary>
public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset);
/// <summary>
/// Gets the blue chroma components channel.
/// </summary>
public byte[] CbPixels { get; }
/// <summary>
/// Gets the index of the first element of red or blue chroma.
/// </summary>
public int COffset { get; }
/// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Cr channel
/// </summary>
public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset);
/// <summary>
/// Gets the red chroma components channel.
/// </summary> /// </summary>
public byte[] CrPixels { get; } public int YStride { get; }
/// <summary> /// <summary>
/// Gets the red and blue chroma slice index delta between vertically adjacent pixels /// Gets the red and blue chroma slice index delta between vertically adjacent pixels
@ -106,34 +103,14 @@ namespace ImageSharp.Formats.Jpg
/// </summary> /// </summary>
public YCbCrSubsampleRatio Ratio { get; set; } public YCbCrSubsampleRatio Ratio { get; set; }
/// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Y channel
/// </summary>
public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset);
/// <summary>
/// Gets the index of the first luminance element.
/// </summary>
public int YOffset { get; }
/// <summary>
/// Gets the luminance components channel.
/// </summary>
public byte[] YPixels { get; }
/// <summary>
/// Gets the Y slice index delta between vertically adjacent pixels.
/// </summary>
public int YStride { get; }
/// <summary> /// <summary>
/// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools. /// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
BytePool.Return(this.YPixels); this.YChannel.ReturnPooled();
BytePool.Return(this.CrPixels); this.CbChannel.ReturnPooled();
BytePool.Return(this.CbPixels); this.CrChannel.ReturnPooled();
} }
/// <summary> /// <summary>
@ -180,43 +157,27 @@ namespace ImageSharp.Formats.Jpg
/// <param name="width">The width.</param> /// <param name="width">The width.</param>
/// <param name="height">The height.</param> /// <param name="height">The height.</param>
/// <param name="ratio">The subsampling ratio.</param> /// <param name="ratio">The subsampling ratio.</param>
/// <param name="chromaWidth">The chroma width.</param> /// <returns>The <see cref="Size"/> of the chrominance channel</returns>
/// <param name="chromaHeight">The chroma height.</param> internal static Size CalculateChrominanceSize(
private static void YCbCrSize(
int width, int width,
int height, int height,
YCbCrSubsampleRatio ratio, YCbCrSubsampleRatio ratio)
out int chromaWidth,
out int chromaHeight)
{ {
switch (ratio) switch (ratio)
{ {
case YCbCrSubsampleRatio.YCbCrSubsampleRatio422: case YCbCrSubsampleRatio.YCbCrSubsampleRatio422:
chromaWidth = (width + 1) / 2; return new Size((width + 1) / 2, height);
chromaHeight = height;
break;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio420: case YCbCrSubsampleRatio.YCbCrSubsampleRatio420:
chromaWidth = (width + 1) / 2; return new Size((width + 1) / 2, (height + 1) / 2);
chromaHeight = (height + 1) / 2;
break;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio440: case YCbCrSubsampleRatio.YCbCrSubsampleRatio440:
chromaWidth = width; return new Size(width, (height + 1) / 2);
chromaHeight = (height + 1) / 2;
break;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio411: case YCbCrSubsampleRatio.YCbCrSubsampleRatio411:
chromaWidth = (width + 3) / 4; return new Size((width + 3) / 4, height);
chromaHeight = height;
break;
case YCbCrSubsampleRatio.YCbCrSubsampleRatio410: case YCbCrSubsampleRatio.YCbCrSubsampleRatio410:
chromaWidth = (width + 3) / 4; return new Size((width + 3) / 4, (height + 1) / 2);
chromaHeight = (height + 1) / 2;
break;
default: default:
// Default to 4:4:4 subsampling. // Default to 4:4:4 subsampling.
chromaWidth = width; return new Size(width, height);
chromaHeight = height;
break;
} }
} }
} }

30
src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs

@ -21,7 +21,7 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public const int MaxComponents = 4; public const int MaxComponents = 4;
// Complex value type field + mutable + available to other classes == the field MUST NOT be private :P // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P
#pragma warning disable SA1401 // FieldsMustBePrivate #pragma warning disable SA1401 // FieldsMustBePrivate
/// <summary> /// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream. /// Holds the unprocessed bits that have been taken from the byte-stream.
@ -650,14 +650,15 @@ namespace ImageSharp.Formats
height, height,
y => y =>
{ {
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
byte cyan = this.ycbcrImage.YPixels[yo + x]; byte cyan = this.ycbcrImage.YChannel.Pixels[yo + x];
byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)]; byte magenta = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)]; byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor); TColor packed = default(TColor);
this.PackCmyk<TColor>(ref packed, cyan, magenta, yellow, x, y); this.PackCmyk<TColor>(ref packed, cyan, magenta, yellow, x, y);
@ -725,14 +726,15 @@ namespace ImageSharp.Formats
Bootstrapper.ParallelOptions, Bootstrapper.ParallelOptions,
y => y =>
{ {
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
byte red = this.ycbcrImage.YPixels[yo + x]; byte red = this.ycbcrImage.YChannel.Pixels[yo + x];
byte green = this.ycbcrImage.CbPixels[co + (x / scale)]; byte green = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte blue = this.ycbcrImage.CrPixels[co + (x / scale)]; byte blue = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromBytes(red, green, blue, 255); packed.PackFromBytes(red, green, blue, 255);
@ -765,14 +767,15 @@ namespace ImageSharp.Formats
Bootstrapper.ParallelOptions, Bootstrapper.ParallelOptions,
y => y =>
{ {
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
byte yy = this.ycbcrImage.YPixels[yo + x]; byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor); TColor packed = default(TColor);
PackYcbCr<TColor>(ref packed, yy, cb, cr); PackYcbCr<TColor>(ref packed, yy, cb, cr);
@ -805,14 +808,15 @@ namespace ImageSharp.Formats
height, height,
y => y =>
{ {
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
byte yy = this.ycbcrImage.YPixels[yo + x]; byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
byte cb = this.ycbcrImage.CbPixels[co + (x / scale)]; byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrPixels[co + (x / scale)]; byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor); TColor packed = default(TColor);
this.PackYcck<TColor>(ref packed, yy, cb, cr, x, y); this.PackYcck<TColor>(ref packed, yy, cb, cr, x, y);

65
tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs

@ -0,0 +1,65 @@
namespace ImageSharp.Tests
{
using ImageSharp.Formats.Jpg;
using Xunit;
using Xunit.Abstractions;
public class YCbCrImageTests
{
public YCbCrImageTests(ITestOutputHelper output)
{
this.Output = output;
}
private ITestOutputHelper Output { get; }
private void PrintChannel(string name, JpegPixelArea channel)
{
this.Output.WriteLine($"{name}: Stride={channel.Stride}");
}
[Theory]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4, 1)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2, 1)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1, 1)]
public void CalculateChrominanceSize(int ratioValue, int expectedDivX, int expectedDivY)
{
YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue;
//this.Output.WriteLine($"RATIO: {ratio}");
Size size = YCbCrImage.CalculateChrominanceSize(400, 400, ratio);
//this.Output.WriteLine($"Ch Size: {size}");
Assert.Equal(new Size(400/expectedDivX, 400/expectedDivY), size);
}
[Theory]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1)]
[InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1)]
public void Create(int ratioValue, int expectedCStrideDiv)
{
YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue;
this.Output.WriteLine($"RATIO: {ratio}");
var img = new YCbCrImage(400, 400, ratio);
//this.PrintChannel("Y", img.YChannel);
//this.PrintChannel("Cb", img.CbChannel);
//this.PrintChannel("Cr", img.CrChannel);
Assert.Equal(img.YChannel.Stride, 400);
Assert.Equal(img.CbChannel.Stride, 400 / expectedCStrideDiv);
Assert.Equal(img.CrChannel.Stride, 400 / expectedCStrideDiv);
}
}
}
Loading…
Cancel
Save