Browse Source

Improve png speed and half memory usage

af/merge-core
James Jackson-South 10 years ago
parent
commit
bfe0d0ff31
  1. 172
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 61
      tests/ImageSharp.Benchmarks/General/ArrayReverse.cs

172
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -20,6 +20,26 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private static readonly Dictionary<int, byte[]> ColorTypes = new Dictionary<int, byte[]>(); private static readonly Dictionary<int, byte[]> ColorTypes = new Dictionary<int, byte[]>();
/// <summary>
/// Reusable buffer for reading chunk types.
/// </summary>
private readonly byte[] chunkTypeBuffer = new byte[4];
/// <summary>
/// Reusable buffer for reading chunk lengths.
/// </summary>
private readonly byte[] chunkLengthBuffer = new byte[4];
/// <summary>
/// Reusable buffer for reading crc values.
/// </summary>
private readonly byte[] crcBuffer = new byte[4];
/// <summary>
/// Reusable buffer for reading char arrays.
/// </summary>
private readonly char[] chars = new char[4];
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
/// </summary> /// </summary>
@ -148,9 +168,12 @@ namespace ImageSharp.Formats
+ $"max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + $"max allowed size '{image.MaxWidth}x{image.MaxHeight}'");
} }
TColor[] pixels = new TColor[this.header.Width * this.header.Height]; image.InitPixels(this.header.Width, this.header.Height);
this.ReadScanlines<TColor, TPacked>(dataStream, pixels);
image.SetPixels(this.header.Width, this.header.Height, pixels); using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{
this.ReadScanlines(dataStream, pixels);
}
} }
} }
@ -165,8 +188,8 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
Array.Reverse(data, 0, 4); this.ReverseBytes(data, 0, 4);
Array.Reverse(data, 4, 4); this.ReverseBytes(data, 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;
@ -194,8 +217,7 @@ namespace ImageSharp.Formats
return 3; return 3;
// PngColorType.RgbWithAlpha // PngColorType.RgbWithAlpha
// TODO: Maybe figure out a way to detect if there are any transparent // TODO: Maybe figure out a way to detect if there are any transparent pixels and encode RGB if none.
// pixels and encode RGB if none.
default: default:
return 4; return 4;
} }
@ -225,7 +247,7 @@ namespace ImageSharp.Formats
/// <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="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels"> The pixel data.</param> /// <param name="pixels"> The pixel data.</param>
private void ReadScanlines<TColor, TPacked>(MemoryStream dataStream, TColor[] pixels) private void ReadScanlines<TColor, TPacked>(MemoryStream dataStream, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
@ -244,10 +266,14 @@ namespace ImageSharp.Formats
{ {
compressedStream.CopyTo(decompressedStream); compressedStream.CopyTo(decompressedStream);
decompressedStream.Flush(); decompressedStream.Flush();
decompressedStream.Position = 0;
byte[] decompressedBytes = decompressedStream.ToArray(); this.DecodePixelData(decompressedStream, pixels);
this.DecodePixelData<TColor, TPacked>(decompressedBytes, pixels); //byte[] decompressedBytes = decompressedStream.ToArray();
//this.DecodePixelData(decompressedBytes, pixels);
} }
//byte[] decompressedBytes = compressedStream.ToArray();
//this.DecodePixelData(decompressedBytes, pixels);
} }
} }
@ -258,16 +284,17 @@ namespace ImageSharp.Formats
/// <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="pixelData">The pixel data.</param>
/// <param name="pixels">The image pixels.</param> /// <param name="pixels">The image pixels.</param>
private void DecodePixelData<TColor, TPacked>(byte[] pixelData, TColor[] pixels) private void DecodePixelData<TColor, TPacked>(Stream pixelData, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
// TODO: ArrayPool<byte>.Shared.Rent(this.bytesPerScanline)
byte[] previousScanline = new byte[this.bytesPerScanline]; byte[] previousScanline = 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++)
{ {
byte[] scanline = new byte[this.bytesPerScanline]; pixelData.Read(scanline, 0, this.bytesPerScanline);
Array.Copy(pixelData, y * this.bytesPerScanline, scanline, 0, this.bytesPerScanline);
FilterType filterType = (FilterType)scanline[0]; FilterType filterType = (FilterType)scanline[0];
byte[] defilteredScanline; byte[] defilteredScanline;
@ -308,7 +335,7 @@ namespace ImageSharp.Formats
} }
previousScanline = defilteredScanline; previousScanline = defilteredScanline;
this.ProcessDefilteredScanline<TColor, TPacked>(defilteredScanline, y, pixels); this.ProcessDefilteredScanline(defilteredScanline, y, pixels);
} }
} }
@ -320,10 +347,11 @@ namespace ImageSharp.Formats
/// <param name="defilteredScanline">The de-filtered scanline</param> /// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="row">The current image row.</param> /// <param name="row">The current image row.</param>
/// <param name="pixels">The image pixels</param> /// <param name="pixels">The image pixels</param>
private void ProcessDefilteredScanline<TColor, TPacked>(byte[] defilteredScanline, int row, TColor[] pixels) private void ProcessDefilteredScanline<TColor, TPacked>(byte[] defilteredScanline, int row, PixelAccessor<TColor, TPacked> pixels)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
TColor color = default(TColor);
switch (this.PngColorType) switch (this.PngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
@ -333,10 +361,8 @@ namespace ImageSharp.Formats
int offset = 1 + (x * this.bytesPerPixel); int offset = 1 + (x * this.bytesPerPixel);
byte intensity = defilteredScanline[offset]; byte intensity = defilteredScanline[offset];
TColor color = default(TColor);
color.PackFromBytes(intensity, intensity, intensity, 255); color.PackFromBytes(intensity, intensity, intensity, 255);
pixels[(row * this.header.Width) + x] = color; pixels[x, row] = color;
} }
break; break;
@ -350,9 +376,8 @@ namespace ImageSharp.Formats
byte intensity = defilteredScanline[offset]; byte intensity = defilteredScanline[offset];
byte alpha = defilteredScanline[offset + this.bytesPerSample]; byte alpha = defilteredScanline[offset + this.bytesPerSample];
TColor color = default(TColor);
color.PackFromBytes(intensity, intensity, intensity, alpha); color.PackFromBytes(intensity, intensity, intensity, alpha);
pixels[(row * this.header.Width) + x] = color; pixels[x, row] = color;
} }
break; break;
@ -365,14 +390,13 @@ namespace ImageSharp.Formats
{ {
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
// channel and we should try to read it. // channel and we should try to read it.
for (int i = 0; i < this.header.Width; i++) for (int x = 0; x < this.header.Width; x++)
{ {
int index = newScanline[i]; int index = newScanline[x];
int offset = (row * this.header.Width) + i;
int pixelOffset = index * 3; int pixelOffset = index * 3;
byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
TColor color = default(TColor);
if (a > 0) if (a > 0)
{ {
byte r = this.palette[pixelOffset]; byte r = this.palette[pixelOffset];
@ -381,24 +405,22 @@ namespace ImageSharp.Formats
color.PackFromBytes(r, g, b, a); color.PackFromBytes(r, g, b, a);
} }
pixels[offset] = color; pixels[x, row] = color;
} }
} }
else else
{ {
for (int i = 0; i < this.header.Width; i++) for (int x = 0; x < this.header.Width; x++)
{ {
int index = newScanline[i]; int index = newScanline[x];
int offset = (row * this.header.Width) + i;
int pixelOffset = index * 3; int pixelOffset = index * 3;
byte r = this.palette[pixelOffset]; byte r = this.palette[pixelOffset];
byte g = this.palette[pixelOffset + 1]; byte g = this.palette[pixelOffset + 1];
byte b = this.palette[pixelOffset + 2]; byte b = this.palette[pixelOffset + 2];
TColor color = default(TColor);
color.PackFromBytes(r, g, b, 255); color.PackFromBytes(r, g, b, 255);
pixels[offset] = color; pixels[x, row] = color;
} }
} }
@ -414,9 +436,8 @@ namespace ImageSharp.Formats
byte g = defilteredScanline[offset + this.bytesPerSample]; byte g = defilteredScanline[offset + this.bytesPerSample];
byte b = defilteredScanline[offset + (2 * this.bytesPerSample)]; byte b = defilteredScanline[offset + (2 * this.bytesPerSample)];
TColor color = default(TColor);
color.PackFromBytes(r, g, b, 255); color.PackFromBytes(r, g, b, 255);
pixels[(row * this.header.Width) + x] = color; pixels[x, row] = color;
} }
break; break;
@ -432,9 +453,8 @@ namespace ImageSharp.Formats
byte b = defilteredScanline[offset + (2 * this.bytesPerSample)]; byte b = defilteredScanline[offset + (2 * this.bytesPerSample)];
byte a = defilteredScanline[offset + (3 * this.bytesPerSample)]; byte a = defilteredScanline[offset + (3 * this.bytesPerSample)];
TColor color = default(TColor);
color.PackFromBytes(r, g, b, a); color.PackFromBytes(r, g, b, a);
pixels[(row * this.header.Width) + x] = color; pixels[x, row] = color;
} }
break; break;
@ -477,17 +497,17 @@ namespace ImageSharp.Formats
{ {
this.header = new PngHeader(); this.header = new PngHeader();
Array.Reverse(data, 0, 4); this.ReverseBytes(data, 0, 4);
Array.Reverse(data, 4, 4); this.ReverseBytes(data, 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);
this.header.BitDepth = data[8]; this.header.BitDepth = data[8];
this.header.ColorType = data[9]; this.header.ColorType = data[9];
this.header.CompressionMethod = data[10];
this.header.FilterMethod = data[11]; this.header.FilterMethod = data[11];
this.header.InterlaceMethod = data[12]; this.header.InterlaceMethod = data[12];
this.header.CompressionMethod = data[10];
} }
/// <summary> /// <summary>
@ -542,10 +562,9 @@ namespace ImageSharp.Formats
return null; return null;
} }
byte[] typeBuffer = this.ReadChunkType(chunk); this.ReadChunkType(chunk);
this.ReadChunkData(chunk); this.ReadChunkData(chunk);
this.ReadChunkCrc(chunk, typeBuffer); this.ReadChunkCrc(chunk);
return chunk; return chunk;
} }
@ -554,26 +573,23 @@ namespace ImageSharp.Formats
/// Reads the cycle redundancy chunk from the data. /// Reads the cycle redundancy chunk from the data.
/// </summary> /// </summary>
/// <param name="chunk">The chunk.</param> /// <param name="chunk">The chunk.</param>
/// <param name="typeBuffer">The type buffer.</param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid or corrupt. /// Thrown if the input stream is not valid or corrupt.
/// </exception> /// </exception>
private void ReadChunkCrc(PngChunk chunk, byte[] typeBuffer) private void ReadChunkCrc(PngChunk chunk)
{ {
byte[] crcBuffer = new byte[4]; int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4);
int numBytes = this.currentStream.Read(crcBuffer, 0, 4);
if (numBytes >= 1 && numBytes <= 3) if (numBytes >= 1 && numBytes <= 3)
{ {
throw new ImageFormatException("Image stream is not valid!"); throw new ImageFormatException("Image stream is not valid!");
} }
Array.Reverse(crcBuffer); this.ReverseBytes(this.crcBuffer);
chunk.Crc = BitConverter.ToUInt32(crcBuffer, 0); chunk.Crc = BitConverter.ToUInt32(this.crcBuffer, 0);
Crc32 crc = new Crc32(); Crc32 crc = new Crc32();
crc.Update(typeBuffer); crc.Update(this.chunkTypeBuffer);
crc.Update(chunk.Data); crc.Update(chunk.Data);
if (crc.Value != chunk.Crc) if (crc.Value != chunk.Crc)
@ -602,25 +618,20 @@ namespace ImageSharp.Formats
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid. /// Thrown if the input stream is not valid.
/// </exception> /// </exception>
private byte[] ReadChunkType(PngChunk chunk) private void ReadChunkType(PngChunk chunk)
{ {
byte[] typeBuffer = new byte[4]; int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4);
int numBytes = this.currentStream.Read(typeBuffer, 0, 4);
if (numBytes >= 1 && numBytes <= 3) if (numBytes >= 1 && numBytes <= 3)
{ {
throw new ImageFormatException("Image stream is not valid!"); throw new ImageFormatException("Image stream is not valid!");
} }
char[] chars = new char[4]; this.chars[0] = (char)this.chunkTypeBuffer[0];
chars[0] = (char)typeBuffer[0]; this.chars[1] = (char)this.chunkTypeBuffer[1];
chars[1] = (char)typeBuffer[1]; this.chars[2] = (char)this.chunkTypeBuffer[2];
chars[2] = (char)typeBuffer[2]; this.chars[3] = (char)this.chunkTypeBuffer[3];
chars[3] = (char)typeBuffer[3];
chunk.Type = new string(chars);
return typeBuffer; chunk.Type = new string(this.chars);
} }
/// <summary> /// <summary>
@ -635,19 +646,46 @@ namespace ImageSharp.Formats
/// </exception> /// </exception>
private int ReadChunkLength(PngChunk chunk) private int ReadChunkLength(PngChunk chunk)
{ {
byte[] lengthBuffer = new byte[4]; int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4);
int numBytes = this.currentStream.Read(lengthBuffer, 0, 4);
if (numBytes >= 1 && numBytes <= 3) if (numBytes >= 1 && numBytes <= 3)
{ {
throw new ImageFormatException("Image stream is not valid!"); throw new ImageFormatException("Image stream is not valid!");
} }
Array.Reverse(lengthBuffer); this.ReverseBytes(this.chunkLengthBuffer);
chunk.Length = BitConverter.ToInt32(lengthBuffer, 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--;
}
}
} }
} }

61
tests/ImageSharp.Benchmarks/General/ArrayReverse.cs

@ -0,0 +1,61 @@
// <copyright file="ArrayReverse.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks.General
{
using System;
using BenchmarkDotNet.Attributes;
public class ArrayReverse
{
[Params(4, 16, 32)]
public int Count { get; set; }
byte[] source;
byte[] destination;
[Setup]
public void SetUp()
{
this.source = new byte[this.Count];
this.destination = new byte[this.Count];
}
[Benchmark(Baseline = true, Description = "Copy using Array.Reverse()")]
public void ReverseArray()
{
Array.Reverse(this.source, 0, this.Count);
}
[Benchmark(Description = "Reverse using loop")]
public void ReverseLoop()
{
this.ReverseBytes(this.source, 0, this.Count);
//for (int i = 0; i < this.source.Length / 2; i++)
//{
// byte tmp = this.source[i];
// this.source[i] = this.source[this.source.Length - i - 1];
// this.source[this.source.Length - i - 1] = tmp;
//}
}
public 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--;
}
}
}
}
Loading…
Cancel
Save