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> /// </summary>
private int restartInterval; private int restartInterval;
// How many mcu's are left to do. /// <summary>
/// How many mcu's are left to do.
/// </summary>
private int todo; 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; private int eobrun;
/// <summary> /// <summary>
@ -54,7 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private readonly HuffmanTable[] acHuffmanTables; private readonly HuffmanTable[] acHuffmanTables;
// The unzig data. /// <summary>
/// The unzig data.
/// </summary>
private ZigZag dctZigZag; private ZigZag dctZigZag;
private HuffmanScanBuffer scanBuffer; private HuffmanScanBuffer scanBuffer;

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

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -136,8 +137,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Finds the next file marker within the byte stream. /// Finds the next file marker within the byte stream.
/// </summary> /// </summary>
/// <param name="marker">The buffer to read file markers to</param> /// <param name="marker">The buffer to read file markers to.</param>
/// <param name="stream">The input stream</param> /// <param name="stream">The input stream.</param>
/// <returns>The <see cref="JpegFileMarker"/></returns> /// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream) 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); 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> /// <summary>
/// Parses the input stream for file markers. /// Parses the input stream for file markers.
/// </summary> /// </summary>
@ -225,7 +287,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Read(this.markerBuffer, 0, 2); stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1]; byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); 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. // Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695 // https://github.com/SixLabors/ImageSharp/issues/695
@ -236,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length // Get the marker length.
int remaining = this.ReadUint16(stream) - 2; int remaining = this.ReadUint16(stream) - 2;
switch (fileMarker.Marker) switch (fileMarker.Marker)
@ -345,7 +407,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Returns the correct colorspace based on the image component count /// Returns the correct colorspace based on the image component count.
/// </summary> /// </summary>
/// <returns>The <see cref="JpegColorSpace"/></returns> /// <returns>The <see cref="JpegColorSpace"/></returns>
private JpegColorSpace DeduceJpegColorSpace(byte componentCount) private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
@ -576,7 +638,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. /// 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> /// </summary>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</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. /// Image data is compressed using modified huffman compression.
/// </summary> /// </summary>
HuffmanRle = 5, 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 internal static class TiffDecompressorsFactory
{ {
public static TiffBaseDecompressor Create( public static TiffBaseDecompressor Create(
Configuration configuration,
TiffDecoderCompressionType method, TiffDecoderCompressionType method,
MemoryAllocator allocator, MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation, TiffPhotometricInterpretation photometricInterpretation,
@ -19,6 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
TiffColorType colorType, TiffColorType colorType,
TiffPredictor predictor, TiffPredictor predictor,
FaxCompressionOptions faxOptions, FaxCompressionOptions faxOptions,
byte[] jpegTables,
TiffFillOrder fillOrder, TiffFillOrder fillOrder,
ByteOrder byteOrder) 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"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); 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: default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
} }

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

@ -105,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
public TiffFillOrder FillOrder { get; set; } public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used.
/// </summary>
public byte[] JpegTables { get; set; }
/// <summary> /// <summary>
/// Gets or sets the planar configuration type to use when decoding the image. /// Gets or sets the planar configuration type to use when decoding the image.
/// </summary> /// </summary>
@ -290,6 +295,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType, this.CompressionType,
this.memoryAllocator, this.memoryAllocator,
this.PhotometricInterpretation, this.PhotometricInterpretation,
@ -298,6 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType, this.ColorType,
this.Predictor, this.Predictor,
this.FaxCompressionOptions, this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder, this.FillOrder,
this.byteOrder); this.byteOrder);
@ -350,6 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer; Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType, this.CompressionType,
this.memoryAllocator, this.memoryAllocator,
this.PhotometricInterpretation, this.PhotometricInterpretation,
@ -358,6 +366,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType, this.ColorType,
this.Predictor, this.Predictor,
this.FaxCompressionOptions, this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder, this.FillOrder,
this.byteOrder); 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.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
options.FillOrder = fillOrder; options.FillOrder = fillOrder;
options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value;
options.ParseColorType(exifProfile); options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -424,6 +425,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break; break;
} }
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
break;
}
default: default:
{ {
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported"); TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported");

Loading…
Cancel
Save