Browse Source

Always assume PhotometricInterpretation to be YCbCr, if the compression is old jpeg

pull/2266/head
Brian Popow 4 years ago
parent
commit
e6d1c94a6a
  1. 35
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegCompressionUtils.cs
  2. 49
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  3. 58
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs
  4. 45
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs
  5. 12
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

35
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegCompressionUtils.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
internal static class JpegCompressionUtils
{
public static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<Rgb24> pixelBuffer)
{
int offset = 0;
for (int y = 0; y < pixelBuffer.Height; y++)
{
Span<Rgb24> pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer[offset..]);
offset += rgbBytes.Length;
}
}
public static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<L8> pixelBuffer)
{
int offset = 0;
for (int y = 0; y < pixelBuffer.Height; y++)
{
Span<L8> pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer[offset..]);
offset += rgbBytes.Length;
}
}
}

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
@ -14,7 +13,7 @@ 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
internal sealed class JpegTiffCompression : TiffBaseDecompressor
{
private readonly JpegDecoderOptions options;
@ -70,16 +69,16 @@ internal class JpegTiffCompression : TiffBaseDecompressor
{
if (this.jpegTables != null)
{
this.DecodeJpegData(stream, buffer, true, cancellationToken);
this.DecodeJpegData(stream, buffer, cancellationToken);
}
else
{
using Image<Rgb24> image = Image.Load<Rgb24>(stream);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
JpegCompressionUtils.CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}
}
protected void DecodeJpegData(BufferedReadStream stream, Span<byte> buffer, bool loadTables, CancellationToken cancellationToken)
private void DecodeJpegData(BufferedReadStream stream, Span<byte> buffer, CancellationToken cancellationToken)
{
using JpegDecoderCore jpegDecoder = new(this.options);
Configuration configuration = this.options.GeneralOptions.Configuration;
@ -91,15 +90,11 @@ internal class JpegTiffCompression : TiffBaseDecompressor
using SpectralConverter<L8> spectralConverterGray = new GrayJpegSpectralConverter<L8>(configuration);
HuffmanScanDecoder scanDecoderGray = new(stream, spectralConverterGray, cancellationToken);
if (loadTables)
{
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray);
}
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray);
jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
JpegCompressionUtils.CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}
@ -109,15 +104,11 @@ internal class JpegTiffCompression : TiffBaseDecompressor
using SpectralConverter<Rgb24> spectralConverter = new TiffJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
HuffmanScanDecoder scanDecoder = new(stream, spectralConverter, cancellationToken);
if (loadTables)
{
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
}
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
JpegCompressionUtils.CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}
@ -127,30 +118,6 @@ internal class JpegTiffCompression : TiffBaseDecompressor
}
}
private static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<Rgb24> pixelBuffer)
{
int offset = 0;
for (int y = 0; y < pixelBuffer.Height; y++)
{
Span<Rgb24> pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer[offset..]);
offset += rgbBytes.Length;
}
}
private static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<L8> pixelBuffer)
{
int offset = 0;
for (int y = 0; y < pixelBuffer.Height; y++)
{
Span<L8> pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer[offset..]);
offset += rgbBytes.Length;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{

58
src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs

@ -2,16 +2,22 @@
// Licensed under the Six Labors Split License.
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.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
internal sealed class OldJpegTiffCompression : JpegTiffCompression
internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
{
private readonly JpegDecoderOptions options;
private readonly uint startOfImageMarker;
private readonly TiffPhotometricInterpretation photometricInterpretation;
public OldJpegTiffCompression(
MemoryAllocator memoryAllocator,
int width,
@ -19,12 +25,58 @@ internal sealed class OldJpegTiffCompression : JpegTiffCompression
JpegDecoderOptions options,
uint startOfImageMarker,
TiffPhotometricInterpretation photometricInterpretation)
: base(memoryAllocator, width, bitsPerPixel, options, photometricInterpretation) => this.startOfImageMarker = startOfImageMarker;
: base(memoryAllocator, width, bitsPerPixel)
{
this.options = options;
this.startOfImageMarker = startOfImageMarker;
this.photometricInterpretation = photometricInterpretation;
}
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
stream.Position = this.startOfImageMarker;
this.DecodeJpegData(stream, buffer, false, cancellationToken);
this.DecodeJpegData(stream, buffer, cancellationToken);
}
private void DecodeJpegData(BufferedReadStream stream, Span<byte> buffer, CancellationToken cancellationToken)
{
using JpegDecoderCore jpegDecoder = new(this.options);
Configuration configuration = this.options.GeneralOptions.Configuration;
switch (this.photometricInterpretation)
{
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
{
using SpectralConverter<L8> spectralConverterGray = new GrayJpegSpectralConverter<L8>(configuration);
jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken);
JpegCompressionUtils.CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}
case TiffPhotometricInterpretation.YCbCr:
case TiffPhotometricInterpretation.Rgb:
{
using SpectralConverter<Rgb24> spectralConverter = new TiffOldJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken);
JpegCompressionUtils.CopyImageBytesToBuffer(buffer, decompressedBuffer);
break;
}
default:
TiffThrowHelper.ThrowNotSupported($"Jpeg compressed tiff with photometric interpretation {this.photometricInterpretation} is not supported");
break;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}

45
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs

@ -0,0 +1,45 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
/// <summary>
/// Spectral converter for YCbCr TIFF's which use the OldJPEG compression.
/// The jpeg data should be always treated as YCbCr color space.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
internal sealed class TiffOldJpegSpectralConverter<TPixel> : SpectralConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TiffPhotometricInterpretation photometricInterpretation;
/// <summary>
/// Initializes a new instance of the <see cref="TiffOldJpegSpectralConverter{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="photometricInterpretation">Tiff photometric interpretation.</param>
public TiffOldJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation)
: base(configuration)
=> this.photometricInterpretation = photometricInterpretation;
/// <inheritdoc/>
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData)
{
JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation);
return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision);
}
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
=> interpretation switch
{
// Like libtiff: Always treat the pixel data as YCbCr when the data is compressed with old jpeg compression.
TiffPhotometricInterpretation.Rgb => JpegColorSpace.YCbCr,
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.YCbCr,
_ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"),
};
}

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

@ -37,7 +37,7 @@ internal static class TiffDecoderOptionsParser
TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data.");
}
var extraSamplesType = (TiffExtraSampleType)extraSamples[0];
TiffExtraSampleType extraSamplesType = (TiffExtraSampleType)extraSamples[0];
options.ExtraSamplesType = extraSamplesType;
if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData))
{
@ -478,18 +478,14 @@ internal static class TiffDecoderOptionsParser
{
options.CompressionType = TiffDecoderCompressionType.OldJpeg;
// Like libtiff: always assume PhotometricInterpretation to be YCbCr, if the compression is old jpeg.
options.PhotometricInterpretation = TiffPhotometricInterpretation.YCbCr;
if (!options.OldJpegCompressionStartOfImageMarker.HasValue)
{
TiffThrowHelper.ThrowNotSupported("Missing SOI marker offset for tiff with old jpeg compression");
}
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr)
{
// Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data.
options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
options.ColorType = TiffColorType.Rgb;
}
break;
}

Loading…
Cancel
Save