Browse Source

merged recent changes to 46 csproj

af/merge-core
Anton Firszov 9 years ago
parent
commit
06a21b3601
  1. 53
      src/ImageSharp46/Common/Extensions/ByteExtensions.cs
  2. 30
      src/ImageSharp46/Common/Extensions/StreamExtensions.cs
  3. 6
      src/ImageSharp46/Formats/Gif/GifDecoderCore.cs
  4. 27
      src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs
  5. 7
      src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs
  6. 9
      src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs
  7. 7
      src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs
  8. 7
      src/ImageSharp46/Formats/Png/Filters/SubFilter.cs
  9. 7
      src/ImageSharp46/Formats/Png/Filters/UpFilter.cs
  10. 121
      src/ImageSharp46/Formats/Png/PngDecoderCore.cs
  11. 371
      src/ImageSharp46/Formats/Png/PngEncoderCore.cs
  12. 4
      src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs
  13. 5
      src/ImageSharp46/ImageSharp46.csproj
  14. 1
      src/ImageSharp46/Properties/AssemblyInfo.cs
  15. 1
      src/ImageSharp46/packages.config
  16. 5
      tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj
  17. 1
      tests/ImageSharp.Tests46/TestFile.cs
  18. 19
      tests/ImageSharp.Tests46/app.config

53
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 /// Converts a byte array to a new array where each value in the original array is represented
/// by a the specified number of bits. /// by a the specified number of bits.
/// </summary> /// </summary>
/// <param name="bytes">The bytes to convert from. Cannot be null.</param> /// <param name="source">The bytes to convert from. Cannot be null.</param>
/// <param name="bits">The number of bits per value.</param> /// <param name="bits">The number of bits per value.</param>
/// <returns>The resulting <see cref="T:byte[]"/> array. Is never null.</returns> /// <returns>The resulting <see cref="T:byte[]"/> array. Is never null.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="bytes"/> is null.</exception> /// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="System.ArgumentException"><paramref name="bits"/> is less than or equals than zero.</exception> /// <exception cref="System.ArgumentException"><paramref name="bits"/> is less than or equals than zero.</exception>
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"); Guard.MustBeGreaterThan(bits, 0, "bits");
byte[] result; byte[] result;
if (bits < 8) if (bits < 8)
{ {
result = new byte[bytes.Length * 8 / bits]; result = new byte[source.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;
int mask = 0xFF >> (8 - bits); int mask = 0xFF >> (8 - bits);
int resultOffset = 0; 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) 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; result[resultOffset] = (byte)colorIndex;
@ -51,10 +50,42 @@ namespace ImageSharp
} }
else else
{ {
result = bytes; result = source;
} }
return result; return result;
} }
/// <summary>
/// Optimized <see cref="T:byte[]"/> reversal algorithm.
/// </summary>
/// <param name="source">The byte array.</param>
public static void ReverseBytes(this byte[] source)
{
ReverseBytes(source, 0, source.Length);
}
/// <summary>
/// Optimized <see cref="T:byte[]"/> reversal algorithm.
/// </summary>
/// <param name="source">The byte array.</param>
/// <param name="index">The index.</param>
/// <param name="length">The length.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
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--;
}
}
} }
} }

30
src/ImageSharp46/Common/Extensions/StreamExtensions.cs

@ -0,0 +1,30 @@
// <copyright file="StreamExtensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}

6
src/ImageSharp46/Formats/Gif/GifDecoderCore.cs

@ -59,7 +59,7 @@ namespace ImageSharp.Formats
this.currentStream = stream; this.currentStream = stream;
// Skip the identifier // Skip the identifier
this.currentStream.Seek(6, SeekOrigin.Current); this.currentStream.Skip(6);
this.ReadLogicalScreenDescriptor(); this.ReadLogicalScreenDescriptor();
if (this.logicalScreenDescriptor.GlobalColorTableFlag) if (this.logicalScreenDescriptor.GlobalColorTableFlag)
@ -192,13 +192,13 @@ namespace ImageSharp.Formats
/// <param name="length">The number of bytes to skip.</param> /// <param name="length">The number of bytes to skip.</param>
private void Skip(int length) private void Skip(int length)
{ {
this.currentStream.Seek(length, SeekOrigin.Current); this.currentStream.Skip(length);
int flag; int flag;
while ((flag = this.currentStream.ReadByte()) != 0) while ((flag = this.currentStream.ReadByte()) != 0)
{ {
this.currentStream.Seek(flag, SeekOrigin.Current); this.currentStream.Skip(flag);
} }
} }

