diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
index 8445625bd..e2b72db05 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
@@ -57,8 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
///
/// The to use for buffer allocations.
/// The instance
- /// Whether to decode metadata only. If this is true, memory allocation for SpectralBlocks will not be necessary
- public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder, bool metadataOnly)
+ public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder)
{
// For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
@@ -81,10 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
- if (!metadataOnly)
- {
- this.SpectralBlocks = memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
- }
+ this.SpectralBlocks = memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
index 66b4601da..875f16ec2 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
@@ -87,8 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
this.IgnoreMetadata = options.IgnoreMetadata;
this.configuration = configuration ?? Configuration.Default;
- this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees();
- this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.Size];
}
@@ -103,10 +101,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
/// Gets the huffman trees
///
- public OrigHuffmanTree[] HuffmanTrees { get; }
+ public OrigHuffmanTree[] HuffmanTrees { get; private set; }
///
- public Block8x8F[] QuantizationTables { get; }
+ public Block8x8F[] QuantizationTables { get; private set; }
///
/// Gets the temporary buffer used to store bytes read from the stream.
@@ -193,7 +191,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
where TPixel : struct, IPixel
{
this.ParseStream(stream);
-
return this.PostProcessIntoImage();
}
@@ -204,7 +201,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
-
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@@ -215,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
foreach (OrigComponent component in this.Components)
{
- component.Dispose();
+ component?.Dispose();
}
}
@@ -233,6 +229,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputStream = stream;
this.InputProcessor = new InputProcessor(stream, this.Temp);
+ if (!metadataOnly)
+ {
+ this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees();
+ this.QuantizationTables = new Block8x8F[MaxTq + 1];
+ }
+
// Check for the Start Of Image marker.
this.InputProcessor.ReadFull(this.Temp, 0, 2);
@@ -332,10 +334,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
case OrigJpegConstants.Markers.SOF2:
this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining, metadataOnly);
- if (metadataOnly && this.isJFif)
- {
- return;
- }
break;
case OrigJpegConstants.Markers.DHT:
@@ -361,19 +359,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
break;
case OrigJpegConstants.Markers.SOS:
- if (metadataOnly)
+ if (!metadataOnly)
{
- return;
+ this.ProcessStartOfScanMarker(remaining);
+ if (this.InputProcessor.ReachedEOF)
+ {
+ // If unexpected EOF reached. We can stop processing bytes as we now have the image data.
+ processBytes = false;
+ }
}
-
- this.ProcessStartOfScanMarker(remaining);
- if (this.InputProcessor.ReachedEOF)
+ else
{
- // If unexpected EOF reached. We can stop processing bytes as we now have the image data.
+ // It's highly unlikely that APPn related data will be found after the SOS marker
+ // We should have gathered everything we need by now.
processBytes = false;
}
break;
+
case OrigJpegConstants.Markers.DRI:
if (metadataOnly)
{
@@ -425,7 +428,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
private void InitDerivedMetaDataProperties()
{
- if (this.isExif)
+ if (this.isJFif)
+ {
+ this.MetaData.HorizontalResolution = this.jFif.XDensity;
+ this.MetaData.VerticalResolution = this.jFif.YDensity;
+ }
+ else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizonalTag)
? ((Rational)horizonalTag.Value).ToDouble()
@@ -441,11 +449,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.MetaData.VerticalResolution = verticalValue;
}
}
- else if (this.isJFif)
- {
- this.MetaData.HorizontalResolution = this.jFif.XDensity;
- this.MetaData.VerticalResolution = this.jFif.YDensity;
- }
}
///
@@ -675,26 +678,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
throw new ImageFormatException("SOF has wrong length");
}
- this.Components = new OrigComponent[this.ComponentCount];
-
- for (int i = 0; i < this.ComponentCount; i++)
+ if (!metadataOnly)
{
- byte componentIdentifier = this.Temp[6 + (3 * i)];
- var component = new OrigComponent(componentIdentifier, i);
- component.InitializeCoreData(this);
- this.Components[i] = component;
- }
+ this.Components = new OrigComponent[this.ComponentCount];
- int h0 = this.Components[0].HorizontalSamplingFactor;
- int v0 = this.Components[0].VerticalSamplingFactor;
+ for (int i = 0; i < this.ComponentCount; i++)
+ {
+ byte componentIdentifier = this.Temp[6 + (3 * i)];
+ var component = new OrigComponent(componentIdentifier, i);
+ component.InitializeCoreData(this);
+ this.Components[i] = component;
+ }
- this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0);
+ int h0 = this.Components[0].HorizontalSamplingFactor;
+ int v0 = this.Components[0].VerticalSamplingFactor;
- this.ColorSpace = this.DeduceJpegColorSpace();
+ this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0);
- foreach (OrigComponent component in this.Components)
- {
- component.InitializeDerivedData(this.configuration.MemoryManager, this, metadataOnly);
+ this.ColorSpace = this.DeduceJpegColorSpace();
+
+ foreach (OrigComponent component in this.Components)
+ {
+ component.InitializeDerivedData(this.configuration.MemoryManager, this);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
new file mode 100644
index 000000000..eb91590e8
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
@@ -0,0 +1,238 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+
+// TODO: This could be useful elsewhere.
+namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
+{
+ ///
+ /// A stream reader that add a secondary level buffer in addition to native stream buffered reading
+ /// to reduce the overhead of small incremental reads.
+ ///
+ internal class DoubleBufferedStreamReader : IDisposable
+ {
+ ///
+ /// The length, in bytes, of the buffering chunk
+ ///
+ public const int ChunkLength = 4096;
+
+ private const int ChunkLengthMinusOne = ChunkLength - 1;
+
+ private readonly Stream stream;
+
+ private readonly IManagedByteBuffer managedBuffer;
+
+ private readonly byte[] bufferChunk;
+
+ private readonly int length;
+
+ private int bytesRead;
+
+ private int position;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use for buffer allocations.
+ /// The input stream.
+ public DoubleBufferedStreamReader(MemoryManager memoryManager, Stream stream)
+ {
+ this.stream = stream;
+ this.length = (int)stream.Length;
+ this.managedBuffer = memoryManager.AllocateCleanManagedByteBuffer(ChunkLength);
+ this.bufferChunk = this.managedBuffer.Array;
+ }
+
+ ///
+ /// Gets the length, in bytes, of the stream
+ ///
+ public long Length => this.length;
+
+ ///
+ /// Gets or sets the current position within the stream
+ ///
+ public long Position
+ {
+ get => this.position;
+
+ set
+ {
+ // Reset everything. It's easier than tracking.
+ this.position = (int)value;
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ this.bytesRead = ChunkLength;
+ }
+ }
+
+ ///
+ /// Reads a byte from the stream and advances the position within the stream by one
+ /// byte, or returns -1 if at the end of the stream.
+ ///
+ /// The unsigned byte cast to an , or -1 if at the end of the stream.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int ReadByte()
+ {
+ if (this.position >= this.length)
+ {
+ return -1;
+ }
+
+ if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne)
+ {
+ return this.ReadByteSlow();
+ }
+
+ this.position++;
+ return this.bufferChunk[this.bytesRead++];
+ }
+
+ ///
+ /// Skips the number of bytes in the stream
+ ///
+ /// The number of bytes to skip
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Skip(int count)
+ {
+ this.Position += count;
+ }
+
+ ///
+ /// Reads a sequence of bytes from the current stream and advances the position within the stream
+ /// by the number of bytes read.
+ ///
+ ///
+ /// An array of bytes. When this method returns, the buffer contains the specified
+ /// byte array with the values between offset and (offset + count - 1) replaced by
+ /// the bytes read from the current source.
+ ///
+ ///
+ /// The zero-based byte offset in buffer at which to begin storing the data read
+ /// from the current stream.
+ ///
+ /// The maximum number of bytes to be read from the current stream.
+ ///
+ /// The total number of bytes read into the buffer. This can be less than the number
+ /// of bytes requested if that many bytes are not currently available, or zero (0)
+ /// if the end of the stream has been reached.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int Read(byte[] buffer, int offset, int count)
+ {
+ if (buffer.Length > ChunkLength)
+ {
+ return this.ReadToBufferSlow(buffer, offset, count);
+ }
+
+ if (this.position == 0 || count + this.bytesRead > ChunkLength)
+ {
+ return this.ReadToChunkSlow(buffer, offset, count);
+ }
+
+ int n = this.GetCount(count);
+ this.CopyBytes(buffer, offset, n);
+
+ this.position += n;
+ this.bytesRead += n;
+
+ return n;
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.managedBuffer?.Dispose();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int ReadByteSlow()
+ {
+ if (this.position != this.stream.Position)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ }
+
+ this.stream.Read(this.bufferChunk, 0, ChunkLength);
+ this.bytesRead = 0;
+
+ this.position++;
+ return this.bufferChunk[this.bytesRead++];
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int ReadToChunkSlow(byte[] buffer, int offset, int count)
+ {
+ // Refill our buffer then copy.
+ if (this.position != this.stream.Position)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ }
+
+ this.stream.Read(this.bufferChunk, 0, ChunkLength);
+ this.bytesRead = 0;
+
+ int n = this.GetCount(count);
+ this.CopyBytes(buffer, offset, n);
+
+ this.position += n;
+ this.bytesRead += n;
+
+ return n;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int ReadToBufferSlow(byte[] buffer, int offset, int count)
+ {
+ // Read to target but don't copy to our chunk.
+ if (this.position != this.stream.Position)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ }
+
+ int n = this.stream.Read(buffer, offset, count);
+ this.Position += n;
+ return n;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetCount(int count)
+ {
+ int n = this.length - this.position;
+ if (n > count)
+ {
+ n = count;
+ }
+
+ if (n < 0)
+ {
+ n = 0;
+ }
+
+ return n;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CopyBytes(byte[] buffer, int offset, int count)
+ {
+ if (count < 9)
+ {
+ int byteCount = count;
+ int read = this.bytesRead;
+ byte[] chunk = this.bufferChunk;
+
+ while (--byteCount > -1)
+ {
+ buffer[offset + byteCount] = chunk[read + byteCount];
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, count);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
index 8e51c0b7c..85c9f9466 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Runtime.CompilerServices;
+
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
///
@@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
/// The marker
/// The position within the stream
- public PdfJsFileMarker(ushort marker, long position)
+ public PdfJsFileMarker(byte marker, long position)
{
this.Marker = marker;
this.Position = position;
@@ -26,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// The marker
/// The position within the stream
/// Whether the current marker is invalid
- public PdfJsFileMarker(ushort marker, long position, bool invalid)
+ public PdfJsFileMarker(byte marker, long position, bool invalid)
{
this.Marker = marker;
this.Position = position;
@@ -36,17 +38,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
/// Gets a value indicating whether the current marker is invalid
///
- public bool Invalid { get; }
+ public bool Invalid
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
///
/// Gets the position of the marker within a stream
///
- public ushort Marker { get; }
+ public byte Marker
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
///
/// Gets the position of the marker within a stream
///
- public long Position { get; }
+ public long Position
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
///
public override string ToString()
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
index 5fcaa6cea..c6b14d6fb 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
@@ -5,7 +5,6 @@ using System;
#if DEBUG
using System.Diagnostics;
#endif
-using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
@@ -21,6 +20,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
private byte[] markerBuffer;
+ private int mcuToRead;
+
+ private int mcusPerLine;
+
+ private int mcu;
+
private int bitsData;
private int bitsCount;
@@ -60,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// The successive approximation bit low end
public void DecodeScan(
PdfJsFrame frame,
- Stream stream,
+ DoubleBufferedStreamReader stream,
PdfJsHuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components,
@@ -82,9 +87,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.unexpectedMarkerReached = false;
bool progressive = frame.Progressive;
- int mcusPerLine = frame.McusPerLine;
+ this.mcusPerLine = frame.McusPerLine;
- int mcu = 0;
+ this.mcu = 0;
int mcuExpected;
if (componentsLength == 1)
{
@@ -92,14 +97,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
else
{
- mcuExpected = mcusPerLine * frame.McusPerColumn;
+ mcuExpected = this.mcusPerLine * frame.McusPerColumn;
}
- PdfJsFileMarker fileMarker;
- while (mcu < mcuExpected)
+ while (this.mcu < mcuExpected)
{
// Reset interval stuff
- int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected;
+ this.mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - this.mcu, resetInterval) : mcuExpected;
for (int i = 0; i < components.Length; i++)
{
PdfJsFrameComponent c = components[i];
@@ -110,30 +114,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
if (!progressive)
{
- this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, stream);
}
else
{
bool isAc = this.specStart != 0;
bool isFirst = successivePrev == 0;
PdfJsHuffmanTables huffmanTables = isAc ? acHuffmanTables : dcHuffmanTables;
- this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, stream);
}
- // Find marker
+ // Reset
+ // TODO: I do not understand why these values are reset? We should surely be tracking the bits across mcu's?
this.bitsCount = 0;
- fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, 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 at {stream.Position}, next marker is: {fileMarker.Marker:X}");
-#endif
- }
+ this.bitsData = 0;
+ this.unexpectedMarkerReached = false;
- ushort marker = fileMarker.Marker;
+ // Some images include more scan blocks than expected, skip past those and
+ // attempt to find the next valid marker
+ PdfJsFileMarker fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
+ byte marker = fileMarker.Marker;
// RSTn - We've already read the bytes and altered the position so no need to skip
if (marker >= PdfJsJpegConstants.Markers.RST0 && marker <= PdfJsJpegConstants.Markers.RST7)
@@ -148,24 +148,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
stream.Position = fileMarker.Position;
break;
}
- }
- fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
-
- // Some images include more Scan blocks than expected, skip past those and
- // attempt to find the next valid marker (fixes issue8182.pdf) ref original code.
- if (fileMarker.Invalid)
- {
#if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
#endif
}
- else
- {
- // We've found a valid marker.
- // Rewind the stream to the position of the marker
- stream.Position = fileMarker.Position;
- }
}
private void DecodeScanBaseline(
@@ -173,10 +160,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components,
int componentsLength,
- int mcusPerLine,
- int mcuToRead,
- ref int mcu,
- Stream stream)
+ DoubleBufferedStreamReader stream)
{
if (componentsLength == 1)
{
@@ -185,20 +169,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
- for (int n = 0; n < mcuToRead; n++)
+ for (int n = 0; n < this.mcuToRead; n++)
{
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{
continue;
}
- this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, mcu, stream);
- mcu++;
+ this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, stream);
+ this.mcu++;
}
}
else
{
- for (int n = 0; n < mcuToRead; n++)
+ for (int n = 0; n < this.mcuToRead; n++)
{
for (int i = 0; i < componentsLength; i++)
{
@@ -218,12 +202,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
continue;
}
- this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
+ this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, j, k, stream);
}
}
}
- mcu++;
+ this.mcu++;
}
}
}
@@ -234,10 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
bool isFirst,
PdfJsFrameComponent[] components,
int componentsLength,
- int mcusPerLine,
- int mcuToRead,
- ref int mcu,
- Stream stream)
+ DoubleBufferedStreamReader stream)
{
if (componentsLength == 1)
{
@@ -245,7 +226,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span));
ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId];
- for (int n = 0; n < mcuToRead; n++)
+ for (int n = 0; n < this.mcuToRead; n++)
{
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{
@@ -256,31 +237,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
if (isFirst)
{
- this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, mcu, stream);
+ this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, stream);
}
else
{
- this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, mcu, stream);
+ this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, stream);
}
}
else
{
if (isFirst)
{
- this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, mcu, stream);
+ this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, stream);
}
else
{
- this.DecodeBlockDCSuccessive(component, ref blockDataRef, mcu, stream);
+ this.DecodeBlockDCSuccessive(component, ref blockDataRef, stream);
}
}
- mcu++;
+ this.mcu++;
}
}
else
{
- for (int n = 0; n < mcuToRead; n++)
+ for (int n = 0; n < this.mcuToRead; n++)
{
for (int i = 0; i < componentsLength; i++)
{
@@ -294,56 +275,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
for (int k = 0; k < h; k++)
{
+ // No need to continue here.
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{
- continue;
+ break;
}
if (isAC)
{
if (isFirst)
{
- this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
+ this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream);
}
else
{
- this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
+ this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, j, k, stream);
}
}
else
{
if (isFirst)
{
- this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
+ this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream);
}
else
{
- this.DecodeMcuDCSuccessive(component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
+ this.DecodeMcuDCSuccessive(component, ref blockDataRef, j, k, stream);
}
}
}
}
}
- mcu++;
+ this.mcu++;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
- int blockRow = mcu / component.WidthInBlocks;
- int blockCol = mcu % component.WidthInBlocks;
+ int blockRow = this.mcu / component.WidthInBlocks;
+ int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
+ int mcuRow = this.mcu / this.mcusPerLine;
+ int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@@ -351,19 +333,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
- int blockRow = mcu / component.WidthInBlocks;
- int blockCol = mcu % component.WidthInBlocks;
+ int blockRow = this.mcu / component.WidthInBlocks;
+ int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
+ int mcuRow = this.mcu / this.mcusPerLine;
+ int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@@ -371,19 +353,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
- int blockRow = mcu / component.WidthInBlocks;
- int blockCol = mcu % component.WidthInBlocks;
+ int blockRow = this.mcu / component.WidthInBlocks;
+ int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
+ int mcuRow = this.mcu / this.mcusPerLine;
+ int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@@ -391,169 +373,257 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
- int blockRow = mcu / component.WidthInBlocks;
- int blockCol = mcu % component.WidthInBlocks;
+ int blockRow = this.mcu / component.WidthInBlocks;
+ int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACFirst(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
+ this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
+ int mcuRow = this.mcu / this.mcusPerLine;
+ int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACFirst(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
+ this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
+ private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
- int blockRow = mcu / component.WidthInBlocks;
- int blockCol = mcu % component.WidthInBlocks;
+ int blockRow = this.mcu / component.WidthInBlocks;
+ int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACSuccessive(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
+ this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
+ int mcuRow = this.mcu / this.mcusPerLine;
+ int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACSuccessive(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
+ this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int ReadBit(Stream stream)
+ private bool TryReadBit(DoubleBufferedStreamReader stream, out int bit)
{
- // TODO: I wonder if we can do this two bytes at a time; libjpeg turbo seems to do that?
- if (this.bitsCount > 0)
+ if (this.bitsCount == 0)
{
- this.bitsCount--;
- return (this.bitsData >> this.bitsCount) & 1;
+ if (!this.TryFillBits(stream))
+ {
+ bit = 0;
+ return false;
+ }
}
- this.bitsData = stream.ReadByte();
+ this.bitsCount--;
+ bit = (this.bitsData >> this.bitsCount) & 1;
+ return true;
+ }
- if (this.bitsData == -0x1)
- {
- // We've encountered the end of the file stream which means there's no EOI marker ref the image
- this.endOfStreamReached = true;
- }
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private bool TryFillBits(DoubleBufferedStreamReader stream)
+ {
+ // TODO: Read more then 1 byte at a time.
+ // In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work
+ // for some images, I'm assuming because I am crossing MCU boundaries and not maintining the correct buffer state.
+ const int MinGetBits = 7;
- if (this.bitsData == PdfJsJpegConstants.Markers.Prefix)
+ if (!this.unexpectedMarkerReached)
{
- int nextByte = stream.ReadByte();
- if (nextByte != 0)
+ // Attempt to load to the minimum bit count.
+ while (this.bitsCount < MinGetBits)
{
+ int c = stream.ReadByte();
+
+ switch (c)
+ {
+ case -0x1:
+
+ // We've encountered the end of the file stream which means there's no EOI marker in the image.
+ this.endOfStreamReached = true;
+ return false;
+
+ case PdfJsJpegConstants.Markers.Prefix:
+ int nextByte = stream.ReadByte();
+
+ if (nextByte == -0x1)
+ {
+ this.endOfStreamReached = true;
+ return false;
+ }
+
+ if (nextByte != 0)
+ {
#if DEBUG
- Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}");
+ Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}");
#endif
- // We've encountered an unexpected marker. Reverse the stream and exit.
- this.unexpectedMarkerReached = true;
- stream.Position -= 2;
- }
+ // We've encountered an unexpected marker. Reverse the stream and exit.
+ this.unexpectedMarkerReached = true;
+ stream.Position -= 2;
+
+ // TODO: double check we need this.
+ // Fill buffer with zero bits.
+ if (this.bitsCount == 0)
+ {
+ this.bitsData <<= MinGetBits;
+ this.bitsCount = MinGetBits;
+ }
+
+ return true;
+ }
- // Unstuff 0
+ break;
+ }
+
+ // OK, load the next byte into bitsData
+ this.bitsData = (this.bitsData << 8) | c;
+ this.bitsCount += 8;
+ }
}
- this.bitsCount = 7;
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int PeekBits(int count)
+ {
+ return this.bitsData >> (this.bitsCount - count) & ((1 << count) - 1);
+ }
- return this.bitsData >> 7;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DropBits(int count)
+ {
+ this.bitsCount -= count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private short DecodeHuffman(ref PdfJsHuffmanTable tree, Stream stream)
+ private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream, out short value)
{
+ value = -1;
+
// TODO: Implement fast Huffman decoding.
- // NOTES # During investigation of the libjpeg implementation it appears that they pull 32bits at a time and operate on those bits
- // using 3 methods: FillBits, PeekBits, and ReadBits. We should attempt to do the same.
- short code = (short)this.ReadBit(stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ // In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream
+ // Then a LUT is used to avoid the loop when decoding the Huffman value.
+ // using 3 methods: FillBits, PeekBits, and DropBits.
+ // The LUT has been ported from LibJpegTurbo as has this code but it doesn't work.
+ // this.TryFillBits(stream);
+ //
+ // const int LookAhead = 8;
+ // int look = this.PeekBits(LookAhead);
+ // look = tree.Lookahead[look];
+ // int bits = look >> LookAhead;
+ //
+ // if (bits <= LookAhead)
+ // {
+ // this.DropBits(bits);
+ // value = (short)(look & ((1 << LookAhead) - 1));
+ // return true;
+ // }
+ if (!this.TryReadBit(stream, out int bit))
{
- return -1;
+ return false;
}
+ short code = (short)bit;
+
// "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
int i = 1;
while (code > tree.MaxCode[i])
{
- code <<= 1;
- code |= (short)this.ReadBit(stream);
-
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryReadBit(stream, out bit))
{
- return -1;
+ return false;
}
+ code <<= 1;
+ code |= (short)bit;
i++;
}
int j = tree.ValOffset[i];
- return tree.HuffVal[(j + code) & 0xFF];
+ value = tree.HuffVal[(j + code) & 0xFF];
+ return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int Receive(int length, Stream stream)
+ private bool TryReceive(int length, DoubleBufferedStreamReader stream, out int value)
{
- int n = 0;
+ value = 0;
while (length > 0)
{
- int bit = this.ReadBit(stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryReadBit(stream, out int bit))
{
- return -1;
+ return false;
}
- n = (n << 1) | bit;
+ value = (value << 1) | bit;
length--;
}
- return n;
+ return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int ReceiveAndExtend(int length, Stream stream)
+ private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value)
{
if (length == 1)
{
- return this.ReadBit(stream) == 1 ? 1 : -1;
- }
+ if (!this.TryReadBit(stream, out value))
+ {
+ return false;
+ }
- int n = this.Receive(length, stream);
- if (n >= 1 << (length - 1))
+ value = value == 1 ? 1 : -1;
+ }
+ else
{
- return n;
+ if (!this.TryReceive(length, stream, out value))
+ {
+ return false;
+ }
+
+ if (value < 1 << (length - 1))
+ {
+ value += (-1 << length) + 1;
+ }
}
- return n + (-1 << length) + 1;
+ return true;
}
- private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
+ private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
- short t = this.DecodeHuffman(ref dcHuffmanTable, stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
{
return;
}
- int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream);
+ int diff = 0;
+ if (t != 0)
+ {
+ if (!this.TryReceiveAndExtend(t, stream, out diff))
+ {
+ return;
+ }
+ }
+
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff);
int k = 1;
while (k < 64)
{
- short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{
return;
}
@@ -574,36 +644,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r;
- if (k > 63)
+ byte z = this.dctZigZag[k];
+
+ if (!this.TryReceiveAndExtend(s, stream, out int re))
{
- break;
+ return;
}
- byte z = this.dctZigZag[k];
- short re = (short)this.ReceiveAndExtend(s, stream);
- Unsafe.Add(ref blockDataRef, offset + z) = re;
+ Unsafe.Add(ref blockDataRef, offset + z) = (short)re;
k++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, Stream stream)
+ private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream)
{
- short t = this.DecodeHuffman(ref dcHuffmanTable, stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
{
return;
}
- int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState;
- Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff);
+ int diff = 0;
+ if (t != 0)
+ {
+ if (!this.TryReceiveAndExtend(t, stream, out diff))
+ {
+ return;
+ }
+ }
+
+ Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff << this.successiveState);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, Stream stream)
+ private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream)
{
- int bit = this.ReadBit(stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryReadBit(stream, out int bit))
{
return;
}
@@ -611,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState);
}
- private void DecodeACFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
+ private void DecodeACFirst(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
if (this.eobrun > 0)
{
@@ -623,8 +699,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
int e = this.specEnd;
while (k <= e)
{
- short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{
return;
}
@@ -636,7 +711,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
if (r < 15)
{
- this.eobrun = this.Receive(r, stream) + (1 << r) - 1;
+ if (!this.TryReceive(r, stream, out int eob))
+ {
+ return;
+ }
+
+ this.eobrun = eob + (1 << r) - 1;
break;
}
@@ -647,12 +727,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r;
byte z = this.dctZigZag[k];
- Unsafe.Add(ref blockDataRef, offset + z) = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState));
+
+ if (!this.TryReceiveAndExtend(s, stream, out int v))
+ {
+ return;
+ }
+
+ Unsafe.Add(ref blockDataRef, offset + z) = (short)(v * (1 << this.successiveState));
k++;
}
}
- private void DecodeACSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
+ private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
int k = this.specStart;
int e = this.specEnd;
@@ -667,8 +753,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
switch (this.successiveACState)
{
case 0: // Initial state
- short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+
+ if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{
return;
}
@@ -679,7 +765,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
if (r < 15)
{
- this.eobrun = this.Receive(r, stream) + (1 << r);
+ if (!this.TryReceive(r, stream, out int eob))
+ {
+ return;
+ }
+
+ this.eobrun = eob + (1 << r);
this.successiveACState = 4;
}
else
@@ -695,7 +786,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
throw new ImageFormatException("Invalid ACn encoding");
}
- this.successiveACNextValue = this.ReceiveAndExtend(s, stream);
+ if (!this.TryReceiveAndExtend(s, stream, out int v))
+ {
+ return;
+ }
+
+ this.successiveACNextValue = v;
this.successiveACState = r > 0 ? 2 : 3;
}
@@ -704,8 +800,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 2:
if (blockOffsetZRef != 0)
{
- int bit = this.ReadBit(stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryReadBit(stream, out int bit))
{
return;
}
@@ -725,8 +820,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 3: // Set value for a zero item
if (blockOffsetZRef != 0)
{
- int bit = this.ReadBit(stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryReadBit(stream, out int bit))
{
return;
}
@@ -743,8 +837,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 4: // Eob
if (blockOffsetZRef != 0)
{
- int bit = this.ReadBit(stream);
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ if (!this.TryReadBit(stream, out int bit))
{
return;
}
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
index 2c369d390..437f77286 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs
@@ -22,98 +22,98 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
/// The Start of Image marker
///
- public const ushort SOI = 0xFFD8;
+ public const byte SOI = 0xD8;
///
/// The End of Image marker
///
- public const ushort EOI = 0xFFD9;
+ public const byte EOI = 0xD9;
///
/// Application specific marker for marking the jpeg format.
///
///
- public const ushort APP0 = 0xFFE0;
+ public const byte APP0 = 0xE0;
///
/// Application specific marker for marking where to store metadata.
///
- public const ushort APP1 = 0xFFE1;
+ public const byte APP1 = 0xE1;
///
/// Application specific marker for marking where to store ICC profile information.
///
- public const ushort APP2 = 0xFFE2;
+ public const byte APP2 = 0xE2;
///
/// Application specific marker.
///
- public const ushort APP3 = 0xFFE3;
+ public const byte APP3 = 0xE3;
///
/// Application specific marker.
///
- public const ushort APP4 = 0xFFE4;
+ public const byte APP4 = 0xE4;
///
/// Application specific marker.
///
- public const ushort APP5 = 0xFFE5;
+ public const byte APP5 = 0xE5;
///
/// Application specific marker.
///
- public const ushort APP6 = 0xFFE6;
+ public const byte APP6 = 0xE6;
///
/// Application specific marker.
///
- public const ushort APP7 = 0xFFE7;
+ public const byte APP7 = 0xE7;
///
/// Application specific marker.
///
- public const ushort APP8 = 0xFFE8;
+ public const byte APP8 = 0xE8;
///
/// Application specific marker.
///
- public const ushort APP9 = 0xFFE9;
+ public const byte APP9 = 0xE9;
///
/// Application specific marker.
///
- public const ushort APP10 = 0xFFEA;
+ public const byte APP10 = 0xEA;
///
/// Application specific marker.
///
- public const ushort APP11 = 0xFFEB;
+ public const byte APP11 = 0xEB;
///
/// Application specific marker.
///
- public const ushort APP12 = 0xFFEC;
+ public const byte APP12 = 0xEC;
///
/// Application specific marker.
///
- public const ushort APP13 = 0xFFED;
+ public const byte APP13 = 0xED;
///
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
///
- public const ushort APP14 = 0xFFEE;
+ public const byte APP14 = 0xEE;
///
/// Application specific marker used by GraphicConverter to store JPEG quality.
///
- public const ushort APP15 = 0xFFEF;
+ public const byte APP15 = 0xEF;
///
/// The text comment marker
///
- public const ushort COM = 0xFFFE;
+ public const byte COM = 0xFE;
///
/// Define Quantization Table(s) marker
@@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more quantization tables.
///
///
- public const ushort DQT = 0xFFDB;
+ public const byte DQT = 0xDB;
///
/// Start of Frame (baseline DCT)
@@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
///
///
- public const ushort SOF0 = 0xFFC0;
+ public const byte SOF0 = 0xC0;
///
/// Start Of Frame (Extended Sequential DCT)
@@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
///
///
- public const ushort SOF1 = 0xFFC1;
+ public const byte SOF1 = 0xC1;
///
/// Start Of Frame (progressive DCT)
@@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
///
///
- public const ushort SOF2 = 0xFFC2;
+ public const byte SOF2 = 0xC2;
///
/// Define Huffman Table(s)
@@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more Huffman tables.
///
///
- public const ushort DHT = 0xFFC4;
+ public const byte DHT = 0xC4;
///
/// Define Restart Interval
@@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
///
///
- public const ushort DRI = 0xFFDD;
+ public const byte DRI = 0xDD;
///
/// Start of Scan
@@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// will contain, and is immediately followed by entropy-coded data.
///
///
- public const ushort SOS = 0xFFDA;
+ public const byte SOS = 0xDA;
///
/// Define First Restart
@@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
///
///
- public const ushort RST0 = 0xFFD0;
+ public const byte RST0 = 0xD0;
///
/// Define Eigth Restart
@@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
///
///
- public const ushort RST7 = 0xFFD7;
+ public const byte RST7 = 0xD7;
///
/// Contains Adobe specific markers
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
index 244d97fba..df803a920 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
@@ -22,7 +22,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
///
/// Performs the jpeg decoding operation.
- /// Ported from with additional fixes to handle common encoding errors
+ /// Originally ported from
+ /// with additional fixes for both performance and common encoding errors.
///
internal sealed class PdfJsJpegDecoderCore : IRawJpegData
{
@@ -31,7 +32,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
public const int SupportedPrecision = 8;
-#pragma warning disable SA1401 // Fields should be private
///
/// The global configuration
///
@@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
/// Gets the input stream.
///
- public Stream InputStream { get; private set; }
+ public DoubleBufferedStreamReader InputStream { get; private set; }
///
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
@@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// The buffer to read file markers to
/// The input stream
/// The
- public static PdfJsFileMarker FindNextFileMarker(byte[] marker, Stream stream)
+ public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{
int value = stream.Read(marker, 0, 2);
@@ -157,7 +157,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
// According to Section B.1.1.2:
// "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF."
- while (marker[1] == PdfJsJpegConstants.Markers.Prefix)
+ int m = marker[1];
+ while (m == PdfJsJpegConstants.Markers.Prefix)
{
int suffix = stream.ReadByte();
if (suffix == -1)
@@ -165,13 +166,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, stream.Length - 2);
}
- marker[1] = (byte)suffix;
+ m = suffix;
}
- return new PdfJsFileMarker(BinaryPrimitives.ReadUInt16BigEndian(marker), stream.Position - 2);
+ return new PdfJsFileMarker((byte)m, stream.Position - 2);
}
- return new PdfJsFileMarker(BinaryPrimitives.ReadUInt16BigEndian(marker), stream.Position - 2, true);
+ return new PdfJsFileMarker(marker[1], stream.Position - 2, true);
}
///
@@ -184,6 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel
{
this.ParseStream(stream);
+ this.AssignResolution();
return this.PostProcessIntoImage();
}
@@ -206,136 +208,142 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public void ParseStream(Stream stream, bool metadataOnly = false)
{
this.MetaData = new ImageMetaData();
- this.InputStream = stream;
+ this.InputStream = new DoubleBufferedStreamReader(this.configuration.MemoryManager, stream);
// Check for the Start Of Image marker.
- var fileMarker = new PdfJsFileMarker(this.ReadUint16(), 0);
+ this.InputStream.Read(this.markerBuffer, 0, 2);
+ var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != PdfJsJpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
- ushort marker = this.ReadUint16();
+ this.InputStream.Read(this.markerBuffer, 0, 2);
+ byte marker = this.markerBuffer[1];
fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2);
- this.QuantizationTables = new Block8x8F[4];
-
- // this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager);
- this.dcHuffmanTables = new PdfJsHuffmanTables();
- this.acHuffmanTables = new PdfJsHuffmanTables();
+ // Only assign what we need
+ if (!metadataOnly)
+ {
+ this.QuantizationTables = new Block8x8F[4];
+ this.dcHuffmanTables = new PdfJsHuffmanTables();
+ this.acHuffmanTables = new PdfJsHuffmanTables();
+ }
while (fileMarker.Marker != PdfJsJpegConstants.Markers.EOI)
{
- // Get the marker length
- int remaining = this.ReadUint16() - 2;
-
- switch (fileMarker.Marker)
+ if (!fileMarker.Invalid)
{
- case PdfJsJpegConstants.Markers.APP0:
- this.ProcessApplicationHeaderMarker(remaining);
- break;
+ // Get the marker length
+ int remaining = this.ReadUint16() - 2;
- case PdfJsJpegConstants.Markers.APP1:
- this.ProcessApp1Marker(remaining);
- break;
-
- case PdfJsJpegConstants.Markers.APP2:
- this.ProcessApp2Marker(remaining);
- break;
- case PdfJsJpegConstants.Markers.APP3:
- case PdfJsJpegConstants.Markers.APP4:
- case PdfJsJpegConstants.Markers.APP5:
- case PdfJsJpegConstants.Markers.APP6:
- case PdfJsJpegConstants.Markers.APP7:
- case PdfJsJpegConstants.Markers.APP8:
- case PdfJsJpegConstants.Markers.APP9:
- case PdfJsJpegConstants.Markers.APP10:
- case PdfJsJpegConstants.Markers.APP11:
- case PdfJsJpegConstants.Markers.APP12:
- case PdfJsJpegConstants.Markers.APP13:
- this.InputStream.Skip(remaining);
- break;
-
- case PdfJsJpegConstants.Markers.APP14:
- this.ProcessApp14Marker(remaining);
- break;
+ switch (fileMarker.Marker)
+ {
+ case PdfJsJpegConstants.Markers.SOF0:
+ case PdfJsJpegConstants.Markers.SOF1:
+ case PdfJsJpegConstants.Markers.SOF2:
+ this.ProcessStartOfFrameMarker(remaining, fileMarker, metadataOnly);
+ break;
+
+ case PdfJsJpegConstants.Markers.SOS:
+ if (!metadataOnly)
+ {
+ this.ProcessStartOfScanMarker();
+ break;
+ }
+ else
+ {
+ // It's highly unlikely that APPn related data will be found after the SOS marker
+ // We should have gathered everything we need by now.
+ return;
+ }
- case PdfJsJpegConstants.Markers.APP15:
- case PdfJsJpegConstants.Markers.COM:
- this.InputStream.Skip(remaining);
- break;
+ case PdfJsJpegConstants.Markers.DHT:
+ if (metadataOnly)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineHuffmanTablesMarker(remaining);
+ }
- case PdfJsJpegConstants.Markers.DQT:
- if (metadataOnly)
- {
- this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineQuantizationTablesMarker(remaining);
- }
+ break;
- break;
+ case PdfJsJpegConstants.Markers.DQT:
+ if (metadataOnly)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineQuantizationTablesMarker(remaining);
+ }
- case PdfJsJpegConstants.Markers.SOF0:
- case PdfJsJpegConstants.Markers.SOF1:
- case PdfJsJpegConstants.Markers.SOF2:
- this.ProcessStartOfFrameMarker(remaining, fileMarker);
- if (metadataOnly && !this.jFif.Equals(default))
- {
- this.InputStream.Skip(remaining);
- }
+ break;
- break;
+ case PdfJsJpegConstants.Markers.DRI:
+ if (metadataOnly)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineRestartIntervalMarker(remaining);
+ }
- case PdfJsJpegConstants.Markers.DHT:
- if (metadataOnly)
- {
+ break;
+
+ case PdfJsJpegConstants.Markers.APP0:
+ this.ProcessApplicationHeaderMarker(remaining);
+ break;
+
+ case PdfJsJpegConstants.Markers.APP1:
+ this.ProcessApp1Marker(remaining);
+ break;
+
+ case PdfJsJpegConstants.Markers.APP2:
+ this.ProcessApp2Marker(remaining);
+ break;
+
+ case PdfJsJpegConstants.Markers.APP3:
+ case PdfJsJpegConstants.Markers.APP4:
+ case PdfJsJpegConstants.Markers.APP5:
+ case PdfJsJpegConstants.Markers.APP6:
+ case PdfJsJpegConstants.Markers.APP7:
+ case PdfJsJpegConstants.Markers.APP8:
+ case PdfJsJpegConstants.Markers.APP9:
+ case PdfJsJpegConstants.Markers.APP10:
+ case PdfJsJpegConstants.Markers.APP11:
+ case PdfJsJpegConstants.Markers.APP12:
+ case PdfJsJpegConstants.Markers.APP13:
this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineHuffmanTablesMarker(remaining);
- }
+ break;
- break;
+ case PdfJsJpegConstants.Markers.APP14:
+ this.ProcessApp14Marker(remaining);
+ break;
- case PdfJsJpegConstants.Markers.DRI:
- if (metadataOnly)
- {
+ case PdfJsJpegConstants.Markers.APP15:
+ case PdfJsJpegConstants.Markers.COM:
this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineRestartIntervalMarker(remaining);
- }
-
- break;
-
- case PdfJsJpegConstants.Markers.SOS:
- if (!metadataOnly)
- {
- this.ProcessStartOfScanMarker();
- }
-
- break;
+ break;
+ }
}
// Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream);
}
-
- this.ImageWidth = this.Frame.SamplesPerLine;
- this.ImageHeight = this.Frame.Scanlines;
- this.ComponentCount = this.Frame.ComponentCount;
}
///
public void Dispose()
{
+ this.InputStream?.Dispose();
this.Frame?.Dispose();
// Set large fields to null.
+ this.InputStream = null;
this.Frame = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
@@ -379,7 +387,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
private void AssignResolution()
{
- if (this.isExif)
+ this.ImageWidth = this.Frame.SamplesPerLine;
+ this.ImageHeight = this.Frame.Scanlines;
+
+ if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
+ {
+ this.MetaData.HorizontalResolution = this.jFif.XDensity;
+ this.MetaData.VerticalResolution = this.jFif.YDensity;
+ }
+ else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag)
? ((Rational)horizontalTag.Value).ToDouble()
@@ -395,11 +411,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.MetaData.VerticalResolution = verticalValue;
}
}
- else if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
- {
- this.MetaData.HorizontalResolution = this.jFif.XDensity;
- this.MetaData.VerticalResolution = this.jFif.YDensity;
- }
}
///
@@ -593,7 +604,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
///
/// The remaining bytes in the segment block.
/// The current frame marker.
- private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker)
+ /// Whether to parse metadata only
+ private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{
@@ -622,11 +634,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int maxV = 0;
int index = 6;
- // No need to pool this. They max out at 4
- this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
- this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
+ this.ComponentCount = this.Frame.ComponentCount;
+ if (!metadataOnly)
+ {
+ // No need to pool this. They max out at 4
+ this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
+ this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
+ }
- for (int i = 0; i < this.Frame.Components.Length; i++)
+ for (int i = 0; i < this.Frame.ComponentCount; i++)
{
byte hv = this.temp[index + 1];
int h = hv >> 4;
@@ -642,17 +658,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
maxV = v;
}
- var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
+ if (!metadataOnly)
+ {
+ var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
- this.Frame.Components[i] = component;
- this.Frame.ComponentIds[i] = component.Id;
+ this.Frame.Components[i] = component;
+ this.Frame.ComponentIds[i] = component.Id;
+ }
index += 3;
}
this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV;
- this.Frame.InitComponents();
+
+ if (!metadataOnly)
+ {
+ this.Frame.InitComponents();
+ }
}
///
@@ -799,7 +822,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel
{
this.ColorSpace = this.DeduceJpegColorSpace();
- this.AssignResolution();
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{
var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
new file mode 100644
index 000000000..1d76d58a5
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
@@ -0,0 +1,114 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
+{
+ [Config(typeof(Config.ShortClr))]
+ public class DoubleBufferedStreams
+ {
+ private byte[] buffer = CreateTestBytes();
+ private byte[] chunk1 = new byte[2];
+ private byte[] chunk2 = new byte[2];
+
+ private MemoryStream stream1;
+ private MemoryStream stream2;
+ private MemoryStream stream3;
+ private MemoryStream stream4;
+ DoubleBufferedStreamReader reader1;
+ DoubleBufferedStreamReader reader2;
+
+ [GlobalSetup]
+ public void CreateStreams()
+ {
+ this.stream1 = new MemoryStream(this.buffer);
+ this.stream2 = new MemoryStream(this.buffer);
+ this.stream3 = new MemoryStream(this.buffer);
+ this.stream4 = new MemoryStream(this.buffer);
+ this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
+ this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
+ }
+
+ [GlobalCleanup]
+ public void DestroyStreams()
+ {
+ this.stream1?.Dispose();
+ this.stream2?.Dispose();
+ this.stream3?.Dispose();
+ this.stream4?.Dispose();
+ this.reader1?.Dispose();
+ this.reader2?.Dispose();
+ }
+
+ [Benchmark(Baseline = true)]
+ public int StandardStreamReadByte()
+ {
+ int r = 0;
+ Stream stream = this.stream1;
+
+ for (int i = 0; i < stream.Length; i++)
+ {
+ r += stream.ReadByte();
+ }
+
+ return r;
+ }
+
+ [Benchmark]
+ public int StandardStreamRead()
+ {
+ int r = 0;
+ Stream stream = this.stream1;
+ byte[] b = this.chunk1;
+
+ for (int i = 0; i < stream.Length / 2; i++)
+ {
+ r += stream.Read(b, 0, 2);
+ }
+
+ return r;
+ }
+
+ [Benchmark]
+ public int DoubleBufferedStreamReadByte()
+ {
+ int r = 0;
+ DoubleBufferedStreamReader reader = this.reader1;
+
+ for (int i = 0; i < reader.Length; i++)
+ {
+ r += reader.ReadByte();
+ }
+
+ return r;
+ }
+
+ [Benchmark]
+ public int DoubleBufferedStreamRead()
+ {
+ int r = 0;
+ DoubleBufferedStreamReader reader = this.reader2;
+ byte[] b = this.chunk2;
+
+ for (int i = 0; i < reader.Length / 2; i++)
+ {
+ r += reader.Read(b, 0, 2);
+ }
+
+ return r;
+ }
+
+ private static byte[] CreateTestBytes()
+ {
+ byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
+ var random = new Random();
+ random.NextBytes(buffer);
+
+ return buffer;
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
new file mode 100644
index 000000000..c3c128100
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
+{
+ [Config(typeof(Config.ShortClr))]
+ public class IdentifyJpeg
+ {
+ private byte[] jpegBytes;
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)]
+ public string TestImage { get; set; }
+
+ [GlobalSetup]
+ public void ReadImages()
+ {
+ if (this.jpegBytes == null)
+ {
+ this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
+ }
+ }
+
+ [Benchmark]
+ public IImageInfo IdentifyGolang()
+ {
+ using (var memoryStream = new MemoryStream(this.jpegBytes))
+ {
+ var decoder = new OrigJpegDecoder();
+
+ return decoder.Identify(Configuration.Default, memoryStream);
+ }
+ }
+
+ [Benchmark]
+ public IImageInfo IdentifyPdfJs()
+ {
+ using (var memoryStream = new MemoryStream(this.jpegBytes))
+ {
+ var decoder = new PdfJsJpegDecoder();
+ return decoder.Identify(Configuration.Default, memoryStream);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
new file mode 100644
index 000000000..be71e554f
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
@@ -0,0 +1,159 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
+using SixLabors.ImageSharp.Memory;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Jpg
+{
+ public class DoubleBufferedStreamReaderTests
+ {
+ private readonly MemoryManager manager = Configuration.Default.MemoryManager;
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadSingleByteFromOrigin()
+ {
+ using (MemoryStream stream = this.CreateTestStream())
+ {
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(this.manager, stream);
+
+ Assert.Equal(expected[0], reader.ReadByte());
+
+ // We've read a whole chunk but increment by 1 in our reader.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ Assert.Equal(1, reader.Position);
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadSubsequentSingleByteCorrectly()
+ {
+ using (MemoryStream stream = this.CreateTestStream())
+ {
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(this.manager, stream);
+
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i], reader.ReadByte());
+ Assert.Equal(i + 1, reader.Position);
+
+ if (i < DoubleBufferedStreamReader.ChunkLength)
+ {
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ }
+ else if (i >= DoubleBufferedStreamReader.ChunkLength && i < DoubleBufferedStreamReader.ChunkLength * 2)
+ {
+ // We should have advanced to the second chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2);
+ }
+ else
+ {
+ // We should have advanced to the third chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadMultipleBytesFromOrigin()
+ {
+ using (MemoryStream stream = this.CreateTestStream())
+ {
+ byte[] buffer = new byte[2];
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(this.manager, stream);
+
+ Assert.Equal(2, reader.Read(buffer, 0, 2));
+ Assert.Equal(expected[0], buffer[0]);
+ Assert.Equal(expected[1], buffer[1]);
+
+ // We've read a whole chunk but increment by the buffer length in our reader.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ Assert.Equal(buffer.Length, reader.Position);
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanReadSubsequentMultipleByteCorrectly()
+ {
+ using (MemoryStream stream = this.CreateTestStream())
+ {
+ byte[] buffer = new byte[2];
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(this.manager, stream);
+
+ for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2)
+ {
+
+ Assert.Equal(2, reader.Read(buffer, 0, 2));
+ Assert.Equal(expected[o], buffer[0]);
+ Assert.Equal(expected[o + 1], buffer[1]);
+ Assert.Equal(o + 2, reader.Position);
+
+ int offset = i * 2;
+ if (offset < DoubleBufferedStreamReader.ChunkLength)
+ {
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
+ }
+ else if (offset >= DoubleBufferedStreamReader.ChunkLength && offset < DoubleBufferedStreamReader.ChunkLength * 2)
+ {
+ // We should have advanced to the second chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2);
+ }
+ else
+ {
+ // We should have advanced to the third chunk now.
+ Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void DoubleBufferedStreamReaderCanSkip()
+ {
+ using (MemoryStream stream = this.CreateTestStream())
+ {
+ byte[] expected = stream.ToArray();
+ var reader = new DoubleBufferedStreamReader(this.manager, stream);
+
+ int skip = 50;
+ int plusOne = 1;
+ int skip2 = DoubleBufferedStreamReader.ChunkLength;
+
+ // Skip
+ reader.Skip(skip);
+ Assert.Equal(skip, reader.Position);
+ Assert.Equal(stream.Position, reader.Position);
+
+ // Read
+ Assert.Equal(expected[skip], reader.ReadByte());
+
+ // Skip Again
+ reader.Skip(skip2);
+
+ // First Skap + First Read + Second Skip
+ int position = skip + plusOne + skip2;
+
+ Assert.Equal(position, reader.Position);
+ Assert.Equal(stream.Position, reader.Position);
+ Assert.Equal(expected[position], reader.ReadByte());
+ }
+ }
+
+ private MemoryStream CreateTestStream()
+ {
+ byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
+ var random = new Random();
+ random.NextBytes(buffer);
+
+ return new MemoryStream(buffer);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
new file mode 100644
index 000000000..7fc949b09
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
@@ -0,0 +1,158 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.MetaData.Profiles.Icc;
+using SixLabors.ImageSharp.PixelFormats;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Tests.Formats.Jpg
+{
+ using System.Runtime.CompilerServices;
+
+ using SixLabors.ImageSharp.Formats.Jpeg;
+
+ public partial class JpegDecoderTests
+ {
+ // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct.
+ // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton)
+ public static readonly TheoryData MetaDataTestData =
+ new TheoryData
+ {
+ { false, TestImages.Jpeg.Progressive.Progress, 24, false, false },
+ { false, TestImages.Jpeg.Progressive.Fb, 24, false, true },
+ { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true },
+ { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true },
+ { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false },
+ { false, TestImages.Jpeg.Baseline.Snake, 24, true, true },
+ { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false },
+
+ { true, TestImages.Jpeg.Progressive.Progress, 24, false, false },
+ { true, TestImages.Jpeg.Progressive.Fb, 24, false, true },
+ { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true },
+ { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true },
+ { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false },
+ { true, TestImages.Jpeg.Baseline.Snake, 24, true, true },
+ { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false },
+ };
+
+ [Theory]
+ [MemberData(nameof(MetaDataTestData))]
+ public void MetaDataIsParsedCorrectly_Orig(
+ bool useIdentify,
+ string imagePath,
+ int expectedPixelSize,
+ bool exifProfilePresent,
+ bool iccProfilePresent)
+ {
+ TestMetaDataImpl(
+ useIdentify,
+ OrigJpegDecoder,
+ imagePath,
+ expectedPixelSize,
+ exifProfilePresent,
+ iccProfilePresent);
+ }
+
+ [Theory]
+ [MemberData(nameof(MetaDataTestData))]
+ public void MetaDataIsParsedCorrectly_PdfJs(
+ bool useIdentify,
+ string imagePath,
+ int expectedPixelSize,
+ bool exifProfilePresent,
+ bool iccProfilePresent)
+ {
+ TestMetaDataImpl(
+ useIdentify,
+ PdfJsJpegDecoder,
+ imagePath,
+ expectedPixelSize,
+ exifProfilePresent,
+ iccProfilePresent);
+ }
+
+ private static void TestMetaDataImpl(
+ bool useIdentify,
+ IImageDecoder decoder,
+ string imagePath,
+ int expectedPixelSize,
+ bool exifProfilePresent,
+ bool iccProfilePresent)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ IImageInfo imageInfo = useIdentify
+ ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream)
+ : decoder.Decode(Configuration.Default, stream);
+
+ Assert.NotNull(imageInfo);
+ Assert.NotNull(imageInfo.PixelType);
+
+ if (useIdentify)
+ {
+ Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
+ }
+ else
+ {
+ // When full Image decoding is performed, BitsPerPixel will match TPixel
+ int bpp32 = Unsafe.SizeOf() * 8;
+ Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel);
+ }
+
+ ExifProfile exifProfile = imageInfo.MetaData.ExifProfile;
+
+ if (exifProfilePresent)
+ {
+ Assert.NotNull(exifProfile);
+ Assert.NotEmpty(exifProfile.Values);
+ }
+ else
+ {
+ Assert.Null(exifProfile);
+ }
+
+ IccProfile iccProfile = imageInfo.MetaData.IccProfile;
+
+ if (iccProfilePresent)
+ {
+ Assert.NotNull(iccProfile);
+ Assert.NotEmpty(iccProfile.Entries);
+ }
+ else
+ {
+ Assert.Null(iccProfile);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void IgnoreMetaData_ControlsWhetherMetaDataIsParsed(bool ignoreMetaData)
+ {
+ var decoder = new JpegDecoder() { IgnoreMetadata = ignoreMetaData };
+
+ // Snake.jpg has both Exif and ICC profiles defined:
+ var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake);
+
+ using (Image image = testFile.CreateImage(decoder))
+ {
+ if (ignoreMetaData)
+ {
+ Assert.Null(image.MetaData.ExifProfile);
+ Assert.Null(image.MetaData.IccProfile);
+ }
+ else
+ {
+ Assert.NotNull(image.MetaData.ExifProfile);
+ Assert.NotNull(image.MetaData.IccProfile);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index 0b8daac72..3138300b9 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -22,7 +22,7 @@ using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
// TODO: Scatter test cases into multiple test classes
- public class JpegDecoderTests
+ public partial class JpegDecoderTests
{
public static string[] BaselineTestJpegs =
{
@@ -115,9 +115,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
- private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder();
+ private static OrigJpegDecoder OrigJpegDecoder => new OrigJpegDecoder();
- private static IImageDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder();
+ private static PdfJsJpegDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder();
[Fact]
public void ParseStream_BasicPropertiesAreCorrect1_PdfJs()
@@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// For 32 bit test enviroments:
provider.Configuration.MemoryManager = ArrayPoolMemoryManager.CreateWithModeratePooling();
- IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder;
+ IImageDecoder decoder = useOldDecoder ? (IImageDecoder)OrigJpegDecoder : PdfJsJpegDecoder;
using (Image image = provider.GetImage(decoder))
{
image.DebugSave(provider);
@@ -406,39 +406,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(72, image.MetaData.VerticalResolution);
}
}
-
- [Fact]
- public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead()
- {
- var decoder = new JpegDecoder()
- {
- IgnoreMetadata = false
- };
-
- var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
-
- using (Image image = testFile.CreateImage(decoder))
- {
- Assert.NotNull(image.MetaData.ExifProfile);
- }
- }
-
- [Fact]
- public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored()
- {
- var options = new JpegDecoder()
- {
- IgnoreMetadata = true
- };
-
- var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
-
- using (Image image = testFile.CreateImage(options))
- {
- Assert.Null(image.MetaData.ExifProfile);
- }
- }
-
+
// DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\"
@@ -470,37 +438,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}");
}
}
-
- [Theory]
- [InlineData(TestImages.Jpeg.Progressive.Progress, 24)]
- [InlineData(TestImages.Jpeg.Progressive.Fb, 24)]
- [InlineData(TestImages.Jpeg.Baseline.Cmyk, 32)]
- [InlineData(TestImages.Jpeg.Baseline.Ycck, 32)]
- [InlineData(TestImages.Jpeg.Baseline.Jpeg400, 8)]
- [InlineData(TestImages.Jpeg.Baseline.Snake, 24)]
- public void DetectPixelSizeGolang(string imagePath, int expectedPixelSize)
- {
- var testFile = TestFile.Create(imagePath);
- using (var stream = new MemoryStream(testFile.Bytes, false))
- {
- Assert.Equal(expectedPixelSize, ((IImageInfoDetector)OrigJpegDecoder).Identify(Configuration.Default, stream)?.PixelType?.BitsPerPixel);
- }
- }
-
- [Theory]
- [InlineData(TestImages.Jpeg.Progressive.Progress, 24)]
- [InlineData(TestImages.Jpeg.Progressive.Fb, 24)]
- [InlineData(TestImages.Jpeg.Baseline.Cmyk, 32)]
- [InlineData(TestImages.Jpeg.Baseline.Ycck, 32)]
- [InlineData(TestImages.Jpeg.Baseline.Jpeg400, 8)]
- [InlineData(TestImages.Jpeg.Baseline.Snake, 24)]
- public void DetectPixelSizePdfJs(string imagePath, int expectedPixelSize)
- {
- var testFile = TestFile.Create(imagePath);
- using (var stream = new MemoryStream(testFile.Bytes, false))
- {
- Assert.Equal(expectedPixelSize, ((IImageInfoDetector)PdfJsJpegDecoder).Identify(Configuration.Default, stream)?.PixelType?.BitsPerPixel);
- }
- }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
index 0d563a7b7..b665d69e8 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
@@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue;
- using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true))
+ using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, false))
{
Assert.Equal(expecteColorSpace, decoder.ColorSpace);
}
@@ -42,11 +42,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact]
public void ComponentScalingIsCorrect_1ChannelJpeg()
{
- using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400, true))
+ using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400, false))
{
Assert.Equal(1, decoder.ComponentCount);
Assert.Equal(1, decoder.Components.Length);
-
+
Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8);
Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU);
@@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var sb = new StringBuilder();
- using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true))
+ using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, false))
{
sb.AppendLine(imageFile);
sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}");
@@ -103,23 +103,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Size fLuma = (Size)expectedLumaFactors;
Size fChroma = (Size)expectedChromaFactors;
- using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true))
+ using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, false))
{
Assert.Equal(componentCount, decoder.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
-
+
OrigComponent c0 = decoder.Components[0];
OrigComponent c1 = decoder.Components[1];
OrigComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1);
- Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma) ;
+ Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma);
Size divisor = fLuma.DivideBy(fChroma);
Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor);
-
+
VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1);
VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor);
VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor);