diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs index 49d9b591f9..04ece04ee8 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Formats.Jpg { + using System; using System.Buffers; /// @@ -33,44 +34,68 @@ namespace ImageSharp.Formats.Jpg public Block8x8F Block; /// - /// The used to pool data in . - /// Should always clean arrays when returning! + /// Store the block data into a at the given index of an . /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(); - - /// - /// Rent an array of -s from the pool. - /// - /// The requested array size - /// An array of -s - public static DecodedBlockMemento[] RentArray(int size) + /// The array + /// The index in the array + /// X coordinate of the block + /// Y coordinate of the block + /// The + public static void Store(ref DecodedBlockMemento.Array blockArray, int index, int bx, int by, ref Block8x8F block) { - return ArrayPool.Rent(size); - } + if (index >= blockArray.Count) + { + throw new IndexOutOfRangeException("Block index is out of range in DecodedBlockMemento.Store()!"); + } - /// - /// Returns the array to the pool. - /// - /// The array - public static void ReturnArray(DecodedBlockMemento[] blockArray) - { - ArrayPool.Return(blockArray, true); + blockArray.Buffer[index].Initialized = true; + blockArray.Buffer[index].Bx = bx; + blockArray.Buffer[index].By = by; + blockArray.Buffer[index].Block = block; } /// - /// Store the block data into a at the given index. + /// Because has no information for rented arrays, we need to store the count and the buffer separately. /// - /// The array of - /// The index in the array - /// X coordinate of the block - /// Y coordinate of the block - /// The - public static void Store(DecodedBlockMemento[] blockArray, int index, int bx, int by, ref Block8x8F block) + public struct Array : IDisposable { - blockArray[index].Initialized = true; - blockArray[index].Bx = bx; - blockArray[index].By = by; - blockArray[index].Block = block; + /// + /// The used to pool data in . + /// Should always clean arrays when returning! + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Initializes a new instance of the struct. Rents a buffer. + /// + /// The number of valid -s + public Array(int count) + { + this.Count = count; + this.Buffer = ArrayPool.Rent(count); + } + + /// + /// Gets the number of actual -s inside + /// + public int Count { get; } + + /// + /// Gets the rented buffer. + /// + public DecodedBlockMemento[] Buffer { get; private set; } + + /// + /// Returns the rented buffer to the pool. + /// + public void Dispose() + { + if (this.Buffer != null) + { + ArrayPool.Return(this.Buffer, true); + this.Buffer = null; + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 2d292ddf93..0e389771c0 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -196,7 +196,7 @@ namespace ImageSharp.Formats.Jpg // Take an existing block (required when progressive): int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block; + this.data.Block = decoder.DecodedBlocks[this.ComponentIndex].Buffer[blockIndex].Block; if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) { @@ -204,8 +204,8 @@ namespace ImageSharp.Formats.Jpg } // Store the decoded block - DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.ComponentIndex]; - DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref this.data.Block); + DecodedBlockMemento.Array blocks = decoder.DecodedBlocks[this.ComponentIndex]; + DecodedBlockMemento.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block); } // for j diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 127b399ab4..91fca3eb68 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -87,7 +87,7 @@ namespace ImageSharp.Formats this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; - this.DecodedBlocks = new DecodedBlockMemento[MaxComponents][]; + this.DecodedBlocks = new DecodedBlockMemento.Array[MaxComponents]; } /// @@ -104,7 +104,7 @@ namespace ImageSharp.Formats /// Gets the saved state between progressive-mode scans. /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) /// - public DecodedBlockMemento[][] DecodedBlocks { get; } + public DecodedBlockMemento.Array[] DecodedBlocks { get; } /// /// Gets the quantization tables, in zigzag order. @@ -176,7 +176,7 @@ namespace ImageSharp.Formats this.ProcessStream(image, stream, metadataOnly); if (!metadataOnly) { - this.ProcessBlockColorsIntoJpegImageChannels(); + this.ProcessBlocksIntoJpegImageChannels(); this.ConvertJpegPixelsToImagePixels(image); } } @@ -191,12 +191,9 @@ namespace ImageSharp.Formats this.HuffmanTrees[i].Dispose(); } - foreach (DecodedBlockMemento[] blockArray in this.DecodedBlocks) + foreach (DecodedBlockMemento.Array blockArray in this.DecodedBlocks) { - if (blockArray != null) - { - DecodedBlockMemento.ReturnArray(blockArray); - } + blockArray.Dispose(); } this.ycbcrImage?.Dispose(); @@ -464,9 +461,11 @@ namespace ImageSharp.Formats /// /// Process the blocks in into Jpeg image channels ( and ) + /// The blocks are expected in a "raw" frequency-domain decoded format. We need to apply IDCT and unzigging to transform them into color-space blocks. + /// We can copy these blocks into -s afterwards. /// /// The pixel type - private void ProcessBlockColorsIntoJpegImageChannels() + private void ProcessBlocksIntoJpegImageChannels() where TColor : struct, IPackedPixel, IEquatable { Parallel.For( @@ -478,10 +477,10 @@ namespace ImageSharp.Formats JpegScanDecoder.Init(&scanDecoder); scanDecoder.ComponentIndex = componentIndex; - DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex]; - for (int i = 0; i < blockArray.Length; i++) + DecodedBlockMemento.Array blockArray = this.DecodedBlocks[componentIndex]; + for (int i = 0; i < blockArray.Count; i++) { - scanDecoder.LoadMemento(ref blockArray[i]); + scanDecoder.LoadMemento(ref blockArray.Buffer[i]); scanDecoder.ProcessBlockColors(this); } }); @@ -1315,9 +1314,9 @@ namespace ImageSharp.Formats // As a preparation for parallelizing Scan decoder, we also allocate DecodedBlocks in the non-progressive case! for (int i = 0; i < this.ComponentCount; i++) { - int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor + int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = DecodedBlockMemento.RentArray(size); + this.DecodedBlocks[i] = new DecodedBlockMemento.Array(count); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 5776bf2e45..12ebee3e5c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -7,6 +7,9 @@ namespace ImageSharp.Tests { using System; + using System.IO; + + using ImageSharp.Formats; using Xunit; @@ -30,7 +33,7 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } - + [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenProgressiveJpeg_SaveBmp(TestImageProvider provider) @@ -40,5 +43,34 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } + + [Theory] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 75)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 100)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 75)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] + [WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] + public void DecodeGenerated_SaveBmp(TestImageProvider provider, JpegSubsample subsample, int qulaity) + where TColor : struct, IPackedPixel, IEquatable + { + Image image = provider.GetImage(); + + JpegEncoder encoder = new JpegEncoder() + { + Subsample = subsample, + Quality = qulaity + }; + + byte[] data = new byte[65536]; + using (MemoryStream ms = new MemoryStream(data)) + { + image.Save(ms, encoder); + } + + // TODO: Automatic image comparers could help here a lot :P + Image mirror = provider.Factory.CreateImage(data); + provider.Utility.TestName += $"_{subsample}_Q{qulaity}"; + provider.Utility.SaveTestOutputFile(mirror, "bmp"); + } } } \ No newline at end of file