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
/// by a the specified number of bits.
/// </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>
/// <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>
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;
}
/// <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;
// 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
/// <param name="length">The number of bytes to skip.</param>
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);
}
}

27
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];
}
}

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="previousScanline">The previous scanline.</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>
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];

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System;
/// <summary>
/// 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
/// </summary>
/// <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>
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;
}

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="previousScanline">The previous scanline.</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>
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];

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

@ -38,14 +38,15 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to encode</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>
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];

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

@ -37,15 +37,16 @@ namespace ImageSharp.Formats
/// Encodes the scanline
/// </summary>
/// <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>
/// <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)
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];

121
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.
// </copyright>
namespace ImageSharp.Formats
{
using System;
@ -115,7 +116,7 @@ namespace ImageSharp.Formats
{
Image<TColor, TPacked> 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<TPacked>
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
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="pixelData">The pixel data.</param>
/// <param name="pixels">The image pixels.</param>
private void DecodePixelData<TColor, TPacked>(Stream pixelData, PixelAccessor<TColor, TPacked> pixels)
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="pixels">The image pixel accessor.</param>
private void DecodePixelData<TColor, TPacked>(Stream compressedStream, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked>
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;
}
/// <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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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;
/// <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>
private byte[] pixelData;
private byte[] palettePixelData;
/// <summary>
/// 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);
}
/// <summary>
@ -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<TPacked>
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);
this.pixelData = quantized.Pixels;
this.palettePixelData = quantized.Pixels;
}
/// <summary>
/// Collects the grayscale pixel data.
/// Collects a row of grayscale pixels.
/// </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>
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 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<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
// 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
}
/// <summary>
/// Collects the true color pixel data.
/// Collects a row of true color pixel data.
/// </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>
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 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())
{
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.
/// </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>
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
List<byte[]> filteredScanlines = new List<byte[]>();
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<byte> result = new List<byte>();
byte[] filteredScanline = this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline, this.bytesPerPixel);
foreach (byte[] encodedScanline in filteredScanlines)
{
result.AddRange(encodedScanline);
}
return result.ToArray();
return filteredScanline;
}
/// <summary>
@ -353,36 +346,42 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="rawScanline">The raw 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>
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)
{
byte[] none = NoneFilter.Encode(rawScanline);
candidates.Add(new Tuple<byte[], int>(none, this.CalculateTotalVariation(none)));
candidates = new Tuple<byte[], int>[1];
byte[] none = NoneFilter.Encode(rawScanline, bytesPerScanline);
candidates[0] = new Tuple<byte[], int>(none, this.CalculateTotalVariation(none));
}
else
{
byte[] sub = SubFilter.Encode(rawScanline, byteCount);
candidates.Add(new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub)));
candidates = new Tuple<byte[], int>[4];
byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel, bytesPerScanline);
candidates[0] = new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub));
byte[] up = UpFilter.Encode(rawScanline, previousScanline);
candidates.Add(new Tuple<byte[], int>(up, this.CalculateTotalVariation(up)));
byte[] up = UpFilter.Encode(rawScanline, bytesPerScanline, previousScanline);
candidates[1] = new Tuple<byte[], int>(up, this.CalculateTotalVariation(up));
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount);
candidates.Add(new Tuple<byte[], int>(average, this.CalculateTotalVariation(average)));
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline);
candidates[2] = new Tuple<byte[], int>(average, this.CalculateTotalVariation(average));
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount);
candidates.Add(new Tuple<byte[], int>(paeth, this.CalculateTotalVariation(paeth)));
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel, bytesPerScanline);
candidates[3] = new Tuple<byte[], int>(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;
}
/// <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>
/// Calculates the correct number of bytes per pixel for the given color type.
/// </summary>
@ -460,18 +446,16 @@ namespace ImageSharp.Formats
/// <param name="header">The <see cref="PngHeader"/>.</param>
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);
}
/// <summary>
@ -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<byte>.Shared.Rent(colorTableLength);
byte[] bytes = ArrayPool<byte>.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<byte>?
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<byte>.Shared.Return(colorTable);
ArrayPool<byte>.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);
}
}
/// <summary>
/// Writes the pixel information to the stream.
/// </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>
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;
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<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.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
}
/// <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>
/// <param name="stream">The <see cref="Stream"/> to write to.</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>
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);
}

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

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

5
src/ImageSharp46/ImageSharp46.csproj

@ -42,6 +42,10 @@
<HintPath>..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll</HintPath>
<Private>True</Private>
</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.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>
@ -138,6 +142,7 @@
<Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Extensions\StreamExtensions.cs" />
<Compile Include="Common\Extensions\Vector4Extensions.cs" />
<Compile Include="Common\Helpers\Guard.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.
[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")]
[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.Win32.Primitives" version="4.0.1" 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.Concurrent" version="4.0.12" 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>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ImageSharp.Tests46</RootNamespace>
<AssemblyName>ImageSharp.Tests46</AssemblyName>
<RootNamespace>ImageSharp.Tests</RootNamespace>
<AssemblyName>ImageSharp.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
@ -118,6 +118,7 @@
<Compile Include="TestImages.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>

1
tests/ImageSharp.Tests46/TestFile.cs

@ -4,7 +4,6 @@
// </copyright>
using System.IO;
using Xunit.Abstractions;
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