mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-08 00:14:35 +08:00
Modernise PngPredictor and refactor LzwFilter and FlateFilter to reduce memory allocation
This commit is contained in:
@@ -40,18 +40,10 @@
|
||||
|
||||
try
|
||||
{
|
||||
var decompressed = Decompress(input);
|
||||
|
||||
if (predictor == -1)
|
||||
{
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
var colors = Math.Min(parameters.GetIntOrDefault(NameToken.Colors, DefaultColors), 32);
|
||||
var bitsPerComponent = parameters.GetIntOrDefault(NameToken.BitsPerComponent, DefaultBitsPerComponent);
|
||||
var columns = parameters.GetIntOrDefault(NameToken.Columns, DefaultColumns);
|
||||
|
||||
return PngPredictor.Decode(decompressed, predictor, colors, bitsPerComponent, columns);
|
||||
return Decompress(input, predictor, colors, bitsPerComponent, columns);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -61,10 +53,9 @@
|
||||
return input;
|
||||
}
|
||||
|
||||
private static byte[] Decompress(Memory<byte> input)
|
||||
private static Memory<byte> Decompress(Memory<byte> input, int predictor, int colors, int bitsPerComponent, int columns)
|
||||
{
|
||||
using (var memoryStream = MemoryHelper.AsReadOnlyMemoryStream(input))
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
// The first 2 bytes are the header which DeflateStream does not support.
|
||||
memoryStream.ReadByte();
|
||||
@@ -73,8 +64,17 @@
|
||||
try
|
||||
{
|
||||
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
|
||||
using (var output = new MemoryStream((int)(input.Length * 1.5)))
|
||||
using (var f = PngPredictor.WrapPredictor(output, predictor, colors, bitsPerComponent, columns))
|
||||
{
|
||||
deflate.CopyTo(output);
|
||||
deflate.CopyTo(f);
|
||||
f.Flush();
|
||||
|
||||
if (output.TryGetBuffer(out var segment))
|
||||
{
|
||||
return segment.AsMemory();
|
||||
}
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ namespace UglyToad.PdfPig.Filters
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Lzw;
|
||||
using System.IO;
|
||||
using Tokens;
|
||||
using Util;
|
||||
|
||||
@@ -37,57 +38,63 @@ namespace UglyToad.PdfPig.Filters
|
||||
|
||||
var earlyChange = parameters.GetIntOrDefault(NameToken.EarlyChange, 1);
|
||||
|
||||
if (predictor > 1)
|
||||
{
|
||||
var decompressed = Decode(input.Span, earlyChange == 1);
|
||||
var colors = Math.Min(parameters.GetIntOrDefault(NameToken.Colors, DefaultColors), 32);
|
||||
var bitsPerComponent = parameters.GetIntOrDefault(NameToken.BitsPerComponent, DefaultBitsPerComponent);
|
||||
var columns = parameters.GetIntOrDefault(NameToken.Columns, DefaultColumns);
|
||||
|
||||
var colors = Math.Min(parameters.GetIntOrDefault(NameToken.Colors, DefaultColors), 32);
|
||||
var bitsPerComponent = parameters.GetIntOrDefault(NameToken.BitsPerComponent, DefaultBitsPerComponent);
|
||||
var columns = parameters.GetIntOrDefault(NameToken.Columns, DefaultColumns);
|
||||
|
||||
return PngPredictor.Decode(decompressed, predictor, colors, bitsPerComponent, columns);
|
||||
}
|
||||
|
||||
return Decode(input.Span, earlyChange == 1);
|
||||
return Decode(input.Span, earlyChange == 1, predictor, colors, bitsPerComponent, columns);
|
||||
}
|
||||
|
||||
private static byte[] Decode(ReadOnlySpan<byte> input, bool isEarlyChange)
|
||||
private static Memory<byte> Decode(ReadOnlySpan<byte> input, bool isEarlyChange, int predictor, int colors, int bitsPerComponent, int columns)
|
||||
{
|
||||
// A guess.
|
||||
var result = new List<byte>((int)(input.Length * 1.5));
|
||||
|
||||
var table = GetDefaultTable();
|
||||
|
||||
var codeBits = 9;
|
||||
|
||||
var data = new BitStream(input);
|
||||
|
||||
var codeOffset = isEarlyChange ? 0 : 1;
|
||||
|
||||
var previous = -1;
|
||||
|
||||
while (true)
|
||||
using (var output = new MemoryStream((int)(input.Length * 1.5))) // A guess.
|
||||
using (var result = PngPredictor.WrapPredictor(output, predictor, colors, bitsPerComponent, columns))
|
||||
{
|
||||
var next = data.Get(codeBits);
|
||||
var table = GetDefaultTable();
|
||||
|
||||
if (next == EodMarker)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (next == ClearTable)
|
||||
{
|
||||
table = GetDefaultTable();
|
||||
previous = -1;
|
||||
codeBits = 9;
|
||||
continue;
|
||||
}
|
||||
var codeBits = 9;
|
||||
|
||||
if (table.TryGetValue(next, out var b))
|
||||
{
|
||||
result.AddRange(b);
|
||||
var data = new BitStream(input);
|
||||
|
||||
if (previous >= 0)
|
||||
var codeOffset = isEarlyChange ? 0 : 1;
|
||||
|
||||
var previous = -1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var next = data.Get(codeBits);
|
||||
|
||||
if (next == EodMarker)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (next == ClearTable)
|
||||
{
|
||||
table = GetDefaultTable();
|
||||
previous = -1;
|
||||
codeBits = 9;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (table.TryGetValue(next, out var b))
|
||||
{
|
||||
result.Write(b,0, b.Length);
|
||||
|
||||
if (previous >= 0)
|
||||
{
|
||||
var lastSequence = table[previous];
|
||||
|
||||
var newSequence = new byte[lastSequence.Length + 1];
|
||||
|
||||
Array.Copy(lastSequence, newSequence, lastSequence.Length);
|
||||
|
||||
newSequence[lastSequence.Length] = b[0];
|
||||
|
||||
table[table.Count] = newSequence;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastSequence = table[previous];
|
||||
|
||||
@@ -95,47 +102,42 @@ namespace UglyToad.PdfPig.Filters
|
||||
|
||||
Array.Copy(lastSequence, newSequence, lastSequence.Length);
|
||||
|
||||
newSequence[lastSequence.Length] = b[0];
|
||||
newSequence[lastSequence.Length] = lastSequence[0];
|
||||
|
||||
result.Write(newSequence, 0, newSequence.Length);
|
||||
|
||||
table[table.Count] = newSequence;
|
||||
}
|
||||
|
||||
previous = next;
|
||||
|
||||
if (table.Count >= ElevenBitBoundary + codeOffset)
|
||||
{
|
||||
codeBits = 12;
|
||||
}
|
||||
else if (table.Count >= TenBitBoundary + codeOffset)
|
||||
{
|
||||
codeBits = 11;
|
||||
}
|
||||
else if (table.Count >= NineBitBoundary + codeOffset)
|
||||
{
|
||||
codeBits = 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
codeBits = 9;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
result.Flush();
|
||||
|
||||
if (output.TryGetBuffer(out var segment))
|
||||
{
|
||||
var lastSequence = table[previous];
|
||||
|
||||
var newSequence = new byte[lastSequence.Length + 1];
|
||||
|
||||
Array.Copy(lastSequence, newSequence, lastSequence.Length);
|
||||
|
||||
newSequence[lastSequence.Length] = lastSequence[0];
|
||||
|
||||
result.AddRange(newSequence);
|
||||
|
||||
table[table.Count] = newSequence;
|
||||
return segment.AsMemory();
|
||||
}
|
||||
|
||||
previous = next;
|
||||
|
||||
if (table.Count >= ElevenBitBoundary + codeOffset)
|
||||
{
|
||||
codeBits = 12;
|
||||
}
|
||||
else if (table.Count >= TenBitBoundary + codeOffset)
|
||||
{
|
||||
codeBits = 11;
|
||||
}
|
||||
else if (table.Count >= NineBitBoundary + codeOffset)
|
||||
{
|
||||
codeBits = 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
codeBits = 9;
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static Dictionary<int, byte[]> GetDefaultTable()
|
||||
|
@@ -1,225 +1,198 @@
|
||||
namespace UglyToad.PdfPig.Filters
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using IO;
|
||||
|
||||
// https://github.com/apache/pdfbox/blob/a53a70db16ea3133994120bcf1e216b9e760c05b/pdfbox/src/main/java/org/apache/pdfbox/filter/Predictor.java#L30
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to contain predictor decoding used by Flate and LZW filter.
|
||||
/// </summary>
|
||||
internal static class PngPredictor
|
||||
{
|
||||
public static byte[] Decode(byte[] inputBytes, int predictor, int colors, int bitsPerComponent, int columns)
|
||||
/// <summary>
|
||||
/// Decodes a single line of data in-place.
|
||||
/// </summary>
|
||||
/// <param name="predictor">Predictor value for the current line.</param>
|
||||
/// <param name="colors">Number of color components, from decode parameters.</param>
|
||||
/// <param name="bitsPerComponent">Number of bits per components, from decode parameters.</param>
|
||||
/// <param name="columns">Number samples in a row, from decode parameters.</param>
|
||||
/// <param name="actline">Current (active) line to decode. Data will be decoded in-place, i.e. - the contents of this buffer will be modified.</param>
|
||||
/// <param name="lastline">The previous decoded line. When decoding the first line, this parameter should be an empty byte array of the same length as <c>actline</c>.</param>
|
||||
public static void DecodePredictorRow(int predictor, int colors, int bitsPerComponent, int columns, byte[] actline, byte[] lastline)
|
||||
{
|
||||
if (inputBytes is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inputBytes));
|
||||
}
|
||||
|
||||
if (predictor == 1)
|
||||
{
|
||||
return inputBytes;
|
||||
// no prediction
|
||||
return;
|
||||
}
|
||||
|
||||
int bitsPerPixel = colors * bitsPerComponent;
|
||||
int bytesPerPixel = (bitsPerPixel + 7) / 8;
|
||||
int rowlength = (columns * bitsPerPixel + 7) / 8;
|
||||
var actline = new byte[rowlength];
|
||||
var lastline = new byte[rowlength];
|
||||
int rowLength = actline.Length;
|
||||
|
||||
int linepredictor = predictor;
|
||||
|
||||
var result = new List<byte>();
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
using (var output = new BinaryWriter(memoryStream))
|
||||
using (var input = new RandomAccessBuffer(inputBytes))
|
||||
switch (predictor)
|
||||
{
|
||||
|
||||
while (input.Available() > 0)
|
||||
{
|
||||
// test for PNG predictor; each value >= 10 (not only 15) indicates usage of PNG predictor
|
||||
if (predictor >= 10)
|
||||
case 2:
|
||||
// PRED TIFF SUB
|
||||
if (bitsPerComponent == 8)
|
||||
{
|
||||
// PNG predictor; each row starts with predictor type (0, 1, 2, 3, 4)
|
||||
// read per line predictor
|
||||
linepredictor = input.Read();
|
||||
if (linepredictor == -1)
|
||||
// for 8 bits per component it is the same algorithm as PRED SUB of PNG format
|
||||
for (int p = bytesPerPixel; p < rowLength; p++)
|
||||
{
|
||||
return result.ToArray();
|
||||
int sub = actline[p] & 0xff;
|
||||
int left = actline[p - bytesPerPixel] & 0xff;
|
||||
actline[p] = (byte)(sub + left);
|
||||
}
|
||||
// add 10 to tread value 0 as 10, 1 as 11, ...
|
||||
linepredictor += 10;
|
||||
}
|
||||
|
||||
// read line
|
||||
int i;
|
||||
int offset = 0;
|
||||
while (offset < rowlength && ((i = input.Read(actline, offset, rowlength - offset)) != -1))
|
||||
else if (bitsPerComponent == 16)
|
||||
{
|
||||
if (i == 0)
|
||||
for (int p = bytesPerPixel; p < rowLength - 1; p += 2)
|
||||
{
|
||||
// TODO: #291, this indicates a bug in reading logic.
|
||||
// This only avoids the infinite loop it does not fix the logic bug.
|
||||
break;
|
||||
int sub = ((actline[p] & 0xff) << 8) + (actline[p + 1] & 0xff);
|
||||
int left = ((actline[p - bytesPerPixel] & 0xff) << 8) + (actline[p - bytesPerPixel + 1] & 0xff);
|
||||
int sum = sub + left;
|
||||
actline[p] = (byte)((sum >> 8) & 0xff);
|
||||
actline[p + 1] = (byte)(sum & 0xff);
|
||||
}
|
||||
|
||||
offset += i;
|
||||
}
|
||||
|
||||
// do prediction as specified input PNG-Specification 1.2
|
||||
switch (linepredictor)
|
||||
else if (bitsPerComponent == 1 && colors == 1)
|
||||
{
|
||||
case 2:
|
||||
// PRED TIFF SUB
|
||||
if (bitsPerComponent == 8)
|
||||
{
|
||||
// for 8 bits per component it is the same algorithm as PRED SUB of PNG format
|
||||
for (int p = bytesPerPixel; p < rowlength; p++)
|
||||
{
|
||||
int sub = actline[p] & 0xff;
|
||||
int left = actline[p - bytesPerPixel] & 0xff;
|
||||
actline[p] = (byte)(sub + left);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (bitsPerComponent == 16)
|
||||
{
|
||||
for (int p = bytesPerPixel; p < rowlength; p += 2)
|
||||
{
|
||||
int sub = ((actline[p] & 0xff) << 8) + (actline[p + 1] & 0xff);
|
||||
int left = (((actline[p - bytesPerPixel] & 0xff) << 8)
|
||||
+ (actline[p - bytesPerPixel + 1] & 0xff));
|
||||
actline[p] = (byte)(((sub + left) >> 8) & 0xff);
|
||||
actline[p + 1] = (byte)((sub + left) & 0xff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (bitsPerComponent == 1 && colors == 1)
|
||||
{
|
||||
// bytesPerPixel cannot be used:
|
||||
// "A row shall occupy a whole number of bytes, rounded up if necessary.
|
||||
// Samples and their components shall be packed into bytes
|
||||
// from high-order to low-order bits."
|
||||
for (int p = 0; p < rowlength; p++)
|
||||
{
|
||||
for (int bit = 7; bit >= 0; --bit)
|
||||
{
|
||||
int sub = (actline[p] >> bit) & 1;
|
||||
if (p == 0 && bit == 7)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int left;
|
||||
if (bit == 7)
|
||||
{
|
||||
// use bit #0 from previous byte
|
||||
left = actline[p - 1] & 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// use "previous" bit
|
||||
left = (actline[p] >> (bit + 1)) & 1;
|
||||
}
|
||||
if (((sub + left) & 1) == 0)
|
||||
{
|
||||
// reset bit
|
||||
actline[p] = (byte)(actline[p] & ~(1 << bit));
|
||||
}
|
||||
else
|
||||
{
|
||||
// set bit
|
||||
actline[p] = (byte)(actline[p] | (1 << bit));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// everything else, i.e. bpc 2 and 4, but has been tested for bpc 1 and 8 too
|
||||
int elements = columns * colors;
|
||||
for (int p = colors; p < elements; ++p)
|
||||
{
|
||||
int bytePosSub = p * bitsPerComponent / 8;
|
||||
int bitPosSub = 8 - p * bitsPerComponent % 8 - bitsPerComponent;
|
||||
int bytePosLeft = (p - colors) * bitsPerComponent / 8;
|
||||
int bitPosLeft = 8 - (p - colors) * bitsPerComponent % 8 - bitsPerComponent;
|
||||
// bytesPerPixel cannot be used:
|
||||
// "A row shall occupy a whole number of bytes, rounded up if necessary.
|
||||
// Samples and their components shall be packed into bytes
|
||||
// from high-order to low-order bits."
|
||||
|
||||
int sub = GetBitSeq(actline[bytePosSub], bitPosSub, bitsPerComponent);
|
||||
int left = GetBitSeq(actline[bytePosLeft], bitPosLeft, bitsPerComponent);
|
||||
actline[bytePosSub] = (byte)CalcSetBitSeq(actline[bytePosSub], bitPosSub,
|
||||
bitsPerComponent,
|
||||
sub + left);
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
// PRED NONE
|
||||
// do nothing
|
||||
break;
|
||||
case 11:
|
||||
// PRED SUB
|
||||
for (int p = bytesPerPixel; p < rowlength; p++)
|
||||
for (int p = 0; p < rowLength; p++)
|
||||
{
|
||||
for (int bit = 7; bit >= 0; --bit)
|
||||
{
|
||||
int sub = actline[p];
|
||||
int left = actline[p - bytesPerPixel];
|
||||
actline[p] = (byte)(sub + left);
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
// PRED UP
|
||||
for (int p = 0; p < rowlength; p++)
|
||||
{
|
||||
int up = actline[p] & 0xff;
|
||||
int prior = lastline[p] & 0xff;
|
||||
actline[p] = (byte)((up + prior) & 0xff);
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
// PRED AVG
|
||||
for (int p = 0; p < rowlength; p++)
|
||||
{
|
||||
int avg = actline[p] & 0xff;
|
||||
int left = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] & 0xff : 0;
|
||||
int up = lastline[p] & 0xff;
|
||||
actline[p] = (byte)((avg + (left + up) / 2) & 0xff);
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
// PRED PAETH
|
||||
for (int p = 0; p < rowlength; p++)
|
||||
{
|
||||
int paeth = actline[p] & 0xff;
|
||||
int a = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] & 0xff : 0; // left
|
||||
int b = lastline[p] & 0xff; // upper
|
||||
int c = p - bytesPerPixel >= 0 ? lastline[p - bytesPerPixel] & 0xff : 0; // upperleft
|
||||
int value = a + b - c;
|
||||
int absa = Math.Abs(value - a);
|
||||
int absb = Math.Abs(value - b);
|
||||
int absc = Math.Abs(value - c);
|
||||
|
||||
if (absa <= absb && absa <= absc)
|
||||
int sub = (actline[p] >> bit) & 1;
|
||||
if (p == 0 && bit == 7)
|
||||
{
|
||||
actline[p] = (byte)((paeth + a) & 0xff);
|
||||
continue;
|
||||
}
|
||||
else if (absb <= absc)
|
||||
|
||||
int left;
|
||||
if (bit == 7)
|
||||
{
|
||||
actline[p] = (byte)((paeth + b) & 0xff);
|
||||
// use bit #0 from previous byte
|
||||
left = actline[p - 1] & 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
actline[p] = (byte)((paeth + c) & 0xff);
|
||||
// use "previous" bit
|
||||
left = (actline[p] >> (bit + 1)) & 1;
|
||||
}
|
||||
|
||||
if (((sub + left) & 1) == 0)
|
||||
{
|
||||
// reset bit
|
||||
actline[p] &= (byte)~(1 << bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set bit
|
||||
actline[p] |= (byte)(1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
Array.Copy(actline, 0, lastline, 0, rowlength);
|
||||
output.Write(actline);
|
||||
}
|
||||
else
|
||||
{
|
||||
// everything else, i.e. bpc 2 and 4, but has been tested for bpc 1 and 8 too
|
||||
int elements = columns * colors;
|
||||
for (int p = colors; p < elements; ++p)
|
||||
{
|
||||
int bytePosSub = p * bitsPerComponent / 8;
|
||||
int bitPosSub = 8 - p * bitsPerComponent % 8 - bitsPerComponent;
|
||||
int bytePosLeft = (p - colors) * bitsPerComponent / 8;
|
||||
int bitPosLeft = 8 - (p - colors) * bitsPerComponent % 8 - bitsPerComponent;
|
||||
|
||||
int sub = GetBitSeq(actline[bytePosSub], bitPosSub, bitsPerComponent);
|
||||
int left = GetBitSeq(actline[bytePosLeft], bitPosLeft, bitsPerComponent);
|
||||
actline[bytePosSub] = (byte)CalcSetBitSeq(actline[bytePosSub], bitPosSub, bitsPerComponent, sub + left);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
return memoryStream.ToArray();
|
||||
case 10:
|
||||
// PRED NONE
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
case 11:
|
||||
// PRED SUB
|
||||
for (int p = bytesPerPixel; p < rowLength; p++)
|
||||
{
|
||||
int sub = actline[p];
|
||||
int left = actline[p - bytesPerPixel];
|
||||
actline[p] = (byte)(sub + left);
|
||||
}
|
||||
break;
|
||||
|
||||
case 12:
|
||||
// PRED UP
|
||||
for (int p = 0; p < rowLength; p++)
|
||||
{
|
||||
int up = actline[p] & 0xff;
|
||||
int prior = lastline[p] & 0xff;
|
||||
actline[p] = (byte)((up + prior) & 0xff);
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
// PRED AVG
|
||||
for (int p = 0; p < rowLength; p++)
|
||||
{
|
||||
int avg = actline[p] & 0xff;
|
||||
int left = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] & 0xff : 0;
|
||||
int up = lastline[p] & 0xff;
|
||||
actline[p] = (byte)((avg + (left + up) / 2) & 0xff);
|
||||
}
|
||||
break;
|
||||
|
||||
case 14:
|
||||
// PRED PAETH
|
||||
for (int p = 0; p < rowLength; p++)
|
||||
{
|
||||
int paeth = actline[p] & 0xff;
|
||||
int a = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] & 0xff : 0;
|
||||
int b = lastline[p] & 0xff;
|
||||
int c = p - bytesPerPixel >= 0 ? lastline[p - bytesPerPixel] & 0xff : 0;
|
||||
int value = a + b - c;
|
||||
int absa = Math.Abs(value - a);
|
||||
int absb = Math.Abs(value - b);
|
||||
int absc = Math.Abs(value - c);
|
||||
|
||||
if (absa <= absb && absa <= absc)
|
||||
{
|
||||
actline[p] = (byte)((paeth + a) & 0xff);
|
||||
}
|
||||
else if (absb <= absc)
|
||||
{
|
||||
actline[p] = (byte)((paeth + b) & 0xff);
|
||||
}
|
||||
else
|
||||
{
|
||||
actline[p] = (byte)((paeth + c) & 0xff);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static int CalculateRowLength(int colors, int bitsPerComponent, int columns)
|
||||
{
|
||||
int bitsPerPixel = colors * bitsPerComponent;
|
||||
return (columns * bitsPerPixel + 7) / 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetLongOrDefault value from bit interval from a byte
|
||||
/// Get value from bit interval from a byte.
|
||||
/// </summary>
|
||||
private static int GetBitSeq(int by, int startBit, int bitSize)
|
||||
{
|
||||
@@ -228,8 +201,13 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set value input a bit interval and return that value
|
||||
/// Set value in a bit interval and return that value.
|
||||
/// </summary>
|
||||
/// <param name="by"></param>
|
||||
/// <param name="startBit"></param>
|
||||
/// <param name="bitSize"></param>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
private static int CalcSetBitSeq(int by, int startBit, int bitSize, int val)
|
||||
{
|
||||
int mask = ((1 << bitSize) - 1);
|
||||
@@ -237,5 +215,136 @@
|
||||
mask = ~(mask << startBit);
|
||||
return (by & mask) | (truncatedVal << startBit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a <see cref="Stream"/> in a predictor decoding stream as necessary.
|
||||
/// <para>If no predictor is specified by the parameters, the original stream is returned as is.</para>
|
||||
/// </summary>
|
||||
/// <param name="outStream">The stream to which decoded data should be written.</param>
|
||||
/// <param name="predictor"></param>
|
||||
/// <param name="colors"></param>
|
||||
/// <param name="bitsPerComponent"></param>
|
||||
/// <param name="columns"></param>
|
||||
/// <returns>A <see cref="Stream"/> is returned, which will write decoded data
|
||||
/// into the given stream. If no predictor is specified, the original stream is returned.</returns>
|
||||
public static Stream WrapPredictor(Stream outStream, int predictor, int colors, int bitsPerComponent, int columns)
|
||||
{
|
||||
if (predictor > 1)
|
||||
{
|
||||
return new PredictorOutputStream(outStream, predictor, colors, bitsPerComponent, columns);
|
||||
}
|
||||
|
||||
return outStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output stream that implements predictor decoding. Data is buffered until a complete
|
||||
* row is available, which is then decoded and written to the underlying stream.
|
||||
* The previous row is retained for decoding the next row.
|
||||
*/
|
||||
private sealed class PredictorOutputStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private int _predictor;
|
||||
private readonly int _colors;
|
||||
private readonly int _bitsPerComponent;
|
||||
private readonly int _columns;
|
||||
private readonly int _rowLength;
|
||||
private readonly bool _predictorPerRow;
|
||||
private byte[] _currentRow;
|
||||
private byte[] _lastRow;
|
||||
private int _currentRowData = 0;
|
||||
private bool _predictorRead = false;
|
||||
|
||||
public PredictorOutputStream(Stream baseStream, int predictor, int colors, int bitsPerComponent, int columns)
|
||||
{
|
||||
_baseStream = baseStream;
|
||||
_predictor = predictor;
|
||||
_colors = colors;
|
||||
_bitsPerComponent = bitsPerComponent;
|
||||
_columns = columns;
|
||||
_rowLength = CalculateRowLength(colors, bitsPerComponent, columns);
|
||||
_predictorPerRow = predictor >= 10;
|
||||
_currentRow = new byte[_rowLength];
|
||||
_lastRow = new byte[_rowLength];
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int currentOffset = offset;
|
||||
int maxOffset = currentOffset + count;
|
||||
while (currentOffset < maxOffset)
|
||||
{
|
||||
if (_predictorPerRow && _currentRowData == 0 && !_predictorRead)
|
||||
{
|
||||
// PNG predictor; each row starts with predictor type (0, 1, 2, 3, 4)
|
||||
// read per line predictor, add 10 to tread value 0 as 10, 1 as 11, ...
|
||||
_predictor = buffer[currentOffset] + 10;
|
||||
currentOffset++;
|
||||
_predictorRead = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int toRead = Math.Min(_rowLength - _currentRowData, maxOffset - currentOffset);
|
||||
Array.Copy(buffer, currentOffset, _currentRow, _currentRowData, toRead);
|
||||
_currentRowData += toRead;
|
||||
currentOffset += toRead;
|
||||
|
||||
// current row is filled, decode it, write it to underlying stream,
|
||||
// and reset the state.
|
||||
if (_currentRowData == _currentRow.Length)
|
||||
{
|
||||
DecodeAndWriteRow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DecodeAndWriteRow()
|
||||
{
|
||||
DecodePredictorRow(_predictor, _colors, _bitsPerComponent, _columns, _currentRow, _lastRow);
|
||||
_baseStream.Write(_currentRow, 0, _currentRow.Length);
|
||||
FlipRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the row buffers (to avoid copying), and resets the current-row index
|
||||
* and predictorRead flag
|
||||
*/
|
||||
private void FlipRows()
|
||||
{
|
||||
(_lastRow, _currentRow) = (_currentRow, _lastRow);
|
||||
_currentRowData = 0;
|
||||
_predictorRead = false;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
// The last row is allowed to be incomplete, and should be completed with zeros.
|
||||
if (_currentRowData > 0)
|
||||
{
|
||||
// public static void fill(int[] a, int fromIndex, int toIndex, int val)
|
||||
// Arrays.fill(currentRow, currentRowData, rowLength, (byte)0);
|
||||
_currentRow.AsSpan(_currentRowData, _rowLength - _currentRowData).Fill(byte.MinValue);
|
||||
|
||||
DecodeAndWriteRow();
|
||||
}
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException("Read not supported");
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
public override long Length => throw new NotSupportedException();
|
||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void WriteByte(byte value) => throw new NotSupportedException("Not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,534 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
namespace UglyToad.PdfPig.IO
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
internal class RandomAccessBuffer : IDisposable
|
||||
{
|
||||
// default chunk size is 1kb
|
||||
private static readonly int DefaultChunkSize = 1024;
|
||||
|
||||
// use the default chunk size
|
||||
private int chunkSize = DefaultChunkSize;
|
||||
|
||||
// list containing all chunks
|
||||
private List<byte[]> bufferList;
|
||||
|
||||
// current chunk
|
||||
private byte[] currentBuffer;
|
||||
|
||||
// current pointer to the whole buffer
|
||||
private long pointer;
|
||||
|
||||
// current pointer for the current chunk
|
||||
private int currentBufferPointer;
|
||||
|
||||
// size of the whole buffer
|
||||
private long size;
|
||||
|
||||
// current chunk list index
|
||||
private int bufferListIndex;
|
||||
|
||||
// maximum chunk list index
|
||||
private int bufferListMaxIndex;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public RandomAccessBuffer() : this(DefaultChunkSize)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
private RandomAccessBuffer(int definedChunkSize)
|
||||
{
|
||||
// starting with one chunk
|
||||
bufferList = new List<byte[]>();
|
||||
chunkSize = definedChunkSize;
|
||||
currentBuffer = new byte[chunkSize];
|
||||
bufferList.Add(currentBuffer);
|
||||
pointer = 0;
|
||||
currentBufferPointer = 0;
|
||||
size = 0;
|
||||
bufferListIndex = 0;
|
||||
bufferListMaxIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a random access buffer using the given byte array.
|
||||
*
|
||||
* @param input the byte array to be read
|
||||
*/
|
||||
public RandomAccessBuffer(byte[] input)
|
||||
{
|
||||
// this is a special case. The given byte array is used as the one
|
||||
// and only chunk.
|
||||
bufferList = new List<byte[]>(1);
|
||||
chunkSize = input.Length;
|
||||
currentBuffer = input;
|
||||
bufferList.Add(currentBuffer);
|
||||
pointer = 0;
|
||||
currentBufferPointer = 0;
|
||||
size = chunkSize;
|
||||
bufferListIndex = 0;
|
||||
bufferListMaxIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a random access buffer of the given input stream by copying the data.
|
||||
*
|
||||
* @param input the input stream to be read
|
||||
* @throws IOException if something went wrong while copying the data
|
||||
*/
|
||||
public RandomAccessBuffer(BinaryReader input)
|
||||
{
|
||||
//this();
|
||||
byte[]
|
||||
byteBuffer = new byte[8192];
|
||||
int bytesRead = 0;
|
||||
while ((bytesRead = input.Read(byteBuffer, 0, 8192)) > -1)
|
||||
{
|
||||
write(byteBuffer, 0, bytesRead);
|
||||
}
|
||||
Seek(0);
|
||||
}
|
||||
|
||||
|
||||
public RandomAccessBuffer Clone()
|
||||
{
|
||||
RandomAccessBuffer copy = new RandomAccessBuffer(chunkSize)
|
||||
{
|
||||
bufferList = new List<byte[]>(bufferList.Count)
|
||||
};
|
||||
|
||||
foreach (byte[] buffer in bufferList)
|
||||
{
|
||||
byte[] newBuffer = new byte[buffer.Length];
|
||||
Array.Copy(buffer, 0, newBuffer, 0, buffer.Length);
|
||||
|
||||
copy.bufferList.Add(newBuffer);
|
||||
}
|
||||
if (currentBuffer != null)
|
||||
{
|
||||
copy.currentBuffer = copy.bufferList[copy.bufferList.Count - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
copy.currentBuffer = null;
|
||||
}
|
||||
copy.pointer = pointer;
|
||||
copy.currentBufferPointer = currentBufferPointer;
|
||||
copy.size = size;
|
||||
copy.bufferListIndex = bufferListIndex;
|
||||
copy.bufferListMaxIndex = bufferListMaxIndex;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
currentBuffer = null;
|
||||
bufferList.Clear();
|
||||
pointer = 0;
|
||||
currentBufferPointer = 0;
|
||||
size = 0;
|
||||
bufferListIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void clear()
|
||||
{
|
||||
bufferList.Clear();
|
||||
currentBuffer = new byte[chunkSize];
|
||||
bufferList.Add(currentBuffer);
|
||||
pointer = 0;
|
||||
currentBufferPointer = 0;
|
||||
size = 0;
|
||||
bufferListIndex = 0;
|
||||
bufferListMaxIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void Seek(long position)
|
||||
{
|
||||
CheckClosed();
|
||||
if (position < 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid position {position}");
|
||||
}
|
||||
pointer = position;
|
||||
if (pointer < size)
|
||||
{
|
||||
// calculate the chunk list index
|
||||
bufferListIndex = (int) (pointer / chunkSize);
|
||||
currentBufferPointer = (int) (pointer % chunkSize);
|
||||
currentBuffer = bufferList[bufferListIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
// it is allowed to jump beyond the end of the file
|
||||
// jump to the end of the buffer
|
||||
bufferListIndex = bufferListMaxIndex;
|
||||
currentBuffer = bufferList[bufferListIndex];
|
||||
currentBufferPointer = (int) (size % chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public long GetPosition()
|
||||
{
|
||||
CheckClosed();
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public int Read()
|
||||
{
|
||||
CheckClosed();
|
||||
if (pointer >= this.size)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (currentBufferPointer >= chunkSize)
|
||||
{
|
||||
if (bufferListIndex >= bufferListMaxIndex)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentBuffer = bufferList[++bufferListIndex];
|
||||
currentBufferPointer = 0;
|
||||
}
|
||||
}
|
||||
pointer++;
|
||||
return currentBuffer[currentBufferPointer++] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public int Read(byte[] b, int offset, int length)
|
||||
{
|
||||
CheckClosed();
|
||||
if (pointer >= size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int bytesRead = ReadRemainingBytes(b, offset, length);
|
||||
while (bytesRead < length && Available() > 0)
|
||||
{
|
||||
bytesRead += ReadRemainingBytes(b, offset + bytesRead, length - bytesRead);
|
||||
if (currentBufferPointer == chunkSize)
|
||||
{
|
||||
NextBuffer();
|
||||
}
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private int ReadRemainingBytes(byte[] b, int offset, int length)
|
||||
{
|
||||
if (pointer >= size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int maxLength = (int) Math.Min(length, size - pointer);
|
||||
int remainingBytes = chunkSize - currentBufferPointer;
|
||||
// no more bytes left
|
||||
if (remainingBytes == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (maxLength >= remainingBytes)
|
||||
{
|
||||
// copy the remaining bytes from the current buffer
|
||||
Array.Copy(currentBuffer, currentBufferPointer, b, offset, remainingBytes);
|
||||
// end of file reached
|
||||
currentBufferPointer += remainingBytes;
|
||||
pointer += remainingBytes;
|
||||
return remainingBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy the remaining bytes from the whole buffer
|
||||
Array.Copy(currentBuffer, currentBufferPointer, b, offset, maxLength);
|
||||
// end of file reached
|
||||
currentBufferPointer += maxLength;
|
||||
pointer += maxLength;
|
||||
return maxLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public long Length()
|
||||
{
|
||||
CheckClosed();
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void write(int b)
|
||||
{
|
||||
CheckClosed();
|
||||
// end of buffer reached?
|
||||
if (currentBufferPointer >= chunkSize)
|
||||
{
|
||||
if (pointer + chunkSize >= int.MaxValue)
|
||||
{
|
||||
throw new OutOfMemoryException("RandomAccessBuffer overflow");
|
||||
}
|
||||
ExpandBuffer();
|
||||
}
|
||||
currentBuffer[currentBufferPointer++] = (byte) b;
|
||||
pointer++;
|
||||
if (pointer > this.size)
|
||||
{
|
||||
this.size = pointer;
|
||||
}
|
||||
// end of buffer reached now?
|
||||
if (currentBufferPointer >= chunkSize)
|
||||
{
|
||||
if (pointer + chunkSize >= int.MaxValue)
|
||||
{
|
||||
throw new OutOfMemoryException("RandomAccessBuffer overflow");
|
||||
}
|
||||
ExpandBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void write(byte[] b)
|
||||
{
|
||||
write(b, 0, b.Length);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void write(byte[] b, int offset, int length)
|
||||
{
|
||||
CheckClosed();
|
||||
long newSize = pointer + length;
|
||||
int remainingBytes = chunkSize - currentBufferPointer;
|
||||
if (length >= remainingBytes)
|
||||
{
|
||||
if (newSize > int.MaxValue)
|
||||
{
|
||||
throw new OutOfMemoryException("RandomAccessBuffer overflow");
|
||||
}
|
||||
// copy the first bytes to the current buffer
|
||||
Array.Copy(b, offset, currentBuffer, currentBufferPointer, remainingBytes);
|
||||
int newOffset = offset + remainingBytes;
|
||||
long remainingBytes2Write = length - remainingBytes;
|
||||
// determine how many buffers are needed for the remaining bytes
|
||||
int numberOfNewArrays = (int) remainingBytes2Write / chunkSize;
|
||||
for (int i = 0; i < numberOfNewArrays; i++)
|
||||
{
|
||||
ExpandBuffer();
|
||||
Array.Copy(b, newOffset, currentBuffer, currentBufferPointer, chunkSize);
|
||||
newOffset += chunkSize;
|
||||
}
|
||||
// are there still some bytes to be written?
|
||||
remainingBytes2Write -= numberOfNewArrays * (long) chunkSize;
|
||||
if (remainingBytes2Write >= 0)
|
||||
{
|
||||
ExpandBuffer();
|
||||
if (remainingBytes2Write > 0)
|
||||
{
|
||||
Array.Copy(b, newOffset, currentBuffer, currentBufferPointer, (int) remainingBytes2Write);
|
||||
}
|
||||
currentBufferPointer = (int) remainingBytes2Write;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(b, offset, currentBuffer, currentBufferPointer, length);
|
||||
currentBufferPointer += length;
|
||||
}
|
||||
pointer += length;
|
||||
if (pointer > this.size)
|
||||
{
|
||||
this.size = pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new buffer chunk and adjust all pointers and indices.
|
||||
*/
|
||||
private void ExpandBuffer()
|
||||
{
|
||||
if (bufferListMaxIndex > bufferListIndex)
|
||||
{
|
||||
// there is already an existing chunk
|
||||
NextBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// create a new chunk and add it to the buffer
|
||||
currentBuffer = new byte[chunkSize];
|
||||
bufferList.Add(currentBuffer);
|
||||
currentBufferPointer = 0;
|
||||
bufferListMaxIndex++;
|
||||
bufferListIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* switch to the next buffer chunk and reset the buffer pointer.
|
||||
*/
|
||||
private void NextBuffer()
|
||||
{
|
||||
if (bufferListIndex == bufferListMaxIndex)
|
||||
{
|
||||
throw new InvalidOperationException("No more chunks available, end of buffer reached");
|
||||
}
|
||||
currentBufferPointer = 0;
|
||||
currentBuffer = bufferList[++bufferListIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the RandomAccessBuffer is not closed
|
||||
* @throws IOException
|
||||
*/
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (currentBuffer is null)
|
||||
{
|
||||
// consider that the rab is closed if there is no current buffer
|
||||
throw new ObjectDisposedException("RandomAccessBuffer already closed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public bool IsClosed()
|
||||
{
|
||||
return currentBuffer is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public bool IsEof()
|
||||
{
|
||||
CheckClosed();
|
||||
return pointer >= size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public int Available()
|
||||
{
|
||||
return (int) Math.Min(Length() - GetPosition(), int.MaxValue);
|
||||
}
|
||||
|
||||
public void ReturnToBeginning()
|
||||
{
|
||||
Seek(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public int Peek()
|
||||
{
|
||||
int result = Read();
|
||||
if (result != -1)
|
||||
{
|
||||
Rewind(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public void Rewind(int bytes)
|
||||
{
|
||||
CheckClosed();
|
||||
Seek(GetPosition() - bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public byte[] ReadFully(int length)
|
||||
{
|
||||
byte[]
|
||||
b = new byte[length];
|
||||
int bytesRead = Read(b);
|
||||
while (bytesRead < length)
|
||||
{
|
||||
bytesRead += Read(b, bytesRead, length - bytesRead);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
public int Read(byte[] b)
|
||||
{
|
||||
return Read(b, 0, b.Length);
|
||||
}
|
||||
|
||||
public void Unread(int b)
|
||||
{
|
||||
Rewind(1);
|
||||
}
|
||||
|
||||
public void Unread(byte[] bytes)
|
||||
{
|
||||
Rewind(bytes.Length);
|
||||
}
|
||||
|
||||
public void Unread(byte[] bytes, int start, int len)
|
||||
{
|
||||
Rewind(len - start);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user