/// <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"></param> /// <param name="previousScanline"></param> /// <param name="bytesPerPixel"></param> /// <returns></returns> private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerPixel) { var tupleList = new List <Tuple <byte[], int> >(); var input1 = SubFilter.Encode(rawScanline, bytesPerPixel); tupleList.Add(new Tuple <byte[], int>(input1, CalculateTotalVariation(input1))); var input2 = UpFilter.Encode(rawScanline, previousScanline); tupleList.Add(new Tuple <byte[], int>(input2, CalculateTotalVariation(input2))); var input3 = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel); tupleList.Add(new Tuple <byte[], int>(input3, CalculateTotalVariation(input3))); var input4 = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel); tupleList.Add(new Tuple <byte[], int>(input4, CalculateTotalVariation(input4))); var maxValue = int.MaxValue; var index1 = 0; for (var index2 = 0; index2 < tupleList.Count; ++index2) { if (tupleList[index2].Item2 < maxValue) { index1 = index2; maxValue = tupleList[index2].Item2; } } return(tupleList[index1].Item1); }
/// <summary> /// Decodes the raw pixel data row by row /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="image">The image to decode to.</param> private void DecodePixelData <TPixel>(Stream compressedStream, ImageFrame <TPixel> image) where TPixel : struct, IPixel <TPixel> { while (this.currentRow < this.header.Height) { int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < this.bytesPerScanline) { return; } this.currentRowBytesRead = 0; var filterType = (FilterType)this.scanline[0]; switch (filterType) { case FilterType.None: break; case FilterType.Sub: SubFilter.Decode(this.scanline, this.bytesPerPixel); break; case FilterType.Up: UpFilter.Decode(this.scanline, this.previousScanline); break; case FilterType.Average: AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); break; case FilterType.Paeth: PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); break; default: throw new ImageFormatException("Unknown filter type."); } this.ProcessDefilteredScanline(this.scanline.Array, image); Swap(ref this.scanline, ref this.previousScanline); this.currentRow++; } }
/// <summary> /// Encodes the pixel data line by line. /// Each scanline is encoded in the most optimal manner to improve compression. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="rowSpan">The row span.</param> /// <param name="quantizedPixelsSpan">The span of quantized pixels. Can be null.</param> /// <param name="row">The row.</param> /// <returns>The <see cref="IManagedByteBuffer"/></returns> private IManagedByteBuffer EncodePixelRow <TPixel>(ReadOnlySpan <TPixel> rowSpan, ReadOnlySpan <byte> quantizedPixelsSpan, int row) where TPixel : struct, IPixel <TPixel> { switch (this.pngColorType) { case PngColorType.Palette: int stride = this.rawScanline.Length(); quantizedPixelsSpan.Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: this.CollectGrayscaleBytes(rowSpan); break; default: this.CollectTPixelBytes(rowSpan); break; } switch (this.pngFilterMethod) { case PngFilterMethod.None: NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); return(this.result); case PngFilterMethod.Sub: SubFilter.Encode(this.rawScanline.GetSpan(), this.sub.GetSpan(), this.bytesPerPixel, out int _); return(this.sub); case PngFilterMethod.Up: UpFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.up.GetSpan(), out int _); return(this.up); case PngFilterMethod.Average: AverageFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.average.GetSpan(), this.bytesPerPixel, out int _); return(this.average); case PngFilterMethod.Paeth: PaethFilter.Encode(this.rawScanline.GetSpan(), this.previousScanline.GetSpan(), this.paeth.GetSpan(), this.bytesPerPixel, out int _); return(this.paeth); default: return(this.GetOptimalFilteredScanline()); } }
/// <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> /// <returns>The <see cref="T:byte[]"/></returns> private Buffer <byte> GetOptimalFilteredScanline() { Span <byte> scanSpan = this.rawScanline.Span; Span <byte> prevSpan = this.previousScanline.Span; // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(this.rawScanline, this.result); return(this.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(scanSpan, prevSpan, this.up); int currentSum = this.CalculateTotalVariation(this.up, int.MaxValue); int lowestSum = currentSum; Buffer <byte> actualResult = this.up; PaethFilter.Encode(scanSpan, prevSpan, this.paeth, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(this.paeth, currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; actualResult = this.paeth; } SubFilter.Encode(scanSpan, this.sub, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(this.sub, int.MaxValue); if (currentSum < lowestSum) { lowestSum = currentSum; actualResult = this.sub; } AverageFilter.Encode(scanSpan, prevSpan, this.average, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(this.average, currentSum); if (currentSum < lowestSum) { actualResult = this.average; } return(actualResult); }
/// <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> /// <returns>The <see cref="T:byte[]"/></returns> private IManagedByteBuffer GetOptimalFilteredScanline() { // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); return(this.result); } Span <byte> scanSpan = this.rawScanline.GetSpan(); Span <byte> prevSpan = this.previousScanline.GetSpan(); // 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(scanSpan, prevSpan, this.up.GetSpan(), out int currentSum); // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. // That way the above comment would actually be true. It used to be anyway... // If we could use SIMD for none branching filters we could really speed it up. int lowestSum = currentSum; IManagedByteBuffer actualResult = this.up; PaethFilter.Encode(scanSpan, prevSpan, this.paeth.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; actualResult = this.paeth; } SubFilter.Encode(scanSpan, this.sub.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; actualResult = this.sub; } AverageFilter.Encode(scanSpan, prevSpan, this.average.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { actualResult = this.average; } return(actualResult); }
/// <summary> /// Encodes the pixel data line by line. /// Each scanline is encoded in the most optimal manner to improve compression. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="rowSpan">The row span.</param> /// <param name="row">The row.</param> /// <returns>The <see cref="IManagedByteBuffer"/></returns> private IManagedByteBuffer EncodePixelRow <TPixel>(ReadOnlySpan <TPixel> rowSpan, int row) where TPixel : struct, IPixel <TPixel> { switch (this.pngColorType) { case PngColorType.Palette: // TODO: Use Span copy! Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length(), this.rawScanline.Array, 0, this.rawScanline.Length()); break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: this.CollectGrayscaleBytes(rowSpan); break; default: this.CollectTPixelBytes(rowSpan); break; } switch (this.pngFilterMethod) { case PngFilterMethod.None: NoneFilter.Encode(this.rawScanline.Span, this.result.Span); return(this.result); case PngFilterMethod.Sub: SubFilter.Encode(this.rawScanline.Span, this.sub.Span, this.bytesPerPixel, out int _); return(this.sub); case PngFilterMethod.Up: UpFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.up.Span, out int _); return(this.up); case PngFilterMethod.Average: AverageFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.average.Span, this.bytesPerPixel, out int _); return(this.average); case PngFilterMethod.Paeth: PaethFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.paeth.Span, this.bytesPerPixel, out int _); return(this.paeth); default: return(this.GetOptimalFilteredScanline()); } }
/// <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> /// <returns>The <see cref="T:byte[]"/></returns> private IManagedByteBuffer GetOptimalFilteredScanline() { // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(this.rawScanline.GetSpan(), this.result.GetSpan()); return(this.result); } Span <byte> scanSpan = this.rawScanline.GetSpan(); Span <byte> prevSpan = this.previousScanline.GetSpan(); // 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(scanSpan, prevSpan, this.up.GetSpan(), out int currentSum); int lowestSum = currentSum; IManagedByteBuffer actualResult = this.up; PaethFilter.Encode(scanSpan, prevSpan, this.paeth.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; actualResult = this.paeth; } SubFilter.Encode(scanSpan, this.sub.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; actualResult = this.sub; } AverageFilter.Encode(scanSpan, prevSpan, this.average.GetSpan(), this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { actualResult = this.average; } return(actualResult); }
private void DecodePixelData(byte[][] pixelData) { // data = new Color[_width * _height]; _colors = new Color[width * height]; pixels = new int[width * height]; var previousScanline = new byte[bytesPerScanline]; for (var y = 0; y < height; ++y) { var scanline = pixelData[y]; byte[] defilteredScanline; switch (scanline[0]) { case 0: defilteredScanline = NoneFilter.Decode(scanline); break; case 1: defilteredScanline = SubFilter.Decode(scanline, bytesPerPixel); break; case 2: defilteredScanline = UpFilter.Decode(scanline, previousScanline); break; case 3: defilteredScanline = AverageFilter.Decode(scanline, previousScanline, bytesPerPixel); break; case 4: defilteredScanline = PaethFilter.Decode(scanline, previousScanline, bytesPerPixel); break; default: throw new Exception("Unknown filter type."); } previousScanline = defilteredScanline; ProcessDefilteredScanline(defilteredScanline, y); } }
/// <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, ImageFrame <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.GetPixelRowSpan(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; } } }