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;
/// <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>
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>
/// 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>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="ratio">The ratio.</param>
public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio)
{
int cw, ch;
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);
Size cSize = CalculateChrominanceSize(width, height, ratio);
this.Ratio = ratio;
this.YOffset = 0;
this.COffset = 0;
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>
/// Provides enumeration of the various available subsample ratios.
/// Provides enumeration of the various available subsample ratios.
/// </summary>
public enum YCbCrSubsampleRatio
{
/// <summary>
/// YCbCrSubsampleRatio444
/// YCbCrSubsampleRatio444
/// </summary>
YCbCrSubsampleRatio444,
/// <summary>
/// YCbCrSubsampleRatio422
/// YCbCrSubsampleRatio422
/// </summary>
YCbCrSubsampleRatio422,
/// <summary>
/// YCbCrSubsampleRatio420
/// YCbCrSubsampleRatio420
/// </summary>
YCbCrSubsampleRatio420,
/// <summary>
/// YCbCrSubsampleRatio440
/// YCbCrSubsampleRatio440
/// </summary>
YCbCrSubsampleRatio440,
/// <summary>
/// YCbCrSubsampleRatio411
/// YCbCrSubsampleRatio411
/// </summary>
YCbCrSubsampleRatio411,
/// <summary>
/// YCbCrSubsampleRatio410
/// YCbCrSubsampleRatio410
/// </summary>
YCbCrSubsampleRatio410,
}
@ -71,77 +88,37 @@ namespace ImageSharp.Formats.Jpg
private static ArrayPool<byte> BytePool => ArrayPool<byte>.Shared;
/// <summary>
/// Gets an offseted <see cref="JpegPixelArea" /> to the Cb channel
/// </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
/// Gets the Y slice index delta between vertically adjacent pixels.
/// </summary>
public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset);
/// <summary>
/// Gets the red chroma components channel.
/// </summary>
public byte[] CrPixels { get; }
public int YStride { get; }
/// <summary>
/// Gets the red and blue chroma slice index delta between vertically adjacent pixels
/// that map to separate chroma samples.
/// Gets the red and blue chroma slice index delta between vertically adjacent pixels
/// that map to separate chroma samples.
/// </summary>
public int CStride { get; }
/// <summary>
/// Gets or sets the subsampling ratio.
/// Gets or sets the subsampling ratio.
/// </summary>
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>
/// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools.
/// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools.
/// </summary>
public void Dispose()
{
BytePool.Return(this.YPixels);
BytePool.Return(this.CrPixels);
BytePool.Return(this.CbPixels);
this.YChannel.ReturnPooled();
this.CbChannel.ReturnPooled();
this.CrChannel.ReturnPooled();
}
/// <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>
/// <param name="y">The row number.</param>
/// <returns>
/// The <see cref="int" />.
/// The <see cref="int" />.
/// </returns>
public int GetRowCOffset(int y)
{
@ -163,11 +140,11 @@ namespace ImageSharp.Formats.Jpg
}
/// <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>
/// <param name="y">The row number.</param>
/// <returns>
/// The <see cref="int" />.
/// The <see cref="int" />.
/// </returns>
public int GetRowYOffset(int y)
{
@ -175,48 +152,32 @@ namespace ImageSharp.Formats.Jpg
}
/// <summary>
/// Returns the height and width of the chroma components
/// Returns the height and width of the chroma components
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="ratio">The subsampling ratio.</param>
/// <param name="chromaWidth">The chroma width.</param>
/// <param name="chromaHeight">The chroma height.</param>
private static void YCbCrSize(
/// <returns>The <see cref="Size"/> of the chrominance channel</returns>
internal static Size CalculateChrominanceSize(
int width,
int height,
YCbCrSubsampleRatio ratio,
out int chromaWidth,
out int chromaHeight)
YCbCrSubsampleRatio ratio)
{
switch (ratio)
{
case YCbCrSubsampleRatio.YCbCrSubsampleRatio422:
chromaWidth = (width + 1) / 2;
chromaHeight = height;
break;
return new Size((width + 1) / 2, height);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio420:
chromaWidth = (width + 1) / 2;
chromaHeight = (height + 1) / 2;
break;
return new Size((width + 1) / 2, (height + 1) / 2);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio440:
chromaWidth = width;
chromaHeight = (height + 1) / 2;
break;
return new Size(width, (height + 1) / 2);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio411:
chromaWidth = (width + 3) / 4;
chromaHeight = height;
break;
return new Size((width + 3) / 4, height);
case YCbCrSubsampleRatio.YCbCrSubsampleRatio410:
chromaWidth = (width + 3) / 4;
chromaHeight = (height + 1) / 2;
break;
return new Size((width + 3) / 4, (height + 1) / 2);
default:
// Default to 4:4:4 subsampling.
chromaWidth = width;
chromaHeight = height;
break;
return new Size(width, height);
}
}
}

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

@ -21,7 +21,7 @@ namespace ImageSharp.Formats
/// </summary>
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
/// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream.
@ -650,14 +650,15 @@ namespace ImageSharp.Formats
height,
y =>
{
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++)
{
byte cyan = this.ycbcrImage.YPixels[yo + x];
byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)];
byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)];
byte cyan = this.ycbcrImage.YChannel.Pixels[yo + x];
byte magenta = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor);
this.PackCmyk<TColor>(ref packed, cyan, magenta, yellow, x, y);
@ -725,14 +726,15 @@ namespace ImageSharp.Formats
Bootstrapper.ParallelOptions,
y =>
{
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++)
{
byte red = this.ycbcrImage.YPixels[yo + x];
byte green = this.ycbcrImage.CbPixels[co + (x / scale)];
byte blue = this.ycbcrImage.CrPixels[co + (x / scale)];
byte red = this.ycbcrImage.YChannel.Pixels[yo + x];
byte green = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte blue = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor);
packed.PackFromBytes(red, green, blue, 255);
@ -765,14 +767,15 @@ namespace ImageSharp.Formats
Bootstrapper.ParallelOptions,
y =>
{
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++)
{
byte yy = this.ycbcrImage.YPixels[yo + x];
byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor);
PackYcbCr<TColor>(ref packed, yy, cb, cr);
@ -805,14 +808,15 @@ namespace ImageSharp.Formats
height,
y =>
{
// TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++)
{
byte yy = this.ycbcrImage.YPixels[yo + x];
byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
byte yy = this.ycbcrImage.YChannel.Pixels[yo + x];
byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)];
TColor packed = default(TColor);
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);
},
$"Decode {fileName}");
}
//[Theory] // Benchmark, enable manually
@ -133,7 +133,7 @@ namespace ImageSharp.Tests
for (int j = 0; j < 10; j++)
{
Vector4 v = new Vector4(i/10f, j/10f, 0, 1);
TColor color = default(TColor);
color.PackFromVector4(v);
@ -171,11 +171,11 @@ namespace ImageSharp.Tests
[Theory]
[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>
{
var src = provider.GetImage();
PixelArea<TColor> area = new PixelArea<TColor>(8, 8, ComponentOrder.Xyz);
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