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); // return decoder.Decode<TPixel>(stream);
// } // }
var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration); using (var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration))
return decoder.Decode<TPixel>(stream); {
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 namespace ImageSharp.Formats.Jpeg.Port.Components
{ {
using System;
/// <summary> /// <summary>
/// Represent a single jpeg frame /// Represent a single jpeg frame
/// </summary> /// </summary>
internal class Frame internal class Frame : IDisposable
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification /// Gets or sets a value indicating whether the frame uses the extended specification
/// </summary> /// </summary>
@ -48,7 +52,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary> /// <summary>
/// Gets or sets the frame component collection /// Gets or sets the frame component collection
/// </summary> /// </summary>
public Component[] Components { get; set; } public FrameComponent[] Components { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum horizontal sampling factor /// 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 /// Gets or sets the number of MCU's per column
/// </summary> /// </summary>
public int McusPerColumn { get; set; } 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. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats.Jpeg.Port.Components namespace ImageSharp.Formats.Jpeg.Port.Components
{ {
using System;
using ImageSharp.Memory;
/// <summary> /// <summary>
/// Represents a single color component /// Represents a single frame component
/// </summary> /// </summary>
internal struct Component internal struct FrameComponent : IDisposable
{ {
/// <summary> /// <summary>
/// Gets or sets the component Id /// Gets or sets the component Id
/// </summary> /// </summary>
public byte Id; public byte Id;
/// <summary>
/// TODO: What does pred stand for?
/// </summary>
public int Pred;
/// <summary> /// <summary>
/// Gets or sets the horizontal sampling factor. /// Gets or sets the horizontal sampling factor.
/// </summary> /// </summary>
@ -38,7 +47,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// <summary> /// <summary>
/// Gets or sets the block data /// Gets or sets the block data
/// </summary> /// </summary>
public short[] BlockData; public Buffer<short> BlockData;
/// <summary> /// <summary>
/// Gets or sets the number of blocks per line /// 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 /// Gets the index for the AC Huffman table
/// </summary> /// </summary>
public int ACHuffmanTableId; 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 namespace ImageSharp.Formats.Jpeg.Port.Components
{ {
using System.Collections.Generic; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Represents a branch in the huffman tree /// Represents a branch in the huffman tree
@ -23,32 +23,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
public short Value; public short Value;
/// <summary> /// <summary>
/// The children /// The children.
/// </summary> /// </summary>
public List<HuffmanBranch> Children; public HuffmanBranch[] Children;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanBranch"/> struct. /// Initializes a new instance of the <see cref="HuffmanBranch"/> struct.
/// </summary> /// </summary>
/// <param name="value">The value</param> /// <param name="value">The value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HuffmanBranch(short value) public HuffmanBranch(short value)
: this(value, new List<HuffmanBranch>())
{ {
this.Index = 0;
this.Value = value;
this.Children = new HuffmanBranch[2];
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanBranch"/> struct. /// Initializes a new instance of the <see cref="HuffmanBranch"/> struct.
/// </summary> /// </summary>
/// <param name="children">The branch children</param> /// <param name="children">The branch children</param>
public HuffmanBranch(List<HuffmanBranch> children) [MethodImpl(MethodImplOptions.AggressiveInlining)]
: this((short)0, children) public HuffmanBranch(HuffmanBranch[] children)
{
}
private HuffmanBranch(short value, List<HuffmanBranch> children)
{ {
this.Index = 0; this.Index = 0;
this.Value = value; this.Value = -1;
this.Children = children; this.Children = children;
} }
} }

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

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

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

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

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

@ -8,6 +8,7 @@ namespace ImageSharp.Formats.Jpeg.Port
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using ImageSharp.Common.Extensions; using ImageSharp.Common.Extensions;
using ImageSharp.Formats.Jpeg.Port.Components; using ImageSharp.Formats.Jpeg.Port.Components;
@ -18,7 +19,7 @@ namespace ImageSharp.Formats.Jpeg.Port
/// Performs the jpeg decoding operation. /// Performs the jpeg decoding operation.
/// Ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/> /// Ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// </summary> /// </summary>
internal class JpegDecoderCore internal class JpegDecoderCore : IDisposable
{ {
/// <summary> /// <summary>
/// The decoder options. /// The decoder options.
@ -48,10 +49,12 @@ namespace ImageSharp.Formats.Jpeg.Port
private ushort resetInterval; private ushort resetInterval;
/// <summary> /// <summary>
/// COntains information about the jFIF marker /// Contains information about the jFIF marker
/// </summary> /// </summary>
private JFif jFif; private JFif jFif;
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
@ -68,6 +71,109 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary> /// </summary>
public Stream InputStream { get; private set; } 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> /// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets /// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image. /// the data to image.
@ -85,27 +191,52 @@ namespace ImageSharp.Formats.Jpeg.Port
return image; 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() private void ParseStream()
{ {
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
ushort fileMarker = this.ReadUint16(); var fileMarker = new FileMarker(this.ReadUint16(), 0);
if (fileMarker != JpegConstants.Markers.SOI) if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
throw new ImageFormatException("Missing SOI marker."); 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.quantizationTables = new QuantizationTables();
this.dcHuffmanTables = new HuffmanTables(); this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new HuffmanTables(); this.acHuffmanTables = new HuffmanTables();
while (fileMarker != JpegConstants.Markers.EOI) while (fileMarker.Marker != JpegConstants.Markers.EOI)
{ {
// Get the marker length // Get the marker length
int remaining = this.ReadUint16() - 2; int remaining = this.ReadUint16() - 2;
switch (fileMarker) switch (fileMarker.Marker)
{ {
case JpegConstants.Markers.APP0: case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining); this.ProcessApplicationHeaderMarker(remaining);
@ -148,12 +279,12 @@ namespace ImageSharp.Formats.Jpeg.Port
break; break;
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
this.ProcessStartOfScan(); this.ProcessStartOfScanMarker();
break; break;
} }
// Read on // Read on
fileMarker = this.FindNextFileMarker(); fileMarker = FindNextFileMarker(this.InputStream);
} }
} }
@ -281,7 +412,7 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary> /// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</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) if (this.frame != null)
{ {
@ -292,8 +423,8 @@ namespace ImageSharp.Formats.Jpeg.Port
this.frame = new Frame this.frame = new Frame
{ {
Extended = frameMarker == JpegConstants.Markers.SOF1, Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker == JpegConstants.Markers.SOF2, Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
Precision = this.temp[0], Precision = this.temp[0],
Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), Scanlines = (short)((this.temp[1] << 8) | this.temp[2]),
SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]),
@ -304,9 +435,9 @@ namespace ImageSharp.Formats.Jpeg.Port
int maxV = 0; int maxV = 0;
int index = 6; 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.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++) for (int i = 0; i < this.frame.ComponentCount; i++)
{ {
@ -389,30 +520,36 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <summary> /// <summary>
/// Processes the SOS (Start of scan marker). /// Processes the SOS (Start of scan marker).
/// </summary> /// </summary>
private void ProcessStartOfScan() private void ProcessStartOfScanMarker()
{ {
int selectorsCount = this.InputStream.ReadByte(); int selectorsCount = this.InputStream.ReadByte();
var components = new List<Component>();
for (int i = 0; i < selectorsCount; i++) for (int i = 0; i < selectorsCount; i++)
{ {
byte componentIndex = this.frame.ComponentIds[this.InputStream.ReadByte() - 1]; 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(); int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4; component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15; component.ACHuffmanTableId = tableSpec & 15;
components.Add(component);
} }
this.InputStream.Read(this.temp, 0, 3); this.InputStream.Read(this.temp, 0, 3);
int spectralStart = this.temp[0]; int spectralStart = this.temp[0];
int spectralEnd = this.temp[1]; int spectralEnd = this.temp[1];
int successiveApproximation = this.temp[2]; int successiveApproximation = this.temp[2];
} var scanDecoder = default(ScanDecoder);
private int DecodeScan(List<Component> components, int spectralStart, int spectralEnd, int successivePrev, int successive) scanDecoder.DecodeScan(
{ this.frame,
return 0; this.InputStream,
this.dcHuffmanTables,
this.acHuffmanTables,
this.frame.Components,
this.resetInterval,
spectralStart,
spectralEnd,
successiveApproximation >> 4,
successiveApproximation & 15);
} }
/// <summary> /// <summary>
@ -430,8 +567,8 @@ namespace ImageSharp.Formats.Jpeg.Port
length--; length--;
} }
// TODO: Check the capacity here. Seems to max at 2 // TODO: Check the branch children capacity here. Seems to max at 2
var code = new List<HuffmanBranch> { new HuffmanBranch(new List<HuffmanBranch>()) }; var code = new List<HuffmanBranch> { new HuffmanBranch(-1) };
HuffmanBranch p = code[0]; HuffmanBranch p = code[0];
int k = 0; int k = 0;
@ -441,7 +578,7 @@ namespace ImageSharp.Formats.Jpeg.Port
for (int j = 0; j < codeLengths[i]; j++) for (int j = 0; j < codeLengths[i]; j++)
{ {
p = code.Pop(); p = code.Pop();
p.Children.SafeInsert(p.Index, new HuffmanBranch(values[k])); p.Children[p.Index] = new HuffmanBranch(values[k]);
while (p.Index > 0) while (p.Index > 0)
{ {
p = code.Pop(); p = code.Pop();
@ -451,9 +588,9 @@ namespace ImageSharp.Formats.Jpeg.Port
code.Add(p); code.Add(p);
while (code.Count <= i) while (code.Count <= i)
{ {
q = new HuffmanBranch(new List<HuffmanBranch>()); q = new HuffmanBranch(-1);
code.Add(q); code.Add(q);
p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children)); p.Children[p.Index] = new HuffmanBranch(q.Children);
p = q; p = q;
} }
@ -463,9 +600,9 @@ namespace ImageSharp.Formats.Jpeg.Port
if (i + 1 < length) if (i + 1 < length)
{ {
// p here points to last code // p here points to last code
q = new HuffmanBranch(new List<HuffmanBranch>()); q = new HuffmanBranch(-1);
code.Add(q); code.Add(q);
p.Children.SafeInsert(p.Index, new HuffmanBranch(q.Children)); p.Children[p.Index] = new HuffmanBranch(q.Children);
p = q; p = q;
} }
} }
@ -478,21 +615,21 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary> /// </summary>
private void PrepareComponents() private void PrepareComponents()
{ {
int mcusPerLine = this.frame.SamplesPerLine / 8 / this.frame.MaxHorizontalFactor; int mcusPerLine = (int)Math.Ceiling(this.frame.SamplesPerLine / 8D / this.frame.MaxHorizontalFactor);
int mcusPerColumn = this.frame.Scanlines / 8 / this.frame.MaxVerticalFactor; int mcusPerColumn = (int)Math.Ceiling(this.frame.Scanlines / 8D / this.frame.MaxVerticalFactor);
for (int i = 0; i < this.frame.ComponentCount; i++) for (int i = 0; i < this.frame.ComponentCount; i++)
{ {
ref var component = ref this.frame.Components[i]; ref var component = ref this.frame.Components[i];
int blocksPerLine = this.frame.SamplesPerLine / 8 * component.HorizontalFactor / this.frame.MaxHorizontalFactor; int blocksPerLine = (int)Math.Ceiling(Math.Ceiling(this.frame.SamplesPerLine / 8D) * component.HorizontalFactor / this.frame.MaxHorizontalFactor);
int blocksPerColumn = this.frame.Scanlines / 8 * component.VerticalFactor / this.frame.MaxVerticalFactor; int blocksPerColumn = (int)Math.Ceiling(Math.Ceiling(this.frame.Scanlines / 8D) * component.VerticalFactor / this.frame.MaxVerticalFactor);
int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor; int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor;
int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor; int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor;
int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
// TODO: Pool this // Pooled. Disposed via frame siposal
component.BlockData = new short[blocksBufferSize]; component.BlockData = new Buffer<short>(blocksBufferSize);
component.BlocksPerLine = blocksPerLine; component.BlocksPerLine = blocksPerLine;
component.BlocksPerColumn = blocksPerColumn; component.BlocksPerColumn = blocksPerColumn;
} }
@ -501,59 +638,11 @@ namespace ImageSharp.Formats.Jpeg.Port
this.frame.McusPerColumn = mcusPerColumn; 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> /// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes /// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// </summary> /// </summary>
/// <returns>The <see cref="ushort"/></returns> /// <returns>The <see cref="ushort"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ushort ReadUint16() private ushort ReadUint16()
{ {
this.InputStream.Read(this.uint16Buffer, 0, 2); 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) 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")] //[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public Size JpegSystemDrawing() //public Size JpegSystemDrawing()
{ //{
using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) // using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
{ // {
using (Image image = Image.FromStream(memoryStream)) // using (Image image = Image.FromStream(memoryStream))
{ // {
return image.Size; // return image.Size;
} // }
} // }
} //}
[Benchmark(Description = "ImageSharp Jpeg")] [Benchmark(Description = "ImageSharp Jpeg")]
public CoreSize JpegCore() public CoreSize JpegCore()

Loading…
Cancel
Save