Browse Source

Can now decode a scan

af/merge-core
James Jackson-South 9 years ago
parent
commit
8b699bf1d4
  1. 6
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  2. 53
      src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs
  3. 41
      src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs
  4. 23
      src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs
  5. 21
      src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs
  6. 6
      src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs
  7. 42
      src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs
  8. 471
      src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs
  9. 18
      src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs
  10. 261
      src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
  11. 24
      tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs

6
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -25,8 +25,10 @@ namespace ImageSharp.Formats
// {
// return decoder.Decode<TPixel>(stream);
// }
var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration);
return decoder.Decode<TPixel>(stream);
using (var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration))
{
return decoder.Decode<TPixel>(stream);
}
}
}
}

53
src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs

@ -0,0 +1,53 @@
// <copyright file="HuffmanTables.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpeg.Port.Components
{
/// <summary>
/// Represents a jpeg file marker
/// </summary>
internal struct FileMarker
{
/// <summary>
/// Initializes a new instance of the <see cref="FileMarker"/> struct.
/// </summary>
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
public FileMarker(ushort marker, long position)
{
this.Marker = marker;
this.Position = position;
this.Invalid = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="FileMarker"/> struct.
/// </summary>
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
/// <param name="invalid">Whether the current marker is invalid</param>
public FileMarker(ushort marker, long position, bool invalid)
{
this.Marker = marker;
this.Position = position;
this.Invalid = invalid;
}
/// <summary>
/// Gets or sets a value indicating whether the current marker is invalid
/// </summary>
public bool Invalid { get; set; }
/// <summary>
/// Gets the position of the marker within a stream
/// </summary>
public ushort Marker { get; }
/// <summary>
/// Gets the position of the marker within a stream
/// </summary>
public long Position { get; }
}
}

41
src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs

@ -5,11 +5,15 @@
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System;
/// <summary>
/// Represent a single jpeg frame
/// </summary>
internal class Frame
internal class Frame : IDisposable
{
private bool isDisposed;
/// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification
/// </summary>
@ -48,7 +52,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary>
/// Gets or sets the frame component collection
/// </summary>
public Component[] Components { get; set; }
public FrameComponent[] Components { get; set; }
/// <summary>
/// Gets or sets the maximum horizontal sampling factor
@ -69,5 +73,36 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// Gets or sets the number of MCU's per column
/// </summary>
public int McusPerColumn { get; set; }
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">Whether to dispose of managed objects</param>
protected virtual void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
foreach (FrameComponent component in this.Components)
{
component.Dispose();
}
}
// Set large fields to null.
this.Components = null;
this.isDisposed = true;
}
}
}
}

23
src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs → src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs

@ -1,20 +1,29 @@
// <copyright file="Component.cs" company="James Jackson-South">
// <copyright file="FrameComponent.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System;
using ImageSharp.Memory;
/// <summary>
/// Represents a single color component
/// Represents a single frame component
/// </summary>
internal struct Component
internal struct FrameComponent : IDisposable
{
/// <summary>
/// Gets or sets the component Id
/// </summary>
public byte Id;
/// <summary>
/// TODO: What does pred stand for?
/// </summary>
public int Pred;
/// <summary>
/// Gets or sets the horizontal sampling factor.
/// </summary>
@ -38,7 +47,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary>
/// Gets or sets the block data
/// </summary>
public short[] BlockData;
public Buffer<short> BlockData;
/// <summary>
/// Gets or sets the number of blocks per line
@ -59,5 +68,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// Gets the index for the AC Huffman table
/// </summary>
public int ACHuffmanTableId;
/// <inheritdoc/>
public void Dispose()
{
this.BlockData?.Dispose();
}
}
}

21
src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanBranch.cs

