Browse Source

Optimization, new jpeg metadata fields

pull/2120/head
Dmitry Pentin 4 years ago
parent
commit
656482d977
  1. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
  2. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  3. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  4. 132
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  5. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
  6. 9
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  7. 5
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  8. 3
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  9. 79
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  10. 21
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  11. 2
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs

@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.scanBuffer = new JpegBitReader(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
bool fullScan = this.frame.Progressive || !this.frame.Interleaved;
this.frame.AllocateComponents(fullScan);
if (this.frame.Progressive)

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

@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.scanBuffer = new JpegBitReader(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
bool fullScan = this.frame.Progressive || !this.frame.Interleaved;
this.frame.AllocateComponents(fullScan);
if (!this.frame.Progressive)

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10;
this.Precision = precision;
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets a value indicating whether the frame uses the extended specification.
/// </summary>
public bool Extended { get; private set; }
public bool IsExtended { get; private set; }
/// <summary>
/// Gets a value indicating whether the frame uses the progressive specification.
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <remarks>
/// This is true for progressive and baseline non-interleaved images.
/// </remarks>
public bool MultiScan { get; set; }
public bool Interleaved { get; set; }
/// <summary>
/// Gets the precision.

132
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -135,7 +135,65 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table);
}
public void EncodeScanBaselineInterleaved<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
public void EncodeScanBaselineInterleaved<TPixel>(JpegEncodingColor color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
switch (color)
{
case JpegEncodingColor.YCbCrRatio444:
case JpegEncodingColor.Rgb:
this.EncodeScanBaselineInterleaved444(frame, converter, cancellationToken);
break;
case JpegEncodingColor.YCbCrRatio420:
this.EncodeScanBaselineInterleaved420(frame, converter, cancellationToken);
break;
default:
this.EncodeScanBaselineInterleavedArbitrarySampling(frame, converter, cancellationToken);
break;
}
this.FlushRemainingBytes();
}
public void EncodeScanBaselineSingleComponent<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
JpegComponent component = frame.Components[0];
int mcuLines = frame.McusPerColumn;
int w = component.WidthInBlocks;
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int i = 0; i < mcuLines; i++)
{
cancellationToken.ThrowIfCancellationRequested();
// Convert from pixels to spectral via given converter
converter.ConvertStrideBaseline();
// Encode spectral to binary
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int k = 0; k < w; k++)
{
this.WriteBlock(
component,
ref Unsafe.Add(ref blockRef, k),
ref dcHuffmanTable,
ref acHuffmanTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
}
}
}
private void EncodeScanBaselineInterleavedArbitrarySampling<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
int mcu = 0;
@ -154,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
// Scan an interleaved mcu... process components in order
int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < frame.ComponentCount; k++)
for (int k = 0; k < frame.Components.Length; k++)
{
JpegComponent component = frame.Components[k];
@ -192,24 +250,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
}
this.FlushRemainingBytes();
}
public void EncodeScanBaselineSingleComponent<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
private void EncodeScanBaselineInterleaved444<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// DEBUG INITIALIZATION SETUP
frame.AllocateComponents(fullScan: false);
int mcusPerColumn = frame.McusPerColumn;
int mcusPerLine = frame.McusPerLine;
JpegComponent component = frame.Components[0];
int mcuLines = frame.McusPerColumn;
int w = component.WidthInBlocks;
int h = component.SamplingFactors.Height;
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
JpegComponent c2 = frame.Components[2];
JpegComponent c1 = frame.Components[1];
JpegComponent c0 = frame.Components[0];
for (int i = 0; i < mcuLines; i++)
ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId];
ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId];
ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId];
ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId];
ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId];
ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId];
ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0));
ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0));
ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0));
for (int j = 0; j < mcusPerColumn; j++)
{
cancellationToken.ThrowIfCancellationRequested();
@ -217,28 +281,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
converter.ConvertStrideBaseline();
// Encode spectral to binary
for (int j = 0; j < h; j++)
for (int i = 0; i < mcusPerLine; i++)
{
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
this.WriteBlock(
c0,
ref Unsafe.Add(ref c0BlockRef, i),
ref c0dcHuffmanTable,
ref c0acHuffmanTable);
this.WriteBlock(
c1,
ref Unsafe.Add(ref c1BlockRef, i),
ref c1dcHuffmanTable,
ref c1acHuffmanTable);
this.WriteBlock(
c2,
ref Unsafe.Add(ref c2BlockRef, i),
ref c2dcHuffmanTable,
ref c2acHuffmanTable);
for (int k = 0; k < w; k++)
if (this.IsStreamFlushNeeded)
{
this.WriteBlock(
component,
ref Unsafe.Add(ref blockRef, k),
ref dcHuffmanTable,
ref acHuffmanTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
this.FlushToStream();
}
}
}
}
private void EncodeScanBaselineInterleaved420<TPixel>(JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
}
private void WriteBlock(
JpegComponent component,
ref Block8x8 block,

6
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++)
for (int i = 0; i < this.Components.Length; i++)
{
JpegComponent component = this.Components[i];
component.Init(this, maxSubFactorH, maxSubFactorV);
@ -50,8 +50,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public int PixelWidth { get; private set; }
public int ComponentCount => this.Components.Length;
public JpegComponent[] Components { get; }
public int McusPerLine { get; }
@ -70,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public void AllocateComponents(bool fullScan)
{
for (int i = 0; i < this.ComponentCount; i++)
for (int i = 0; i < this.Components.Length; i++)
{
JpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);

9
src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

@ -14,5 +14,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Defaults to <value>75</value>.
/// </summary>
public int? Quality { get; set; }
/// <summary>
/// Gets or sets the component encoding mode.
/// </summary>
/// <remarks>
/// Interleaved encoding mode encodes all color components in a single scan.
/// Non-interleaved encoding mode encodes each color component in a separate scan.
/// </remarks>
public bool? Interleaved { get; set; }
}
}

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

