|
|
|
@ -80,32 +80,32 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// <summary>
|
|
|
|
/// The raw data of previous scanline.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer previousScanline; |
|
|
|
private IMemoryOwner<byte> previousScanline; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The raw data of current scanline.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer currentScanline; |
|
|
|
private IMemoryOwner<byte> currentScanline; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The common buffer for the filters.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer filterBuffer; |
|
|
|
private IMemoryOwner<byte> filterBuffer; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The ext buffer for the sub filter, <see cref="PngFilterMethod.Adaptive"/>.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer subFilter; |
|
|
|
private IMemoryOwner<byte> subFilter; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The ext buffer for the average filter, <see cref="PngFilterMethod.Adaptive"/>.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer averageFilter; |
|
|
|
private IMemoryOwner<byte> averageFilter; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The ext buffer for the Paeth filter, <see cref="PngFilterMethod.Adaptive"/>.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer paethFilter; |
|
|
|
private IMemoryOwner<byte> paethFilter; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
|
|
|
|
@ -278,21 +278,17 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
else |
|
|
|
{ |
|
|
|
// 1, 2, and 4 bit grayscale
|
|
|
|
using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer( |
|
|
|
rowSpan.Length, |
|
|
|
AllocationOptions.Clean)) |
|
|
|
{ |
|
|
|
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); |
|
|
|
Span<byte> tempSpan = temp.GetSpan(); |
|
|
|
|
|
|
|
// We need to first create an array of luminance bytes then scale them down to the correct bit depth.
|
|
|
|
PixelOperations<TPixel>.Instance.ToL8Bytes( |
|
|
|
this.configuration, |
|
|
|
rowSpan, |
|
|
|
tempSpan, |
|
|
|
rowSpan.Length); |
|
|
|
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); |
|
|
|
} |
|
|
|
using IMemoryOwner<byte> temp = this.memoryAllocator.Allocate<byte>(rowSpan.Length, AllocationOptions.Clean); |
|
|
|
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); |
|
|
|
Span<byte> tempSpan = temp.GetSpan(); |
|
|
|
|
|
|
|
// We need to first create an array of luminance bytes then scale them down to the correct bit depth.
|
|
|
|
PixelOperations<TPixel>.Instance.ToL8Bytes( |
|
|
|
this.configuration, |
|
|
|
rowSpan, |
|
|
|
tempSpan, |
|
|
|
rowSpan.Length); |
|
|
|
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -453,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// <summary>
|
|
|
|
/// Apply filter for the raw scanline.
|
|
|
|
/// </summary>
|
|
|
|
private IManagedByteBuffer FilterPixelBytes() |
|
|
|
private IMemoryOwner<byte> FilterPixelBytes() |
|
|
|
{ |
|
|
|
switch (this.options.FilterMethod) |
|
|
|
{ |
|
|
|
@ -490,8 +486,8 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// <param name="rowSpan">The row span.</param>
|
|
|
|
/// <param name="quantized">The 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, IndexedImageFrame<TPixel> quantized, int row) |
|
|
|
/// <returns>The <see cref="IMemoryOwner{Byte}"/></returns>
|
|
|
|
private IMemoryOwner<byte> EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
this.CollectPixelBytes(rowSpan, quantized, row); |
|
|
|
@ -502,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="rowSpan">The row span.</param>
|
|
|
|
private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan<byte> rowSpan) |
|
|
|
private IMemoryOwner<byte> EncodeAdam7IndexedPixelRow(ReadOnlySpan<byte> rowSpan) |
|
|
|
{ |
|
|
|
// CollectPixelBytes
|
|
|
|
if (this.bitDepth < 8) |
|
|
|
@ -522,7 +518,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// to be most compressible, using lowest total variation as proxy for compressibility.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>The <see cref="T:byte[]"/></returns>
|
|
|
|
private IManagedByteBuffer GetOptimalFilteredScanline() |
|
|
|
private IMemoryOwner<byte> GetOptimalFilteredScanline() |
|
|
|
{ |
|
|
|
// Palette images don't compress well with adaptive filtering.
|
|
|
|
if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) |
|
|
|
@ -543,7 +539,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
// 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.filterBuffer; |
|
|
|
IMemoryOwner<byte> actualResult = this.filterBuffer; |
|
|
|
|
|
|
|
PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum); |
|
|
|
|
|
|
|
@ -612,8 +608,8 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
int colorTableLength = paletteLength * Unsafe.SizeOf<Rgb24>(); |
|
|
|
bool hasAlpha = false; |
|
|
|
|
|
|
|
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); |
|
|
|
using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); |
|
|
|
using IMemoryOwner<byte> colorTable = this.memoryAllocator.Allocate<byte>(colorTableLength); |
|
|
|
using IMemoryOwner<byte> alphaTable = this.memoryAllocator.Allocate<byte>(paletteLength); |
|
|
|
|
|
|
|
ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, Rgb24>(colorTable.GetSpan())); |
|
|
|
ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); |
|
|
|
@ -640,12 +636,12 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
Unsafe.Add(ref alphaTableRef, i) = alpha; |
|
|
|
} |
|
|
|
|
|
|
|
this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); |
|
|
|
this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength); |
|
|
|
|
|
|
|
// Write the transparency data
|
|
|
|
if (hasAlpha) |
|
|
|
{ |
|
|
|
this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); |
|
|
|
this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -938,9 +934,9 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
this.previousScanline?.Dispose(); |
|
|
|
this.currentScanline?.Dispose(); |
|
|
|
this.filterBuffer?.Dispose(); |
|
|
|
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); |
|
|
|
this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); |
|
|
|
this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); |
|
|
|
this.previousScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean); |
|
|
|
this.currentScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean); |
|
|
|
this.filterBuffer = this.memoryAllocator.Allocate<byte>(resultLength, AllocationOptions.Clean); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -952,9 +948,9 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
{ |
|
|
|
int resultLength = this.filterBuffer.Length(); |
|
|
|
|
|
|
|
this.subFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); |
|
|
|
this.averageFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); |
|
|
|
this.paethFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); |
|
|
|
this.subFilter = this.memoryAllocator.Allocate<byte>(resultLength, AllocationOptions.Clean); |
|
|
|
this.averageFilter = this.memoryAllocator.Allocate<byte>(resultLength, AllocationOptions.Clean); |
|
|
|
this.paethFilter = this.memoryAllocator.Allocate<byte>(resultLength, AllocationOptions.Clean); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -974,10 +970,10 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
|
|
|
|
for (int y = 0; y < this.height; y++) |
|
|
|
{ |
|
|
|
IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); |
|
|
|
deflateStream.Write(r.Array, 0, resultLength); |
|
|
|
IMemoryOwner<byte> r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); |
|
|
|
deflateStream.Write(r.GetSpan(), 0, resultLength); |
|
|
|
|
|
|
|
IManagedByteBuffer temp = this.currentScanline; |
|
|
|
IMemoryOwner<byte> temp = this.currentScanline; |
|
|
|
this.currentScanline = this.previousScanline; |
|
|
|
this.previousScanline = temp; |
|
|
|
} |
|
|
|
@ -1027,10 +1023,10 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
// encode data
|
|
|
|
// note: quantized parameter not used
|
|
|
|
// note: row parameter not used
|
|
|
|
IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan<TPixel>)destSpan, null, -1); |
|
|
|
deflateStream.Write(r.Array, 0, resultLength); |
|
|
|
IMemoryOwner<byte> r = this.EncodePixelRow((ReadOnlySpan<TPixel>)destSpan, null, -1); |
|
|
|
deflateStream.Write(r.GetSpan(), 0, resultLength); |
|
|
|
|
|
|
|
IManagedByteBuffer temp = this.currentScanline; |
|
|
|
IMemoryOwner<byte> temp = this.currentScanline; |
|
|
|
this.currentScanline = this.previousScanline; |
|
|
|
this.previousScanline = temp; |
|
|
|
} |
|
|
|
@ -1080,10 +1076,10 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
} |
|
|
|
|
|
|
|
// encode data
|
|
|
|
IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan); |
|
|
|
deflateStream.Write(r.Array, 0, resultLength); |
|
|
|
IMemoryOwner<byte> r = this.EncodeAdam7IndexedPixelRow(destSpan); |
|
|
|
deflateStream.Write(r.GetSpan(), 0, resultLength); |
|
|
|
|
|
|
|
IManagedByteBuffer temp = this.currentScanline; |
|
|
|
IMemoryOwner<byte> temp = this.currentScanline; |
|
|
|
this.currentScanline = this.previousScanline; |
|
|
|
this.previousScanline = temp; |
|
|
|
} |
|
|
|
@ -1103,7 +1099,8 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
|
/// <param name="type">The type of chunk to write.</param>
|
|
|
|
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
|
|
|
|
private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); |
|
|
|
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data) |
|
|
|
=> this.WriteChunk(stream, type, data, 0, data.Length); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Writes a chunk of a specified length to the stream at the given offset.
|
|
|
|
@ -1113,7 +1110,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
|
|
|
|
/// <param name="offset">The position to offset the data at.</param>
|
|
|
|
/// <param name="length">The of the data to write.</param>
|
|
|
|
private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length) |
|
|
|
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length) |
|
|
|
{ |
|
|
|
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length); |
|
|
|
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); |
|
|
|
@ -1126,7 +1123,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
{ |
|
|
|
stream.Write(data, offset, length); |
|
|
|
|
|
|
|
crc = Crc32.Calculate(crc, data.AsSpan(offset, length)); |
|
|
|
crc = Crc32.Calculate(crc, data.Slice(offset, length)); |
|
|
|
} |
|
|
|
|
|
|
|
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc); |
|
|
|
|