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