diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs
deleted file mode 100644
index caa30e62d1..0000000000
--- a/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp.Formats.Jpg
-{
- ///
- /// Represents a grayscale image
- ///
- internal class GrayImage
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The width.
- /// The height.
- public GrayImage(int width, int height)
- {
- this.Width = width;
- this.Height = height;
- this.Pixels = new byte[width * height];
- this.Stride = width;
- this.Offset = 0;
- }
-
- ///
- /// Prevents a default instance of the class from being created.
- ///
- private GrayImage()
- {
- }
-
- ///
- /// Gets or sets the pixels.
- ///
- public byte[] Pixels { get; set; }
-
- ///
- /// Gets or sets the stride.
- ///
- public int Stride { get; set; }
-
- ///
- /// Gets or sets the horizontal position.
- ///
- public int X { get; set; }
-
- ///
- /// Gets or sets the vertical position.
- ///
- public int Y { get; set; }
-
- ///
- /// Gets or sets the width.
- ///
- public int Width { get; set; }
-
- ///
- /// Gets or sets the height.
- ///
- public int Height { get; set; }
-
- ///
- /// Gets or sets the offset
- ///
- public int Offset { get; set; }
-
- ///
- /// Gets an image made up of a subset of the originals pixels.
- ///
- /// The x-coordinate of the image.
- /// The y-coordinate of the image.
- /// The width.
- /// The height.
- ///
- /// The .
- ///
- public GrayImage Subimage(int x, int y, int width, int height)
- {
- return new GrayImage
- {
- Width = width,
- Height = height,
- Pixels = this.Pixels,
- Stride = this.Stride,
- Offset = (y * this.Stride) + x
- };
- }
-
- ///
- /// Gets the row offset at the given position
- ///
- /// The y-coordinate of the image.
- /// The
- public int GetRowOffset(int y)
- {
- return this.Offset + (y * this.Stride);
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs
new file mode 100644
index 0000000000..c4168dd1f1
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs
@@ -0,0 +1,116 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpg
+{
+
+ using System;
+ using System.Buffers;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents a grayscale image
+ ///
+ internal struct JpegPixelArea
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The width.
+ /// The height.
+ public static JpegPixelArea CreatePooled(int width, int height)
+ {
+ int size = width * height;
+ //var pixels = ArrayPool.Shared.Rent(size);
+ //Array.Clear(pixels, 0, size);
+ var pixels = ArrayPoolManager.RentCleanArray(size);
+ return new JpegPixelArea(pixels, width, 0);
+ }
+
+ public JpegPixelArea(byte[] pixels, int widthOrStride, int offset)
+ {
+ this.Stride = widthOrStride;
+ this.Pixels = pixels;
+ this.Offset = offset;
+ }
+
+ public void ReturnPooled()
+ {
+ if (this.Pixels == null) return;
+ ArrayPoolManager.ReturnArray(this.Pixels);
+ this.Pixels = null;
+ }
+
+ ///
+ /// Gets or sets the pixels.
+ ///
+ public byte[] Pixels { get; private set; }
+
+ public bool Created => this.Pixels != null;
+
+ ///
+ /// Gets or sets the width.
+ ///
+ public int Stride { get; private set; }
+
+ ///
+ /// Gets or sets the offset
+ ///
+ public int Offset { get; private set; }
+
+ ///
+ /// Gets an image made up of a subset of the originals pixels.
+ ///
+ /// The x-coordinate of the image.
+ /// The y-coordinate of the image.
+ /// The width.
+ /// The height.
+ ///
+ /// The .
+ ///
+ public JpegPixelArea Subimage(int x, int y, int width, int height)
+ {
+ return new JpegPixelArea
+ {
+ Stride = width,
+ Pixels = this.Pixels,
+ Offset = (y * this.Stride) + x
+ };
+ }
+
+ ///
+ /// Get the subarea that belongs to the given block indices
+ ///
+ /// The block X index
+ /// The block Y index
+ ///
+ public JpegPixelArea GetOffsetedAreaForBlock(int bx, int by)
+ {
+ int offset = this.Offset + 8 * (by * this.Stride + bx);
+ return new JpegPixelArea(this.Pixels, this.Stride, offset);
+ }
+
+ public byte this[int x, int y] => this.Pixels[y * this.Stride + x];
+
+ ///
+ /// Gets the row offset at the given position
+ ///
+ /// The y-coordinate of the image.
+ /// The
+ public int GetRowOffset(int y)
+ {
+ return this.Offset + (y * this.Stride);
+ }
+
+ public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp)
+ {
+ // Level shift by +128, clip to [0, 255], and write to dst.
+ block->CopyColorsTo(new MutableSpan(this.Pixels, this.Offset), this.Stride, temp);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
index cba9c4461b..6ae1d3540b 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
@@ -5,6 +5,8 @@
namespace ImageSharp.Formats.Jpg
{
+ using System.Buffers;
+
///
/// Represents an image made up of three color components (luminance, blue chroma, red chroma)
///
@@ -20,25 +22,29 @@ namespace ImageSharp.Formats.Jpg
{
int cw, ch;
YCbCrSize(width, height, ratio, out cw, out ch);
- this.YChannel = new byte[width * height];
- this.CbChannel = new byte[cw * ch];
- this.CrChannel = new byte[cw * ch];
+ this.YPixels = new byte[width * height];
+ this.CbPixels = new byte[cw * ch];
+ this.CrPixels = new byte[cw * ch];
this.Ratio = ratio;
this.YStride = width;
this.CStride = cw;
this.X = 0;
this.Y = 0;
- this.Width = width;
- this.Height = height;
}
+ public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset);
+
+ public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset);
+
+ public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset);
+
///
/// Prevents a default instance of the class from being created.
///
private YCbCrImage()
{
}
-
+
///
/// Provides enumeration of the various available subsample ratios.
///
@@ -78,38 +84,38 @@ namespace ImageSharp.Formats.Jpg
///
/// Gets or sets the luminance components channel.
///
- public byte[] YChannel { get; set; }
+ public byte[] YPixels { get; private set; }
///
/// Gets or sets the blue chroma components channel.
///
- public byte[] CbChannel { get; set; }
+ public byte[] CbPixels { get; private set; }
///
/// Gets or sets the red chroma components channel.
///
- public byte[] CrChannel { get; set; }
+ public byte[] CrPixels { get; private set; }
///
/// Gets or sets the Y slice index delta between vertically adjacent pixels.
///
- public int YStride { get; set; }
+ public int YStride { get; private set; }
///
/// Gets or sets the red and blue chroma slice index delta between vertically adjacent pixels
/// that map to separate chroma samples.
///
- public int CStride { get; set; }
+ public int CStride { get; private set; }
///
/// Gets or sets the index of the first luminance element.
///
- public int YOffset { get; set; }
+ public int YOffset { get; private set; }
///
/// Gets or sets the index of the first element of red or blue chroma.
///
- public int COffset { get; set; }
+ public int COffset { get; private set; }
///
/// Gets or sets the horizontal position.
@@ -120,50 +126,38 @@ namespace ImageSharp.Formats.Jpg
/// Gets or sets the vertical position.
///
public int Y { get; set; }
-
- ///
- /// Gets or sets the width.
- ///
- public int Width { get; set; }
-
- ///
- /// Gets or sets the height.
- ///
- public int Height { get; set; }
-
+
///
/// Gets or sets the subsampling ratio.
///
public YCbCrSubsampleRatio Ratio { get; set; }
- ///
- /// Gets an image made up of a subset of the originals pixels.
- ///
- /// The x-coordinate of the image.
- /// The y-coordinate of the image.
- /// The width.
- /// The height.
- ///
- /// The .
- ///
- public YCbCrImage Subimage(int x, int y, int width, int height)
- {
- YCbCrImage ret = new YCbCrImage
- {
- Width = width,
- Height = height,
- YChannel = this.YChannel,
- CbChannel = this.CbChannel,
- CrChannel = this.CrChannel,
- Ratio = this.Ratio,
- YStride = this.YStride,
- CStride = this.CStride,
- YOffset = (y * this.YStride) + x,
- COffset = (y * this.CStride) + x
- };
- return ret;
- }
-
+ /////
+ ///// Gets an image made up of a subset of the originals pixels.
+ /////
+ ///// The x-coordinate of the image.
+ ///// The y-coordinate of the image.
+ ///// The width.
+ ///// The height.
+ /////
+ ///// The .
+ /////
+ //public YCbCrImage Subimage(int x, int y, int width, int height)
+ //{
+ // YCbCrImage ret = new YCbCrImage
+ // {
+ // YPixels = this.YPixels,
+ // CbPixels = this.CbPixels,
+ // CrPixels = this.CrPixels,
+ // Ratio = this.Ratio,
+ // YStride = this.YStride,
+ // CStride = this.CStride,
+ // YOffset = (y * this.YStride) + x,
+ // COffset = (y * this.CStride) + x
+ // };
+ // return ret;
+ //}
+
///
/// Returns the offset of the first luminance component at the given row
///
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index 761ad891ef..2b53ebaaf2 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -122,22 +122,15 @@ namespace ImageSharp.Formats
///
/// A grayscale image to decode to.
///
- private GrayImage grayImage;
+ private JpegPixelArea grayImage;
///
/// The full color image to decode to.
///
private YCbCrImage ycbcrImage;
- ///
- /// The array of keyline pixels in a CMYK image
- ///
- private byte[] blackPixels;
-
- ///
- /// The width in bytes or a single row of keyline pixels in a CMYK image
- ///
- private int blackStride;
+
+ private JpegPixelArea blackImage;
///
/// The restart interval
@@ -420,7 +413,7 @@ namespace ImageSharp.Formats
}
}
- if (this.grayImage != null)
+ if (this.grayImage.Created)
{
this.ConvertFromGrayScale(this.imageWidth, this.imageHeight, image);
}
@@ -480,6 +473,8 @@ namespace ImageSharp.Formats
}
this.bytes.Dispose();
+ this.grayImage.ReturnPooled();
+ this.blackImage.ReturnPooled();
}
///
@@ -1228,9 +1223,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < width; x++)
{
- byte yy = this.ycbcrImage.YChannel[yo + x];
- byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+ byte yy = this.ycbcrImage.YPixels[yo + x];
+ byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
TColor packed = default(TColor);
this.PackYcck(ref packed, yy, cb, cr, x, y);
@@ -1268,9 +1263,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < width; x++)
{
- byte cyan = this.ycbcrImage.YChannel[yo + x];
- byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)];
+ byte cyan = this.ycbcrImage.YPixels[yo + x];
+ byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)];
TColor packed = default(TColor);
this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
@@ -1343,9 +1338,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < width; x++)
{
- byte yy = this.ycbcrImage.YChannel[yo + x];
- byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+ byte yy = this.ycbcrImage.YPixels[yo + x];
+ byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
TColor packed = default(TColor);
PackYcbCr(ref packed, yy, cb, cr);
@@ -1383,9 +1378,9 @@ namespace ImageSharp.Formats
for (int x = 0; x < width; x++)
{
- byte red = this.ycbcrImage.YChannel[yo + x];
- byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
+ byte red = this.ycbcrImage.YPixels[yo + x];
+ byte green = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte blue = this.ycbcrImage.CrPixels[co + (x / scale)];
TColor packed = default(TColor);
packed.PackFromBytes(red, green, blue, 255);
@@ -1506,11 +1501,8 @@ namespace ImageSharp.Formats
int v0 = this.componentArray[0].VerticalFactor;
int mxx = (this.imageWidth + (8 * h0) - 1) / (8 * h0);
int myy = (this.imageHeight + (8 * v0) - 1) / (8 * v0);
-
- if (this.grayImage == null && this.ycbcrImage == null)
- {
- this.MakeImage(mxx, myy);
- }
+
+ this.MakeImage(mxx, myy);
if (this.isProgressive)
{
@@ -1540,6 +1532,8 @@ namespace ImageSharp.Formats
// blocks: the third block in the first row has (bx, by) = (2, 0).
int bx, by, blockCount = 0;
+ // TODO: A DecoderScanProcessor struct could clean up this mess
+
Block8x8F b = default(Block8x8F);
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
@@ -1601,7 +1595,8 @@ namespace ImageSharp.Formats
int qtIndex = this.componentArray[compIndex].Selector;
- // TODO: Find a way to clean up this mess
+ // TODO: A DecoderScanProcessor struct could clean up this mess
+ // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. (The first one could be async)
fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex])
{
// Load the previous partially decoded coefficients, if applicable.
@@ -1812,52 +1807,33 @@ namespace ImageSharp.Formats
DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2);
- byte[] dst;
- int offset;
- int stride;
+ var destChannel = this.GetDestinationChannel(compIndex);
+ var destArea = destChannel.GetOffsetedAreaForBlock(bx, by);
+ destArea.LoadColorsFrom(temp1, temp2);
+ }
+ private JpegPixelArea GetDestinationChannel(int compIndex)
+ {
if (this.componentCount == 1)
{
- dst = this.grayImage.Pixels;
- stride = this.grayImage.Stride;
- offset = this.grayImage.Offset + (8 * ((@by * this.grayImage.Stride) + bx));
+ return this.grayImage;
}
else
{
switch (compIndex)
{
case 0:
- dst = this.ycbcrImage.YChannel;
- stride = this.ycbcrImage.YStride;
- offset = this.ycbcrImage.YOffset + (8 * ((@by * this.ycbcrImage.YStride) + bx));
- break;
-
+ return this.ycbcrImage.YChannel;
case 1:
- dst = this.ycbcrImage.CbChannel;
- stride = this.ycbcrImage.CStride;
- offset = this.ycbcrImage.COffset + (8 * ((@by * this.ycbcrImage.CStride) + bx));
- break;
-
+ return this.ycbcrImage.CbChannel;
case 2:
- dst = this.ycbcrImage.CrChannel;
- stride = this.ycbcrImage.CStride;
- offset = this.ycbcrImage.COffset + (8 * ((@by * this.ycbcrImage.CStride) + bx));
- break;
-
+ return this.ycbcrImage.CrChannel;
case 3:
-
- dst = this.blackPixels;
- stride = this.blackStride;
- offset = 8 * ((@by * this.blackStride) + bx);
- break;
-
+ return this.blackImage;
default:
throw new ImageFormatException("Too many components");
}
}
-
- // Level shift by +128, clip to [0, 255], and write to dst.
- temp1->CopyColorsTo(new MutableSpan(dst, offset), stride, temp2);
}
private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv)
@@ -2071,7 +2047,7 @@ namespace ImageSharp.Formats
return zig;
}
-
+
///
/// Makes the image from the buffer.
///
@@ -2079,9 +2055,11 @@ namespace ImageSharp.Formats
/// The vertical MCU count
private void MakeImage(int mxx, int myy)
{
+ if (this.grayImage.Created || this.ycbcrImage != null) return;
+
if (this.componentCount == 1)
{
- GrayImage gray = new GrayImage(8 * mxx, 8 * myy);
+ JpegPixelArea gray = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy);
this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight);
}
else
@@ -2114,15 +2092,16 @@ namespace ImageSharp.Formats
break;
}
- YCbCrImage ycbcr = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio);
- this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight);
+ this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio);
+ //this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight);
if (this.componentCount == 4)
{
int h3 = this.componentArray[3].HorizontalFactor;
int v3 = this.componentArray[3].VerticalFactor;
- this.blackPixels = new byte[8 * h3 * mxx * 8 * v3 * myy];
- this.blackStride = 8 * h3 * mxx;
+
+ this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy);
+
}
}
}
@@ -2179,7 +2158,7 @@ namespace ImageSharp.Formats
float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F;
// Get keyline
- float keyline = (255 - this.blackPixels[(yy * this.blackStride) + xx]) / 255F;
+ float keyline = (255 - this.blackImage[xx, yy]) / 255F;
// Convert back to RGB
byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255);
@@ -2204,7 +2183,7 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel, IEquatable
{
// Get keyline
- float keyline = (255 - this.blackPixels[(yy * this.blackStride) + xx]) / 255F;
+ float keyline = (255 - this.blackImage[xx, yy]) / 255F;
// Convert back to RGB. CMY are not inverted
byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
diff --git a/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs b/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs
new file mode 100644
index 0000000000..cb3cd0fe5d
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Utils/ArrayPoolManager.cs
@@ -0,0 +1,13 @@
+namespace ImageSharp.Formats
+{
+ using System.Buffers;
+
+ internal class ArrayPoolManager
+ {
+ private static readonly ArrayPool Pool = ArrayPool.Create();
+
+ public static T[] RentCleanArray(int minimumLength) => Pool.Rent(minimumLength);
+
+ public static void ReturnArray(T[] array) => Pool.Return(array, true);
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
index 3bf7ed92b1..662ec97287 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
@@ -70,7 +70,7 @@ namespace ImageSharp.Tests
private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; //PixelTypes.Color | PixelTypes.Argb;
- [Theory] // Benchmark, enable manually
+ //[Theory] // Benchmark, enable manually
[InlineData(TestImages.Jpeg.Cmyk)]
[InlineData(TestImages.Jpeg.Ycck)]
[InlineData(TestImages.Jpeg.Calliphora)]