/// <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> /// 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); }