#47 improve flate filter performance by streaming all data in single operation

also improves page constructor performance by removing linq and invoking stringbuilder directly. removes page rotation overhead by skipping multiplication for non-rotated pages and using cached transformation matrices for rotations. removes linq from filter provider and shares instances of filter types.
This commit is contained in:
Eliot Jones
2019-08-19 19:48:02 +01:00
parent 11b244eda1
commit 0fa3b27ad3
4 changed files with 49 additions and 53 deletions

View File

@@ -2,7 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Annotations;
using Graphics.Operations;
using Tokens;
@@ -104,7 +104,13 @@
return string.Empty;
}
return string.Join(string.Empty, content.Letters.Select(x => x.Value));
var builder = new StringBuilder();
for (var i = 0; i < content.Letters.Count; i++)
{
builder.Append(content.Letters[i].Value);
}
return builder.ToString();
}
/// <summary>

View File

@@ -9,6 +9,10 @@
/// </summary>
public struct PageRotationDegrees : IEquatable<PageRotationDegrees>
{
private static readonly TransformationMatrix Rotate90 = TransformationMatrix.FromArray(new[] {0m, -1, 1, 0});
private static readonly TransformationMatrix Rotate180 = TransformationMatrix.FromArray(new[] { -1m, 0, 0, -1 });
private static readonly TransformationMatrix Rotate270 = TransformationMatrix.FromArray(new[] { 0m, 1, -1, 0 });
/// <summary>
/// The rotation of the page in degrees clockwise.
/// </summary>
@@ -54,26 +58,19 @@
[Pure]
internal TransformationMatrix Rotate(TransformationMatrix matrix)
{
TransformationMatrix thisMatrix;
switch (Value)
{
case 0:
thisMatrix = TransformationMatrix.FromArray(new[]{ 1m, 0, 0, 1 });
break;
return matrix;
case 90:
thisMatrix = TransformationMatrix.FromArray(new[] {0m, -1, 1, 0});
break;
return Rotate90.Multiply(matrix);
case 180:
thisMatrix = TransformationMatrix.FromArray(new[] {-1m, 0, 0, -1});
break;
return Rotate180.Multiply(matrix);
case 270:
thisMatrix = TransformationMatrix.FromArray(new[] {0m, 1, -1, 0});
break;
return Rotate270.Multiply(matrix);
default:
throw new InvalidOperationException($"Invalid value for rotation: {Value}.");
}
return thisMatrix.Multiply(matrix);
}
/// <inheritdoc />

View File

@@ -76,6 +76,7 @@
try
{
using (var memoryStream = new MemoryStream(input))
using (var output = new MemoryStream())
{
// The first 2 bytes are the header which DeflateStream does not support.
memoryStream.ReadByte();
@@ -83,23 +84,8 @@
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
{
var bytes = new List<byte>();
var x = deflate.ReadByte();
while (x != -1)
{
bytes.Add((byte)x);
x = deflate.ReadByte();
}
var result = new byte[bytes.Count];
for (var i = 0; i < bytes.Count; i++)
{
result[i] = bytes[i];
}
return result;
deflate.CopyTo(output);
return output.ToArray();
}
}
}

View File

@@ -2,35 +2,35 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using Exceptions;
using Logging;
using Tokens;
using Util;
internal class MemoryFilterProvider : IFilterProvider
{
private readonly IReadOnlyDictionary<string, Func<IFilter>> filterFactories;
private readonly IReadOnlyDictionary<string, IFilter> filterInstances;
public MemoryFilterProvider(IDecodeParameterResolver decodeParameterResolver, IPngPredictor pngPredictor, ILog log)
{
IFilter Ascii85Func() => new Ascii85Filter();
IFilter AsciiHexFunc() => new AsciiHexDecodeFilter();
IFilter FlateFunc() => new FlateFilter(decodeParameterResolver, pngPredictor, log);
IFilter RunLengthFunc() => new RunLengthFilter();
IFilter LzwFunc() => new LzwFilter(decodeParameterResolver, pngPredictor);
var ascii85 = new Ascii85Filter();
var asciiHex = new AsciiHexDecodeFilter();
var flate = new FlateFilter(decodeParameterResolver, pngPredictor, log);
var runLength = new RunLengthFilter();
var lzw = new LzwFilter(decodeParameterResolver, pngPredictor);
filterFactories = new Dictionary<string, Func<IFilter>>
filterInstances = new Dictionary<string, IFilter>
{
{NameToken.Ascii85Decode.Data, Ascii85Func},
{NameToken.Ascii85DecodeAbbreviation.Data, Ascii85Func},
{NameToken.AsciiHexDecode.Data, AsciiHexFunc},
{NameToken.AsciiHexDecodeAbbreviation.Data, AsciiHexFunc},
{NameToken.FlateDecode.Data, FlateFunc},
{NameToken.FlateDecodeAbbreviation.Data, FlateFunc},
{NameToken.RunLengthDecode.Data, RunLengthFunc},
{NameToken.RunLengthDecodeAbbreviation.Data, RunLengthFunc},
{NameToken.LzwDecode, LzwFunc},
{NameToken.LzwDecodeAbbreviation, LzwFunc}
{NameToken.Ascii85Decode.Data, ascii85},
{NameToken.Ascii85DecodeAbbreviation.Data, ascii85},
{NameToken.AsciiHexDecode.Data, asciiHex},
{NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex},
{NameToken.FlateDecode.Data, flate},
{NameToken.FlateDecodeAbbreviation.Data, flate},
{NameToken.RunLengthDecode.Data, runLength},
{NameToken.RunLengthDecodeAbbreviation.Data, runLength},
{NameToken.LzwDecode, lzw},
{NameToken.LzwDecodeAbbreviation, lzw}
};
}
@@ -43,14 +43,21 @@
if (!dictionary.TryGet(NameToken.Filter, out var token))
{
return new IFilter[0];
return EmptyArray<IFilter>.Instance;
}
switch (token)
{
case ArrayToken filters:
// TODO: presumably this may be invalid...
return filters.Data.Select(x => GetFilterStrict(((NameToken)x).Data)).ToList();
var result = new IFilter[filters.Data.Count];
for (var i = 0; i < filters.Data.Count; i++)
{
var filterToken = filters.Data[i];
var filterName = ((NameToken) filterToken).Data;
result[i] = GetFilterStrict(filterName);
}
return result;
case NameToken name:
return new[] { GetFilterStrict(name.Data) };
default:
@@ -60,12 +67,12 @@
private IFilter GetFilterStrict(string name)
{
if (!filterFactories.TryGetValue(name, out var factory))
if (!filterInstances.TryGetValue(name, out var factory))
{
throw new NotSupportedException($"The filter with the name {name} is not supported yet. Please raise an issue.");
}
return factory();
return factory;
}
public IReadOnlyList<IFilter> GetAllFilters()