mirror of https://github.com/SixLabors/ImageSharp
11 changed files with 828 additions and 138 deletions
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue