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