diff --git a/src/ImageSharp/Common/Extensions/ByteExtensions.cs b/src/ImageSharp/Common/Extensions/ByteExtensions.cs
index 89cfe69742..cca0140736 100644
--- a/src/ImageSharp/Common/Extensions/ByteExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/ByteExtensions.cs
@@ -24,7 +24,7 @@ namespace ImageSharp
public static byte[] ToArrayByBitsLength(this byte[] source, int bits)
{
Guard.NotNull(source, nameof(source));
- Guard.MustBeGreaterThan(bits, 0, "bits");
+ Guard.MustBeGreaterThan(bits, 0, nameof(bits));
byte[] result;
diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
index 8f056ff9d4..f6a05eefe3 100644
--- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
@@ -165,5 +165,18 @@ namespace ImageSharp
{
return (byte)value.Clamp(0, 255);
}
+
+ ///
+ /// Swaps the references to two objects in memory.
+ ///
+ /// The first reference.
+ /// The second reference.
+ /// The type of object.
+ public static void Swap(ref T first, ref T second)
+ {
+ T temp = second;
+ second = first;
+ first = temp;
+ }
}
}
diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
index 09156a5066..3855b289da 100644
--- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
@@ -20,13 +20,9 @@ namespace ImageSharp.Formats
/// The scanline to decode
/// The previous scanline.
/// The bytes per pixel.
- ///
- /// The
- ///
- public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
-
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
{
@@ -38,8 +34,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + Average(left, above)) % 256);
}
}
-
- return scanline;
}
///
@@ -47,13 +41,12 @@ namespace ImageSharp.Formats
///
/// The scanline to encode
/// The previous scanline.
+ /// The filtered scanline result.
/// The bytes per pixel.
/// The number of bytes per scanline
- /// The
- public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
+ public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
{
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
- byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
@@ -68,8 +61,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256);
}
}
-
- return result;
}
///
diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
index e624420347..426f9f1d9b 100644
--- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
@@ -29,14 +29,13 @@ namespace ImageSharp.Formats
/// Encodes the scanline
///
/// The scanline to encode
+ /// The filtered scanline result.
/// The number of bytes per scanline
- /// The
- public static byte[] Encode(byte[] scanline, int bytesPerScanline)
+ public static void Encode(byte[] scanline, byte[] result, int bytesPerScanline)
{
// Insert a byte before the data.
- byte[] result = new byte[bytesPerScanline + 1];
+ result[0] = 0;
Buffer.BlockCopy(scanline, 0, result, 1, bytesPerScanline);
- return result;
}
}
}
diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
index af274a891b..692ccfed8e 100644
--- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
@@ -21,11 +21,9 @@ namespace ImageSharp.Formats
/// The scanline to decode
/// The previous scanline.
/// The bytes per pixel.
- /// The
- public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
-
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
{
@@ -38,8 +36,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + PaethPredicator(left, above, upperLeft)) % 256);
}
}
-
- return scanline;
}
///
@@ -47,13 +43,12 @@ namespace ImageSharp.Formats
///
/// The scanline to encode
/// The previous scanline.
+ /// The filtered scanline result.
/// The bytes per pixel.
/// The number of bytes per scanline
- /// The
- public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
+ public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
{
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
- byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
@@ -69,8 +64,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256);
}
}
-
- return result;
}
///
diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
index 6b8d8fbd51..6cf5c6cdb4 100644
--- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs
@@ -17,11 +17,9 @@ namespace ImageSharp.Formats
///
/// The scanline to decode
/// The bytes per pixel.
- /// The
- public static byte[] Decode(byte[] scanline, int bytesPerPixel)
+ public static void Decode(byte[] scanline, int bytesPerPixel)
{
// Sub(x) + Raw(x-bpp)
-
fixed (byte* scan = scanline)
{
for (int x = 1; x < scanline.Length; x++)
@@ -30,21 +28,18 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + priorRawByte) % 256);
}
}
-
- return scanline;
}
///
/// Encodes the scanline
///
/// The scanline to encode
+ /// The filtered scanline result.
/// The bytes per pixel.
/// The number of bytes per scanline
- /// The
- public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline)
+ public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
{
// Sub(x) = Raw(x) - Raw(x-bpp)
- byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* res = result)
{
@@ -57,8 +52,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256);
}
}
-
- return result;
}
}
}
diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
index 1b30cd24eb..e4281fb736 100644
--- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
+++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs
@@ -17,11 +17,9 @@ namespace ImageSharp.Formats
///
/// The scanline to decode
/// The previous scanline.
- /// The
- public static byte[] Decode(byte[] scanline, byte[] previousScanline)
+ public static void Decode(byte[] scanline, byte[] previousScanline)
{
// Up(x) + Prior(x)
-
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
{
@@ -32,8 +30,6 @@ namespace ImageSharp.Formats
scan[x] = (byte)((scan[x] + above) % 256);
}
}
-
- return scanline;
}
///
@@ -41,12 +37,11 @@ namespace ImageSharp.Formats
///
/// The scanline to encode
/// The previous scanline.
+ /// The filtered scanline result.
/// The number of bytes per scanline
- /// The
- public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerScanline)
+ public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerScanline)
{
// Up(x) = Raw(x) - Prior(x)
- byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
@@ -60,8 +55,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - above) % 256);
}
}
-
- return result;
}
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 6f5ea55d9b..f31af2b49d 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -12,6 +12,8 @@ namespace ImageSharp.Formats
using System.Linq;
using System.Text;
+ using static ComparableExtensions;
+
///
/// Performs the png decoding operation.
///
@@ -112,7 +114,8 @@ namespace ImageSharp.Formats
/// Thrown if the image is larger than the maximum allowable size.
///
public void Decode(Image image, Stream stream)
- where TColor : struct, IPackedPixel where TPacked : struct
+ where TColor : struct, IPackedPixel
+ where TPacked : struct
{
Image currentImage = image;
this.currentStream = stream;
@@ -130,39 +133,49 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image does not end with end chunk.");
}
- switch (currentChunk.Type)
+ try
{
- 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;
+ switch (currentChunk.Type)
+ {
+ 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.Length);
+ break;
+ case PngChunkTypes.Palette:
+ byte[] pal = new byte[currentChunk.Length];
+ Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
+ this.palette = pal;
+ image.Quality = pal.Length / 3;
+ break;
+ case PngChunkTypes.PaletteAlpha:
+ byte[] alpha = new byte[currentChunk.Length];
+ Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length);
+ this.paletteAlpha = alpha;
+ break;
+ case PngChunkTypes.Text:
+ this.ReadTextChunk(currentImage, currentChunk.Data, currentChunk.Length);
+ break;
+ case PngChunkTypes.End:
+ isEndChunkReached = true;
+ break;
+ }
+ }
+ finally
+ {
+ // Data is rented in ReadChunkData()
+ ArrayPool.Shared.Return(currentChunk.Data);
}
}
if (this.header.Width > image.MaxWidth || this.header.Height > image.MaxHeight)
{
- throw new ArgumentOutOfRangeException(
- $"The input png '{this.header.Width}x{this.header.Height}' is bigger than the "
- + $"max allowed size '{image.MaxWidth}x{image.MaxHeight}'");
+ throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{image.MaxWidth}x{image.MaxHeight}'");
}
image.InitPixels(this.header.Width, this.header.Height);
@@ -182,7 +195,8 @@ namespace ImageSharp.Formats
/// The image to read to.
/// The data containing physical data.
private void ReadPhysicalChunk(Image image, byte[] data)
- where TColor : struct, IPackedPixel where TPacked : struct
+ where TColor : struct, IPackedPixel
+ where TPacked : struct
{
data.ReverseBytes(0, 4);
data.ReverseBytes(4, 4);
@@ -243,7 +257,8 @@ namespace ImageSharp.Formats
/// The containing data.
/// The pixel data.
private void ReadScanlines(MemoryStream dataStream, PixelAccessor pixels)
- where TColor : struct, IPackedPixel where TPacked : struct
+ where TColor : struct, IPackedPixel
+ where TPacked : struct
{
this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength() + 1;
@@ -268,7 +283,8 @@ namespace ImageSharp.Formats
/// The compressed pixel data stream.
/// The image pixel accessor.
private void DecodePixelData(Stream compressedStream, PixelAccessor pixels)
- where TColor : struct, IPackedPixel where TPacked : struct
+ where TColor : struct, IPackedPixel
+ where TPacked : struct
{
byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline);
byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline);
@@ -319,9 +335,7 @@ namespace ImageSharp.Formats
this.ProcessDefilteredScanline(scanline, y, pixels);
- byte[] temp = previousScanline;
- previousScanline = scanline;
- scanline = temp;
+ Swap(ref scanline, ref previousScanline);
}
}
finally
@@ -396,6 +410,10 @@ namespace ImageSharp.Formats
byte b = this.palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, a);
}
+ else
+ {
+ color.PackFromBytes(0, 0, 0, 0);
+ }
pixels[x, row] = color;
}
@@ -460,13 +478,14 @@ namespace ImageSharp.Formats
/// The packed format. uint, long, float.
/// The image to decode to.
/// The containing data.
- private void ReadTextChunk(Image image, byte[] data)
+ /// The maximum length to read.
+ private void ReadTextChunk(Image image, byte[] data, int length)
where TColor : struct, IPackedPixel
where TPacked : struct
{
int zeroIndex = 0;
- for (int i = 0; i < data.Length; i++)
+ for (int i = 0; i < length; i++)
{
if (data[i] == 0)
{
@@ -476,7 +495,7 @@ namespace ImageSharp.Formats
}
string name = Encoding.Unicode.GetString(data, 0, zeroIndex);
- string value = Encoding.Unicode.GetString(data, zeroIndex + 1, data.Length - zeroIndex - 1);
+ string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
image.Properties.Add(new ImageProperty(name, value));
}
@@ -577,7 +596,7 @@ namespace ImageSharp.Formats
Crc32 crc = new Crc32();
crc.Update(this.chunkTypeBuffer);
- crc.Update(chunk.Data);
+ crc.Update(chunk.Data, 0, chunk.Length);
if (crc.Value != chunk.Crc)
{
@@ -591,8 +610,8 @@ namespace ImageSharp.Formats
/// The chunk.
private void ReadChunkData(PngChunk chunk)
{
- // TODO: It might be possible to rent this but that could also lead to issues assigning the data to various properties
- chunk.Data = new byte[chunk.Length];
+ // We rent the buffer here to return it afterwards in Decode()
+ chunk.Data = ArrayPool.Shared.Rent(chunk.Length);
this.currentStream.Read(chunk.Data, 0, chunk.Length);
}
@@ -645,4 +664,4 @@ namespace ImageSharp.Formats
chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index f72fbc1808..95f50cf276 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -13,6 +13,8 @@ namespace ImageSharp.Formats
using Quantizers;
+ using static ComparableExtensions;
+
///
/// Performs the png encoding operation.
///
@@ -58,6 +60,26 @@ namespace ImageSharp.Formats
///
private int bytesPerPixel;
+ ///
+ /// The buffer for the sub filter
+ ///
+ private byte[] sub;
+
+ ///
+ /// The buffer for the up filter
+ ///
+ private byte[] up;
+
+ ///
+ /// The buffer for the average filter
+ ///
+ private byte[] average;
+
+ ///
+ /// The buffer for the paeth filter
+ ///
+ private byte[] paeth;
+
///
/// Gets or sets the quality of output for images.
///
@@ -312,9 +334,10 @@ namespace ImageSharp.Formats
/// The row.
/// The previous scanline.
/// The raw scanline.
+ /// The filtered scanline result.
/// The number of bytes per scanline.
/// The
- private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline)
+ private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result, int bytesPerScanline)
where TColor : struct, IPackedPixel
where TPacked : struct
{
@@ -332,7 +355,7 @@ namespace ImageSharp.Formats
break;
}
- return this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline);
+ return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result, bytesPerScanline);
}
///
@@ -341,45 +364,51 @@ namespace ImageSharp.Formats
///
/// The raw scanline
/// The previous scanline
+ /// The filtered scanline result.
/// The number of bytes per scanline
/// The
- private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline)
+ private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result, int bytesPerScanline)
{
// Palette images don't compress well with adaptive filtering.
if (this.PngColorType == PngColorType.Palette)
{
- return NoneFilter.Encode(rawScanline, bytesPerScanline);
+ NoneFilter.Encode(rawScanline, result, bytesPerScanline);
+ return result;
}
- // TODO: it would be nice to avoid the memory allocation here.
- Tuple[] candidates = new Tuple[4];
+ SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel, bytesPerScanline);
+ int currentTotalVariation = this.CalculateTotalVariation(this.sub, bytesPerScanline);
+ int lowestTotalVariation = currentTotalVariation;
- byte[] sub = SubFilter.Encode(rawScanline, this.bytesPerPixel, bytesPerScanline);
- candidates[0] = new Tuple(sub, this.CalculateTotalVariation(sub, bytesPerScanline));
+ result = this.sub;
- byte[] up = UpFilter.Encode(rawScanline, previousScanline, bytesPerScanline);
- candidates[1] = new Tuple(up, this.CalculateTotalVariation(up, bytesPerScanline));
+ UpFilter.Encode(rawScanline, previousScanline, this.up, bytesPerScanline);
+ currentTotalVariation = this.CalculateTotalVariation(this.up, bytesPerScanline);
- byte[] average = AverageFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline);
- candidates[2] = new Tuple(average, this.CalculateTotalVariation(average, bytesPerScanline));
+ if (currentTotalVariation < lowestTotalVariation)
+ {
+ lowestTotalVariation = currentTotalVariation;
+ result = this.up;
+ }
- byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline);
- candidates[3] = new Tuple(paeth, this.CalculateTotalVariation(paeth, bytesPerScanline));
+ AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel, bytesPerScanline);
+ currentTotalVariation = this.CalculateTotalVariation(this.average, bytesPerScanline);
- int lowestTotalVariation = int.MaxValue;
- int lowestTotalVariationIndex = 0;
+ if (currentTotalVariation < lowestTotalVariation)
+ {
+ lowestTotalVariation = currentTotalVariation;
+ result = this.average;
+ }
- for (int i = 0; i < candidates.Length; i++)
+ PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel, bytesPerScanline);
+ currentTotalVariation = this.CalculateTotalVariation(this.paeth, bytesPerScanline);
+
+ if (currentTotalVariation < lowestTotalVariation)
{
- if (candidates[i].Item2 < lowestTotalVariation)
- {
- lowestTotalVariationIndex = i;
- lowestTotalVariation = candidates[i].Item2;
- }
+ result = this.paeth;
}
- // ReSharper disable once RedundantAssignment
- return candidates[lowestTotalVariationIndex].Item1;
+ return result;
}
///
@@ -592,6 +621,15 @@ namespace ImageSharp.Formats
byte[] previousScanline = ArrayPool.Shared.Rent(bytesPerScanline);
byte[] rawScanline = ArrayPool.Shared.Rent(bytesPerScanline);
int resultLength = bytesPerScanline + 1;
+ byte[] result = ArrayPool.Shared.Rent(resultLength);
+
+ if (this.PngColorType != PngColorType.Palette)
+ {
+ this.sub = ArrayPool.Shared.Rent(resultLength);
+ this.up = ArrayPool.Shared.Rent(resultLength);
+ this.average = ArrayPool.Shared.Rent(resultLength);
+ this.paeth = ArrayPool.Shared.Rent(resultLength);
+ }
byte[] buffer;
int bufferLength;
@@ -603,12 +641,9 @@ namespace ImageSharp.Formats
{
for (int y = 0; y < this.height; y++)
{
- deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, bytesPerScanline), 0, resultLength);
+ deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, result, bytesPerScanline), 0, resultLength);
- // Do a bit of shuffling;
- byte[] tmp = rawScanline;
- rawScanline = previousScanline;
- previousScanline = tmp;
+ Swap(ref rawScanline, ref previousScanline);
}
}
@@ -620,6 +655,15 @@ namespace ImageSharp.Formats
memoryStream?.Dispose();
ArrayPool.Shared.Return(previousScanline);
ArrayPool.Shared.Return(rawScanline);
+ ArrayPool.Shared.Return(result);
+
+ if (this.PngColorType != PngColorType.Palette)
+ {
+ ArrayPool.Shared.Return(this.sub);
+ ArrayPool.Shared.Return(this.up);
+ ArrayPool.Shared.Return(this.average);
+ ArrayPool.Shared.Return(this.paeth);
+ }
}
// Store the chunks in repeated 64k blocks.
diff --git a/src/ImageSharp/IO/EndianBitConverter.cs b/src/ImageSharp/IO/EndianBitConverter.cs
index d7a8d91faf..e99e38db28 100644
--- a/src/ImageSharp/IO/EndianBitConverter.cs
+++ b/src/ImageSharp/IO/EndianBitConverter.cs
@@ -20,9 +20,11 @@ namespace ImageSharp.IO
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")]
+ [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:DoNotUseRegions", Justification = "Reviewed. Suppression is OK here. Better readability.")]
internal abstract class EndianBitConverter
{
#region Endianness of this converter
+
///
/// Indicates the byte order ("endianness") in which data is converted using this class.
///
@@ -41,6 +43,7 @@ namespace ImageSharp.IO
#endregion
#region Factory properties
+
///
/// The little-endian bit converter.
///
@@ -65,6 +68,7 @@ namespace ImageSharp.IO
#endregion
#region Double/primitive conversions
+
///
/// Converts the specified double-precision floating point number to a
/// 64-bit signed integer. Note: the endianness of this converter does not
@@ -115,6 +119,7 @@ namespace ImageSharp.IO
#endregion
#region To(PrimitiveType) conversions
+
///
/// Returns a Boolean value converted from one byte at a specified position in a byte array.
///
@@ -279,6 +284,7 @@ namespace ImageSharp.IO
#endregion
#region ToString conversions
+
///
/// Returns a String converted from the elements of a byte array.
///
@@ -326,6 +332,7 @@ namespace ImageSharp.IO
#endregion
#region Decimal conversions
+
///
/// Returns a decimal value converted from sixteen bytes
/// at a specified position in a byte array.
@@ -382,6 +389,7 @@ namespace ImageSharp.IO
#endregion
#region GetBytes conversions
+
///
/// Returns an array with the given number of bytes formed
/// from the least significant bytes of the specified value.
@@ -508,6 +516,7 @@ namespace ImageSharp.IO
#endregion
#region CopyBytes conversions
+
///
/// Copies the given number of bytes from the least-specific
/// end of the specified value into the specified byte array, beginning
@@ -669,6 +678,7 @@ namespace ImageSharp.IO
#endregion
#region Private struct used for Single/Int32 conversions
+
///
/// Union used solely for the equivalent of DoubleToInt64Bits and vice versa.
///
diff --git a/src/ImageSharp/Numerics/Rectangle.cs b/src/ImageSharp/Numerics/Rectangle.cs
index fb623c2eb1..be457fc5a3 100644
--- a/src/ImageSharp/Numerics/Rectangle.cs
+++ b/src/ImageSharp/Numerics/Rectangle.cs
@@ -213,6 +213,16 @@ namespace ImageSharp
return !left.Equals(right);
}
+ ///
+ /// Returns the center point of the given
+ ///
+ /// The rectangle
+ ///
+ public static Point Center(Rectangle rectangle)
+ {
+ return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
+ }
+
///
/// Determines if the specfied point is contained within the rectangular region defined by
/// this .
@@ -229,16 +239,6 @@ namespace ImageSharp
&& y < this.Bottom;
}
- ///
- /// Returns the center point of the given
- ///
- /// The rectangle
- ///
- public static Point Center(Rectangle rectangle)
- {
- return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
- }
-
///
public override int GetHashCode()
{
diff --git a/src/ImageSharp/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Profiles/Exif/ExifReader.cs
index 7bbfb3d061..0b845c4914 100644
--- a/src/ImageSharp/Profiles/Exif/ExifReader.cs
+++ b/src/ImageSharp/Profiles/Exif/ExifReader.cs
@@ -15,8 +15,6 @@ namespace ImageSharp
///
internal sealed class ExifReader
{
- private delegate TDataType ConverterMethod(byte[] data);
-
private readonly Collection invalidTags = new Collection();
private byte[] exifData;
private uint currentIndex;
@@ -25,6 +23,13 @@ namespace ImageSharp
private uint gpsOffset;
private uint startIndex;
+ private delegate TDataType ConverterMethod(byte[] data);
+
+ ///
+ /// Gets the invalid tags.
+ ///
+ public IEnumerable InvalidTags => this.invalidTags;
+
///
/// Gets the thumbnail length in the byte stream
///
@@ -112,10 +117,40 @@ namespace ImageSharp
return result;
}
- ///
- /// Gets the invalid tags.
- ///
- public IEnumerable InvalidTags => this.invalidTags;
+ private static TDataType[] ToArray(ExifDataType dataType, byte[] data, ConverterMethod converter)
+ {
+ int dataTypeSize = (int)ExifValue.GetSize(dataType);
+ int length = data.Length / dataTypeSize;
+
+ TDataType[] result = new TDataType[length];
+ byte[] buffer = new byte[dataTypeSize];
+
+ for (int i = 0; i < length; i++)
+ {
+ Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize);
+
+ result.SetValue(converter(buffer), i);
+ }
+
+ return result;
+ }
+
+ private static byte ToByte(byte[] data)
+ {
+ return data[0];
+ }
+
+ private static string ToString(byte[] data)
+ {
+ string result = Encoding.UTF8.GetString(data, 0, data.Length);
+ int nullCharIndex = result.IndexOf('\0');
+ if (nullCharIndex != -1)
+ {
+ result = result.Substring(0, nullCharIndex);
+ }
+
+ return result;
+ }
///
/// Adds the collection of EXIF values to the reader.
@@ -369,29 +404,6 @@ namespace ImageSharp
}
}
- private static TDataType[] ToArray(ExifDataType dataType, byte[] data, ConverterMethod converter)
- {
- int dataTypeSize = (int)ExifValue.GetSize(dataType);
- int length = data.Length / dataTypeSize;
-
- TDataType[] result = new TDataType[length];
- byte[] buffer = new byte[dataTypeSize];
-
- for (int i = 0; i < length; i++)
- {
- Array.Copy(data, i * dataTypeSize, buffer, 0, dataTypeSize);
-
- result.SetValue(converter(buffer), i);
- }
-
- return result;
- }
-
- private static byte ToByte(byte[] data)
- {
- return data[0];
- }
-
private double ToDouble(byte[] data)
{
if (!this.ValidateArray(data, 8))
@@ -432,18 +444,6 @@ namespace ImageSharp
return BitConverter.ToSingle(data, 0);
}
- private static string ToString(byte[] data)
- {
- string result = Encoding.UTF8.GetString(data, 0, data.Length);
- int nullCharIndex = result.IndexOf('\0');
- if (nullCharIndex != -1)
- {
- result = result.Substring(0, nullCharIndex);
- }
-
- return result;
- }
-
private Rational ToRational(byte[] data)
{
if (!this.ValidateArray(data, 8, 4))
diff --git a/src/ImageSharp/Profiles/Exif/ExifValue.cs b/src/ImageSharp/Profiles/Exif/ExifValue.cs
index db62be4c23..c5bc2e3924 100644
--- a/src/ImageSharp/Profiles/Exif/ExifValue.cs
+++ b/src/ImageSharp/Profiles/Exif/ExifValue.cs
@@ -41,6 +41,24 @@ namespace ImageSharp
}
}
+ internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray)
+ {
+ this.Tag = tag;
+ this.DataType = dataType;
+ this.IsArray = isArray;
+
+ if (dataType == ExifDataType.Ascii)
+ {
+ this.IsArray = false;
+ }
+ }
+
+ internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
+ : this(tag, dataType, isArray)
+ {
+ this.exifValue = value;
+ }
+
///
/// Gets the data type of the exif value.
///
@@ -81,6 +99,57 @@ namespace ImageSharp
}
}
+ internal bool HasValue
+ {
+ get
+ {
+ if (this.exifValue == null)
+ {
+ return false;
+ }
+
+ if (this.DataType == ExifDataType.Ascii)
+ {
+ return ((string)this.exifValue).Length > 0;
+ }
+
+ return true;
+ }
+ }
+
+ internal int Length
+ {
+ get
+ {
+ if (this.exifValue == null)
+ {
+ return 4;
+ }
+
+ int size = (int)(GetSize(this.DataType) * this.NumberOfComponents);
+
+ return size < 4 ? 4 : size;
+ }
+ }
+
+ internal int NumberOfComponents
+ {
+ get
+ {
+ if (this.DataType == ExifDataType.Ascii)
+ {
+ return Encoding.UTF8.GetBytes((string)this.exifValue).Length;
+ }
+
+ if (this.IsArray)
+ {
+ return ((Array)this.exifValue).Length;
+ }
+
+ return 1;
+ }
+ }
+
///
/// Determines whether the specified ExifValue instances are considered equal.
///
@@ -173,75 +242,6 @@ namespace ImageSharp
return sb.ToString();
}
- internal bool HasValue
- {
- get
- {
- if (this.exifValue == null)
- {
- return false;
- }
-
- if (this.DataType == ExifDataType.Ascii)
- {
- return ((string)this.exifValue).Length > 0;
- }
-
- return true;
- }
- }
-
- internal int Length
- {
- get
- {
- if (this.exifValue == null)
- {
- return 4;
- }
-
- int size = (int)(GetSize(this.DataType) * this.NumberOfComponents);
-
- return size < 4 ? 4 : size;
- }
- }
-
- internal int NumberOfComponents
- {
- get
- {
- if (this.DataType == ExifDataType.Ascii)
- {
- return Encoding.UTF8.GetBytes((string)this.exifValue).Length;
- }
-
- if (this.IsArray)
- {
- return ((Array)this.exifValue).Length;
- }
-
- return 1;
- }
- }
-
- internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray)
- {
- this.Tag = tag;
- this.DataType = dataType;
- this.IsArray = isArray;
-
- if (dataType == ExifDataType.Ascii)
- {
- this.IsArray = false;
- }
- }
-
- internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
- : this(tag, dataType, isArray)
- {
- this.exifValue = value;
- }
-
internal static ExifValue Create(ExifTag tag, object value)
{
Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag");
@@ -574,6 +574,26 @@ namespace ImageSharp
}
}
+ private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray)
+ {
+ if (type == null || type == typeof(ushort))
+ {
+ return new ExifValue(tag, ExifDataType.Short, isArray);
+ }
+
+ if (type == typeof(short))
+ {
+ return new ExifValue(tag, ExifDataType.SignedShort, isArray);
+ }
+
+ if (type == typeof(uint))
+ {
+ return new ExifValue(tag, ExifDataType.Long, isArray);
+ }
+
+ return new ExifValue(tag, ExifDataType.SignedLong, isArray);
+ }
+
private void CheckValue(object value)
{
if (value == null)
@@ -639,26 +659,6 @@ namespace ImageSharp
}
}
- private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray)
- {
- if (type == null || type == typeof(ushort))
- {
- return new ExifValue(tag, ExifDataType.Short, isArray);
- }
-
- if (type == typeof(short))
- {
- return new ExifValue(tag, ExifDataType.SignedShort, isArray);
- }
-
- if (type == typeof(uint))
- {
- return new ExifValue(tag, ExifDataType.Long, isArray);
- }
-
- return new ExifValue(tag, ExifDataType.SignedLong, isArray);
- }
-
private string ToString(object value)
{
string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, value);
diff --git a/src/ImageSharp/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Profiles/Exif/ExifWriter.cs
index f7653b240f..e75f3f23ec 100644
--- a/src/ImageSharp/Profiles/Exif/ExifWriter.cs
+++ b/src/ImageSharp/Profiles/Exif/ExifWriter.cs
@@ -12,6 +12,8 @@ namespace ImageSharp
internal sealed class ExifWriter
{
+ private const int StartIndex = 6;
+
private static readonly ExifTag[] IfdTags = new ExifTag[127]
{
ExifTag.SubfileType,
@@ -274,8 +276,6 @@ namespace ImageSharp
ExifTag.GPSDifferential
};
- private const int StartIndex = 6;
-
private ExifParts allowedParts;
private Collection values;
private Collection dataOffsets;
@@ -379,6 +379,13 @@ namespace ImageSharp
return result;
}
+ private static int Write(byte[] source, byte[] destination, int offset)
+ {
+ Buffer.BlockCopy(source, 0, destination, offset, source.Length);
+
+ return offset + source.Length;
+ }
+
private int GetIndex(Collection indexes, ExifTag tag)
{
foreach (int index in indexes)
@@ -443,13 +450,6 @@ namespace ImageSharp
return length;
}
- private static int Write(byte[] source, byte[] destination, int offset)
- {
- Buffer.BlockCopy(source, 0, destination, offset, source.Length);
-
- return offset + source.Length;
- }
-
private int WriteArray(ExifValue value, byte[] destination, int offset)
{
if (value.DataType == ExifDataType.Ascii)
diff --git a/src/ImageSharp/Samplers/Processors/RotateProcessor.cs b/src/ImageSharp/Samplers/Processors/RotateProcessor.cs
index 05cbda36e8..a9e69678df 100644
--- a/src/ImageSharp/Samplers/Processors/RotateProcessor.cs
+++ b/src/ImageSharp/Samplers/Processors/RotateProcessor.cs
@@ -33,23 +33,6 @@ namespace ImageSharp.Processors
///
public bool Expand { get; set; } = true;
- ///
- protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- const float Epsilon = .0001F;
-
- if (Math.Abs(this.Angle) < Epsilon || Math.Abs(this.Angle - 90) < Epsilon || Math.Abs(this.Angle - 180) < Epsilon || Math.Abs(this.Angle - 270) < Epsilon)
- {
- return;
- }
-
- this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
- if (this.Expand)
- {
- CreateNewTarget(target, sourceRectangle, this.processMatrix);
- }
- }
-
///
public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
@@ -83,6 +66,23 @@ namespace ImageSharp.Processors
}
}
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ const float Epsilon = .0001F;
+
+ if (Math.Abs(this.Angle) < Epsilon || Math.Abs(this.Angle - 90) < Epsilon || Math.Abs(this.Angle - 180) < Epsilon || Math.Abs(this.Angle - 270) < Epsilon)
+ {
+ return;
+ }
+
+ this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
+ if (this.Expand)
+ {
+ CreateNewTarget(target, sourceRectangle, this.processMatrix);
+ }
+ }
+
///
/// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
///
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs
index 468ed6e135..e0ecc563ae 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs
@@ -8,6 +8,7 @@ using ImageSharp.Formats;
namespace ImageSharp.Tests
{
using System.IO;
+ using System.Threading.Tasks;
using Formats;
@@ -31,5 +32,23 @@ namespace ImageSharp.Tests
}
}
}
+
+ [Fact]
+ public void ImageCanSavePngInParallel()
+ {
+ string path = this.CreateOutputDirectory("Png");
+
+ Parallel.ForEach(
+ Files,
+ file =>
+ {
+ Image image = file.CreateImage();
+
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png"))
+ {
+ image.Save(output, new PngFormat());
+ }
+ });
+ }
}
}
\ No newline at end of file