Skip to content

Commit

Permalink
Merge pull request #1734 from SixLabors/bp/tiffjpegcompression
Browse files Browse the repository at this point in the history
Adds support for Tiff's with jpeg compression
  • Loading branch information
JimBobSquarePants authored Aug 26, 2021
2 parents ce97da5 + f25ce22 commit 659daff
Show file tree
Hide file tree
Showing 61 changed files with 1,102 additions and 307 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,14 @@ internal class HuffmanScanDecoder
/// </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>
Expand All @@ -54,14 +58,16 @@ internal class HuffmanScanDecoder
/// </summary>
private readonly HuffmanTable[] acHuffmanTables;

// The unzig data.
/// <summary>
/// The unzig data.
/// </summary>
private ZigZag dctZigZag;

private HuffmanScanBuffer scanBuffer;

private readonly SpectralConverter spectralConverter;

private CancellationToken cancellationToken;
private readonly CancellationToken cancellationToken;

/// <summary>
/// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
Expand Down Expand Up @@ -30,5 +32,13 @@ internal abstract class SpectralConverter
/// Actual stride height depends on the subsampling factor of the given component.
/// </remarks>
public abstract void ConvertStrideBaseline();

/// <summary>
/// Gets the color converter.
/// </summary>
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
public virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal sealed class SpectralConverter<TPixel> : SpectralConverter, IDisposable
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;

private CancellationToken cancellationToken;
private readonly CancellationToken cancellationToken;

private JpegComponentPostProcessor[] componentProcessors;

Expand Down Expand Up @@ -59,6 +59,7 @@ public Buffer2D<TPixel> PixelBuffer
}
}

/// <inheritdoc/>
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
MemoryAllocator allocator = this.configuration.MemoryAllocator;
Expand All @@ -85,9 +86,10 @@ public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);

// color converter from Rgba32 to TPixel
this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
this.colorConverter = this.GetColorConverter(frame, jpegData);
}

/// <inheritdoc/>
public override void ConvertStrideBaseline()
{
// Convert next pixel stride using single spectral `stride'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal class HuffmanScanEncoder
private int emitLen = 0;

/// <summary>
/// Emmited bits 'micro buffer' before being transfered to the <see cref="emitBuffer"/>.
/// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>.
/// </summary>
private int accumulatedBits;

Expand All @@ -58,18 +58,15 @@ internal class HuffmanScanEncoder
/// </summary>
private readonly Stream target;

public HuffmanScanEncoder(Stream outputStream)
{
this.target = outputStream;
}
public HuffmanScanEncoder(Stream outputStream) => this.target = outputStream;

/// <summary>
/// Encodes the image with no subsampling.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
Expand Down Expand Up @@ -128,8 +125,8 @@ public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuant
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
Expand Down Expand Up @@ -196,7 +193,7 @@ public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuant
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
Expand Down Expand Up @@ -234,6 +231,64 @@ public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanc
this.FlushInternalBuffer();
}

/// <summary>
/// Encodes the image with no subsampling and keeps the pixel data as Rgb24.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeRgb<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;

var unzig = ZigZag.CreateUnzigTable();

// ReSharper disable once InconsistentNaming
int prevDCR = 0, prevDCG = 0, prevDCB = 0;

ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;

var pixelConverter = new RgbForwardConverter<TPixel>(frame);

for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);

for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(x, y, ref currentRows);

prevDCR = this.WriteBlock(
QuantIndex.Luminance,
prevDCR,
ref pixelConverter.R,
ref luminanceQuantTable,
ref unzig);

prevDCG = this.WriteBlock(
QuantIndex.Luminance,
prevDCG,
ref pixelConverter.G,
ref luminanceQuantTable,
ref unzig);

prevDCB = this.WriteBlock(
QuantIndex.Luminance,
prevDCB,
ref pixelConverter.B,
ref luminanceQuantTable,
ref unzig);
}
}

this.FlushInternalBuffer();
}

/// <summary>
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
Expand Down Expand Up @@ -437,7 +492,7 @@ internal static int GetHuffmanEncodingLength(uint value)
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max");
#if SUPPORTS_BITOPERATIONS
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation
// But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
// But internal log2 is implemented like this: (31 - (int)Lzcnt.LeadingZeroCount(value))

// BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0
// Lzcnt would return 32 for input value of 0 - no need to check that with branching
Expand All @@ -449,7 +504,7 @@ internal static int GetHuffmanEncodingLength(uint value)
// if 0 - return 0 in this case
// else - return log2(value) + 1
//
// Hack based on input value constaint:
// Hack based on input value constraint:
// We know that input values are guaranteed to be maximum 16 bit large for huffman encoding
// We can safely shift input value for one bit -> log2(value << 1)
// Because of the 16 bit value constraint it won't overflow
Expand Down
10 changes: 5 additions & 5 deletions src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the quantization tables
/// Enumerates the quantization tables.
/// </summary>
internal enum QuantIndex
{
/// <summary>
/// The luminance quantization table index
/// The luminance quantization table index.
/// </summary>
Luminance = 0,

/// <summary>
/// The chrominance quantization table index
/// The chrominance quantization table index.
/// </summary>
Chrominance = 1,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on.</typeparam>
internal ref struct RgbForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;

/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;

/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer.
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);

/// <summary>
/// The Red component.
/// </summary>
public Block8x8F R;

/// <summary>
/// The Green component.
/// </summary>
public Block8x8F G;

/// <summary>
/// The Blue component.
/// </summary>
public Block8x8F B;

/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;

/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data.
/// </summary>
private readonly Span<Rgb24> rgbSpan;

/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private readonly Size samplingAreaSize;

/// <summary>
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private readonly Configuration config;

public RgbForwardConverter(ImageFrame<TPixel> frame)
{
this.R = default;
this.G = default;
this.B = default;

// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());

// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}

/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);

PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);

ref Block8x8F redBlock = ref this.R;
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;

CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}

private static void CopyToBlock(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);

for (int i = 0; i < Block8x8F.Size; i++)
{
Rgb24 c = Unsafe.Add(ref rgbStart, i);

redBlock[i] = c.R;
greenBlock[i] = c.G;
blueBlock[i] = c.B;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,22 @@ internal ref struct YCbCrForwardConverter420<TPixel>
/// <summary>
/// Temporal 16x8 block to hold TPixel data
/// </summary>
private Span<TPixel> pixelSpan;
private readonly Span<TPixel> pixelSpan;

/// <summary>
/// Temporal RGB block
/// </summary>
private Span<Rgb24> rgbSpan;
private readonly Span<Rgb24> rgbSpan;

/// <summary>
/// Sampled pixel buffer size
/// </summary>
private Size samplingAreaSize;
private readonly Size samplingAreaSize;

/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private Configuration config;
private readonly Configuration config;

public YCbCrForwardConverter420(ImageFrame<TPixel> frame)
{
Expand Down
Loading

0 comments on commit 659daff

Please sign in to comment.