27
src/ImageSharp46/Formats/Jpg/JpegEncoderCore.cs

@ -787,24 +787,21 @@ namespace ImageSharp.Formats
{ {
HuffmanSpec spec = specs[i]; HuffmanSpec spec = specs[i];
int len = 0; int len = 0;
fixed (byte* huffman = this.huffmanBuffer) 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++] = values[v];
{
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];
}
}
} }
} }

7
src/ImageSharp46/Formats/Png/Filters/AverageFilter.cs

@ -45,15 +45,16 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
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) // 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; 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 left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
byte above = previousScanline[x]; byte above = previousScanline[x];

9
src/ImageSharp46/Formats/Png/Filters/NoneFilter.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
/// <summary> /// <summary>
/// The None filter, the scanline is transmitted unmodified; it is only necessary to /// The None filter, the scanline is transmitted unmodified; it is only necessary to
/// insert a filter type byte before the data. /// insert a filter type byte before the data.
@ -27,13 +29,14 @@ namespace ImageSharp.Formats
/// Encodes the scanline /// Encodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline) public static byte[] Encode(byte[] scanline, int bytesPerScanline)
{ {
// Insert a byte before the data. // Insert a byte before the data.
byte[] encodedScanline = new byte[scanline.Length + 1]; byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.None; encodedScanline[0] = (byte)FilterType.None;
scanline.CopyTo(encodedScanline, 1); Buffer.BlockCopy(scanline, 0, encodedScanline, 1, bytesPerScanline);
return encodedScanline; return encodedScanline;
} }

7
src/ImageSharp46/Formats/Png/Filters/PaethFilter.cs

@ -45,14 +45,15 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
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)) // 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; 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 left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
byte above = previousScanline[x]; byte above = previousScanline[x];

7
src/ImageSharp46/Formats/Png/Filters/SubFilter.cs

@ -38,14 +38,15 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
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) // 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; 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]; byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];

7
src/ImageSharp46/Formats/Png/Filters/UpFilter.cs