@ -488,6 +488,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, stream);
}
this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved;
}
/// <inheritdoc/>
@ -1178,6 +1180,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive;
remaining -= length;
@ -1386,7 +1389,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// selectorsCount*2 bytes: component index + huffman tables indices
stream.Read(this.temp, 0, selectorsBytes);
this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount;
this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount;
for (int i = 0; i < selectorsBytes; i += 2)
{
// 1 byte: Component id

3
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -24,6 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public int? Quality { get; set; }
/// <inheritdoc/>
public bool? Interleaved { get; set; }
/// <summary>
/// Sets jpeg color for encoding.
/// </summary>

79
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -38,9 +38,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private readonly int? quality;
/// <summary>
/// Gets or sets the colorspace to use.
/// </summary>
private readonly bool? interleaved;
private JpegEncodingColor? colorType;
private JpegFrameConfig frameConfig;
@ -60,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig)
{
this.quality = options.Quality;
this.interleaved = options.Interleaved;
this.frameConfig = frameConfig;
this.colorType = frameConfig.EncodingColor;
@ -121,20 +121,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the quantization tables.
this.WriteDefineQuantizationTables(this.frameConfig.QuantizationTables, jpegMetadata);
// Write the scan header.
this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components);
var spectralConverter = new SpectralConverter<TPixel>(frame, image, this.QuantizationTables, Configuration.Default);
// TODO: change this for non-interleaved scans
frame.AllocateComponents(fullScan: false);
if (frame.ComponentCount > 1)
if (frame.Components.Length == 1)
{
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(this.frameConfig.Components);
this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken);
}
else if (this.interleaved ?? jpegMetadata.Interleaved ?? true)
{
this.scanEncoder.EncodeScanBaselineInterleaved(frame, spectralConverter, cancellationToken);
frame.AllocateComponents(fullScan: false);
this.WriteStartOfScan(this.frameConfig.Components);
this.scanEncoder.EncodeScanBaselineInterleaved(this.frameConfig.EncodingColor, frame, spectralConverter, cancellationToken);
}
else
{
this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken);
throw new NotImplementedException();
}
// Write the End Of Image marker.
@ -143,50 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Flush();
}
/// <summary>
/// If color type was not set, set it based on the given image.
/// Note, if there is no metadata and the image has multiple components this method
/// returns <see langword="null"/> defering the field assignment
/// to <see cref="WriteDefineQuantizationTables"/>.
/// </summary>
private static JpegEncodingColor? GetFallbackColorType<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// First inspect the image metadata.
JpegEncodingColor? colorType = null;
JpegMetadata metadata = image.Metadata.GetJpegMetadata();
if (IsSupportedColorType(metadata.ColorType))
{
return metadata.ColorType;
}
// Secondly, inspect the pixel type.
// TODO: PixelTypeInfo should contain a component count!
bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
// We don't set multi-component color types here since we can set it based upon
// the quality in InitQuantizationTables.
if (isGrayscale)
{
colorType = JpegEncodingColor.Luminance;
}
return colorType;
}
/// <summary>
/// Returns true, if the color type is supported by the encoder.
/// </summary>
/// <param name="colorType">The color type.</param>
/// <returns>true, if color type is supported.</returns>
private static bool IsSupportedColorType(JpegEncodingColor? colorType)
=> colorType == JpegEncodingColor.YCbCrRatio444
|| colorType == JpegEncodingColor.YCbCrRatio420
|| colorType == JpegEncodingColor.Luminance
|| colorType == JpegEncodingColor.Rgb;
/// <summary>
/// Write the start of image marker.
/// </summary>
@ -611,7 +572,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the StartOfScan marker.
/// </summary>
private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components)
private void WriteStartOfScan(Span<JpegComponentConfig> components)
{
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c",
@ -626,13 +587,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[1] = JpegConstants.Markers.SOS;
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
int sosSize = 6 + (2 * componentCount);
int sosSize = 6 + (2 * components.Length);
this.buffer[2] = 0x00;
this.buffer[3] = (byte)sosSize;
this.buffer[4] = (byte)componentCount; // Number of components in a scan
this.buffer[4] = (byte)components.Length; // Number of components in a scan
// Components data
for (int i = 0; i < componentCount; i++)
for (int i = 0; i < components.Length; i++)
{
int i2 = 2 * i;

21
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -99,9 +99,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Gets or sets the color type.
/// Gets the color type.
/// </summary>
public JpegEncodingColor? ColorType { get; set; }
public JpegEncodingColor? ColorType { get; internal set; }
/// <summary>
/// Gets the component encoding mode.
/// </summary>
/// <remarks>
/// Interleaved encoding mode encodes all color components in a single scan.
/// Non-interleaved encoding mode encodes each color component in a separate scan.
/// </remarks>
public bool? Interleaved { get; internal set; }
/// <summary>
/// Gets the scan encoding mode.
/// </summary>
/// <remarks>
/// Progressive jpeg images encode component data across multiple scans.
/// </remarks>
public bool? Progressive { get; internal set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetadata(this);

2
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
// Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing
// Progressive and multi-scan images must be loaded manually
if (this.frame.Progressive || this.frame.MultiScan)
if (this.frame.Progressive || !this.frame.Interleaved)
{
LibJpegTools.ComponentData[] components = this.spectralData.Components;
for (int i = 0; i < components.Length; i++)

Loading…
Cancel
Save