diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
index 70a4465121..28ef6a96f7 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
@@ -38,10 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
private int restartInterval;
- // How many mcu's are left to do.
+ ///
+ /// How many mcu's are left to do.
+ ///
private int todo;
- // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
+ ///
+ /// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
+ ///
private int eobrun;
///
@@ -54,7 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
private readonly HuffmanTable[] acHuffmanTables;
- // The unzig data.
+ ///
+ /// The unzig data.
+ ///
private ZigZag dctZigZag;
private HuffmanScanBuffer scanBuffer;
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 896e5f0aaf..4f54f5078a 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/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
///
/// Finds the next file marker within the byte stream.
///
- /// The buffer to read file markers to
- /// The input stream
+ /// The buffer to read file markers to.
+ /// The input stream.
/// The
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);
}
+ ///
+ /// 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).
+ ///
+ /// The table bytes.
+ /// The scan decoder.
+ 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);
+ }
+ }
+
///
/// Parses the input stream for file markers.
///
@@ -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
}
///
- /// Returns the correct colorspace based on the image component count
+ /// Returns the correct colorspace based on the image component count.
///
/// The
private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
@@ -576,7 +638,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// 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.
///
/// The input stream.
/// The remaining bytes in the segment block.
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
new file mode 100644
index 0000000000..5a26df95ac
--- /dev/null
+++ b/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
+{
+ ///
+ /// Class to handle cases where TIFF image data is compressed as a jpeg stream.
+ ///
+ internal class JpegTiffCompression : TiffBaseDecompressor
+ {
+ private readonly Configuration configuration;
+
+ private readonly byte[] jpegTables;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The memoryAllocator to use for buffer allocations.
+ /// The image width.
+ /// The bits per pixel.
+ /// The JPEG tables containing the quantization and/or Huffman tables.
+ /// The predictor.
+ 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;
+ }
+
+ ///
+ protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer)
+ {
+ var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder());
+
+ // Should we pass through the CancellationToken from the tiff decoder?
+ using var spectralConverter = new SpectralConverter(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(this.configuration, spectralConverter.PixelBuffer, new ImageMetadata());
+ int offset = 0;
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span pixelRowSpan = image.GetPixelRowSpan(y);
+ Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
+ rgbBytes.CopyTo(buffer.Slice(offset));
+ offset += rgbBytes.Length;
+ }
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
index 80bc0af5ab..6dd19c5552 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
+++ b/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.
///
HuffmanRle = 5,
+
+ ///
+ /// The image data is compressed as a JPEG stream.
+ ///
+ Jpeg = 6,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
index b1562223aa..ee44a70219 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
+++ b/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));
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 28afe4c6f2..5b8c974b46 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -105,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
///
public TiffFillOrder FillOrder { get; set; }
+ ///
+ /// Gets or sets the JPEG tables when jpeg compression is used.
+ ///
+ public byte[] JpegTables { get; set; }
+
///
/// Gets or sets the planar configuration type to use when decoding the image.
///
@@ -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 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);
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index bb435affcc..2a2be6d742 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/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");