@ -37,15 +37,16 @@ namespace ImageSharp.Formats
/// Encodes the scanline /// Encodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline) public static byte[] Encode(byte[] scanline, int bytesPerScanline, byte[] previousScanline)
{ {
// Up(x) = Raw(x) - Prior(x) // Up(x) = Raw(x) - Prior(x)
byte[] encodedScanline = new byte[scanline.Length + 1]; byte[] encodedScanline = new byte[bytesPerScanline + 1];
encodedScanline[0] = (byte)FilterType.Up; encodedScanline[0] = (byte)FilterType.Up;
for (int x = 0; x < scanline.Length; x++) for (int x = 0; x < bytesPerScanline; x++)
{ {
byte above = previousScanline[x]; byte above = previousScanline[x];

121
src/ImageSharp46/Formats/Png/PngDecoderCore.cs

@ -2,6 +2,7 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
@ -115,7 +116,7 @@ namespace ImageSharp.Formats
{ {
Image<TColor, TPacked> currentImage = image; Image<TColor, TPacked> currentImage = image;
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Seek(8, SeekOrigin.Current); this.currentStream.Skip(8);
bool isEndChunkReached = false; bool isEndChunkReached = false;
@ -129,35 +130,31 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image does not end with end chunk."); throw new ImageFormatException("Image does not end with end chunk.");
} }
if (currentChunk.Type == PngChunkTypes.Header) switch (currentChunk.Type)
{
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)
{ {
this.palette = currentChunk.Data; case PngChunkTypes.Header:
image.Quality = this.palette.Length / 3; this.ReadHeaderChunk(currentChunk.Data);
} this.ValidateHeader();
else if (currentChunk.Type == PngChunkTypes.PaletteAlpha) break;
{ case PngChunkTypes.Physical:
this.paletteAlpha = currentChunk.Data; this.ReadPhysicalChunk(currentImage, currentChunk.Data);
} break;
else if (currentChunk.Type == PngChunkTypes.Text) case PngChunkTypes.Data:
{ dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length);
this.ReadTextChunk(currentImage, currentChunk.Data); break;
} case PngChunkTypes.Palette:
else if (currentChunk.Type == PngChunkTypes.End) this.palette = currentChunk.Data;
{ image.Quality = this.palette.Length / 3;
isEndChunkReached = true; 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<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
this.ReverseBytes(data, 0, 4); data.ReverseBytes(0, 4);
this.ReverseBytes(data, 4, 4); data.ReverseBytes(4, 4);
// 39.3700787 = inches in a meter. // 39.3700787 = inches in a meter.
image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d;
@ -216,8 +213,7 @@ namespace ImageSharp.Formats
case PngColorType.Rgb: case PngColorType.Rgb:
return 3; return 3;
// PngColorType.RgbWithAlpha // PngColorType.RgbWithAlpha:
// TODO: Maybe figure out a way to detect if there are any transparent pixels and encode RGB if none.
default: default:
return 4; return 4;
} }
@ -262,18 +258,7 @@ namespace ImageSharp.Formats
dataStream.Position = 0; dataStream.Position = 0;
using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream))
{ {
using (MemoryStream decompressedStream = new MemoryStream()) this.DecodePixelData(compressedStream, pixels);
{
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);
} }
} }
@ -282,9 +267,9 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="pixelData">The pixel data.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="pixels">The image pixels.</param> /// <param name="pixels">The image pixel accessor.</param>
private void DecodePixelData<TColor, TPacked>(Stream pixelData, PixelAccessor<TColor, TPacked> pixels) private void DecodePixelData<TColor, TPacked>(Stream compressedStream, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
@ -293,11 +278,12 @@ namespace ImageSharp.Formats
byte[] scanline = new byte[this.bytesPerScanline]; byte[] scanline = new byte[this.bytesPerScanline];
for (int y = 0; y < this.header.Height; y++) 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]; FilterType filterType = (FilterType)scanline[0];
byte[] defilteredScanline; byte[] defilteredScanline;
// TODO: It would be good if we can reduce the memory usage here. Each filter is creating a new row.
switch (filterType) switch (filterType)
{ {
case FilterType.None: case FilterType.None:
@ -497,8 +483,8 @@ namespace ImageSharp.Formats
{ {
this.header = new PngHeader(); this.header = new PngHeader();
this.ReverseBytes(data, 0, 4); data.ReverseBytes(0, 4);
this.ReverseBytes(data, 4, 4); data.ReverseBytes(4, 4);
this.header.Width = BitConverter.ToInt32(data, 0); this.header.Width = BitConverter.ToInt32(data, 0);
this.header.Height = BitConverter.ToInt32(data, 4); this.header.Height = BitConverter.ToInt32(data, 4);
@ -584,7 +570,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image stream is not valid!"); throw new ImageFormatException("Image stream is not valid!");
} }
this.ReverseBytes(this.crcBuffer); this.crcBuffer.ReverseBytes();
chunk.Crc = BitConverter.ToUInt32(this.crcBuffer, 0); chunk.Crc = BitConverter.ToUInt32(this.crcBuffer, 0);
@ -649,40 +635,11 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Image stream is not valid!"); throw new ImageFormatException("Image stream is not valid!");
} }
this.ReverseBytes(this.chunkLengthBuffer); this.chunkLengthBuffer.ReverseBytes();
chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0);
return numBytes; return numBytes;
} }
/// <summary>
/// Optimized <see cref="T:byte[]"/> reversal algorithm.
/// </summary>
/// <param name="source">The byte array.</param>
private void ReverseBytes(byte[] source)
{
this.ReverseBytes(source, 0, source.Length);
}
/// <summary>
/// Optimized <see cref="T:byte[]"/> reversal algorithm.
/// </summary>
/// <param name="source">The byte array.</param>
/// <param name="index">The index.</param>
/// <param name="length">The length.</param>
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--;
}
}
} }
} }

371
src/ImageSharp46/Formats/Png/PngEncoderCore.cs

