|
|
|
@ -4,6 +4,7 @@ |
|
|
|
using System; |
|
|
|
using System.Buffers; |
|
|
|
using System.Buffers.Binary; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.IO; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
@ -97,7 +98,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// <summary>
|
|
|
|
/// Scan decoder.
|
|
|
|
/// </summary>
|
|
|
|
private HuffmanScanDecoder scanDecoder; |
|
|
|
private IJpegScanDecoder scanDecoder; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The arithmetic decoding tables.
|
|
|
|
/// </summary>
|
|
|
|
private List<ArithmeticDecodingTable> arithmeticDecodingTables; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
|
|
|
|
@ -137,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// <summary>
|
|
|
|
/// Gets the components.
|
|
|
|
/// </summary>
|
|
|
|
public JpegComponent[] Components => this.Frame.Components; |
|
|
|
public IJpegComponent[] Components => this.Frame.Components; |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
IJpegComponent[] IRawJpegData.Components => this.Components; |
|
|
|
@ -188,9 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
{ |
|
|
|
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration); |
|
|
|
|
|
|
|
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); |
|
|
|
|
|
|
|
this.ParseStream(stream, scanDecoder, cancellationToken); |
|
|
|
this.ParseStream(stream, spectralConverter, cancellationToken); |
|
|
|
this.InitExifProfile(); |
|
|
|
this.InitIccProfile(); |
|
|
|
this.InitIptcProfile(); |
|
|
|
@ -206,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// <inheritdoc/>
|
|
|
|
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
this.ParseStream(stream, scanDecoder: null, cancellationToken); |
|
|
|
this.ParseStream(stream, spectralConverter: null, cancellationToken); |
|
|
|
this.InitExifProfile(); |
|
|
|
this.InitIccProfile(); |
|
|
|
this.InitIptcProfile(); |
|
|
|
@ -222,12 +226,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips).
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="tableBytes">The table bytes.</param>
|
|
|
|
/// <param name="huffmanScanDecoder">The scan decoder.</param>
|
|
|
|
public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder) |
|
|
|
/// <param name="scanDecoder">The scan decoder.</param>
|
|
|
|
public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder) |
|
|
|
{ |
|
|
|
this.Metadata = new ImageMetadata(); |
|
|
|
this.QuantizationTables = new Block8x8F[4]; |
|
|
|
this.scanDecoder = huffmanScanDecoder; |
|
|
|
this.scanDecoder = scanDecoder; |
|
|
|
using var ms = new MemoryStream(tableBytes); |
|
|
|
using var stream = new BufferedReadStream(this.Configuration, ms); |
|
|
|
|
|
|
|
@ -282,13 +286,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// Parses the input stream for file markers.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The input stream.</param>
|
|
|
|
/// <param name="scanDecoder">Scan decoder used exclusively to decode SOS marker.</param>
|
|
|
|
/// <param name="spectralConverter">The spectral converter to use.</param>
|
|
|
|
/// <param name="cancellationToken">The token to monitor cancellation.</param>
|
|
|
|
internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken) |
|
|
|
internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralConverter, CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
bool metadataOnly = scanDecoder == null; |
|
|
|
|
|
|
|
this.scanDecoder = scanDecoder; |
|
|
|
bool metadataOnly = spectralConverter == null; |
|
|
|
|
|
|
|
this.Metadata = new ImageMetadata(); |
|
|
|
|
|
|
|
@ -322,7 +324,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
case JpegConstants.Markers.SOF0: |
|
|
|
case JpegConstants.Markers.SOF1: |
|
|
|
case JpegConstants.Markers.SOF2: |
|
|
|
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); |
|
|
|
this.scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); |
|
|
|
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, ComponentType.Huffman, metadataOnly); |
|
|
|
break; |
|
|
|
|
|
|
|
case JpegConstants.Markers.SOF9: |
|
|
|
case JpegConstants.Markers.SOF10: |
|
|
|
case JpegConstants.Markers.SOF13: |
|
|
|
case JpegConstants.Markers.SOF14: |
|
|
|
this.scanDecoder = new ArithmeticScanDecoder(stream, spectralConverter, cancellationToken); |
|
|
|
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, ComponentType.Arithmetic, metadataOnly); |
|
|
|
break; |
|
|
|
|
|
|
|
case JpegConstants.Markers.SOF5: |
|
|
|
@ -338,13 +349,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported."); |
|
|
|
break; |
|
|
|
|
|
|
|
case JpegConstants.Markers.SOF9: |
|
|
|
case JpegConstants.Markers.SOF10: |
|
|
|
case JpegConstants.Markers.SOF11: |
|
|
|
case JpegConstants.Markers.SOF13: |
|
|
|
case JpegConstants.Markers.SOF14: |
|
|
|
case JpegConstants.Markers.SOF15: |
|
|
|
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported."); |
|
|
|
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with lossless arithmetic coding is not supported."); |
|
|
|
break; |
|
|
|
|
|
|
|
case JpegConstants.Markers.SOS: |
|
|
|
@ -428,7 +435,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
break; |
|
|
|
|
|
|
|
case JpegConstants.Markers.DAC: |
|
|
|
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported."); |
|
|
|
if (metadataOnly) |
|
|
|
{ |
|
|
|
stream.Skip(remaining); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.ProcessArithmeticTable(stream, remaining); |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -856,6 +871,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void ProcessArithmeticTable(BufferedReadStream stream, int remaining) |
|
|
|
{ |
|
|
|
this.arithmeticDecodingTables ??= new List<ArithmeticDecodingTable>(4); |
|
|
|
|
|
|
|
while (remaining > 0) |
|
|
|
{ |
|
|
|
int tableClassAndIdentifier = stream.ReadByte(); |
|
|
|
remaining--; |
|
|
|
byte tableClass = (byte)(tableClassAndIdentifier >> 4); |
|
|
|
byte identifier = (byte)(tableClassAndIdentifier & 0xF); |
|
|
|
|
|
|
|
byte conditioningTableValue = (byte)stream.ReadByte(); |
|
|
|
remaining--; |
|
|
|
|
|
|
|
var arithmeticTable = new ArithmeticDecodingTable(tableClass, identifier); |
|
|
|
arithmeticTable.Configure(conditioningTableValue); |
|
|
|
|
|
|
|
bool tableEntryReplaced = false; |
|
|
|
for (int i = 0; i < this.arithmeticDecodingTables.Count; i++) |
|
|
|
{ |
|
|
|
ArithmeticDecodingTable item = this.arithmeticDecodingTables[i]; |
|
|
|
if (item.TableClass == arithmeticTable.TableClass && item.Identifier == arithmeticTable.Identifier) |
|
|
|
{ |
|
|
|
this.arithmeticDecodingTables[i] = arithmeticTable; |
|
|
|
tableEntryReplaced = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!tableEntryReplaced) |
|
|
|
{ |
|
|
|
this.arithmeticDecodingTables.Add(arithmeticTable); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Reads the adobe image resource block name: a Pascal string (padded to make size even).
|
|
|
|
/// </summary>
|
|
|
|
@ -917,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// <param name="stream">The input stream.</param>
|
|
|
|
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|
|
|
/// <exception cref="ImageFormatException">
|
|
|
|
/// Thrown if the tables do not match the header
|
|
|
|
/// Thrown if the tables do not match the header.
|
|
|
|
/// </exception>
|
|
|
|
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining) |
|
|
|
{ |
|
|
|
@ -1023,8 +1074,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
/// <param name="stream">The input stream.</param>
|
|
|
|
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|
|
|
/// <param name="frameMarker">The current frame marker.</param>
|
|
|
|
/// <param name="metadataOnly">Whether to parse metadata only</param>
|
|
|
|
private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, bool metadataOnly) |
|
|
|
/// <param name="decodingComponentType">The jpeg decoding component type.</param>
|
|
|
|
/// <param name="metadataOnly">Whether to parse metadata only.</param>
|
|
|
|
private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) |
|
|
|
{ |
|
|
|
if (this.Frame != null) |
|
|
|
{ |
|
|
|
@ -1036,17 +1088,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); |
|
|
|
} |
|
|
|
|
|
|
|
// Read initial marker definitions
|
|
|
|
// Read initial marker definitions.
|
|
|
|
const int length = 6; |
|
|
|
stream.Read(this.temp, 0, length); |
|
|
|
int bytesRead = stream.Read(this.temp, 0, length); |
|
|
|
if (bytesRead != length) |
|
|
|
{ |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data."); |
|
|
|
} |
|
|
|
|
|
|
|
// 1 byte: Bits/sample precision
|
|
|
|
// 1 byte: Bits/sample precision.
|
|
|
|
byte precision = this.temp[0]; |
|
|
|
|
|
|
|
// Validate: only 8-bit and 12-bit precisions are supported
|
|
|
|
// Validate: only 8-bit and 12-bit precisions are supported.
|
|
|
|
if (Array.IndexOf(this.supportedPrecisions, precision) == -1) |
|
|
|
{ |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported."); |
|
|
|
} |
|
|
|
|
|
|
|
// 2 byte: Height
|
|
|
|
@ -1055,18 +1111,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
// 2 byte: Width
|
|
|
|
int frameWidth = (this.temp[3] << 8) | this.temp[4]; |
|
|
|
|
|
|
|
// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
|
|
|
|
// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that).
|
|
|
|
if (frameHeight == 0 || frameWidth == 0) |
|
|
|
{ |
|
|
|
JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight); |
|
|
|
} |
|
|
|
|
|
|
|
// 1 byte: Number of components
|
|
|
|
// 1 byte: Number of components.
|
|
|
|
byte componentCount = this.temp[5]; |
|
|
|
|
|
|
|
// Validate: componentCount more than 4 can lead to a buffer overflow during stream
|
|
|
|
// reading so we must limit it to 4
|
|
|
|
// We do not support jpeg images with more than 4 components anyway
|
|
|
|
// reading so we must limit it to 4.
|
|
|
|
// We do not support jpeg images with more than 4 components anyway.
|
|
|
|
if (componentCount > 4) |
|
|
|
{ |
|
|
|
JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); |
|
|
|
@ -1135,7 +1191,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex); |
|
|
|
} |
|
|
|
|
|
|
|
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); |
|
|
|
IJpegComponent component = decodingComponentType is ComponentType.Huffman ? |
|
|
|
new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) : |
|
|
|
new ArithmeticDecodingComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); |
|
|
|
|
|
|
|
this.Frame.Components[i] = component; |
|
|
|
this.Frame.ComponentIds[i] = componentId; |
|
|
|
@ -1165,11 +1223,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
const int codeValuesMaxByteSize = 256; |
|
|
|
const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize; |
|
|
|
|
|
|
|
var huffmanScanDecoder = this.scanDecoder as HuffmanScanDecoder; |
|
|
|
if (huffmanScanDecoder is null) |
|
|
|
{ |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("missing huffman table data"); |
|
|
|
} |
|
|
|
|
|
|
|
int length = remaining; |
|
|
|
using (IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(totalBufferSize)) |
|
|
|
{ |
|
|
|
Span<byte> bufferSpan = buffer.GetSpan(); |
|
|
|
Span<byte> huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize); |
|
|
|
Span<byte> huffmanLengthsSpan = bufferSpan.Slice(0, codeLengthsByteSize); |
|
|
|
Span<byte> huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize); |
|
|
|
Span<uint> tableWorkspace = MemoryMarshal.Cast<byte, uint>(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); |
|
|
|
|
|
|
|
@ -1191,12 +1255,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); |
|
|
|
} |
|
|
|
|
|
|
|
stream.Read(huffmanLegthsSpan, 1, 16); |
|
|
|
stream.Read(huffmanLengthsSpan, 1, 16); |
|
|
|
|
|
|
|
int codeLengthSum = 0; |
|
|
|
for (int j = 1; j < 17; j++) |
|
|
|
{ |
|
|
|
codeLengthSum += huffmanLegthsSpan[j]; |
|
|
|
codeLengthSum += huffmanLengthsSpan[j]; |
|
|
|
} |
|
|
|
|
|
|
|
length -= 17; |
|
|
|
@ -1210,10 +1274,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
|
|
|
|
i += 17 + codeLengthSum; |
|
|
|
|
|
|
|
this.scanDecoder.BuildHuffmanTable( |
|
|
|
huffmanScanDecoder!.BuildHuffmanTable( |
|
|
|
tableType, |
|
|
|
tableIndex, |
|
|
|
huffmanLegthsSpan, |
|
|
|
huffmanLengthsSpan, |
|
|
|
huffmanValuesSpan.Slice(0, codeLengthSum), |
|
|
|
tableWorkspace); |
|
|
|
} |
|
|
|
@ -1221,8 +1285,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
|
|
|
|
/// macroblocks
|
|
|
|
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers,
|
|
|
|
/// in macroblocks.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The input stream.</param>
|
|
|
|
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|
|
|
@ -1246,7 +1310,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); |
|
|
|
} |
|
|
|
|
|
|
|
// 1 byte: Number of components in scan
|
|
|
|
// 1 byte: Number of components in scan.
|
|
|
|
int selectorsCount = stream.ReadByte(); |
|
|
|
|
|
|
|
// Validate: 0 < count <= totalComponents
|
|
|
|
@ -1256,7 +1320,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}."); |
|
|
|
} |
|
|
|
|
|
|
|
// Validate: marker must contain exactly (4 + selectorsCount*2) bytes
|
|
|
|
// Validate: Marker must contain exactly (4 + selectorsCount*2) bytes
|
|
|
|
int selectorsBytes = selectorsCount * 2; |
|
|
|
if (remaining != 4 + selectorsBytes) |
|
|
|
{ |
|
|
|
@ -1283,7 +1347,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Validate: must be found among registered components
|
|
|
|
// Validate: Must be found among registered components.
|
|
|
|
if (componentIndex == -1) |
|
|
|
{ |
|
|
|
// TODO: extract as separate method?
|
|
|
|
@ -1292,7 +1356,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
|
|
|
|
this.Frame.ComponentOrder[i / 2] = (byte)componentIndex; |
|
|
|
|
|
|
|
JpegComponent component = this.Frame.Components[componentIndex]; |
|
|
|
IJpegComponent component = this.Frame.Components[componentIndex]; |
|
|
|
|
|
|
|
// 1 byte: Huffman table selectors.
|
|
|
|
// 4 bits - dc
|
|
|
|
@ -1308,8 +1372,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}"); |
|
|
|
} |
|
|
|
|
|
|
|
component.DCHuffmanTableId = dcTableIndex; |
|
|
|
component.ACHuffmanTableId = acTableIndex; |
|
|
|
component.DcTableId = dcTableIndex; |
|
|
|
component.AcTableId = acTableIndex; |
|
|
|
} |
|
|
|
|
|
|
|
// 3 bytes: Progressive scan decoding data
|
|
|
|
@ -1325,18 +1389,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg |
|
|
|
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; |
|
|
|
this.scanDecoder.SuccessiveLow = successiveApproximation & 15; |
|
|
|
|
|
|
|
if (this.scanDecoder is ArithmeticScanDecoder arithmeticScanDecoder) |
|
|
|
{ |
|
|
|
arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables); |
|
|
|
} |
|
|
|
|
|
|
|
this.scanDecoder.ParseEntropyCodedData(selectorsCount); |
|
|
|
} |
|
|
|
|
|
|
|
/// <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>
|
|
|
|
/// <param name="stream">The input stream.</param>
|
|
|
|
/// <returns>The <see cref="ushort"/></returns>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
private ushort ReadUint16(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
stream.Read(this.markerBuffer, 0, 2); |
|
|
|
int bytesRead = stream.Read(this.markerBuffer, 0, 2); |
|
|
|
if (bytesRead != 2) |
|
|
|
{ |
|
|
|
JpegThrowHelper.ThrowInvalidImageContentException("stream does not contain enough data, could not read ushort."); |
|
|
|
} |
|
|
|
|
|
|
|
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); |
|
|
|
} |
|
|
|
} |
|
|
|
|