Browse Source

simplified YCbCrImage

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

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

@ -8,62 +8,79 @@ namespace ImageSharp.Formats.Jpg
using System.Buffers; using System.Buffers;
/// <summary> /// <summary>
/// Represents an image made up of three color components (luminance, blue chroma, red chroma) /// Represents an image made up of three color components (luminance, blue chroma, red chroma)
/// </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> /// <summary>
/// Initializes a new instance of the <see cref="YCbCrImage" /> class. /// 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>
/// Initializes a new instance of the <see cref="YCbCrImage" /> class.
/// </summary> /// </summary>
/// <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 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>
/// Provides enumeration of the various available subsample ratios. /// Provides enumeration of the various available subsample ratios.
/// </summary> /// </summary>
public enum YCbCrSubsampleRatio public enum YCbCrSubsampleRatio
{ {
/// <summary> /// <summary>
/// YCbCrSubsampleRatio444 /// YCbCrSubsampleRatio444
/// </summary> /// </summary>
YCbCrSubsampleRatio444, YCbCrSubsampleRatio444,
/// <summary> /// <summary>
/// YCbCrSubsampleRatio422 /// YCbCrSubsampleRatio422
/// </summary> /// </summary>
YCbCrSubsampleRatio422, YCbCrSubsampleRatio422,
/// <summary> /// <summary>
/// YCbCrSubsampleRatio420 /// YCbCrSubsampleRatio420
/// </summary> /// </summary>
YCbCrSubsampleRatio420, YCbCrSubsampleRatio420,
/// <summary> /// <summary>
/// YCbCrSubsampleRatio440 /// YCbCrSubsampleRatio440
/// </summary> /// </summary>
YCbCrSubsampleRatio440, YCbCrSubsampleRatio440,
/// <summary> /// <summary>
/// YCbCrSubsampleRatio411 /// YCbCrSubsampleRatio411
/// </summary> /// </summary>
YCbCrSubsampleRatio411, YCbCrSubsampleRatio411,
/// <summary> /// <summary>
/// YCbCrSubsampleRatio410 /// YCbCrSubsampleRatio410
/// </summary> /// </summary>
YCbCrSubsampleRatio410, YCbCrSubsampleRatio410,
} }
@ -71,77 +88,37 @@ 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> /// </summary>
public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset); public int YStride { get; }
/// <summary>
/// Gets the red chroma components channel.
/// </summary>
public byte[] CrPixels { 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
/// that map to separate chroma samples. /// that map to separate chroma samples.
/// </summary> /// </summary>
public int CStride { get; } public int CStride { get; }
/// <summary> /// <summary>
/// Gets or sets the subsampling ratio. /// Gets or sets the subsampling ratio.
/// </summary> /// </summary>
public YCbCrSubsampleRatio Ratio { get; set; } public YCbCrSubsampleRatio Ratio { get; set; }
/// <summary> /// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Y channel /// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools.
/// </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>
/// 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>
/// Returns the offset of the first chroma component at the given row /// Returns the offset of the first chroma component at the given row
/// </summary> /// </summary>
/// <param name="y">The row number.</param> /// <param name="y">The row number.</param>
/// <returns> /// <returns>
/// The <see cref="int" />. /// The <see cref="int" />.
/// </returns> /// </returns>
public int GetRowCOffset(int y) public int GetRowCOffset(int y)
{ {
@ -163,11 +140,11 @@ namespace ImageSharp.Formats.Jpg
} }
/// <summary> /// <summary>
/// Returns the offset of the first luminance component at the given row /// Returns the offset of the first luminance component at the given row
/// </summary> /// </summary>
/// <param name="y">The row number.</param> /// <param name="y">The row number.</param>
/// <returns> /// <returns>
/// The <see cref="int" />. /// The <see cref="int" />.
/// </returns> /// </returns>
public int GetRowYOffset(int y) public int GetRowYOffset(int y)
{ {
@ -175,48 +152,32 @@ namespace ImageSharp.Formats.Jpg
} }
/// <summary> /// <summary>
/// Returns the height and width of the chroma components /// Returns the height and width of the chroma components
/// </summary> /// </summary>
/// <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);

8
tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs

@ -94,7 +94,7 @@ namespace ImageSharp.Tests
Image img = new Image(bytes); Image img = new Image(bytes);
}, },
$"Decode {fileName}"); $"Decode {fileName}");
} }
//[Theory] // Benchmark, enable manually //[Theory] // Benchmark, enable manually
@ -133,7 +133,7 @@ namespace ImageSharp.Tests
for (int j = 0; j < 10; j++) for (int j = 0; j < 10; j++)
{ {
Vector4 v = new Vector4(i/10f, j/10f, 0, 1); Vector4 v = new Vector4(i/10f, j/10f, 0, 1);
TColor color = default(TColor); TColor color = default(TColor);
color.PackFromVector4(v); color.PackFromVector4(v);
@ -171,11 +171,11 @@ namespace ImageSharp.Tests
[Theory] [Theory]
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)]
public void CopyStretchedRGBTo_WithOffset<TColor>(TestImageProvider<TColor> provider) public void CopyStretchedRGBTo_WithOffset<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
var src = provider.GetImage(); var src = provider.GetImage();
PixelArea<TColor> area = new PixelArea<TColor>(8, 8, ComponentOrder.Xyz); PixelArea<TColor> area = new PixelArea<TColor>(8, 8, ComponentOrder.Xyz);
var dest = provider.Factory.CreateImage(8, 8); var dest = provider.Factory.CreateImage(8, 8);

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