@ -2,13 +2,14 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Quantizers; using Quantizers;
@ -24,9 +25,20 @@ namespace ImageSharp.Formats
private const int MaxBlockSize = 65535; private const int MaxBlockSize = 65535;
/// <summary> /// <summary>
/// Contains the raw pixel data from the image. /// Reusable buffer for writing chunk types.
/// </summary>
private readonly byte[] chunkTypeBuffer = new byte[4];
/// <summary>
/// Reusable buffer for writing chunk data.
/// </summary>
private readonly byte[] chunkDataBuffer = new byte[16];
/// <summary>
/// Contains the raw pixel data from an indexed image.
/// </summary> /// </summary>
private byte[] pixelData; private byte[] palettePixelData;
/// <summary> /// <summary>
/// The image width. /// The image width.
@ -106,20 +118,16 @@ namespace ImageSharp.Formats
this.height = image.Height; this.height = image.Height;
// Write the png header. // Write the png header.
stream.Write( this.chunkDataBuffer[0] = 0x89; // Set the high bit.
new byte[] this.chunkDataBuffer[1] = 0x50; // P
{ this.chunkDataBuffer[2] = 0x4E; // N
0x89, // Set the high bit. this.chunkDataBuffer[3] = 0x47; // G
0x50, // P this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF
0x4E, // N this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF
0x47, // G this.chunkDataBuffer[6] = 0x1A; // EOF
0x0D, // Line ending CRLF this.chunkDataBuffer[7] = 0x0A; // LF
0x0A, // Line ending CRLF
0x1A, // EOF stream.Write(this.chunkDataBuffer, 0, 8);
0x0A // LF
},
0,
8);
// Ensure that quality can be set but has a fallback. // Ensure that quality can be set but has a fallback.
int quality = this.Quality > 0 ? this.Quality : image.Quality; int quality = this.Quality > 0 ? this.Quality : image.Quality;
@ -131,6 +139,11 @@ namespace ImageSharp.Formats
this.PngColorType = PngColorType.Palette; this.PngColorType = PngColorType.Palette;
} }
if (this.PngColorType == PngColorType.Palette && this.Quality > 256)
{
this.Quality = 256;
}
// Set correct bit depth. // Set correct bit depth.
this.bitDepth = this.Quality <= 256 this.bitDepth = this.Quality <= 256
? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)
@ -161,23 +174,15 @@ namespace ImageSharp.Formats
this.WriteHeaderChunk(stream, header); this.WriteHeaderChunk(stream, header);
// Collect the pixel data // Collect the indexed pixel data
if (this.PngColorType == PngColorType.Palette) if (this.PngColorType == PngColorType.Palette)
{ {
this.CollectIndexedBytes(image, stream, header); 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.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream); this.WriteGammaChunk(stream);
this.WriteDataChunks(stream); this.WriteDataChunks(image, stream);
this.WriteEndChunk(stream); this.WriteEndChunk(stream);
stream.Flush(); stream.Flush();
} }
@ -192,8 +197,8 @@ namespace ImageSharp.Formats
{ {
byte[] buffer = BitConverter.GetBytes(value); byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer); buffer.ReverseBytes();
Array.Copy(buffer, 0, data, offset, 4); Buffer.BlockCopy(buffer, 0, data, offset, 4);
} }
/// <summary> /// <summary>
@ -205,8 +210,7 @@ namespace ImageSharp.Formats
{ {
byte[] buffer = BitConverter.GetBytes(value); byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer); buffer.ReverseBytes();
stream.Write(buffer, 0, 4); stream.Write(buffer, 0, 4);
} }
@ -219,8 +223,7 @@ namespace ImageSharp.Formats
{ {
byte[] buffer = BitConverter.GetBytes(value); byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer); buffer.ReverseBytes();
stream.Write(buffer, 0, 4); stream.Write(buffer, 0, 4);
} }
@ -236,46 +239,44 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
// Quatize the image and get the pixels // Quantize the image and get the pixels.
QuantizedImage<TColor, TPacked> quantized = this.WritePaletteChunk(stream, header, image); QuantizedImage<TColor, TPacked> quantized = this.WritePaletteChunk(stream, header, image);
this.pixelData = quantized.Pixels; this.palettePixelData = quantized.Pixels;
} }
/// <summary> /// <summary>
/// Collects the grayscale pixel data. /// Collects a row of grayscale pixels.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="image">The image to encode.</param> /// <param name="image">The image to encode.</param>
private void CollectGrayscaleBytes<TColor, TPacked>(ImageBase<TColor, TPacked> image) /// <param name="row">The row index.</param>
/// <param name="rawScanline">The raw scanline.</param>
private void CollectGrayscaleBytes<TColor, TPacked>(ImageBase<TColor, TPacked> image, int row, byte[] rawScanline)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
// Copy the pixels across from the image. // Copy the pixels across from the image.
this.pixelData = new byte[this.width * this.height * this.bytesPerPixel]; // Reuse the chunk type buffer.
int stride = this.width * this.bytesPerPixel;
byte[] bytes = new byte[4];
using (PixelAccessor<TColor, TPacked> pixels = image.Lock()) using (PixelAccessor<TColor, TPacked> 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 if (i == 0)
// Optionally store the original color alpha. {
int dataOffset = (y * stride) + (x * this.bytesPerPixel); rawScanline[offset] = luminance;
pixels[x, y].ToBytes(bytes, 0, ComponentOrder.XYZW); }
YCbCr luminance = new Color(bytes[0], bytes[1], bytes[2], bytes[3]); else
for (int i = 0; i < this.bytesPerPixel; i++)
{ {
if (i == 0) rawScanline[offset + i] = this.chunkTypeBuffer[3];
{
this.pixelData[dataOffset] = (byte)luminance.Y;
}
else
{
this.pixelData[dataOffset + i] = bytes[3];
}
} }
} }
} }
@ -283,34 +284,24 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// Collects the true color pixel data. /// Collects a row of true color pixel data.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="image">The image to encode.</param> /// <param name="image">The image to encode.</param>
private void CollectColorBytes<TColor, TPacked>(ImageBase<TColor, TPacked> image) /// <param name="row">The row index.</param>
/// <param name="rawScanline">The raw scanline.</param>
private void CollectColorBytes<TColor, TPacked>(ImageBase<TColor, TPacked> image, int row, byte[] rawScanline)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct 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<TColor, TPacked> pixels = image.Lock()) using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{ {
int bpp = this.bytesPerPixel; int bpp = this.bytesPerPixel;
Parallel.For( for (int x = 0; x < this.width; x++)
0, {
this.height, pixels[x, row].ToBytes(rawScanline, x * this.bytesPerPixel, bpp == 4 ? ComponentOrder.XYZW : ComponentOrder.XYZ);
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);
}
});
} }
} }
@ -318,33 +309,35 @@ namespace ImageSharp.Formats
/// Encodes the pixel data line by line. /// Encodes the pixel data line by line.
/// Each scanline is encoded in the most optimal manner to improve compression. /// Each scanline is encoded in the most optimal manner to improve compression.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="row">The row.</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="rawScanline">The raw scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private byte[] EncodePixelData() private byte[] EncodePixelRow<TColor, TPacked>(ImageBase<TColor, TPacked> image, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{ {
// TODO: Use pointers switch (this.PngColorType)
List<byte[]> filteredScanlines = new List<byte[]>();
byte[] previousScanline = new byte[this.width * this.bytesPerPixel];
for (int y = 0; y < this.height; y++)
{ {
byte[] rawScanline = this.GetRawScanline(y); case PngColorType.Palette:
byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, this.bytesPerPixel); Buffer.BlockCopy(this.palettePixelData, row * bytesPerScanline, rawScanline, 0, bytesPerScanline);
break;
filteredScanlines.Add(filteredScanline); case PngColorType.Grayscale:
case PngColorType.GrayscaleWithAlpha:
previousScanline = rawScanline; 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. byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline, this.bytesPerPixel);
List<byte> result = new List<byte>();
foreach (byte[] encodedScanline in filteredScanlines) return filteredScanline;
{
result.AddRange(encodedScanline);
}
return result.ToArray();
} }
/// <summary> /// <summary>
@ -353,36 +346,42 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="rawScanline">The raw scanline</param> /// <param name="rawScanline">The raw scanline</param>
/// <param name="previousScanline">The previous scanline</param> /// <param name="previousScanline">The previous scanline</param>
/// <param name="byteCount">The number of bytes per pixel</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The number of bytes per pixel</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int byteCount) private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel)
{ {
List<Tuple<byte[], int>> candidates = new List<Tuple<byte[], int>>(); Tuple<byte[], int>[] candidates;
// Palette images don't compress well with adaptive filtering.
if (this.PngColorType == PngColorType.Palette) if (this.PngColorType == PngColorType.Palette)
{ {
byte[] none = NoneFilter.Encode(rawScanline); candidates = new Tuple<byte[], int>[1];
candidates.Add(new Tuple<byte[], int>(none, this.CalculateTotalVariation(none)));
byte[] none = NoneFilter.Encode(rawScanline, bytesPerScanline);
candidates[0] = new Tuple<byte[], int>(none, this.CalculateTotalVariation(none));
} }
else else
{ {
byte[] sub = SubFilter.Encode(rawScanline, byteCount); candidates = new Tuple<byte[], int>[4];
candidates.Add(new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub)));
byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel, bytesPerScanline);
candidates[0] = new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub));
byte[] up = UpFilter.Encode(rawScanline, previousScanline); byte[] up = UpFilter.Encode(rawScanline, bytesPerScanline, previousScanline);
candidates.Add(new Tuple<byte[], int>(up, this.CalculateTotalVariation(up))); candidates[1] = new Tuple<byte[], int>(up, this.CalculateTotalVariation(up));
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount); byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline);
candidates.Add(new Tuple<byte[], int>(average, this.CalculateTotalVariation(average))); candidates[2] = new Tuple<byte[], int>(average, this.CalculateTotalVariation(average));
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount); byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline);
candidates.Add(new Tuple<byte[], int>(paeth, this.CalculateTotalVariation(paeth))); candidates[3] = new Tuple<byte[], int>(paeth, this.CalculateTotalVariation(paeth));
} }
int lowestTotalVariation = int.MaxValue; int lowestTotalVariation = int.MaxValue;
int lowestTotalVariationIndex = 0; int lowestTotalVariationIndex = 0;
for (int i = 0; i < candidates.Count; i++) for (int i = 0; i < candidates.Length; i++)
{ {
if (candidates[i].Item2 < lowestTotalVariation) if (candidates[i].Item2 < lowestTotalVariation)
{ {
@ -412,19 +411,6 @@ namespace ImageSharp.Formats
return totalVariation; return totalVariation;
} }
/// <summary>
/// Get the raw scanline data from the pixel data
/// </summary>
/// <param name="y">The row number</param>
/// <returns>The <see cref="T:byte[]"/></returns>
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;
}
/// <summary> /// <summary>
/// Calculates the correct number of bytes per pixel for the given color type. /// Calculates the correct number of bytes per pixel for the given color type.
/// </summary> /// </summary>
@ -460,18 +446,16 @@ namespace ImageSharp.Formats
/// <param name="header">The <see cref="PngHeader"/>.</param> /// <param name="header">The <see cref="PngHeader"/>.</param>
private void WriteHeaderChunk(Stream stream, PngHeader header) private void WriteHeaderChunk(Stream stream, PngHeader header)
{ {
byte[] chunkData = new byte[13]; WriteInteger(this.chunkDataBuffer, 0, header.Width);
WriteInteger(this.chunkDataBuffer, 4, header.Height);
WriteInteger(chunkData, 0, header.Width);
WriteInteger(chunkData, 4, header.Height);
chunkData[8] = header.BitDepth; this.chunkDataBuffer[8] = header.BitDepth;
chunkData[9] = header.ColorType; this.chunkDataBuffer[9] = header.ColorType;
chunkData[10] = header.CompressionMethod; this.chunkDataBuffer[10] = header.CompressionMethod;
chunkData[11] = header.FilterMethod; this.chunkDataBuffer[11] = header.FilterMethod;
chunkData[12] = header.InterlaceMethod; this.chunkDataBuffer[12] = header.InterlaceMethod;
this.WriteChunk(stream, PngChunkTypes.Header, chunkData); this.WriteChunk(stream, PngChunkTypes.Header, this.chunkDataBuffer, 0, 13);
} }
/// <summary> /// <summary>
@ -507,36 +491,44 @@ namespace ImageSharp.Formats
// Get max colors for bit depth. // Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = new byte[colorTableLength]; byte[] colorTable = ArrayPool<byte>.Shared.Rent(colorTableLength);
byte[] bytes = ArrayPool<byte>.Shared.Rent(4);
// TODO: Optimize this.
Parallel.For( try
0, {
pixelCount, for (int i = 0; i < pixelCount; i++)
Bootstrapper.Instance.ParallelOptions,
i =>
{ {
int offset = i * 3; int offset = i * 3;
Color color = new Color(palette[i].ToVector4()); palette[i].ToBytes(bytes, 0, ComponentOrder.XYZW);
int alpha = color.A;
int alpha = bytes[3];
// Premultiply the color. This helps prevent banding. // Premultiply the color. This helps prevent banding.
// TODO: Vector<byte>?
if (alpha < 255 && alpha > this.Threshold) 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] = bytes[0];
colorTable[offset + 1] = color.G; colorTable[offset + 1] = bytes[1];
colorTable[offset + 2] = color.B; colorTable[offset + 2] = bytes[2];
if (alpha <= this.Threshold) if (alpha <= this.Threshold)
{ {
transparentPixels.Add((byte)offset); transparentPixels.Add((byte)offset);
} }
}); }
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
}
finally
{
ArrayPool<byte>.Shared.Return(colorTable);
ArrayPool<byte>.Shared.Return(bytes);
}
// Write the transparency data // Write the transparency data
if (transparentPixels.Any()) if (transparentPixels.Any())
@ -565,14 +557,12 @@ namespace ImageSharp.Formats
int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D); int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D);
int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D); int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D);
byte[] chunkData = new byte[9]; WriteInteger(this.chunkDataBuffer, 0, dpmX);
WriteInteger(this.chunkDataBuffer, 4, dpmY);
WriteInteger(chunkData, 0, dpmX);
WriteInteger(chunkData, 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) if (this.WriteGamma)
{ {
int gammaValue = (int)(this.Gamma * 100000f); int gammaValue = (int)(this.Gamma * 100000F);
byte[] fourByteData = new byte[4];
byte[] size = BitConverter.GetBytes(gammaValue); byte[] size = BitConverter.GetBytes(gammaValue);
fourByteData[0] = size[3]; this.chunkDataBuffer[0] = size[3];
fourByteData[1] = size[2]; this.chunkDataBuffer[1] = size[2];
fourByteData[2] = size[1]; this.chunkDataBuffer[2] = size[1];
fourByteData[3] = size[0]; this.chunkDataBuffer[3] = size[0];
this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData); this.WriteChunk(stream, PngChunkTypes.Gamma, this.chunkDataBuffer, 0, 4);
} }
} }
/// <summary> /// <summary>
/// Writes the pixel information to the stream. /// Writes the pixel information to the stream.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
private void WriteDataChunks(Stream stream) private void WriteDataChunks<TColor, TPacked>(ImageBase<TColor, TPacked> image, Stream stream)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{ {
byte[] data = this.EncodePixelData(); int bytesPerScanline = this.width * this.bytesPerPixel;
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
byte[] rawScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
byte[] buffer; byte[] buffer;
int bufferLength; int bufferLength;
MemoryStream memoryStream = null; MemoryStream memoryStream = null;
try try
{ {
memoryStream = new MemoryStream(); memoryStream = new MemoryStream();
using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) 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; bufferLength = (int)memoryStream.Length;
buffer = memoryStream.ToArray(); buffer = memoryStream.ToArray();
}
} }
finally finally
{ {
ArrayPool<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(rawScanline);
memoryStream?.Dispose(); 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; int numChunks = bufferLength / MaxBlockSize;
if (bufferLength % MaxBlockSize != 0) if (bufferLength % MaxBlockSize != 0)
@ -669,7 +676,7 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param> /// <param name="type">The type of chunk to write.</param>
@ -678,26 +685,24 @@ namespace ImageSharp.Formats
/// <param name="length">The of the data to write.</param> /// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length) private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length)
{ {
// Chunk length
WriteInteger(stream, length); WriteInteger(stream, length);
byte[] typeArray = new byte[4]; // Chunk type
typeArray[0] = (byte)type[0]; this.chunkTypeBuffer[0] = (byte)type[0];
typeArray[1] = (byte)type[1]; this.chunkTypeBuffer[1] = (byte)type[1];
typeArray[2] = (byte)type[2]; this.chunkTypeBuffer[2] = (byte)type[2];
typeArray[3] = (byte)type[3]; this.chunkTypeBuffer[3] = (byte)type[3];
stream.Write(typeArray, 0, 4);
if (data != null) stream.Write(this.chunkTypeBuffer, 0, 4);
{
stream.Write(data, offset, length);
}
Crc32 crc32 = new Crc32(); Crc32 crc32 = new Crc32();
crc32.Update(typeArray); crc32.Update(this.chunkTypeBuffer);
// Chunk data
if (data != null) if (data != null)
{ {
stream.Write(data, offset, length);
crc32.Update(data, offset, length); crc32.Update(data, offset, length);
} }

4
src/ImageSharp46/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -37,7 +37,9 @@ namespace ImageSharp.Formats
/// </remarks> /// </remarks>
private bool isDisposed; private bool isDisposed;
// The stream responsible for decompressing the input stream. /// <summary>
/// The stream responsible for compressing the input stream.
/// </summary>
private DeflateStream deflateStream; private DeflateStream deflateStream;
/// <summary> /// <summary>

5
src/ImageSharp46/ImageSharp46.csproj

@ -42,6 +42,10 @@
<HintPath>..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll</HintPath> <HintPath>..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System.Buffers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Buffers.4.0.0\lib\netstandard1.1\System.Buffers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll</HintPath> <HintPath>..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll</HintPath>
@ -138,6 +142,7 @@
<Compile Include="Common\Extensions\ByteExtensions.cs" /> <Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" /> <Compile Include="Common\Extensions\ComparableExtensions.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" /> <Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Extensions\StreamExtensions.cs" />
<Compile Include="Common\Extensions\Vector4Extensions.cs" /> <Compile Include="Common\Extensions\Vector4Extensions.cs" />
<Compile Include="Common\Helpers\Guard.cs" /> <Compile Include="Common\Helpers\Guard.cs" />
<Compile Include="Common\Helpers\ImageMaths.cs" /> <Compile Include="Common\Helpers\ImageMaths.cs" />

1
src/ImageSharp46/Properties/AssemblyInfo.cs

@ -37,4 +37,3 @@ using System.Runtime.CompilerServices;
// Ensure the internals can be tested. // Ensure the internals can be tested.
[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] [assembly: InternalsVisibleTo("ImageSharp.Benchmarks")]
[assembly: InternalsVisibleTo("ImageSharp.Tests")] [assembly: InternalsVisibleTo("ImageSharp.Tests")]
[assembly: InternalsVisibleTo("ImageSharp.Tests46")]

1
src/ImageSharp46/packages.config

@ -3,6 +3,7 @@
<package id="Microsoft.NETCore.Platforms" version="1.0.1" targetFramework="net461" /> <package id="Microsoft.NETCore.Platforms" version="1.0.1" targetFramework="net461" />
<package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="net461" /> <package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="net461" />
<package id="System.AppContext" version="4.1.0" targetFramework="net461" /> <package id="System.AppContext" version="4.1.0" targetFramework="net461" />
<package id="System.Buffers" version="4.0.0" targetFramework="net461" />
<package id="System.Collections" version="4.0.11" targetFramework="net461" /> <package id="System.Collections" version="4.0.11" targetFramework="net461" />
<package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net461" /> <package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net461" />
<package id="System.Console" version="4.0.0" targetFramework="net461" /> <package id="System.Console" version="4.0.0" targetFramework="net461" />

5
tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj

@ -7,8 +7,8 @@
<ProjectGuid>{635E0A15-3893-4763-A7F6-FCCFF85BCCA4}</ProjectGuid> <ProjectGuid>{635E0A15-3893-4763-A7F6-FCCFF85BCCA4}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ImageSharp.Tests46</RootNamespace> <RootNamespace>ImageSharp.Tests</RootNamespace>
<AssemblyName>ImageSharp.Tests46</AssemblyName> <AssemblyName>ImageSharp.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
@ -118,6 +118,7 @@
<Compile Include="TestImages.cs" /> <Compile Include="TestImages.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

1
tests/ImageSharp.Tests46/TestFile.cs

@ -4,7 +4,6 @@
// </copyright> // </copyright>
using System.IO; using System.IO;
using Xunit.Abstractions;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {

19
tests/ImageSharp.Tests46/app.config

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.X509Certificates" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Win32.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IO.Compression" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Loading…
Cancel
Save