Browse Source

Add decompressor for tiff's with jpeg compression

pull/1734/head
Brian Popow 5 years ago
parent
commit
ea05900d3b
  1. 12
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  2. 74
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  3. 70
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  4. 5
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  5. 6
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  6. 9
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  7. 7
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

12
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -38,10 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private int restartInterval;
// How many mcu's are left to do.
/// <summary>
/// How many mcu's are left to do.
/// </summary>
private int todo;
// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
/// <summary>
/// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
/// </summary>
private int eobrun;
/// <summary>
@ -54,7 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private readonly HuffmanTable[] acHuffmanTables;
// The unzig data.
/// <summary>
/// The unzig data.
/// </summary>
private ZigZag dctZigZag;
private HuffmanScanBuffer scanBuffer;

74
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
@ -136,8 +137,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Finds the next file marker within the byte stream.
/// </summary>
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <param name="marker">The buffer to read file markers to.</param>
/// <param name="stream">The input stream.</param>
/// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream)
{
@ -200,6 +201,67 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
}
/// <summary>
/// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's,
/// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips).
/// </summary>
/// <param name="tableBytes">The table bytes.</param>
/// <param name="huffmanScanDecoder">The scan decoder.</param>
public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder)
{
this.Metadata = new ImageMetadata();
this.QuantizationTables = new Block8x8F[4];
this.scanDecoder = huffmanScanDecoder;
using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms);
// Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
}
// Read next marker.
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
// Get the marker length.
int remaining = this.ReadUint16(stream) - 2;
switch (fileMarker.Marker)
{
case JpegConstants.Markers.SOI:
break;
case JpegConstants.Markers.RST0:
case JpegConstants.Markers.RST7:
break;
case JpegConstants.Markers.DHT:
this.ProcessDefineHuffmanTablesMarker(stream, remaining);
break;
case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining);
break;
case JpegConstants.Markers.DRI:
this.ProcessDefineRestartIntervalMarker(stream, remaining);
break;
case JpegConstants.Markers.EOI:
return;
}
}
// Read next marker.
stream.Read(this.markerBuffer, 0, 2);
fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
}
}
/// <summary>
/// Parses the input stream for file markers.
/// </summary>
@ -225,7 +287,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
this.QuantizationTables = new Block8x8F[4];
this.QuantizationTables ??= new Block8x8F[4];
// Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695
@ -236,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid)
{
// Get the marker length
// Get the marker length.
int remaining = this.ReadUint16(stream) - 2;
switch (fileMarker.Marker)
@ -345,7 +407,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Returns the correct colorspace based on the image component count
/// Returns the correct colorspace based on the image component count.
/// </summary>
/// <returns>The <see cref="JpegColorSpace"/></returns>
private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
@ -576,7 +638,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop.
/// The content of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks.
/// The tableBytes of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>

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

@ -0,0 +1,70 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed as a jpeg stream.
/// </summary>
internal class JpegTiffCompression : TiffBaseDecompressor
{
private readonly Configuration configuration;
private readonly byte[] jpegTables;
/// <summary>
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
/// <param name="predictor">The predictor.</param>
public JpegTiffCompression(Configuration configuration, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, byte[] jpegTables, TiffPredictor predictor = TiffPredictor.None)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{
this.configuration = configuration;
this.jpegTables = jpegTables;
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder());
// Should we pass through the CancellationToken from the tiff decoder?
using var spectralConverter = new SpectralConverter<Rgb24>(this.configuration, CancellationToken.None);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
scanDecoder.ResetInterval = 0;
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
var image = new Image<Rgb24>(this.configuration, spectralConverter.PixelBuffer, new ImageMetadata());
int offset = 0;
for (int y = 0; y < image.Height; y++)
{
Span<Rgb24> pixelRowSpan = image.GetPixelRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer.Slice(offset));
offset += rgbBytes.Length;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

5
src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs

@ -37,5 +37,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// Image data is compressed using modified huffman compression.
/// </summary>
HuffmanRle = 5,
/// <summary>
/// The image data is compressed as a JPEG stream.
/// </summary>
Jpeg = 6,
}
}

6
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
internal static class TiffDecompressorsFactory
{
public static TiffBaseDecompressor Create(
Configuration configuration,
TiffDecoderCompressionType method,
MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation,
@ -19,6 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
TiffColorType colorType,
TiffPredictor predictor,
FaxCompressionOptions faxOptions,
byte[] jpegTables,
TiffFillOrder fillOrder,
ByteOrder byteOrder)
{
@ -50,6 +52,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
case TiffDecoderCompressionType.Jpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables);
default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
}

9
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -105,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used.
/// </summary>
public byte[] JpegTables { get; set; }
/// <summary>
/// Gets or sets the planar configuration type to use when decoding the image.
/// </summary>
@ -290,6 +295,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
@ -298,6 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType,
this.Predictor,
this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder,
this.byteOrder);
@ -350,6 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
@ -358,6 +366,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType,
this.Predictor,
this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder,
this.byteOrder);

7
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -87,6 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
options.FillOrder = fillOrder;
options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value;
options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -424,6 +425,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
break;
}
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported");

Loading…
Cancel
Save