@ -5,7 +5,7 @@
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System.Collections.Generic;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents a branch in the huffman tree
@ -23,32 +23,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
public short Value;
/// <summary>
/// The children
/// The children.
/// </summary>
public List<HuffmanBranch> Children;
public HuffmanBranch[] Children;
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanBranch"/> struct.
/// </summary>
/// <param name="value">The value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HuffmanBranch(short value)
: this(value, new List<HuffmanBranch>())
{
this.Index = 0;
this.Value = value;
this.Children = new HuffmanBranch[2];
}
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanBranch"/> struct.
/// </summary>
/// <param name="children">The branch children</param>
public HuffmanBranch(List<HuffmanBranch> children)
: this((short)0, children)
{
}
private HuffmanBranch(short value, List<HuffmanBranch> children)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HuffmanBranch(HuffmanBranch[] children)
{
this.Index = 0;
this.Value = value;
this.Value = -1;
this.Children = children;
}
}

6
src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs

@ -13,16 +13,16 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// </summary>
internal class HuffmanTables
{
private List<HuffmanBranch> first = new List<HuffmanBranch>();
private HuffmanBranch[] first;
private List<HuffmanBranch> second = new List<HuffmanBranch>();
private HuffmanBranch[] second;
/// <summary>
/// Gets or sets the table at the given index.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The <see cref="List{HuffmanBranch}"/></returns>
public List<HuffmanBranch> this[int index]
public HuffmanBranch[] this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get

42
src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System.Runtime.CompilerServices;
using ImageSharp.Memory;
/// <summary>
@ -16,24 +18,30 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary>
/// Gets the ZigZag scan table
/// </summary>
public static byte[] DctZigZag { get; } =
public static byte[] DctZigZag
{
0,
1, 8,
16, 9, 2,
3, 10, 17, 24,
32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40,
48, 41, 34, 27, 20, 13, 6,
7, 14, 21, 28, 35, 42, 49, 56,
57, 50, 43, 36, 29, 22, 15,
23, 30, 37, 44, 51, 58,
59, 52, 45, 38, 31,
39, 46, 53, 60,
61, 54, 47,
55, 62,
63
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
=
{
0,
1, 8,
16, 9, 2,
3, 10, 17, 24,
32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40,
48, 41, 34, 27, 20, 13, 6,
7, 14, 21, 28, 35, 42, 49, 56,
57, 50, 43, 36, 29, 22, 15,
23, 30, 37, 44, 51, 58,
59, 52, 45, 38, 31,
39, 46, 53, 60,
61, 54, 47,
55, 62,
63
};
/// <summary>
/// Gets or sets the quantization tables.

471
src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs

@ -0,0 +1,471 @@
// <copyright file="ScanDecoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System;
#if DEBUG
using System.Diagnostics;
#endif
using System.IO;
using System.Runtime.CompilerServices;
/// <summary>
/// Encapsulates a decode method. TODO: This may well be a bottleneck
/// </summary>
/// <param name="component">The component</param>
/// <param name="offset">The offset</param>
/// <param name="dcHuffmanTables">The DC Huffman tables</param>
/// <param name="acHuffmanTables">The AC Huffman tables</param>
/// <param name="stream">The input stream</param>
internal delegate void DecodeAction(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream);
/// <summary>
/// Provides the means to decode a spectral scan
/// </summary>
internal struct ScanDecoder
{
private int bitsData;
private int bitsCount;
private int specStart;
private int specEnd;
private int eobrun;
private int successiveState;
private int successiveACState;
/// <summary>
/// Decodes the spectral scan
/// </summary>
/// <param name="frame">The image frame</param>
/// <param name="stream">The input stream</param>
/// <param name="dcHuffmanTables">The DC Huffman tables</param>
/// <param name="acHuffmanTables">The AC Huffman tables</param>
/// <param name="components">The scan components</param>
/// <param name="resetInterval">The reset interval</param>
/// <param name="spectralStart">The spectral selection start</param>
/// <param name="spectralEnd">The spectral selection end</param>
/// <param name="successivePrev">The successive approximation bit high end</param>
/// <param name="successive">The successive approximation bit low end</param>
public void DecodeScan(Frame frame, Stream stream, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, FrameComponent[] components, ushort resetInterval, int spectralStart, int spectralEnd, int successivePrev, int successive)
{
this.specStart = spectralStart;
this.specEnd = spectralEnd;
this.successiveState = successive;
bool progressive = frame.Progressive;
int componentsLength = components.Length;
int mcusPerLine = frame.McusPerLine;
// TODO: Delegate action will not be fast
DecodeAction decodeFn;
if (progressive)
{
if (this.specStart == 0)
{
if (successivePrev == 0)
{
decodeFn = this.DecodeDCFirst;
}
else
{
decodeFn = this.DecodeDCSuccessive;
}
}
else
{
if (successivePrev == 0)
{
decodeFn = this.DecodeACFirst;
}
else
{
decodeFn = this.DecodeACSuccessive;
}
}
}
else
{
decodeFn = this.DecodeBaseline;
}
int mcu = 0;
int mcuExpected;
if (componentsLength == 1)
{
mcuExpected = components[0].BlocksPerLine * components[0].BlocksPerColumn;
}
else
{
mcuExpected = mcusPerLine * frame.McusPerColumn;
}
FileMarker fileMarker;
while (mcu < mcuExpected)
{
// Reset interval stuff
int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected;
for (int i = 0; i < componentsLength; i++)
{
ref FrameComponent c = ref components[i];
c.Pred = 0;
}
this.eobrun = 0;
if (componentsLength == 1)
{
ref FrameComponent component = ref components[0];
for (int n = 0; n < mcuToRead; n++)
{
DecodeBlock(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcu, stream);
mcu++;
}
}
else
{
for (int n = 0; n < mcuToRead; n++)
{
for (int i = 0; i < componentsLength; i++)
{
ref FrameComponent component = ref components[i];
int h = component.HorizontalFactor;
int v = component.VerticalFactor;
for (int j = 0; j < v; j++)
{
for (int k = 0; k < h; k++)
{
DecodeMcu(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcusPerLine, mcu, j, k, stream);
}
}
}
mcu++;
}
}
// Find marker
this.bitsCount = 0;
// TODO: We need to make sure we are not overwriting anything here.
fileMarker = JpegDecoderCore.FindNextFileMarker(stream);
// Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past
// those to attempt to find a valid marker (fixes issue4090.pdf) in original code.
if (fileMarker.Invalid)
{
#if DEBUG
Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X"));
#endif
}
ushort marker = fileMarker.Marker;
if (marker <= 0xFF00)
{
throw new ImageFormatException("Marker was not found");
}
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{
// RSTx
stream.Skip(2);
}
else
{
break;
}
}
fileMarker = JpegDecoderCore.FindNextFileMarker(stream);
// Some images include more Scan blocks than expected, skip past those and
// attempt to find the next valid marker (fixes issue8182.pdf) in original code.
if (fileMarker.Invalid)
{
#if DEBUG
Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X"));
#endif
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetBlockBufferOffset(FrameComponent component, int row, int col)
{
return 64 * (((component.BlocksPerLine + 1) * row) + col);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DecodeMcu(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcusPerLine, int mcu, int row, int col, Stream stream)
{
int mcuRow = (mcu / mcusPerLine) | 0;
int mcuCol = mcu % mcusPerLine;
int blockRow = (mcuRow * component.VerticalFactor) + row;
int blockCol = (mcuCol * component.HorizontalFactor) + col;
int offset = GetBlockBufferOffset(component, blockRow, blockCol);
decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DecodeBlock(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcu, Stream stream)
{
int blockRow = (mcu / component.BlocksPerLine) | 0;
int blockCol = mcu % component.BlocksPerLine;
int offset = GetBlockBufferOffset(component, blockRow, blockCol);
decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadBit(Stream stream)
{
if (this.bitsCount > 0)
{
this.bitsCount--;
return (this.bitsData >> this.bitsCount) & 1;
}
this.bitsData = stream.ReadByte();
if (this.bitsData == 0xFF)
{
int nextByte = stream.ReadByte();
if (nextByte > 0)
{
throw new ImageFormatException($"Unexpected marker {(this.bitsData << 8) | nextByte}");
}
// Unstuff 0
}
this.bitsCount = 7;
return this.bitsData >> 7;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private short DecodeHuffman(HuffmanBranch[] tree, Stream stream)
{
HuffmanBranch[] node = tree;
while (true)
{
int index;
index = this.ReadBit(stream);
HuffmanBranch branch = node[index];
node = branch.Children;
if (branch.Value > -1)
{
return branch.Value;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int Receive(int length, Stream stream)
{
int n = 0;
while (length > 0)
{
n = (n << 1) | this.ReadBit(stream);
length--;
}
return n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReceiveAndExtend(int length, Stream stream)
{
if (length == 1)
{
return this.ReadBit(stream) == 1 ? 1 : -1;
}
int n = this.Receive(length, stream);
if (n >= 1 << (length - 1))
{
return n;
}
return n + (-1 << length) + 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeBaseline(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream)
{
int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream);
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream);
component.BlockData[offset] = (short)(component.Pred += diff);
int k = 1;
while (k < 64)
{
int rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream);
int s = rs & 15;
int r = rs >> 4;
if (s == 0)
{
if (r < 15)
{
break;
}
k += 16;
continue;
}
k += r;
byte z = QuantizationTables.DctZigZag[k];
short re = (short)this.ReceiveAndExtend(s, stream);
component.BlockData[offset + z] = re;
k++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream)
{
int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream);
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState;
component.BlockData[offset] = (short)(component.Pred += diff);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeDCSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream)
{
component.BlockData[offset] |= (short)(this.ReadBit(stream) << this.successiveState);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream)
{
if (this.eobrun > 0)
{
this.eobrun--;
return;
}
int k = this.specStart;
int e = this.specEnd;
while (k <= e)
{
short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream);
int s = rs & 15;
int r = rs >> 4;
if (s == 0)
{
if (r < 15)
{
this.eobrun = this.Receive(r, stream) + (1 << r) - 1;
break;
}
k += 16;
continue;
}
k += r;
byte z = QuantizationTables.DctZigZag[k];
component.BlockData[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState));
k++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream)
{
int k = this.specStart;
int e = this.specEnd;
int r = 0;
while (k <= e)
{
byte z = QuantizationTables.DctZigZag[k];
int successiveACNextValue = 0;
switch (this.successiveACState)
{
case 0: // Initial state
short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream);
int s = rs & 15;
r = rs >> 4;
if (s == 0)
{
if (r < 15)
{
this.eobrun = this.Receive(r, stream) + (1 << r);
this.successiveACState = 4;
}
else
{
r = 16;
this.successiveACState = 1;
}
}
else
{
if (s != 1)
{
throw new ImageFormatException("Invalid ACn encoding");
}
successiveACNextValue = this.ReceiveAndExtend(s, stream);
this.successiveACState = r > 0 ? 2 : 3;
}
continue;
case 1: // Skipping r zero items
case 2:
if (component.BlockData[offset + z] > 0)
{
component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState);
}
else
{
r--;
if (r == 0)
{
this.successiveACState = this.successiveACState == 2 ? 3 : 0;
}
}
break;
case 3: // Set value for a zero item
if (component.BlockData[offset + z] > 0)
{
component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState);
}
else
{
component.BlockData[offset + z] = (short)(successiveACNextValue << this.successiveState);
this.successiveACState = 0;
}
break;
case 4: // Eob
if (component.BlockData[offset + z] > 0)
{
component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState);
}
break;
}
k++;
}
if (this.successiveACState == 4)
{
this.eobrun--;
if (this.eobrun == 0)
{
this.successiveACState = 0;
}
}
}
}
}

18
src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs

@ -173,6 +173,24 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary>
public const ushort SOS = 0xFFDA;
/// <summary>
/// Define First Restart
/// <remarks>
/// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks>
/// </summary>
public const ushort RST0 = 0xFFD0;
/// <summary>
/// Define Eigth Restart
/// <remarks>
/// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks>
/// </summary>
public const ushort RST7 = 0xFFD7;
/// <summary>
/// Contains JFIF specific markers
/// </summary>

261
src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs

@ -8,6 +8,7 @@ namespace ImageSharp.Formats.Jpeg.Port
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using ImageSharp.Common.Extensions;
using ImageSharp.Formats.Jpeg.Port.Components;
@ -18,7 +19,7 @@ namespace ImageSharp.Formats.Jpeg.Port
/// Performs the jpeg decoding operation.
/// Ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// </summary>
internal class JpegDecoderCore
internal class JpegDecoderCore : IDisposable
{
/// <summary>
/// The decoder options.
@ -48,10 +49,12 @@ namespace ImageSharp.Formats.Jpeg.Port
private ushort resetInterval;
/// <summary>
/// COntains information about the jFIF marker
/// Contains information about the jFIF marker
/// </summary>
private JFif jFif;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
@ -68,6 +71,109 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary>
public Stream InputStream { get; private set; }
/// <summary>
/// Finds the next file marker within the byte stream. Not used but I'm keeping it for now for testing
/// </summary>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="FileMarker"/></returns>
public static FileMarker FindNextFileMarkerOld(Stream stream)
{
byte[] buffer = new byte[2];
while (true)
{
int value = stream.Read(buffer, 0, 2);
if (value == 0)
{
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true);
}
while (buffer[0] != JpegConstants.Markers.Prefix)
{
// Strictly speaking, this is a format error. However, libjpeg is
// liberal in what it accepts. As of version 9, next_marker in
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// continues to decode the stream. Even before next_marker sees
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// bytes as it can, possibly past the end of a scan's data. It
// effectively puts back any markers that it overscanned (e.g. an
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
// and thus it can silently ignore a small number of extraneous
// non-marker bytes before next_marker has a chance to see them (and
// print a warning).
// We are therefore also liberal in what we accept. Extraneous data
// is silently ignore
// This is similar to, but not exactly the same as, the restart
// mechanism within a scan (the RST[0-7] markers).
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
// "\xff\x00", and so are detected a little further down below.
buffer[0] = buffer[1];
value = stream.ReadByte();
if (value == -1)
{
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true);
}
buffer[1] = (byte)value;
}
return new FileMarker((ushort)((buffer[0] << 8) | buffer[1]), (int)(stream.Position - 2));
}
}
/// <summary>
/// Finds the next file marker within the byte stream
/// </summary>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="FileMarker"/></returns>
public static FileMarker FindNextFileMarker(Stream stream)
{
byte[] buffer = new byte[2];
long maxPos = stream.Length - 1;
long currentPos = stream.Position;
long newPos = currentPos;
if (currentPos >= maxPos)
{
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true);
}
int value = stream.Read(buffer, 0, 2);
if (value == 0)
{
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true);
}
ushort currentMarker = (ushort)((buffer[0] << 8) | buffer[1]);
if (currentMarker >= JpegConstants.Markers.SOF0 && currentMarker <= JpegConstants.Markers.COM)
{
return new FileMarker(currentMarker, stream.Position - 2);
}
value = stream.Read(buffer, 0, 2);
if (value == 0)
{
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true);
}
ushort newMarker = (ushort)((buffer[0] << 8) | buffer[1]);
while (!(newMarker >= JpegConstants.Markers.SOF0 && newMarker <= JpegConstants.Markers.COM))
{
if (++newPos >= maxPos)
{
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true);
}
stream.Read(buffer, 0, 2);
newMarker = (ushort)((buffer[0] << 8) | buffer[1]);
}
return new FileMarker(newMarker, newPos, true);
}
/// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image.
@ -85,27 +191,52 @@ namespace ImageSharp.Formats.Jpeg.Port
return image;
}
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">Whether to dispose of managed objects</param>
protected virtual void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this.frame.Dispose();
}
// TODO: set large fields to null.
this.isDisposed = true;
}
}
private void ParseStream()
{
// Check for the Start Of Image marker.
ushort fileMarker = this.ReadUint16();
if (fileMarker != JpegConstants.Markers.SOI)
var fileMarker = new FileMarker(this.ReadUint16(), 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
fileMarker = this.ReadUint16();
ushort marker = this.ReadUint16();
fileMarker = new FileMarker(marker, (int)this.InputStream.Position - 2);
this.quantizationTables = new QuantizationTables();
this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new HuffmanTables();
while (fileMarker != JpegConstants.Markers.EOI)
while (fileMarker.Marker != JpegConstants.Markers.EOI)
{
// Get the marker length
int remaining = this.ReadUint16() - 2;
switch (fileMarker)
switch (fileMarker.Marker)
{
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining);
@ -148,12 +279,12 @@ namespace ImageSharp.Formats.Jpeg.Port
break;
case JpegConstants.Markers.SOS:
this.ProcessStartOfScan();
this.ProcessStartOfScanMarker();
break;
}
// Read on
fileMarker = this.FindNextFileMarker();
fileMarker = FindNextFileMarker(this.InputStream);
}
}
@ -281,7 +412,7 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
private void ProcessStartOfFrameMarker(int remaining, ushort frameMarker)
private void ProcessStartOfFrameMarker(int remaining, FileMarker frameMarker)
{
if (this.frame != null)
{
@ -292,8 +423,8 @@ namespace ImageSharp.Formats.Jpeg.Port
this.frame = new Frame
{
Extended = frameMarker == JpegConstants.Markers.SOF1,
Progressive = frameMarker == JpegConstants.Markers.SOF2,
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
Precision = this.temp[0],
Scanlines = (short)((this.temp[1] << 8) | this.temp[2]),
SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]),
@ -304,9 +435,9 @@ namespace ImageSharp.Formats.Jpeg.Port
int maxV = 0;
int index = 6;
// TODO: Pool this.
// No need to pool this. They max out at 4
this.frame.ComponentIds = new byte[this.frame.ComponentCount];
this.frame.Components = new Component[this.frame.ComponentCount];
this.frame.Components = new FrameComponent[this.frame.ComponentCount];
for (int i = 0; i < this.frame.ComponentCount; i++)
{
@ -389,30 +520,36 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
private void ProcessStartOfScan()
private void ProcessStartOfScanMarker()
{
int selectorsCount = this.InputStream.ReadByte();
var components = new List<Component>();
for (int i = 0; i < selectorsCount; i++)
{
byte componentIndex = this.frame.ComponentIds[this.InputStream.ReadByte() - 1];
Component component = this.frame.Components[componentIndex];
ref FrameComponent component = ref this.frame.Components[componentIndex];
int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
components.Add(component);
}
this.InputStream.Read(this.temp, 0, 3);
int spectralStart = this.temp[0];
int spectralEnd = this.temp[1];
int successiveApproximation = this.temp[2];
}
private int DecodeScan(List<Component> components, int spectralStart, int spectralEnd, int successivePrev, int successive)
{
return 0;
var scanDecoder = default(ScanDecoder);
scanDecoder.DecodeScan(
this.frame,
this.InputStream,
this.dcHuffmanTables,
this.acHuffmanTables,
this.frame.Components,
this.resetInterval,
spectralStart,
spectralEnd,
successiveApproximation >> 4,
successiveApproximation & 15);
}
/// <summary>
@ -430,8 +567,8 @@ namespace ImageSharp.Formats.Jpeg.Port
length--;
}
// TODO: Check the capacity here. Seems to max at 2
var code = new List<HuffmanBranch> { new HuffmanBranch(new List<HuffmanBranch>()) };
// TODO: Check the branch children capacity here. Seems to max at 2
var code = new List<HuffmanBranch> { new HuffmanBranch(-1) };
HuffmanBranch p = code[0];
int k = 0;
@ -441,7 +578,7 @@ namespace ImageSharp.Formats.Jpeg.Port
for (int j = 0; j < codeLengths[i]; j++)
{
p = code.Pop();
p.Children.SafeInsert(p.Index, new HuffmanBranch(values[k]));
p.Children[p.Index] = new HuffmanBranch(values[k]);
while (p.Index > 0)
{
p = code.Pop();
@ -451,9 +588,9 @@ namespace ImageSharp.Formats.Jpeg.Port
code.Add(p);
while (code.Count <= i)
{
q = new HuffmanBranch(new List<HuffmanBranch>());
q = new HuffmanBranch(-1);
code.Add(q);
p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children));
p.Children[p.Index] = new HuffmanBranch(q.Children);
p = q;
}
@ -463,9 +600,9 @@ namespace ImageSharp.Formats.Jpeg.Port
if (i + 1 < length)
{
// p here points to last code
q = new HuffmanBranch(new List<HuffmanBranch>());
q = new HuffmanBranch(-1);
code.Add(q);
p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children));
p.Children[p.Index] = new HuffmanBranch(q.Children);
p = q;
}
}
@ -478,21 +615,21 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary>
private void PrepareComponents()
{
int mcusPerLine = this.frame.SamplesPerLine / 8 / this.frame.MaxHorizontalFactor;
int mcusPerColumn = this.frame.Scanlines / 8 / this.frame.MaxVerticalFactor;
int mcusPerLine = (int)Math.Ceiling(this.frame.SamplesPerLine / 8D / this.frame.MaxHorizontalFactor);
int mcusPerColumn = (int)Math.Ceiling(this.frame.Scanlines / 8D / this.frame.MaxVerticalFactor);
for (int i = 0; i < this.frame.ComponentCount; i++)
{
ref var component = ref this.frame.Components[i];
int blocksPerLine = this.frame.SamplesPerLine / 8 * component.HorizontalFactor / this.frame.MaxHorizontalFactor;
int blocksPerColumn = this.frame.Scanlines / 8 * component.VerticalFactor / this.frame.MaxVerticalFactor;
int blocksPerLine = (int)Math.Ceiling(Math.Ceiling(this.frame.SamplesPerLine / 8D) * component.HorizontalFactor / this.frame.MaxHorizontalFactor);
int blocksPerColumn = (int)Math.Ceiling(Math.Ceiling(this.frame.Scanlines / 8D) * component.VerticalFactor / this.frame.MaxVerticalFactor);
int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor;
int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor;
int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
// TODO: Pool this
component.BlockData = new short[blocksBufferSize];
// Pooled. Disposed via frame siposal
component.BlockData = new Buffer<short>(blocksBufferSize);
component.BlocksPerLine = blocksPerLine;
component.BlocksPerColumn = blocksPerColumn;
}
@ -501,59 +638,11 @@ namespace ImageSharp.Formats.Jpeg.Port
this.frame.McusPerColumn = mcusPerColumn;
}
/// <summary>
/// Finds the next file marker within the byte stream
/// </summary>
/// <returns>The <see cref="ushort"/></returns>
private ushort FindNextFileMarker()
{
while (true)
{
int value = this.InputStream.Read(this.uint16Buffer, 0, 2);
if (value == 0)
{
return JpegConstants.Markers.EOI;
}
while (this.uint16Buffer[0] != JpegConstants.Markers.Prefix)
{
// Strictly speaking, this is a format error. However, libjpeg is
// liberal in what it accepts. As of version 9, next_marker in
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// continues to decode the stream. Even before next_marker sees
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// bytes as it can, possibly past the end of a scan's data. It
// effectively puts back any markers that it overscanned (e.g. an
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
// and thus it can silently ignore a small number of extraneous
// non-marker bytes before next_marker has a chance to see them (and
// print a warning).
// We are therefore also liberal in what we accept. Extraneous data
// is silently ignore
// This is similar to, but not exactly the same as, the restart
// mechanism within a scan (the RST[0-7] markers).
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
// "\xff\x00", and so are detected a little further down below.
this.uint16Buffer[0] = this.uint16Buffer[1];
value = this.InputStream.ReadByte();
if (value == -1)
{
return JpegConstants.Markers.EOI;
}
this.uint16Buffer[1] = (byte)value;
}
return (ushort)((this.uint16Buffer[0] << 8) | this.uint16Buffer[1]);
}
}
/// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// </summary>
/// <returns>The <see cref="ushort"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ushort ReadUint16()
{
this.InputStream.Read(this.uint16Buffer, 0, 2);

24
tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs

@ -23,21 +23,21 @@ namespace ImageSharp.Benchmarks.Image
{
if (this.jpegBytes == null)
{
this.jpegBytes = File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Jpg/Baseline/Calliphora.jpg");
this.jpegBytes = File.ReadAllBytes("../../../../../../../../ImageSharp.Tests/TestImages/Formats/Jpg/Baseline/Calliphora.jpg");
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public Size JpegSystemDrawing()
{
using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
{
using (Image image = Image.FromStream(memoryStream))
{
return image.Size;
}
}
}
//[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
//public Size JpegSystemDrawing()
//{
// using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
// {
// using (Image image = Image.FromStream(memoryStream))
// {
// return image.Size;
// }
// }
//}
[Benchmark(Description = "ImageSharp Jpeg")]
public CoreSize JpegCore()

Loading…
Cancel
Save