Browse Source

Avoid reading all compressed data at once: instead read byte by byte from the stream to avoid allocation

pull/2134/head
Brian Popow 4 years ago
parent
commit
962466144a
  1. 10
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
  2. 12
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  3. 70
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  4. 31
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  5. 6
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
  6. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

10
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs

@ -3,7 +3,6 @@
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -19,14 +18,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
: base(input, fillOrder, bytesToRead)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < 6;
/// <inheritdoc/>
public override bool IsEndOfScanLine
@ -53,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
base.StartNewRow();
int remainder = this.BitsRead & 7; // bit-hack for % 8
int remainder = Numerics.Modulo8(this.BitsRead);
if (remainder != 0)
{
// Skip padding bits, move to next byte.

12
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

@ -42,11 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator);
var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount);
buffer.Clear();
int bitsWritten = 0;
uint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
@ -68,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
if (pixelsWritten == this.Width)
{
bitReader.StartNewRow();
rowsWritten++;
pixelsWritten = 0;
// Write padding bits, if necessary.
@ -78,6 +79,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad;
}
if (rowsWritten >= stripHeight)
{
break;
}
bitReader.StartNewRow();
}
if (pixelsWritten > this.Width)

70
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs

@ -1,22 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bitreader for reading compressed CCITT T4 1D data.
/// </summary>
internal class T4BitReader : IDisposable
internal class T4BitReader
{
/// <summary>
/// The logical order of bits within a byte.
@ -204,20 +199,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 }
};
/// <summary>
/// The compressed input stream.
/// </summary>
private readonly BufferedReadStream stream;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitReader" /> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false)
public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, bool eolPadding = false)
{
this.stream = input;
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.DataLength = bytesToRead;
this.BitsRead = 0;
this.Value = 0;
@ -230,8 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.RunLength = 0;
this.eolPadding = eolPadding;
Span<byte> dataSpan = this.Data.GetSpan();
this.DataAtPosition = dataSpan[(int)this.Position];
this.ReadNextByte();
if (this.eolPadding)
{
@ -269,11 +265,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
protected ulong Position { get; set; }
/// <summary>
/// Gets the compressed image data.
/// </summary>
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Gets a value indicating whether there is more data to read left.
/// </summary>
@ -400,9 +391,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.terminationCodeFound = false;
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary>
/// An EOL is expected before the first data.
/// </summary>
@ -465,14 +453,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Advances the position by one byte.
/// </summary>
/// <returns>True, if data could be advanced by one byte.</returns>
/// <returns>True, if data could be advanced by one byte, otherwise false.</returns>
protected bool AdvancePosition()
{
this.LoadNewByte();
if (this.Position < (ulong)this.DataLength)
if (this.LoadNewByte())
{
Span<byte> dataSpan = this.Data.GetSpan();
this.DataAtPosition = Unsafe.Add(ref MemoryMarshal.GetReference(dataSpan), (int)this.Position);
return true;
}
@ -833,6 +818,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint GetBit()
{
if (this.BitsRead >= 8)
@ -847,24 +833,34 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return bit;
}
private void LoadNewByte()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool LoadNewByte()
{
if (this.Position < (ulong)this.DataLength)
{
this.ReadNextByte();
this.Position++;
return true;
}
this.Position++;
this.ResetBitsRead();
this.DataAtPosition = 0;
return false;
}
private void ReadImageDataFromStream(Stream input, int bytesToRead)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadNextByte()
{
Span<byte> dataSpan = this.Data.GetSpan();
input.Read(dataSpan, 0, bytesToRead);
if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst)
int nextByte = this.stream.ReadByte();
if (nextByte == -1)
{
for (int i = 0; i < dataSpan.Length; i++)
{
dataSpan[i] = ReverseBits(dataSpan[i]);
}
TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data.");
}
this.ResetBitsRead();
this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst
? ReverseBits((byte)nextByte)
: (byte)nextByte;
}
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits

31
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs

@ -61,11 +61,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding);
var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, eolPadding);
buffer.Clear();
uint bitsWritten = 0;
uint pixelWritten = 0;
int bitsWritten = 0;
uint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
@ -74,41 +75,47 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
this.WritePixelRun(buffer, bitReader, bitsWritten);
bitsWritten += bitReader.RunLength;
pixelWritten += bitReader.RunLength;
bitsWritten += (int)bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (bitReader.IsEndOfScanLine)
{
// Write padding bytes, if necessary.
uint pad = 8 - (bitsWritten % 8);
int pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, (int)pad, 0);
BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad;
}
pixelWritten = 0;
pixelsWritten = 0;
rowsWritten++;
if (rowsWritten >= stripHeight)
{
break;
}
}
}
// Edge case for when we are at the last byte, but there are still some unwritten pixels left.
if (pixelWritten > 0 && pixelWritten < this.width)
if (pixelsWritten > 0 && pixelsWritten < this.width)
{
bitReader.ReadNextRun();
this.WritePixelRun(buffer, bitReader, bitsWritten);
}
}
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, uint bitsWritten)
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, int bitsWritten)
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, (int)bitReader.RunLength, this.whiteValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, (int)bitReader.RunLength, this.blackValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue);
}
}

6
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs

@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -56,9 +55,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
: base(input, fillOrder, bytesToRead)
{
}

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width);
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width);
using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator);
var bitReader = new T6BitReader(stream, this.FillOrder, byteCount);
var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width);
int bitsWritten = 0;

Loading…
Cancel
Save