From 44d7359ae50882e09f930d90d92fc5715ac704d0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Nov 2016 16:49:37 +0100 Subject: [PATCH] Merge branch 'master' into HEAD --- Settings.StyleCop | 2 + .../Common/Extensions/StreamExtensions.cs | 20 ++- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 3 +- .../Formats/Png/Filters/AverageFilter.cs | 43 +++--- .../Formats/Png/Filters/NoneFilter.cs | 11 +- .../Formats/Png/Filters/PaethFilter.cs | 42 +++--- .../Formats/Png/Filters/SubFilter.cs | 32 +++-- .../Formats/Png/Filters/UpFilter.cs | 36 +++-- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 131 +++++++++--------- src/ImageSharp/IO/EndianBinaryWriter.cs | 26 ++-- src/ImageSharp/Image.cs | 3 +- src/ImageSharp/Image/Image.cs | 2 + src/ImageSharp/Numerics/Ellipse.cs | 15 +- src/ImageSharp/Profiles/Exif/ExifReader.cs | 8 ++ src/ImageSharp/Profiles/Exif/ExifTag.cs | 3 +- .../Samplers/Options/Orientation.cs | 38 +++++ .../Samplers/Processors/RotateProcessor.cs | 2 +- .../Samplers/Processors/SkewProcessor.cs | 22 +-- .../ImageSharp.Benchmarks/Image/EncodeBmp.cs | 4 +- .../ImageSharp.Benchmarks/Image/EncodeGif.cs | 4 +- .../ImageSharp.Benchmarks/Image/EncodePng.cs | 4 +- 22 files changed, 282 insertions(+), 174 deletions(-) diff --git a/Settings.StyleCop b/Settings.StyleCop index b7a5355e0..aeee88627 100644 --- a/Settings.StyleCop +++ b/Settings.StyleCop @@ -32,6 +32,8 @@ Vol pp cmyk + Paeth + th diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 87d5a6c32..6de94dd22 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -5,10 +5,19 @@ namespace ImageSharp { + using System.Buffers; using System.IO; + /// + /// Extension methods for the type. + /// internal static class StreamExtensions { + /// + /// Skips the number of bytes in the given stream. + /// + /// The stream. + /// The count. public static void Skip(this Stream stream, int count) { if (count < 1) @@ -22,8 +31,15 @@ namespace ImageSharp } else { - byte[] foo = new byte[count]; - stream.Read(foo, 0, count); + byte[] foo = ArrayPool.Shared.Rent(count); + try + { + stream.Read(foo, 0, count); + } + finally + { + ArrayPool.Shared.Return(foo); + } } } } diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 9cb4fce26..69161eb02 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -246,7 +246,6 @@ namespace ImageSharp.Formats /// /// The AC luminance huffman table index /// - LuminanceAC = 1, // ReSharper restore UnusedMember.Local @@ -851,6 +850,7 @@ namespace ImageSharp.Formats Block b = new Block(); Block cb = new Block(); Block cr = new Block(); + // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; @@ -880,6 +880,7 @@ namespace ImageSharp.Formats Block b = new Block(); Block[] cb = new Block[4]; Block[] cr = new Block[4]; + // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index bef124541..cd95eba56 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats /// the value of a pixel. /// /// - internal static class AverageFilter + internal static unsafe class AverageFilter { /// /// Decodes the scanline @@ -28,12 +28,17 @@ namespace ImageSharp.Formats // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) byte[] result = new byte[scanline.Length]; - for (int x = 1; x < scanline.Length; x++) + fixed (byte* scan = scanline) + fixed (byte* prev = previousScanline) + fixed (byte* res = result) { - byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel]; - byte above = previousScanline[x]; + for (int x = 1; x < scanline.Length; x++) + { + byte left = (x - bytesPerPixel < 1) ? (byte)0 : res[x - bytesPerPixel]; + byte above = prev[x]; - result[x] = (byte)((scanline[x] + Average(left, above)) % 256); + res[x] = (byte)((scan[x] + Average(left, above)) % 256); + } } return result; @@ -44,25 +49,29 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The previous scanline. + /// The encoded scanline. /// 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 byte[] Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline) { // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - byte[] encodedScanline = new byte[bytesPerScanline + 1]; - - encodedScanline[0] = (byte)FilterType.Average; - - for (int x = 0; x < bytesPerScanline; x++) + fixed (byte* scan = scanline) + fixed (byte* prev = previousScanline) + fixed (byte* res = result) { - byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; - byte above = previousScanline[x]; + res[0] = 3; - encodedScanline[x + 1] = (byte)((scanline[x] - Average(left, above)) % 256); + for (int x = 0; x < bytesPerScanline; x++) + { + byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; + byte above = prev[x]; + + res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256); + } } - return encodedScanline; + return result; } /// @@ -73,7 +82,7 @@ namespace ImageSharp.Formats /// The private static int Average(byte left, byte above) { - return Convert.ToInt32(Math.Floor((left + above) / 2.0D)); + return (left + above) >> 1; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index 175e5affa..ef41fc631 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -29,16 +29,13 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The encoded scanline. /// 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[] encodedScanline = new byte[bytesPerScanline + 1]; - encodedScanline[0] = (byte)FilterType.None; - Buffer.BlockCopy(scanline, 0, encodedScanline, 1, bytesPerScanline); - - return encodedScanline; + result[0] = 0; + Buffer.BlockCopy(scanline, 0, result, 1, bytesPerScanline); } } } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 232d7cc3d..16c0378e9 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Formats /// This technique is due to Alan W. Paeth. /// /// - internal static class PaethFilter + internal static unsafe class PaethFilter { /// /// Decodes the scanline @@ -27,13 +27,18 @@ namespace ImageSharp.Formats // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) byte[] result = new byte[scanline.Length]; - for (int x = 1; x < scanline.Length; x++) + fixed (byte* scan = scanline) + fixed (byte* prev = previousScanline) + fixed (byte* res = result) { - byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel]; - byte above = previousScanline[x]; - byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : previousScanline[x - bytesPerPixel]; + for (int x = 1; x < scanline.Length; x++) + { + byte left = (x - bytesPerPixel < 1) ? (byte)0 : res[x - bytesPerPixel]; + byte above = prev[x]; + byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : prev[x - bytesPerPixel]; - result[x] = (byte)((scanline[x] + PaethPredicator(left, above, upperLeft)) % 256); + res[x] = (byte)((scan[x] + PaethPredicator(left, above, upperLeft)) % 256); + } } return result; @@ -44,25 +49,30 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The previous scanline. + /// The encoded scanline. /// 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 byte[] 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[] encodedScanline = new byte[bytesPerScanline + 1]; - encodedScanline[0] = (byte)FilterType.Paeth; - - for (int x = 0; x < bytesPerScanline; x++) + fixed (byte* scan = scanline) + fixed (byte* prev = previousScanline) + fixed (byte* res = result) { - byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; - byte above = previousScanline[x]; - byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : previousScanline[x - bytesPerPixel]; + res[0] = 4; + + for (int x = 0; x < bytesPerScanline; x++) + { + byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; + byte above = prev[x]; + byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : prev[x - bytesPerPixel]; - encodedScanline[x + 1] = (byte)((scanline[x] - PaethPredicator(left, above, upperLeft)) % 256); + res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256); + } } - return encodedScanline; + return result; } /// diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index c4fbe3e51..63e41a1d5 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// of the prior pixel. /// /// - internal static class SubFilter + internal static unsafe class SubFilter { /// /// Decodes the scanline @@ -23,11 +23,15 @@ namespace ImageSharp.Formats // Sub(x) + Raw(x-bpp) byte[] result = new byte[scanline.Length]; - for (int x = 1; x < scanline.Length; x++) + fixed (byte* scan = scanline) + fixed (byte* res = result) { - byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel]; + for (int x = 1; x < scanline.Length; x++) + { + byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : res[x - bytesPerPixel]; - result[x] = (byte)((scanline[x] + priorRawByte) % 256); + res[x] = (byte)((scan[x] + priorRawByte) % 256); + } } return result; @@ -37,23 +41,27 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The encoded scanline. /// The bytes per pixel. /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline) + public static byte[] Encode(byte[] scanline, byte[] result, int bytesPerPixel, int bytesPerScanline) { // Sub(x) = Raw(x) - Raw(x-bpp) - byte[] encodedScanline = new byte[bytesPerScanline + 1]; - encodedScanline[0] = (byte)FilterType.Sub; - - for (int x = 0; x < bytesPerScanline; x++) + fixed (byte* scan = scanline) + fixed (byte* res = result) { - byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel]; + res[0] = 1; + + for (int x = 0; x < bytesPerScanline; x++) + { + byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; - encodedScanline[x + 1] = (byte)((scanline[x] - priorRawByte) % 256); + res[x + 1] = (byte)((scan[x] - priorRawByte) % 256); + } } - return encodedScanline; + return result; } } } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 026070421..b40aa944b 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// rather than just to its left, is used as the predictor. /// /// - internal static class UpFilter + internal static unsafe class UpFilter { /// /// Decodes the scanline @@ -23,11 +23,16 @@ namespace ImageSharp.Formats // Up(x) + Prior(x) byte[] result = new byte[scanline.Length]; - for (int x = 1; x < scanline.Length; x++) + fixed (byte* scan = scanline) + fixed (byte* prev = previousScanline) + fixed (byte* res = result) { - byte above = previousScanline[x]; + for (int x = 1; x < scanline.Length; x++) + { + byte above = prev[x]; - result[x] = (byte)((scanline[x] + above) % 256); + res[x] = (byte)((scan[x] + above) % 256); + } } return result; @@ -37,23 +42,28 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode - /// The number of bytes per scanline /// The previous scanline. + /// The encoded scanline. + /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline, int bytesPerScanline, byte[] previousScanline) + public static byte[] Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerScanline) { // Up(x) = Raw(x) - Prior(x) - byte[] encodedScanline = new byte[bytesPerScanline + 1]; - encodedScanline[0] = (byte)FilterType.Up; - - for (int x = 0; x < bytesPerScanline; x++) + fixed (byte* scan = scanline) + fixed (byte* prev = previousScanline) + fixed (byte* res = result) { - byte above = previousScanline[x]; + res[0] = 2; - encodedScanline[x + 1] = (byte)((scanline[x] - above) % 256); + for (int x = 0; x < bytesPerScanline; x++) + { + byte above = prev[x]; + + res[x + 1] = (byte)((scan[x] - above) % 256); + } } - return encodedScanline; + return result; } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cad32d9f2..186e99437 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -283,7 +283,9 @@ namespace ImageSharp.Formats 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. + // TODO: It would be good if we can reduce the memory usage here - Each filter is creating a new row. + // Every time I try to use the same approach as I have in the encoder though I keep messing up. + // Fingers crossed someone with a big brain and a kind heart will come along and finish optimizing this for me. switch (filterType) { case FilterType.None: @@ -590,6 +592,7 @@ 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]; this.currentStream.Read(chunk.Data, 0, chunk.Length); } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index a06e306f5..764d2f538 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -34,7 +34,6 @@ namespace ImageSharp.Formats /// private readonly byte[] chunkDataBuffer = new byte[16]; - /// /// Contains the raw pixel data from an indexed image. /// @@ -182,7 +181,11 @@ namespace ImageSharp.Formats this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); - this.WriteDataChunks(image, stream); + using (PixelAccessor pixels = image.Lock()) + { + this.WriteDataChunks(pixels, stream); + } + this.WriteEndChunk(stream); stream.Flush(); } @@ -249,35 +252,32 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The packed format. uint, long, float. - /// The image to encode. + /// The image pixels accessor. /// The row index. /// The raw scanline. - private void CollectGrayscaleBytes(ImageBase image, int row, byte[] rawScanline) + private void CollectGrayscaleBytes(PixelAccessor pixels, int row, byte[] rawScanline) where TColor : struct, IPackedPixel where TPacked : struct { // Copy the pixels across from the image. // Reuse the chunk type buffer. - using (PixelAccessor pixels = image.Lock()) + 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])); + // 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++) + for (int i = 0; i < this.bytesPerPixel; i++) + { + if (i == 0) + { + rawScanline[offset] = luminance; + } + else { - if (i == 0) - { - rawScanline[offset] = luminance; - } - else - { - rawScanline[offset + i] = this.chunkTypeBuffer[3]; - } + rawScanline[offset + i] = this.chunkTypeBuffer[3]; } } } @@ -288,20 +288,17 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The packed format. uint, long, float. - /// The image to encode. + /// The image pixel accessor. /// The row index. /// The raw scanline. - private void CollectColorBytes(ImageBase image, int row, byte[] rawScanline) + private void CollectColorBytes(PixelAccessor pixels, int row, byte[] rawScanline) where TColor : struct, IPackedPixel where TPacked : struct { - using (PixelAccessor pixels = image.Lock()) + int bpp = this.bytesPerPixel; + for (int x = 0; x < this.width; x++) { - int bpp = this.bytesPerPixel; - for (int x = 0; x < this.width; x++) - { - pixels[x, row].ToBytes(rawScanline, x * this.bytesPerPixel, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ); - } + pixels[x, row].ToBytes(rawScanline, x * this.bytesPerPixel, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ); } } @@ -311,13 +308,13 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The packed format. uint, long, float. - /// The image to encode. + /// The image pixel accessor. /// The row. /// The previous scanline. /// The raw scanline. + /// The resultant filtered scanline. /// The number of bytes per scanline. - /// The - private byte[] EncodePixelRow(ImageBase image, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline) + private void EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result, int bytesPerScanline) where TColor : struct, IPackedPixel where TPacked : struct { @@ -328,16 +325,14 @@ namespace ImageSharp.Formats break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: - this.CollectGrayscaleBytes(image, row, rawScanline); + this.CollectGrayscaleBytes(pixels, row, rawScanline); break; default: - this.CollectColorBytes(image, row, rawScanline); + this.CollectColorBytes(pixels, row, rawScanline); break; } - byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline, this.bytesPerPixel); - - return filteredScanline; + this.GetOptimalFilteredScanline(rawScanline, previousScanline, result, bytesPerScanline); } /// @@ -346,37 +341,30 @@ namespace ImageSharp.Formats /// /// The raw scanline /// The previous scanline + /// The filtered scanline result /// The number of bytes per scanline - /// The number of bytes per pixel - /// The - private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) + private void GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result, int bytesPerScanline) { - Tuple[] candidates; - // Palette images don't compress well with adaptive filtering. if (this.PngColorType == PngColorType.Palette) { - candidates = new Tuple[1]; - - byte[] none = NoneFilter.Encode(rawScanline, bytesPerScanline); - candidates[0] = new Tuple(none, this.CalculateTotalVariation(none)); + NoneFilter.Encode(rawScanline, result, bytesPerScanline); + return; } - else - { - candidates = new Tuple[4]; - byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel, bytesPerScanline); - candidates[0] = new Tuple(sub, this.CalculateTotalVariation(sub)); + Tuple[] candidates = new Tuple[4]; - byte[] up = UpFilter.Encode(rawScanline, bytesPerScanline, previousScanline); - candidates[1] = new Tuple(up, this.CalculateTotalVariation(up)); + byte[] sub = SubFilter.Encode(rawScanline, result, this.bytesPerPixel, bytesPerScanline); + candidates[0] = new Tuple(sub, this.CalculateTotalVariation(sub)); - byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline); - candidates[2] = new Tuple(average, this.CalculateTotalVariation(average)); + byte[] up = UpFilter.Encode(rawScanline, previousScanline, result, bytesPerScanline); + candidates[1] = new Tuple(up, this.CalculateTotalVariation(up)); - byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline); - candidates[3] = new Tuple(paeth, this.CalculateTotalVariation(paeth)); - } + byte[] average = AverageFilter.Encode(rawScanline, previousScanline, result, this.bytesPerPixel, bytesPerScanline); + candidates[2] = new Tuple(average, this.CalculateTotalVariation(average)); + + byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, result, this.bytesPerPixel, bytesPerScanline); + candidates[3] = new Tuple(paeth, this.CalculateTotalVariation(paeth)); int lowestTotalVariation = int.MaxValue; int lowestTotalVariationIndex = 0; @@ -390,7 +378,8 @@ namespace ImageSharp.Formats } } - return candidates[lowestTotalVariationIndex].Item1; + // ReSharper disable once RedundantAssignment + result = candidates[lowestTotalVariationIndex].Item1; } /// @@ -592,15 +581,22 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The packed format. uint, long, float. - /// The image to encode. + /// The pixel accessor. /// The stream. - private void WriteDataChunks(ImageBase image, Stream stream) + private void WriteDataChunks(PixelAccessor pixels, Stream stream) where TColor : struct, IPackedPixel where TPacked : struct { int bytesPerScanline = this.width * this.bytesPerPixel; byte[] previousScanline = ArrayPool.Shared.Rent(bytesPerScanline); byte[] rawScanline = ArrayPool.Shared.Rent(bytesPerScanline); + int resultLength = bytesPerScanline + 1; + byte[] result = ArrayPool.Shared.Rent(resultLength); + + // TODO: Clearing this array makes the visual tests work again when encoding multiple images in a row. + // The png analyser tool I use still cannot decompress the image though my own decoder, chome and edge browsers, and paint can. Twitter also cannot read the file. + // It's 2am now so I'm going to check in what I have and cry. :'( + Array.Clear(result, 0, resultLength); byte[] buffer; int bufferLength; @@ -612,9 +608,8 @@ namespace ImageSharp.Formats { 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(); + this.EncodePixelRow(pixels, y, previousScanline, rawScanline, result, bytesPerScanline); + deflateStream.Write(result, 0, resultLength); // Do a bit of shuffling; byte[] tmp = rawScanline; @@ -622,18 +617,20 @@ namespace ImageSharp.Formats previousScanline = tmp; } - bufferLength = (int)memoryStream.Length; + deflateStream.Flush(); buffer = memoryStream.ToArray(); + bufferLength = buffer.Length; } } finally { + memoryStream?.Dispose(); ArrayPool.Shared.Return(previousScanline); ArrayPool.Shared.Return(rawScanline); - memoryStream?.Dispose(); + ArrayPool.Shared.Return(result); } - // Store the chunks in repeated 64k blocks. + // Store the chunks in repeated 64k blocks. // This reduces the memory load for decoding the image for many decoders. int numChunks = bufferLength / MaxBlockSize; diff --git a/src/ImageSharp/IO/EndianBinaryWriter.cs b/src/ImageSharp/IO/EndianBinaryWriter.cs index b10ae79b4..c10d118cf 100644 --- a/src/ImageSharp/IO/EndianBinaryWriter.cs +++ b/src/ImageSharp/IO/EndianBinaryWriter.cs @@ -346,6 +346,19 @@ namespace ImageSharp.IO this.BaseStream.Write(this.buffer, 0, index); } + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.Flush(); + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + /// /// Checks whether or not the writer has been disposed, throwing an exception if so. /// @@ -368,18 +381,5 @@ namespace ImageSharp.IO this.CheckDisposed(); this.BaseStream.Write(bytes, 0, length); } - - /// - /// Disposes of the underlying stream. - /// - public void Dispose() - { - if (!this.disposed) - { - this.Flush(); - this.disposed = true; - ((IDisposable)this.BaseStream).Dispose(); - } - } } } \ No newline at end of file diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 83a4b9dff..9014f8653 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -16,8 +16,7 @@ namespace ImageSharp public class Image : Image { /// - /// Initializes a new instance of the class - /// with the height and the width of the image. + /// Initializes a new instance of the class. /// public Image() { diff --git a/src/ImageSharp/Image/Image.cs b/src/ImageSharp/Image/Image.cs index 337f38fcf..2915b5365 100644 --- a/src/ImageSharp/Image/Image.cs +++ b/src/ImageSharp/Image/Image.cs @@ -191,6 +191,7 @@ namespace ImageSharp /// /// The stream to save the image to. /// Thrown if the stream is null. + /// The public Image Save(Stream stream) { Guard.NotNull(stream, nameof(stream)); @@ -204,6 +205,7 @@ namespace ImageSharp /// The stream to save the image to. /// The format to save the image as. /// Thrown if the stream is null. + /// The public Image Save(Stream stream, IImageFormat format) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Numerics/Ellipse.cs b/src/ImageSharp/Numerics/Ellipse.cs index f464c4b26..9aba745e4 100644 --- a/src/ImageSharp/Numerics/Ellipse.cs +++ b/src/ImageSharp/Numerics/Ellipse.cs @@ -9,18 +9,27 @@ namespace ImageSharp using System.ComponentModel; using System.Numerics; + /// + /// Represents an ellipse. + /// public struct Ellipse : IEquatable { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Ellipse Empty = default(Ellipse); + /// /// The center point. /// private Point center; /// - /// Represents a that has X and Y values set to zero. + /// Initializes a new instance of the struct. /// - public static readonly Ellipse Empty = default(Ellipse); - + /// The center point. + /// The x-radius. + /// The y-radius. public Ellipse(Point center, float radiusX, float radiusY) { this.center = center; diff --git a/src/ImageSharp/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Profiles/Exif/ExifReader.cs index eef6f6f3a..7bbfb3d06 100644 --- a/src/ImageSharp/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Profiles/Exif/ExifReader.cs @@ -112,8 +112,16 @@ namespace ImageSharp return result; } + /// + /// Gets the invalid tags. + /// public IEnumerable InvalidTags => this.invalidTags; + /// + /// Adds the collection of EXIF values to the reader. + /// + /// The values. + /// The index. private void AddValues(Collection values, uint index) { this.currentIndex = this.startIndex + index; diff --git a/src/ImageSharp/Profiles/Exif/ExifTag.cs b/src/ImageSharp/Profiles/Exif/ExifTag.cs index 43f725f0c..894f5a064 100644 --- a/src/ImageSharp/Profiles/Exif/ExifTag.cs +++ b/src/ImageSharp/Profiles/Exif/ExifTag.cs @@ -3,12 +3,11 @@ // Licensed under the Apache License, Version 2.0. // -// Descriptions from: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html - namespace ImageSharp { /// /// All exif tags from the Exif standard 2.2 + /// Descriptions from: /// public enum ExifTag { diff --git a/src/ImageSharp/Samplers/Options/Orientation.cs b/src/ImageSharp/Samplers/Options/Orientation.cs index dfe57a4b0..d7e96ddf4 100644 --- a/src/ImageSharp/Samplers/Options/Orientation.cs +++ b/src/ImageSharp/Samplers/Options/Orientation.cs @@ -5,16 +5,54 @@ namespace ImageSharp { + /// + /// Enumerates the available orientation values supplied by EXIF metadata. + /// internal enum Orientation : ushort { + /// + /// Unknown rotation. + /// Unknown = 0, + + /// + /// The 0th row at the top, the 0th column on the left. + /// TopLeft = 1, + + /// + /// The 0th row at the top, the 0th column on the right. + /// TopRight = 2, + + /// + /// The 0th row at the bottom, the 0th column on the right. + /// BottomRight = 3, + + /// + /// The 0th row at the bottom, the 0th column on the left. + /// BottomLeft = 4, + + /// + /// The 0th row on the left, the 0th column at the top. + /// LeftTop = 5, + + /// + /// The 0th row at the right, the 0th column at the top. + /// RightTop = 6, + + /// + /// The 0th row on the right, the 0th column at the bottom. + /// RightBottom = 7, + + /// + /// The 0th row on the left, the 0th column at the bottom. + /// LeftBottom = 8 } } diff --git a/src/ImageSharp/Samplers/Processors/RotateProcessor.cs b/src/ImageSharp/Samplers/Processors/RotateProcessor.cs index 0fdc187bd..05cbda36e 100644 --- a/src/ImageSharp/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageSharp/Samplers/Processors/RotateProcessor.cs @@ -88,7 +88,7 @@ namespace ImageSharp.Processors /// /// The target image. /// The source image. - /// + /// The private bool OptimizedApply(ImageBase target, ImageBase source) { const float Epsilon = .0001F; diff --git a/src/ImageSharp/Samplers/Processors/SkewProcessor.cs b/src/ImageSharp/Samplers/Processors/SkewProcessor.cs index b21301a87..1db363a77 100644 --- a/src/ImageSharp/Samplers/Processors/SkewProcessor.cs +++ b/src/ImageSharp/Samplers/Processors/SkewProcessor.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Processors where TPacked : struct { /// - /// The tranform matrix to apply. + /// The transform matrix to apply. /// private Matrix3x2 processMatrix; @@ -38,16 +38,6 @@ namespace ImageSharp.Processors /// public bool Expand { get; set; } = true; - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); - if (this.Expand) - { - CreateNewTarget(target, sourceRectangle, this.processMatrix); - } - } - /// public override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { @@ -75,5 +65,15 @@ namespace ImageSharp.Processors }); } } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); + if (this.Expand) + { + CreateNewTarget(target, sourceRectangle, this.processMatrix); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs index 7e2060ce9..261a7f269 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public void JpegSystemDrawing() + public void BmpSystemDrawing() { using (MemoryStream memoryStream = new MemoryStream()) { @@ -41,7 +41,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Description = "ImageSharp Bmp")] - public void JpegCore() + public void BmpCore() { using (MemoryStream memoryStream = new MemoryStream()) { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs index 7128089f9..e2acb4db5 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public void JpegSystemDrawing() + public void GifSystemDrawing() { using (MemoryStream memoryStream = new MemoryStream()) { @@ -41,7 +41,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Description = "ImageSharp Gif")] - public void JpegCore() + public void GifCore() { using (MemoryStream memoryStream = new MemoryStream()) { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index d9a83f556..52d953775 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public void JpegSystemDrawing() + public void PngSystemDrawing() { using (MemoryStream memoryStream = new MemoryStream()) { @@ -41,7 +41,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Description = "ImageSharp Png")] - public void JpegCore() + public void PngCore() { using (MemoryStream memoryStream = new MemoryStream()) {