diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
index 61308ee2f8..6f7140fa2f 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
@@ -8,62 +8,79 @@ namespace ImageSharp.Formats.Jpg
using System.Buffers;
///
- /// 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)
///
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
///
- /// Initializes a new instance of the class.
+ /// Gets the luminance components channel as .
+ ///
+ public JpegPixelArea YChannel;
+
+ ///
+ /// Gets the blue chroma components channel as .
+ ///
+ public JpegPixelArea CbChannel;
+
+ ///
+ /// Gets an offseted to the Cr channel
+ ///
+ public JpegPixelArea CrChannel;
+#pragma warning restore SA1401
+
+ ///
+ /// Initializes a new instance of the class.
///
/// The width.
/// The height.
/// The ratio.
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);
}
///
- /// Provides enumeration of the various available subsample ratios.
+ /// Provides enumeration of the various available subsample ratios.
///
public enum YCbCrSubsampleRatio
{
///
- /// YCbCrSubsampleRatio444
+ /// YCbCrSubsampleRatio444
///
YCbCrSubsampleRatio444,
///
- /// YCbCrSubsampleRatio422
+ /// YCbCrSubsampleRatio422
///
YCbCrSubsampleRatio422,
///
- /// YCbCrSubsampleRatio420
+ /// YCbCrSubsampleRatio420
///
YCbCrSubsampleRatio420,
///
- /// YCbCrSubsampleRatio440
+ /// YCbCrSubsampleRatio440
///
YCbCrSubsampleRatio440,
///
- /// YCbCrSubsampleRatio411
+ /// YCbCrSubsampleRatio411
///
YCbCrSubsampleRatio411,
///
- /// YCbCrSubsampleRatio410
+ /// YCbCrSubsampleRatio410
///
YCbCrSubsampleRatio410,
}
@@ -71,77 +88,37 @@ namespace ImageSharp.Formats.Jpg
private static ArrayPool BytePool => ArrayPool.Shared;
///
- /// Gets an offseted to the Cb channel
- ///
- public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset);
-
- ///
- /// Gets the blue chroma components channel.
- ///
- public byte[] CbPixels { get; }
-
- ///
- /// Gets the index of the first element of red or blue chroma.
- ///
- public int COffset { get; }
-
- ///
- /// Gets an offseted to the Cr channel
+ /// Gets the Y slice index delta between vertically adjacent pixels.
///
- public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset);
-
- ///
- /// Gets the red chroma components channel.
- ///
- public byte[] CrPixels { get; }
+ public int YStride { get; }
///
- /// 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.
///
public int CStride { get; }
///
- /// Gets or sets the subsampling ratio.
+ /// Gets or sets the subsampling ratio.
///
public YCbCrSubsampleRatio Ratio { get; set; }
///
- /// Gets an offseted to the Y channel
- ///
- public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset);
-
- ///
- /// Gets the index of the first luminance element.
- ///
- public int YOffset { get; }
-
- ///
- /// Gets the luminance components channel.
- ///
- public byte[] YPixels { get; }
-
- ///
- /// Gets the Y slice index delta between vertically adjacent pixels.
- ///
- public int YStride { get; }
-
- ///
- /// Disposes the returning rented arrays to the pools.
+ /// Disposes the returning rented arrays to the pools.
///
public void Dispose()
{
- BytePool.Return(this.YPixels);
- BytePool.Return(this.CrPixels);
- BytePool.Return(this.CbPixels);
+ this.YChannel.ReturnPooled();
+ this.CbChannel.ReturnPooled();
+ this.CrChannel.ReturnPooled();
}
///
- /// Returns the offset of the first chroma component at the given row
+ /// Returns the offset of the first chroma component at the given row
///
/// The row number.
///
- /// The .
+ /// The .
///
public int GetRowCOffset(int y)
{
@@ -163,11 +140,11 @@ namespace ImageSharp.Formats.Jpg
}
///
- /// Returns the offset of the first luminance component at the given row
+ /// Returns the offset of the first luminance component at the given row
///
/// The row number.
///
- /// The .
+ /// The .
///
public int GetRowYOffset(int y)
{
@@ -175,48 +152,32 @@ namespace ImageSharp.Formats.Jpg
}
///
- /// Returns the height and width of the chroma components
+ /// Returns the height and width of the chroma components
///
/// The width.
/// The height.
/// The subsampling ratio.
- /// The chroma width.
- /// The chroma height.
- private static void YCbCrSize(
+ /// The of the chrominance channel
+ 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index a792a19912..284ae807b4 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -21,7 +21,7 @@ namespace ImageSharp.Formats
///
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
///
/// 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(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(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(ref packed, yy, cb, cr, x, y);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
index 81156d9703..53c44d836c 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
+++ b/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(TestImageProvider provider)
+ public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider)
where TColor : struct, IPackedPixel, IEquatable
{
var src = provider.GetImage();
-
+
PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz);
var dest = provider.Factory.CreateImage(8, 8);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs
new file mode 100644
index 0000000000..b90973a9cf
--- /dev/null
+++ b/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);
+ }
+
+ }
+}
\ No newline at end of file