/// <summary> /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// </summary> /// <param name="rawScanline">The raw scanline</param> /// <param name="previousScanline">The previous scanline</param> /// <param name="result">The filtered scanline result.</param> /// <returns>The <see cref="T:byte[]"/></returns> private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) { // Palette images don't compress well with adaptive filtering. if (this.PngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(rawScanline, result); return(result); } // This order, while different to the enumerated order is more likely to produce a smaller sum // early on which shaves a couple of milliseconds off the processing time. UpFilter.Encode(rawScanline, previousScanline, this.up); int currentSum = this.CalculateTotalVariation(this.up, int.MaxValue); int lowestSum = currentSum; result = this.up; PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(this.paeth, currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; result = this.paeth; } SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(this.sub, int.MaxValue); if (currentSum < lowestSum) { lowestSum = currentSum; result = this.sub; } AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(this.average, currentSum); if (currentSum < lowestSum) { result = this.average; } return(result); }
/// <summary> /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// </summary> /// <param name="rawScanline">The raw scanline</param> /// <param name="previousScanline">The previous scanline</param> /// <param name="result">The filtered scanline result.</param> /// <returns>The <see cref="T:byte[]"/></returns> private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) { // Palette images don't compress well with adaptive filtering. if (this.PngColorType == PngColorType.Palette) { NoneFilter.Encode(rawScanline, result); return(result); } SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel); int currentTotalVariation = this.CalculateTotalVariation(this.sub); int lowestTotalVariation = currentTotalVariation; result = this.sub; UpFilter.Encode(rawScanline, previousScanline, this.up); currentTotalVariation = this.CalculateTotalVariation(this.up); if (currentTotalVariation < lowestTotalVariation) { lowestTotalVariation = currentTotalVariation; result = this.up; } AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel); currentTotalVariation = this.CalculateTotalVariation(this.average); if (currentTotalVariation < lowestTotalVariation) { lowestTotalVariation = currentTotalVariation; result = this.average; } PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel); currentTotalVariation = this.CalculateTotalVariation(this.paeth); if (currentTotalVariation < lowestTotalVariation) { result = this.paeth; } return(result); }
/// <summary> /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// </summary> /// <param name="rawScanline">The raw scanline</param> /// <param name="previousScanline">The previous scanline</param> /// <param name="result">The filtered scanline result</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param> private void GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result, int bytesPerScanline) { // Palette images don't compress well with adaptive filtering. if (this.PngColorType == PngColorType.Palette) { NoneFilter.Encode(rawScanline, result, bytesPerScanline); return; } Tuple <byte[], int>[] candidates = new Tuple <byte[], int> [4]; byte[] sub = SubFilter.Encode(rawScanline, this.bytesPerPixel, bytesPerScanline); candidates[0] = new Tuple <byte[], int>(sub, this.CalculateTotalVariation(sub)); byte[] up = UpFilter.Encode(rawScanline, previousScanline, result, bytesPerScanline); candidates[1] = new Tuple <byte[], int>(up, this.CalculateTotalVariation(up)); byte[] average = AverageFilter.Encode(rawScanline, previousScanline, result, this.bytesPerPixel, bytesPerScanline); candidates[2] = new Tuple <byte[], int>(average, this.CalculateTotalVariation(average)); byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, result, this.bytesPerPixel, bytesPerScanline); candidates[3] = new Tuple <byte[], int>(paeth, this.CalculateTotalVariation(paeth)); int lowestTotalVariation = int.MaxValue; int lowestTotalVariationIndex = 0; for (int i = 0; i < candidates.Length; i++) { if (candidates[i].Item2 < lowestTotalVariation) { lowestTotalVariationIndex = i; lowestTotalVariation = candidates[i].Item2; } } // ReSharper disable once RedundantAssignment result = candidates[lowestTotalVariationIndex].Item1; }
/// <summary> /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// </summary> /// <param name="rawScanline">The raw scanline</param> /// <param name="previousScanline">The previous scanline</param> /// <param name="byteCount">The number of bytes per pixel</param> /// <returns>The <see cref="T:byte[]"/></returns> private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int byteCount) { List <Tuple <byte[], int> > candidates = new List <Tuple <byte[], int> >(); if (this.PngColorType == PngColorType.Palette) { byte[] none = NoneFilter.Encode(rawScanline); candidates.Add(new Tuple <byte[], int>(none, this.CalculateTotalVariation(none))); } else { byte[] sub = SubFilter.Encode(rawScanline, byteCount); candidates.Add(new Tuple <byte[], int>(sub, this.CalculateTotalVariation(sub))); byte[] up = UpFilter.Encode(rawScanline, previousScanline); candidates.Add(new Tuple <byte[], int>(up, this.CalculateTotalVariation(up))); byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount); candidates.Add(new Tuple <byte[], int>(average, this.CalculateTotalVariation(average))); byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount); candidates.Add(new Tuple <byte[], int>(paeth, this.CalculateTotalVariation(paeth))); } int lowestTotalVariation = int.MaxValue; int lowestTotalVariationIndex = 0; for (int i = 0; i < candidates.Count; i++) { if (candidates[i].Item2 < lowestTotalVariation) { lowestTotalVariationIndex = i; lowestTotalVariation = candidates[i].Item2; } } return(candidates[lowestTotalVariationIndex].Item1); }
/// <summary> /// Decodes the raw interlaced pixel data row by row /// <see href="https://github.com/juehv/DentalImageViewer/blob/8a1a4424b15d6cc453b5de3f273daf3ff5e3a90d/DentalImageViewer/lib/jiu-0.14.3/net/sourceforge/jiu/codecs/PNGCodec.java"/> /// </summary> /// <typeparam name="TColor">The pixel format.</typeparam> /// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="pixels">The image pixel accessor.</param> private void DecodeInterlacedPixelData <TColor>(Stream compressedStream, PixelAccessor <TColor> pixels) where TColor : struct, IPixel <TColor> { byte[] previousScanline = ArrayPool <byte> .Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool <byte> .Shared.Rent(this.bytesPerScanline); try { for (int pass = 0; pass < 7; pass++) { // Zero out the scanlines, because the bytes that are rented from the arraypool may not be zero. Array.Clear(scanline, 0, this.bytesPerScanline); Array.Clear(previousScanline, 0, this.bytesPerScanline); int y = Adam7FirstRow[pass]; int numColumns = this.ComputeColumnsAdam7(pass); if (numColumns == 0) { // This pass contains no data; skip to next pass continue; } int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; while (y < this.header.Height) { compressedStream.Read(scanline, 0, bytesPerInterlaceScanline); FilterType filterType = (FilterType)scanline[0]; switch (filterType) { case FilterType.None: NoneFilter.Decode(scanline); break; case FilterType.Sub: SubFilter.Decode(scanline, bytesPerInterlaceScanline, this.bytesPerPixel); break; case FilterType.Up: UpFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline); break; case FilterType.Average: AverageFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); break; case FilterType.Paeth: PaethFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); break; default: throw new ImageFormatException("Unknown filter type."); } this.ProcessInterlacedDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); Swap(ref scanline, ref previousScanline); y += Adam7RowIncrement[pass]; } } } finally { ArrayPool <byte> .Shared.Return(previousScanline); ArrayPool <byte> .Shared.Return(scanline); } }
/// <summary> /// Decodes the raw pixel data row by row /// </summary> /// <typeparam name="TColor">The pixel format.</typeparam> /// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="pixels">The image pixel accessor.</param> private void DecodePixelData <TColor>(Stream compressedStream, PixelAccessor <TColor> pixels) where TColor : struct, IPixel <TColor> { byte[] previousScanline = ArrayPool <byte> .Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool <byte> .Shared.Rent(this.bytesPerScanline); // Zero out the scanlines, because the bytes that are rented from the arraypool may not be zero. Array.Clear(scanline, 0, this.bytesPerScanline); Array.Clear(previousScanline, 0, this.bytesPerScanline); try { for (int y = 0; y < this.header.Height; y++) { compressedStream.Read(scanline, 0, this.bytesPerScanline); FilterType filterType = (FilterType)scanline[0]; switch (filterType) { case FilterType.None: NoneFilter.Decode(scanline); break; case FilterType.Sub: SubFilter.Decode(scanline, this.bytesPerScanline, this.bytesPerPixel); break; case FilterType.Up: UpFilter.Decode(scanline, previousScanline, this.bytesPerScanline); break; case FilterType.Average: AverageFilter.Decode(scanline, previousScanline, this.bytesPerScanline, this.bytesPerPixel); break; case FilterType.Paeth: PaethFilter.Decode(scanline, previousScanline, this.bytesPerScanline, this.bytesPerPixel); break; default: throw new ImageFormatException("Unknown filter type."); } this.ProcessDefilteredScanline(scanline, y, pixels); Swap(ref scanline, ref previousScanline); } } finally { ArrayPool <byte> .Shared.Return(previousScanline); ArrayPool <byte> .Shared.Return(scanline); } }
/// <summary> /// Decodes the raw interlaced pixel data row by row /// <see href="https://github.com/juehv/DentalImageViewer/blob/8a1a4424b15d6cc453b5de3f273daf3ff5e3a90d/DentalImageViewer/lib/jiu-0.14.3/net/sourceforge/jiu/codecs/PNGCodec.java"/> /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="image">The current image.</param> private void DecodeInterlacedPixelData <TPixel>(Stream compressedStream, Image <TPixel> image) where TPixel : struct, IPixel <TPixel> { while (true) { int numColumns = this.ComputeColumnsAdam7(this.pass); if (numColumns == 0) { this.pass++; // This pass contains no data; skip to next pass continue; } int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; while (this.currentRow < this.header.Height) { int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < bytesPerInterlaceScanline) { return; } this.currentRowBytesRead = 0; Span <byte> scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline); Span <byte> prevSpan = this.previousScanline.Slice(0, bytesPerInterlaceScanline); var filterType = (FilterType)scanSpan[0]; switch (filterType) { case FilterType.None: break; case FilterType.Sub: SubFilter.Decode(scanSpan, this.bytesPerPixel); break; case FilterType.Up: UpFilter.Decode(scanSpan, prevSpan); break; case FilterType.Average: AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; case FilterType.Paeth: PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; default: throw new ImageFormatException("Unknown filter type."); } Span <TPixel> rowSpan = image.GetRowSpan(this.currentRow); this.ProcessInterlacedDefilteredScanline(this.scanline.Array, rowSpan, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); Swap(ref this.scanline, ref this.previousScanline); this.currentRow += Adam7RowIncrement[this.pass]; } this.pass++; this.previousScanline.Clear(); if (this.pass < 7) { this.currentRow = Adam7FirstRow[this.pass]; } else { this.pass = 0; break; } } }