diff --git a/src/ImageSharp46/Common/Extensions/ByteExtensions.cs b/src/ImageSharp46/Common/Extensions/ByteExtensions.cs
index 00eab47fc..89cfe6974 100644
--- a/src/ImageSharp46/Common/Extensions/ByteExtensions.cs
+++ b/src/ImageSharp46/Common/Extensions/ByteExtensions.cs
@@ -16,32 +16,31 @@ namespace ImageSharp
/// Converts a byte array to a new array where each value in the original array is represented
/// by a the specified number of bits.
///
- /// The bytes to convert from. Cannot be null.
+ /// The bytes to convert from. Cannot be null.
/// The number of bits per value.
/// The resulting array. Is never null.
- /// is null.
+ /// is null.
/// is less than or equals than zero.
- public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits)
+ public static byte[] ToArrayByBitsLength(this byte[] source, int bits)
{
- Guard.NotNull(bytes, "bytes");
+ Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThan(bits, 0, "bits");
byte[] result;
if (bits < 8)
{
- result = new byte[bytes.Length * 8 / bits];
-
- // BUGFIX I dont think it should be there, but I am not sure if it breaks something else
- // int factor = (int)Math.Pow(2, bits) - 1;
+ result = new byte[source.Length * 8 / bits];
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
- foreach (byte b in bytes)
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (int i = 0; i < source.Length; i++)
{
+ byte b = source[i];
for (int shift = 0; shift < 8; shift += bits)
{
- int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor);
+ int colorIndex = (b >> (8 - bits - shift)) & mask;
result[resultOffset] = (byte)colorIndex;
@@ -51,10 +50,42 @@ namespace ImageSharp
}
else
{
- result = bytes;
+ result = source;
}
return result;
}
+
+ ///
+ /// Optimized reversal algorithm.
+ ///
+ /// The byte array.
+ public static void ReverseBytes(this byte[] source)
+ {
+ ReverseBytes(source, 0, source.Length);
+ }
+
+ ///
+ /// Optimized reversal algorithm.
+ ///
+ /// The byte array.
+ /// The index.
+ /// The length.
+ /// is null.
+ public static void ReverseBytes(this byte[] source, int index, int length)
+ {
+ Guard.NotNull(source, nameof(source));
+
+ int i = index;
+ int j = index + length - 1;
+ while (i < j)
+ {
+ byte temp = source[i];
+ source[i] = source[j];
+ source[j] = temp;
+ i++;
+ j--;
+ }
+ }
}
}
diff --git a/src/ImageSharp46/Common/Extensions/StreamExtensions.cs b/src/ImageSharp46/Common/Extensions/StreamExtensions.cs
new file mode 100644
index 000000000..87d5a6c32
--- /dev/null
+++ b/src/ImageSharp46/Common/Extensions/StreamExtensions.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.IO;
+
+ internal static class StreamExtensions
+ {
+ public static void Skip(this Stream stream, int count)
+ {
+ if (count < 1)
+ {
+ return;
+ }
+
+ if (stream.CanSeek)
+ {
+ stream.Position += count;
+ }
+ else
+ {
+ byte[] foo = new byte[count];
+ stream.Read(foo, 0, count);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs
index 29b34aa9a..44bb5553b 100644
--- a/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp46/Formats/Gif/GifDecoderCore.cs
@@ -59,7 +59,7 @@ namespace ImageSharp.Formats
this.currentStream = stream;
// Skip the identifier
- this.currentStream.Seek(6, SeekOrigin.Current);
+ this.currentStream.Skip(6);
this.ReadLogicalScreenDescriptor();
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
@@ -192,13 +192,13 @@ namespace ImageSharp.Formats
/// The number of bytes to skip.
private void Skip(int length)
{
- this.currentStream.Seek(length, SeekOrigin.Current);
+ this.currentStream.Skip(length);
int flag;
while ((flag = this.currentStream.ReadByte()) != 0)
{
- this.currentStream.Seek(flag, SeekOrigin.Current);
+ this.currentStream.Skip(flag);
}
}
diff --git a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs
index 8d9b48a20..9cb4fce26 100644
--- a/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs
+++ b/src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs
@@ -787,24 +787,21 @@ namespace ImageSharp.Formats
{
HuffmanSpec spec = specs[i];
int len = 0;
+
fixed (byte* huffman = this.huffmanBuffer)
+ fixed (byte* count = spec.Count)
+ fixed (byte* values = spec.Values)
{
- fixed (byte* count = spec.Count)
+ huffman[len++] = headers[i];
+
+ for (int c = 0; c < spec.Count.Length; c++)
+ {
+ huffman[len++] = count[c];
+ }
+
+ for (int v = 0; v < spec.Values.Length; v++)
{
- fixed (byte* values = spec.Values)
- {
- huffman[len++] = headers[i];
-
- for (int c = 0; c < spec.Count.Length; c++)
- {
- huffman[len++] = count[c];
- }
-
- for (int v = 0; v < spec.Values.Length; v++)
- {
- huffman[len++] = values[v];
- }
- }
+ huffman[len++] = values[v];
}
}
diff --git a/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs
index 97ded289c..bef124541 100644
--- a/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs
+++ b/src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs
@@ -45,15 +45,16 @@ namespace ImageSharp.Formats
/// The scanline to encode
/// The previous scanline.
/// The bytes per pixel.
+ /// The number of bytes per scanline
/// The
- public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
{
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
- byte[] encodedScanline = new byte[scanline.Length + 1];
+ byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.Average;
- for (int x = 0; x < scanline.Length; x++)
+ for (int x = 0; x < bytesPerScanline; x++)
{
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
byte above = previousScanline[x];
diff --git a/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs
index 2ac590648..175e5affa 100644
--- a/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs
+++ b/src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs
@@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
+ using System;
+
///
/// The None filter, the scanline is transmitted unmodified; it is only necessary to
/// insert a filter type byte before the data.
@@ -27,13 +29,14 @@ namespace ImageSharp.Formats
/// Encodes the scanline
///
/// The scanline to encode
+ /// The number of bytes per scanline
/// The
- public static byte[] Encode(byte[] scanline)
+ public static byte[] Encode(byte[] scanline, int bytesPerScanline)
{
// Insert a byte before the data.
- byte[] encodedScanline = new byte[scanline.Length + 1];
+ byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.None;
- scanline.CopyTo(encodedScanline, 1);
+ Buffer.BlockCopy(scanline, 0, encodedScanline, 1, bytesPerScanline);
return encodedScanline;
}
diff --git a/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs
index 1234cb6ef..232d7cc3d 100644
--- a/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs
+++ b/src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs
@@ -45,14 +45,15 @@ namespace ImageSharp.Formats
/// The scanline to encode
/// The previous scanline.
/// The bytes per pixel.
+ /// The number of bytes per scanline
/// The
- public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
{
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
- byte[] encodedScanline = new byte[scanline.Length + 1];
+ byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.Paeth;
- for (int x = 0; x < scanline.Length; x++)
+ for (int x = 0; x < bytesPerScanline; x++)
{
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
byte above = previousScanline[x];
diff --git a/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs
index eb5bd9bfd..c4fbe3e51 100644
--- a/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs
+++ b/src/ImageSharp46/Formats/Png/Filters/SubFilter.cs
@@ -38,14 +38,15 @@ namespace ImageSharp.Formats
///
/// The scanline to encode
/// The bytes per pixel.
+ /// The number of bytes per scanline
/// The
- public static byte[] Encode(byte[] scanline, int bytesPerPixel)
+ public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline)
{
// Sub(x) = Raw(x) - Raw(x-bpp)
- byte[] encodedScanline = new byte[scanline.Length + 1];
+ byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.Sub;
- for (int x = 0; x < scanline.Length; x++)
+ for (int x = 0; x < bytesPerScanline; x++)
{
byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
diff --git a/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs
index ebb5e3b54..026070421 100644
--- a/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs
+++ b/src/ImageSharp46/Formats/Png/Filters/UpFilter.cs
@@ -37,15 +37,16 @@ namespace ImageSharp.Formats
/// Encodes the scanline
///
/// The scanline to encode
+ /// The number of bytes per scanline
/// The previous scanline.
/// The
- public static byte[] Encode(byte[] scanline, byte[] previousScanline)
+ public static byte[] Encode(byte[] scanline, int bytesPerScanline, byte[] previousScanline)
{
// Up(x) = Raw(x) - Prior(x)
- byte[] encodedScanline = new byte[scanline.Length + 1];
+ byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.Up;
- for (int x = 0; x < scanline.Length; x++)
+ for (int x = 0; x < bytesPerScanline; x++)
{
byte above = previousScanline[x];
diff --git a/src/ImageSharp46/Formats/Png/PngDecoderCore.cs b/src/ImageSharp46/Formats/Png/PngDecoderCore.cs
index d57ba8d4e..cad32d9f2 100644
--- a/src/ImageSharp46/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp46/Formats/Png/PngDecoderCore.cs
@@ -2,6 +2,7 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
+
namespace ImageSharp.Formats
{
using System;
@@ -115,7 +116,7 @@ namespace ImageSharp.Formats
{
Image currentImage = image;
this.currentStream = stream;
- this.currentStream.Seek(8, SeekOrigin.Current);
+ this.currentStream.Skip(8);
bool isEndChunkReached = false;
@@ -129,35 +130,31 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image does not end with end chunk.");
}
- if (currentChunk.Type == PngChunkTypes.Header)
- {
- this.ReadHeaderChunk(currentChunk.Data);
- this.ValidateHeader();
- }
- else if (currentChunk.Type == PngChunkTypes.Physical)
- {
- this.ReadPhysicalChunk(currentImage, currentChunk.Data);
- }
- else if (currentChunk.Type == PngChunkTypes.Data)
- {
- dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length);
- }
- else if (currentChunk.Type == PngChunkTypes.Palette)
+ switch (currentChunk.Type)
{
- this.palette = currentChunk.Data;
- image.Quality = this.palette.Length / 3;
- }
- else if (currentChunk.Type == PngChunkTypes.PaletteAlpha)
- {
- this.paletteAlpha = currentChunk.Data;
- }
- else if (currentChunk.Type == PngChunkTypes.Text)
- {
- this.ReadTextChunk(currentImage, currentChunk.Data);
- }
- else if (currentChunk.Type == PngChunkTypes.End)
- {
- isEndChunkReached = true;
+ case PngChunkTypes.Header:
+ this.ReadHeaderChunk(currentChunk.Data);
+ this.ValidateHeader();
+ break;
+ case PngChunkTypes.Physical:
+ this.ReadPhysicalChunk(currentImage, currentChunk.Data);
+ break;
+ case PngChunkTypes.Data:
+ dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length);
+ break;
+ case PngChunkTypes.Palette:
+ this.palette = currentChunk.Data;
+ image.Quality = this.palette.Length / 3;
+ break;
+ case PngChunkTypes.PaletteAlpha:
+ this.paletteAlpha = currentChunk.Data;
+ break;
+ case PngChunkTypes.Text:
+ this.ReadTextChunk(currentImage, currentChunk.Data);
+ break;
+ case PngChunkTypes.End:
+ isEndChunkReached = true;
+ break;
}
}
@@ -188,8 +185,8 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- this.ReverseBytes(data, 0, 4);
- this.ReverseBytes(data, 4, 4);
+ data.ReverseBytes(0, 4);
+ data.ReverseBytes(4, 4);
// 39.3700787 = inches in a meter.
image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d;
@@ -216,8 +213,7 @@ namespace ImageSharp.Formats
case PngColorType.Rgb:
return 3;
- // PngColorType.RgbWithAlpha
- // TODO: Maybe figure out a way to detect if there are any transparent pixels and encode RGB if none.
+ // PngColorType.RgbWithAlpha:
default:
return 4;
}
@@ -262,18 +258,7 @@ namespace ImageSharp.Formats
dataStream.Position = 0;
using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream))
{
- using (MemoryStream decompressedStream = new MemoryStream())
- {
- compressedStream.CopyTo(decompressedStream);
- decompressedStream.Flush();
- decompressedStream.Position = 0;
- this.DecodePixelData(decompressedStream, pixels);
- //byte[] decompressedBytes = decompressedStream.ToArray();
- //this.DecodePixelData(decompressedBytes, pixels);
- }
-
- //byte[] decompressedBytes = compressedStream.ToArray();
- //this.DecodePixelData(decompressedBytes, pixels);
+ this.DecodePixelData(compressedStream, pixels);
}
}
@@ -282,9 +267,9 @@ namespace ImageSharp.Formats
///
/// The pixel format.
/// The packed format. uint, long, float.
- /// The pixel data.
- /// The image pixels.
- private void DecodePixelData(Stream pixelData, PixelAccessor pixels)
+ /// The compressed pixel data stream.
+ /// The image pixel accessor.
+ private void DecodePixelData(Stream compressedStream, PixelAccessor pixels)
where TColor : struct, IPackedPixel
where TPacked : struct
{
@@ -293,11 +278,12 @@ namespace ImageSharp.Formats
byte[] scanline = new byte[this.bytesPerScanline];
for (int y = 0; y < this.header.Height; y++)
{
- pixelData.Read(scanline, 0, this.bytesPerScanline);
+ compressedStream.Read(scanline, 0, this.bytesPerScanline);
FilterType filterType = (FilterType)scanline[0];
byte[] defilteredScanline;
+ // TODO: It would be good if we can reduce the memory usage here. Each filter is creating a new row.
switch (filterType)
{
case FilterType.None:
@@ -497,8 +483,8 @@ namespace ImageSharp.Formats
{
this.header = new PngHeader();
- this.ReverseBytes(data, 0, 4);
- this.ReverseBytes(data, 4, 4);
+ data.ReverseBytes(0, 4);
+ data.ReverseBytes(4, 4);
this.header.Width = BitConverter.ToInt32(data, 0);
this.header.Height = BitConverter.ToInt32(data, 4);
@@ -584,7 +570,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image stream is not valid!");
}
- this.ReverseBytes(this.crcBuffer);
+ this.crcBuffer.ReverseBytes();
chunk.Crc = BitConverter.ToUInt32(this.crcBuffer, 0);
@@ -649,40 +635,11 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image stream is not valid!");
}
- this.ReverseBytes(this.chunkLengthBuffer);
+ this.chunkLengthBuffer.ReverseBytes();
chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0);
return numBytes;
}
-
- ///
- /// Optimized reversal algorithm.
- ///
- /// The byte array.
- private void ReverseBytes(byte[] source)
- {
- this.ReverseBytes(source, 0, source.Length);
- }
-
- ///
- /// Optimized reversal algorithm.
- ///
- /// The byte array.
- /// The index.
- /// The length.
- private void ReverseBytes(byte[] source, int index, int length)
- {
- int i = index;
- int j = index + length - 1;
- while (i < j)
- {
- byte temp = source[i];
- source[i] = source[j];
- source[j] = temp;
- i++;
- j--;
- }
- }
}
}
diff --git a/src/ImageSharp46/Formats/Png/PngEncoderCore.cs b/src/ImageSharp46/Formats/Png/PngEncoderCore.cs
index 03f74d5a7..a06e306f5 100644
--- a/src/ImageSharp46/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp46/Formats/Png/PngEncoderCore.cs
@@ -2,13 +2,14 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
+
namespace ImageSharp.Formats
{
using System;
+ using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
- using System.Threading.Tasks;
using Quantizers;
@@ -24,9 +25,20 @@ namespace ImageSharp.Formats
private const int MaxBlockSize = 65535;
///
- /// Contains the raw pixel data from the image.
+ /// Reusable buffer for writing chunk types.
+ ///
+ private readonly byte[] chunkTypeBuffer = new byte[4];
+
+ ///
+ /// Reusable buffer for writing chunk data.
+ ///
+ private readonly byte[] chunkDataBuffer = new byte[16];
+
+
+ ///
+ /// Contains the raw pixel data from an indexed image.
///
- private byte[] pixelData;
+ private byte[] palettePixelData;
///
/// The image width.
@@ -106,20 +118,16 @@ namespace ImageSharp.Formats
this.height = image.Height;
// Write the png header.
- stream.Write(
- new byte[]
- {
- 0x89, // Set the high bit.
- 0x50, // P
- 0x4E, // N
- 0x47, // G
- 0x0D, // Line ending CRLF
- 0x0A, // Line ending CRLF
- 0x1A, // EOF
- 0x0A // LF
- },
- 0,
- 8);
+ this.chunkDataBuffer[0] = 0x89; // Set the high bit.
+ this.chunkDataBuffer[1] = 0x50; // P
+ this.chunkDataBuffer[2] = 0x4E; // N
+ this.chunkDataBuffer[3] = 0x47; // G
+ this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF
+ this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF
+ this.chunkDataBuffer[6] = 0x1A; // EOF
+ this.chunkDataBuffer[7] = 0x0A; // LF
+
+ stream.Write(this.chunkDataBuffer, 0, 8);
// Ensure that quality can be set but has a fallback.
int quality = this.Quality > 0 ? this.Quality : image.Quality;
@@ -131,6 +139,11 @@ namespace ImageSharp.Formats
this.PngColorType = PngColorType.Palette;
}
+ if (this.PngColorType == PngColorType.Palette && this.Quality > 256)
+ {
+ this.Quality = 256;
+ }
+
// Set correct bit depth.
this.bitDepth = this.Quality <= 256
? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)
@@ -161,23 +174,15 @@ namespace ImageSharp.Formats
this.WriteHeaderChunk(stream, header);
- // Collect the pixel data
+ // Collect the indexed pixel data
if (this.PngColorType == PngColorType.Palette)
{
this.CollectIndexedBytes(image, stream, header);
}
- else if (this.PngColorType == PngColorType.Grayscale || this.PngColorType == PngColorType.GrayscaleWithAlpha)
- {
- this.CollectGrayscaleBytes(image);
- }
- else
- {
- this.CollectColorBytes(image);
- }
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
- this.WriteDataChunks(stream);
+ this.WriteDataChunks(image, stream);
this.WriteEndChunk(stream);
stream.Flush();
}
@@ -192,8 +197,8 @@ namespace ImageSharp.Formats
{
byte[] buffer = BitConverter.GetBytes(value);
- Array.Reverse(buffer);
- Array.Copy(buffer, 0, data, offset, 4);
+ buffer.ReverseBytes();
+ Buffer.BlockCopy(buffer, 0, data, offset, 4);
}
///
@@ -205,8 +210,7 @@ namespace ImageSharp.Formats
{
byte[] buffer = BitConverter.GetBytes(value);
- Array.Reverse(buffer);
-
+ buffer.ReverseBytes();
stream.Write(buffer, 0, 4);
}
@@ -219,8 +223,7 @@ namespace ImageSharp.Formats
{
byte[] buffer = BitConverter.GetBytes(value);
- Array.Reverse(buffer);
-
+ buffer.ReverseBytes();
stream.Write(buffer, 0, 4);
}
@@ -236,46 +239,44 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- // Quatize the image and get the pixels
+ // Quantize the image and get the pixels.
QuantizedImage quantized = this.WritePaletteChunk(stream, header, image);
- this.pixelData = quantized.Pixels;
+ this.palettePixelData = quantized.Pixels;
}
///
- /// Collects the grayscale pixel data.
+ /// Collects a row of grayscale pixels.
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The image to encode.
- private void CollectGrayscaleBytes(ImageBase image)
+ /// The row index.
+ /// The raw scanline.
+ private void CollectGrayscaleBytes(ImageBase image, int row, byte[] rawScanline)
where TColor : struct, IPackedPixel
where TPacked : struct
{
// Copy the pixels across from the image.
- this.pixelData = new byte[this.width * this.height * this.bytesPerPixel];
- int stride = this.width * this.bytesPerPixel;
- byte[] bytes = new byte[4];
+ // Reuse the chunk type buffer.
using (PixelAccessor pixels = image.Lock())
{
- for (int y = 0; y < this.height; y++)
+ for (int x = 0; x < this.width; x++)
{
- for (int x = 0; x < this.width; x++)
+ // Convert the color to YCbCr and store the luminance
+ // Optionally store the original color alpha.
+ int offset = x * this.bytesPerPixel;
+ pixels[x, row].ToBytes(this.chunkTypeBuffer, 0, ComponentOrder.XYZW);
+ byte luminance = (byte)((0.299F * this.chunkTypeBuffer[0]) + (0.587F * this.chunkTypeBuffer[1]) + (0.114F * this.chunkTypeBuffer[2]));
+
+ for (int i = 0; i < this.bytesPerPixel; i++)
{
- // Convert the color to YCbCr and store the luminance
- // Optionally store the original color alpha.
- int dataOffset = (y * stride) + (x * this.bytesPerPixel);
- pixels[x, y].ToBytes(bytes, 0, ComponentOrder.XYZW);
- YCbCr luminance = new Color(bytes[0], bytes[1], bytes[2], bytes[3]);
- for (int i = 0; i < this.bytesPerPixel; i++)
+ if (i == 0)
+ {
+ rawScanline[offset] = luminance;
+ }
+ else
{
- if (i == 0)
- {
- this.pixelData[dataOffset] = (byte)luminance.Y;
- }
- else
- {
- this.pixelData[dataOffset + i] = bytes[3];
- }
+ rawScanline[offset + i] = this.chunkTypeBuffer[3];
}
}
}
@@ -283,34 +284,24 @@ namespace ImageSharp.Formats
}
///
- /// Collects the true color pixel data.
+ /// Collects a row of true color pixel data.
///
/// The pixel format.
/// The packed format. uint, long, float.
/// The image to encode.
- private void CollectColorBytes(ImageBase image)
+ /// The row index.
+ /// The raw scanline.
+ private void CollectColorBytes(ImageBase image, int row, byte[] rawScanline)
where TColor : struct, IPackedPixel
where TPacked : struct
{
- // Copy the pixels across from the image.
- // TODO: This could be sped up more if we add a method to PixelAccessor that does this by row directly to a byte array.
- this.pixelData = new byte[this.width * this.height * this.bytesPerPixel];
- int stride = this.width * this.bytesPerPixel;
using (PixelAccessor pixels = image.Lock())
{
int bpp = this.bytesPerPixel;
- Parallel.For(
- 0,
- this.height,
- Bootstrapper.Instance.ParallelOptions,
- y =>
- {
- for (int x = 0; x < this.width; x++)
- {
- int dataOffset = (y * stride) + (x * this.bytesPerPixel);
- pixels[x, y].ToBytes(this.pixelData, dataOffset, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ);
- }
- });
+ for (int x = 0; x < this.width; x++)
+ {
+ pixels[x, row].ToBytes(rawScanline, x * this.bytesPerPixel, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ);
+ }
}
}
@@ -318,33 +309,35 @@ namespace ImageSharp.Formats
/// Encodes the pixel data line by line.
/// Each scanline is encoded in the most optimal manner to improve compression.
///
+ /// The pixel format.
+ /// The packed format. uint, long, float.
+ /// The image to encode.
+ /// The row.
+ /// The previous scanline.
+ /// The raw scanline.
+ /// The number of bytes per scanline.
/// The
- private byte[] EncodePixelData()
+ private byte[] EncodePixelRow(ImageBase image, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct
{
- // TODO: Use pointers
- List filteredScanlines = new List();
-
- byte[] previousScanline = new byte[this.width * this.bytesPerPixel];
-
- for (int y = 0; y < this.height; y++)
+ switch (this.PngColorType)
{
- byte[] rawScanline = this.GetRawScanline(y);
- byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, this.bytesPerPixel);
-
- filteredScanlines.Add(filteredScanline);
-
- previousScanline = rawScanline;
+ case PngColorType.Palette:
+ Buffer.BlockCopy(this.palettePixelData, row * bytesPerScanline, rawScanline, 0, bytesPerScanline);
+ break;
+ case PngColorType.Grayscale:
+ case PngColorType.GrayscaleWithAlpha:
+ this.CollectGrayscaleBytes(image, row, rawScanline);
+ break;
+ default:
+ this.CollectColorBytes(image, row, rawScanline);
+ break;
}
- // TODO: We should be able to use a byte array when not using interlaced encoding.
- List result = new List();
+ byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline, this.bytesPerPixel);
- foreach (byte[] encodedScanline in filteredScanlines)
- {
- result.AddRange(encodedScanline);
- }
-
- return result.ToArray();
+ return filteredScanline;
}
///
@@ -353,36 +346,42 @@ namespace ImageSharp.Formats
///
/// The raw scanline
/// The previous scanline
- /// The number of bytes per pixel
+ /// The number of bytes per scanline
+ /// The number of bytes per pixel
/// The
- private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int byteCount)
+ private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel)
{
- List> candidates = new List>();
+ Tuple[] candidates;
+ // Palette images don't compress well with adaptive filtering.
if (this.PngColorType == PngColorType.Palette)
{
- byte[] none = NoneFilter.Encode(rawScanline);
- candidates.Add(new Tuple(none, this.CalculateTotalVariation(none)));
+ candidates = new Tuple[1];
+
+ byte[] none = NoneFilter.Encode(rawScanline, bytesPerScanline);
+ candidates[0] = new Tuple(none, this.CalculateTotalVariation(none));
}
else
{
- byte[] sub = SubFilter.Encode(rawScanline, byteCount);
- candidates.Add(new Tuple(sub, this.CalculateTotalVariation(sub)));
+ candidates = new Tuple[4];
+
+ byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel, bytesPerScanline);
+ candidates[0] = new Tuple(sub, this.CalculateTotalVariation(sub));
- byte[] up = UpFilter.Encode(rawScanline, previousScanline);
- candidates.Add(new Tuple(up, this.CalculateTotalVariation(up)));
+ byte[] up = UpFilter.Encode(rawScanline, bytesPerScanline, previousScanline);
+ candidates[1] = new Tuple(up, this.CalculateTotalVariation(up));
- byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount);
- candidates.Add(new Tuple(average, this.CalculateTotalVariation(average)));
+ byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline);
+ candidates[2] = new Tuple(average, this.CalculateTotalVariation(average));
- byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount);
- candidates.Add(new Tuple(paeth, this.CalculateTotalVariation(paeth)));
+ byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline);
+ candidates[3] = new Tuple(paeth, this.CalculateTotalVariation(paeth));
}
int lowestTotalVariation = int.MaxValue;
int lowestTotalVariationIndex = 0;
- for (int i = 0; i < candidates.Count; i++)
+ for (int i = 0; i < candidates.Length; i++)
{
if (candidates[i].Item2 < lowestTotalVariation)
{
@@ -412,19 +411,6 @@ namespace ImageSharp.Formats
return totalVariation;
}
- ///
- /// Get the raw scanline data from the pixel data
- ///
- /// The row number
- /// The
- private byte[] GetRawScanline(int y)
- {
- int stride = this.bytesPerPixel * this.width;
- byte[] rawScanline = new byte[stride];
- Array.Copy(this.pixelData, y * stride, rawScanline, 0, stride);
- return rawScanline;
- }
-
///
/// Calculates the correct number of bytes per pixel for the given color type.
///
@@ -460,18 +446,16 @@ namespace ImageSharp.Formats
/// The .
private void WriteHeaderChunk(Stream stream, PngHeader header)
{
- byte[] chunkData = new byte[13];
-
- WriteInteger(chunkData, 0, header.Width);
- WriteInteger(chunkData, 4, header.Height);
+ WriteInteger(this.chunkDataBuffer, 0, header.Width);
+ WriteInteger(this.chunkDataBuffer, 4, header.Height);
- chunkData[8] = header.BitDepth;
- chunkData[9] = header.ColorType;
- chunkData[10] = header.CompressionMethod;
- chunkData[11] = header.FilterMethod;
- chunkData[12] = header.InterlaceMethod;
+ this.chunkDataBuffer[8] = header.BitDepth;
+ this.chunkDataBuffer[9] = header.ColorType;
+ this.chunkDataBuffer[10] = header.CompressionMethod;
+ this.chunkDataBuffer[11] = header.FilterMethod;
+ this.chunkDataBuffer[12] = header.InterlaceMethod;
- this.WriteChunk(stream, PngChunkTypes.Header, chunkData);
+ this.WriteChunk(stream, PngChunkTypes.Header, this.chunkDataBuffer, 0, 13);
}
///
@@ -507,36 +491,44 @@ namespace ImageSharp.Formats
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
- byte[] colorTable = new byte[colorTableLength];
-
- // TODO: Optimize this.
- Parallel.For(
- 0,
- pixelCount,
- Bootstrapper.Instance.ParallelOptions,
- i =>
+ byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength);
+ byte[] bytes = ArrayPool.Shared.Rent(4);
+
+ try
+ {
+ for (int i = 0; i < pixelCount; i++)
{
int offset = i * 3;
- Color color = new Color(palette[i].ToVector4());
- int alpha = color.A;
+ palette[i].ToBytes(bytes, 0, ComponentOrder.XYZW);
+
+ int alpha = bytes[3];
// Premultiply the color. This helps prevent banding.
+ // TODO: Vector?
if (alpha < 255 && alpha > this.Threshold)
{
- color = Color.Multiply(color, new Color(alpha, alpha, alpha, 255));
+ bytes[0] = (byte)(bytes[0] * alpha).Clamp(0, 255);
+ bytes[1] = (byte)(bytes[1] * alpha).Clamp(0, 255);
+ bytes[2] = (byte)(bytes[2] * alpha).Clamp(0, 255);
}
- colorTable[offset] = color.R;
- colorTable[offset + 1] = color.G;
- colorTable[offset + 2] = color.B;
+ colorTable[offset] = bytes[0];
+ colorTable[offset + 1] = bytes[1];
+ colorTable[offset + 2] = bytes[2];
if (alpha <= this.Threshold)
{
transparentPixels.Add((byte)offset);
}
- });
+ }
- this.WriteChunk(stream, PngChunkTypes.Palette, colorTable);
+ this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(colorTable);
+ ArrayPool.Shared.Return(bytes);
+ }
// Write the transparency data
if (transparentPixels.Any())
@@ -565,14 +557,12 @@ namespace ImageSharp.Formats
int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D);
int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D);
- byte[] chunkData = new byte[9];
-
- WriteInteger(chunkData, 0, dpmX);
- WriteInteger(chunkData, 4, dpmY);
+ WriteInteger(this.chunkDataBuffer, 0, dpmX);
+ WriteInteger(this.chunkDataBuffer, 4, dpmY);
- chunkData[8] = 1;
+ this.chunkDataBuffer[8] = 1;
- this.WriteChunk(stream, PngChunkTypes.Physical, chunkData);
+ this.WriteChunk(stream, PngChunkTypes.Physical, this.chunkDataBuffer, 0, 9);
}
}
@@ -584,50 +574,67 @@ namespace ImageSharp.Formats
{
if (this.WriteGamma)
{
- int gammaValue = (int)(this.Gamma * 100000f);
-
- byte[] fourByteData = new byte[4];
+ int gammaValue = (int)(this.Gamma * 100000F);
byte[] size = BitConverter.GetBytes(gammaValue);
- fourByteData[0] = size[3];
- fourByteData[1] = size[2];
- fourByteData[2] = size[1];
- fourByteData[3] = size[0];
+ this.chunkDataBuffer[0] = size[3];
+ this.chunkDataBuffer[1] = size[2];
+ this.chunkDataBuffer[2] = size[1];
+ this.chunkDataBuffer[3] = size[0];
- this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData);
+ this.WriteChunk(stream, PngChunkTypes.Gamma, this.chunkDataBuffer, 0, 4);
}
}
///
/// Writes the pixel information to the stream.
///
+ /// The pixel format.
+ /// The packed format. uint, long, float.
+ /// The image to encode.
/// The stream.
- private void WriteDataChunks(Stream stream)
+ private void WriteDataChunks(ImageBase image, Stream stream)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct
{
- byte[] data = this.EncodePixelData();
+ int bytesPerScanline = this.width * this.bytesPerPixel;
+ byte[] previousScanline = ArrayPool.Shared.Rent(bytesPerScanline);
+ byte[] rawScanline = ArrayPool.Shared.Rent(bytesPerScanline);
byte[] buffer;
int bufferLength;
-
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream();
-
using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel))
{
- deflateStream.Write(data, 0, data.Length);
- }
+ for (int y = 0; y < this.height; y++)
+ {
+ byte[] data = this.EncodePixelRow(image, y, previousScanline, rawScanline, bytesPerScanline);
+ deflateStream.Write(data, 0, data.Length);
+ deflateStream.Flush();
+
+ // Do a bit of shuffling;
+ byte[] tmp = rawScanline;
+ rawScanline = previousScanline;
+ previousScanline = tmp;
+ }
- bufferLength = (int)memoryStream.Length;
- buffer = memoryStream.ToArray();
+ bufferLength = (int)memoryStream.Length;
+ buffer = memoryStream.ToArray();
+ }
}
finally
{
+ ArrayPool.Shared.Return(previousScanline);
+ ArrayPool.Shared.Return(rawScanline);
memoryStream?.Dispose();
}
+ // Store the chunks in repeated 64k blocks.
+ // This reduces the memory load for decoding the image for many decoders.
int numChunks = bufferLength / MaxBlockSize;
if (bufferLength % MaxBlockSize != 0)
@@ -669,7 +676,7 @@ namespace ImageSharp.Formats
}
///
- /// Writes a chunk of a specified length to the stream at the given offset.
+ /// Writes a chunk of a specified length to the stream at the given offset.
///
/// The to write to.
/// The type of chunk to write.
@@ -678,26 +685,24 @@ namespace ImageSharp.Formats
/// The of the data to write.
private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length)
{
+ // Chunk length
WriteInteger(stream, length);
- byte[] typeArray = new byte[4];
- typeArray[0] = (byte)type[0];
- typeArray[1] = (byte)type[1];
- typeArray[2] = (byte)type[2];
- typeArray[3] = (byte)type[3];
-
- stream.Write(typeArray, 0, 4);
+ // Chunk type
+ this.chunkTypeBuffer[0] = (byte)type[0];
+ this.chunkTypeBuffer[1] = (byte)type[1];
+ this.chunkTypeBuffer[2] = (byte)type[2];
+ this.chunkTypeBuffer[3] = (byte)type[3];
- if (data != null)
- {
- stream.Write(data, offset, length);
- }
+ stream.Write(this.chunkTypeBuffer, 0, 4);
Crc32 crc32 = new Crc32();
- crc32.Update(typeArray);
+ crc32.Update(this.chunkTypeBuffer);
+ // Chunk data
if (data != null)
{
+ stream.Write(data, offset, length);
crc32.Update(data, offset, length);
}
diff --git a/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs
index 3fa61ff56..2deb7dcf0 100644
--- a/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs
+++ b/src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs
@@ -37,7 +37,9 @@ namespace ImageSharp.Formats
///
private bool isDisposed;
- // The stream responsible for decompressing the input stream.
+ ///
+ /// The stream responsible for compressing the input stream.
+ ///
private DeflateStream deflateStream;
///
diff --git a/src/ImageSharp46/ImageSharp46.csproj b/src/ImageSharp46/ImageSharp46.csproj
index b2b5d3755..bf7829e22 100644
--- a/src/ImageSharp46/ImageSharp46.csproj
+++ b/src/ImageSharp46/ImageSharp46.csproj
@@ -42,6 +42,10 @@
..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll
True
+
+ ..\..\packages\System.Buffers.4.0.0\lib\netstandard1.1\System.Buffers.dll
+ True
+
..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll
@@ -138,6 +142,7 @@
+
diff --git a/src/ImageSharp46/Properties/AssemblyInfo.cs b/src/ImageSharp46/Properties/AssemblyInfo.cs
index 3a9fc9d7e..ec405e1d0 100644
--- a/src/ImageSharp46/Properties/AssemblyInfo.cs
+++ b/src/ImageSharp46/Properties/AssemblyInfo.cs
@@ -37,4 +37,3 @@ using System.Runtime.CompilerServices;
// Ensure the internals can be tested.
[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")]
[assembly: InternalsVisibleTo("ImageSharp.Tests")]
-[assembly: InternalsVisibleTo("ImageSharp.Tests46")]
\ No newline at end of file
diff --git a/src/ImageSharp46/packages.config b/src/ImageSharp46/packages.config
index 9faeca551..b67050550 100644
--- a/src/ImageSharp46/packages.config
+++ b/src/ImageSharp46/packages.config
@@ -3,6 +3,7 @@
+
diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj
index 2fd552efe..843e5c4b5 100644
--- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj
+++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj
@@ -7,8 +7,8 @@
{635E0A15-3893-4763-A7F6-FCCFF85BCCA4}
Library
Properties
- ImageSharp.Tests46
- ImageSharp.Tests46
+ ImageSharp.Tests
+ ImageSharp.Tests
v4.6.1
512
@@ -118,6 +118,7 @@
+
diff --git a/tests/ImageSharp.Tests46/TestFile.cs b/tests/ImageSharp.Tests46/TestFile.cs
index 4f9f6fa32..f7e7f8517 100644
--- a/tests/ImageSharp.Tests46/TestFile.cs
+++ b/tests/ImageSharp.Tests46/TestFile.cs
@@ -4,7 +4,6 @@
//
using System.IO;
-using Xunit.Abstractions;
namespace ImageSharp.Tests
{
diff --git a/tests/ImageSharp.Tests46/app.config b/tests/ImageSharp.Tests46/app.config
new file mode 100644
index 000000000..5e95024db
--- /dev/null
+++ b/tests/ImageSharp.Tests46/app.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file