diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
index 4c3c7689f..49d9b591f 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
@@ -7,22 +7,67 @@ namespace ImageSharp.Formats.Jpg
{
using System.Buffers;
+ ///
+ /// A structure to store unprocessed instances and their coordinates while scanning the image.
+ ///
internal struct DecodedBlockMemento
{
///
- /// The used to pool data in .
- /// Should always clean arrays when returning!
+ /// A value indicating whether the instance is initialized.
///
- public static readonly ArrayPool ArrayPool = ArrayPool.Create();
+ public bool Initialized;
+ ///
+ /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
+ ///
public int Bx;
+ ///
+ /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
+ ///
public int By;
+ ///
+ /// The
+ ///
public Block8x8F Block;
+ ///
+ /// The used to pool data in .
+ /// Should always clean arrays when returning!
+ ///
+ private static readonly ArrayPool ArrayPool = ArrayPool.Create();
+
+ ///
+ /// Rent an array of -s from the pool.
+ ///
+ /// The requested array size
+ /// An array of -s
+ public static DecodedBlockMemento[] RentArray(int size)
+ {
+ return ArrayPool.Rent(size);
+ }
+
+ ///
+ /// Returns the array to the pool.
+ ///
+ /// The array
+ public static void ReturnArray(DecodedBlockMemento[] blockArray)
+ {
+ ArrayPool.Return(blockArray, true);
+ }
+
+ ///
+ /// Store the block data into a at the given index.
+ ///
+ /// The array of
+ /// The index in the array
+ /// X coordinate of the block
+ /// Y coordinate of the block
+ /// The
public static void Store(DecodedBlockMemento[] blockArray, int index, int bx, int by, ref Block8x8F block)
{
+ blockArray[index].Initialized = true;
blockArray[index].Bx = bx;
blockArray[index].By = by;
blockArray[index].Block = block;
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
index ab376ee3c..4dcf6def8 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
@@ -30,12 +30,12 @@ namespace ImageSharp.Formats.Jpg
///
/// The AC table index
///
- private const int AcTableIndex = 1;
+ public const int AcTableIndex = 1;
///
/// The DC table index
///
- private const int DcTableIndex = 0;
+ public const int DcTableIndex = 0;
///
/// The current component index
@@ -98,7 +98,7 @@ namespace ImageSharp.Formats.Jpg
private ComputationData data;
///
- /// Initializes the default instance after creation.
+ /// Initializes a default-constructed instance for reading data from -s stream.
///
/// Pointer to on the stack
/// The instance
@@ -109,12 +109,20 @@ namespace ImageSharp.Formats.Jpg
p->InitStreamReadingImpl(decoder, remaining);
}
+ ///
+ /// Initializes a default-constructed instance, filling the data and setting the pointers.
+ ///
+ /// Pointer to on the stack
public static void Init(JpegScanDecoder* p)
{
p->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data);
}
+ ///
+ /// Loads the data from the given into the block.
+ ///
+ /// The
public void LoadMemento(ref DecodedBlockMemento memento)
{
this.bx = memento.Bx;
@@ -184,7 +192,6 @@ namespace ImageSharp.Formats.Jpg
}
this.ReadBlock(decoder, scanIndex);
- //this.ProcessBlock(decoder);
}
// for j
@@ -415,10 +422,6 @@ namespace ImageSharp.Formats.Jpg
DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref *b);
}
- private bool IsProgressiveBlockFinished(JpegDecoderCore decoder)
- => decoder.IsProgressive && (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0);
-
-
private DecoderErrorCode DecodeEobRun(int count, JpegDecoderCore decoder)
{
uint bitsResult;
@@ -663,7 +666,5 @@ namespace ImageSharp.Formats.Jpg
return zig;
}
-
-
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
index f8adedd26..2184e9e1f 100644
--- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
@@ -40,8 +40,6 @@ namespace ImageSharp.Formats
public Bytes Bytes;
#pragma warning restore SA401
-
-
///
/// The App14 marker color-space
///
@@ -180,254 +178,10 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel, IEquatable
{
this.ProcessStream(image, stream, metadataOnly);
- if (metadataOnly) return;
- this.ConvertBlocksToImagePixels(image);
- }
-
- private void ConvertBlocksToImagePixels(Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- this.ProcessBlocks();
-
- if (this.grayImage.IsInitialized)
- {
- this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image);
- }
- else if (this.ycbcrImage != null)
- {
- if (this.ComponentCount == 4)
- {
- if (!this.adobeTransformValid)
- {
- throw new ImageFormatException(
- "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
- }
-
- // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
- // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
- // TODO: YCbCrA?
- if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck)
- {
- this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image);
- }
- else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
- {
- // Assume CMYK
- this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image);
- }
-
- return;
- }
-
- if (this.ComponentCount == 3)
- {
- if (this.IsRGB())
- {
- this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image);
- return;
- }
-
- this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image);
- return;
- }
-
- throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces.");
- }
- else
- {
- throw new ImageFormatException("Missing SOS marker.");
- }
- }
-
- private void ProcessBlocks()
- where TColor : struct, IPackedPixel, IEquatable
- {
- JpegScanDecoder scanDecoder = default(JpegScanDecoder);
- JpegScanDecoder.Init(&scanDecoder);
-
- for(int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++)
+ if (!metadataOnly)
{
- scanDecoder.ComponentIndex = componentIndex;
- DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex];
- for (int i = 0; i < blockArray.Length; i++)
- {
- scanDecoder.LoadMemento(ref blockArray[i]);
- scanDecoder.ProcessBlock(this);
- }
- }
- }
-
- private void ProcessStream(Image image, Stream stream, bool metadataOnly)
- where TColor : struct, IPackedPixel, IEquatable
- {
- this.InputStream = stream;
-
- // Check for the Start Of Image marker.
- this.ReadFull(this.Temp, 0, 2);
- if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
- {
- throw new ImageFormatException("Missing SOI marker.");
- }
-
- // Process the remaining segments until the End Of Image marker.
- bool processBytes = true;
-
- // we can't currently short circute progressive images so don't try.
- while (processBytes)
- {
- this.ReadFull(this.Temp, 0, 2);
- while (this.Temp[0] != 0xff)
- {
- // Strictly speaking, this is a format error. However, libjpeg is
- // liberal in what it accepts. As of version 9, next_marker in
- // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
- // continues to decode the stream. Even before next_marker sees
- // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
- // bytes as it can, possibly past the end of a scan's data. It
- // effectively puts back any markers that it overscanned (e.g. an
- // "\xff\xd9" EOI marker), but it does not put back non-marker data,
- // and thus it can silently ignore a small number of extraneous
- // non-marker bytes before next_marker has a chance to see them (and
- // print a warning).
- // We are therefore also liberal in what we accept. Extraneous data
- // is silently ignore
- // This is similar to, but not exactly the same as, the restart
- // mechanism within a scan (the RST[0-7] markers).
- // Note that extraneous 0xff bytes in e.g. SOS data are escaped as
- // "\xff\x00", and so are detected a little further down below.
- this.Temp[0] = this.Temp[1];
- this.Temp[1] = this.ReadByte();
- }
-
- byte marker = this.Temp[1];
- if (marker == 0)
- {
- // Treat "\xff\x00" as extraneous data.
- continue;
- }
-
- while (marker == 0xff)
- {
- // Section B.1.1.2 says, "Any marker may optionally be preceded by any
- // number of fill bytes, which are bytes assigned code X'FF'".
- marker = this.ReadByte();
- }
-
- // End Of Image.
- if (marker == JpegConstants.Markers.EOI)
- {
- break;
- }
-
- if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
- {
- // Figures B.2 and B.16 of the specification suggest that restart markers should
- // only occur between Entropy Coded Segments and not after the final ECS.
- // However, some encoders may generate incorrect JPEGs with a final restart
- // marker. That restart marker will be seen here instead of inside the ProcessSOS
- // method, and is ignored as a harmless error. Restart markers have no extra data,
- // so we check for this before we read the 16-bit length of the segment.
- continue;
- }
-
- // Read the 16-bit length of the segment. The value includes the 2 bytes for the
- // length itself, so we subtract 2 to get the number of remaining bytes.
- this.ReadFull(this.Temp, 0, 2);
- int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
- if (remaining < 0)
- {
- throw new ImageFormatException("Short segment length.");
- }
-
- switch (marker)
- {
- case JpegConstants.Markers.SOF0:
- case JpegConstants.Markers.SOF1:
- case JpegConstants.Markers.SOF2:
- this.IsProgressive = marker == JpegConstants.Markers.SOF2;
- this.ProcessStartOfFrameMarker(remaining);
- if (metadataOnly && this.isJfif)
- {
- return;
- }
-
- break;
- case JpegConstants.Markers.DHT:
- if (metadataOnly)
- {
- this.Skip(remaining);
- }
- else
- {
- this.ProcessDefineHuffmanTablesMarker(remaining);
- }
-
- break;
- case JpegConstants.Markers.DQT:
- if (metadataOnly)
- {
- this.Skip(remaining);
- }
- else
- {
- this.ProcessDqt(remaining);
- }
-
- break;
- case JpegConstants.Markers.SOS:
- if (metadataOnly)
- {
- return;
- }
-
- // when this is a progressive image this gets called a number of times
- // need to know how many times this should be called in total.
- this.ProcessStartOfScan(remaining);
- if (!this.IsProgressive)
- {
- // if this is not a progressive image we can stop processing bytes as we now have the image data.
- processBytes = false;
- }
-
- break;
- case JpegConstants.Markers.DRI:
- if (metadataOnly)
- {
- this.Skip(remaining);
- }
- else
- {
- this.ProcessDefineRestartIntervalMarker(remaining);
- }
-
- break;
- case JpegConstants.Markers.APP0:
- this.ProcessApplicationHeader(remaining);
- break;
- case JpegConstants.Markers.APP1:
- this.ProcessApp1Marker(remaining, image);
- break;
- case JpegConstants.Markers.APP14:
- this.ProcessApp14Marker(remaining);
- break;
- default:
- if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
- || marker == JpegConstants.Markers.COM)
- {
- this.Skip(remaining);
- }
- else if (marker < JpegConstants.Markers.SOF0)
- {
- // See Table B.1 "Marker code assignments".
- throw new ImageFormatException("Unknown marker");
- }
- else
- {
- throw new ImageFormatException("Unknown marker");
- }
-
- break;
- }
+ this.DecodeBlocksIntoJpegImageChannels();
+ this.ConvertJpegPixelsToImagePixels(image);
}
}
@@ -445,7 +199,7 @@ namespace ImageSharp.Formats
{
if (blockArray != null)
{
- DecodedBlockMemento.ArrayPool.Return(blockArray, true);
+ DecodedBlockMemento.ReturnArray(blockArray);
}
}
@@ -701,6 +455,267 @@ namespace ImageSharp.Formats
packed.PackFromBytes(r, g, b, 255);
}
+ ///
+ /// Read metadata from stream and read the blocks in the scans into .
+ ///
+ /// The pixel type
+ /// The
+ /// The stream
+ /// Whether to decode metadata only.
+ private void ProcessStream(Image image, Stream stream, bool metadataOnly)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ this.InputStream = stream;
+
+ // Check for the Start Of Image marker.
+ this.ReadFull(this.Temp, 0, 2);
+ if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
+ {
+ throw new ImageFormatException("Missing SOI marker.");
+ }
+
+ // Process the remaining segments until the End Of Image marker.
+ bool processBytes = true;
+
+ // we can't currently short circute progressive images so don't try.
+ while (processBytes)
+ {
+ this.ReadFull(this.Temp, 0, 2);
+ while (this.Temp[0] != 0xff)
+ {
+ // Strictly speaking, this is a format error. However, libjpeg is
+ // liberal in what it accepts. As of version 9, next_marker in
+ // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
+ // continues to decode the stream. Even before next_marker sees
+ // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
+ // bytes as it can, possibly past the end of a scan's data. It
+ // effectively puts back any markers that it overscanned (e.g. an
+ // "\xff\xd9" EOI marker), but it does not put back non-marker data,
+ // and thus it can silently ignore a small number of extraneous
+ // non-marker bytes before next_marker has a chance to see them (and
+ // print a warning).
+ // We are therefore also liberal in what we accept. Extraneous data
+ // is silently ignore
+ // This is similar to, but not exactly the same as, the restart
+ // mechanism within a scan (the RST[0-7] markers).
+ // Note that extraneous 0xff bytes in e.g. SOS data are escaped as
+ // "\xff\x00", and so are detected a little further down below.
+ this.Temp[0] = this.Temp[1];
+ this.Temp[1] = this.ReadByte();
+ }
+
+ byte marker = this.Temp[1];
+ if (marker == 0)
+ {
+ // Treat "\xff\x00" as extraneous data.
+ continue;
+ }
+
+ while (marker == 0xff)
+ {
+ // Section B.1.1.2 says, "Any marker may optionally be preceded by any
+ // number of fill bytes, which are bytes assigned code X'FF'".
+ marker = this.ReadByte();
+ }
+
+ // End Of Image.
+ if (marker == JpegConstants.Markers.EOI)
+ {
+ break;
+ }
+
+ if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
+ {
+ // Figures B.2 and B.16 of the specification suggest that restart markers should
+ // only occur between Entropy Coded Segments and not after the final ECS.
+ // However, some encoders may generate incorrect JPEGs with a final restart
+ // marker. That restart marker will be seen here instead of inside the ProcessSOS
+ // method, and is ignored as a harmless error. Restart markers have no extra data,
+ // so we check for this before we read the 16-bit length of the segment.
+ continue;
+ }
+
+ // Read the 16-bit length of the segment. The value includes the 2 bytes for the
+ // length itself, so we subtract 2 to get the number of remaining bytes.
+ this.ReadFull(this.Temp, 0, 2);
+ int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
+ if (remaining < 0)
+ {
+ throw new ImageFormatException("Short segment length.");
+ }
+
+ switch (marker)
+ {
+ case JpegConstants.Markers.SOF0:
+ case JpegConstants.Markers.SOF1:
+ case JpegConstants.Markers.SOF2:
+ this.IsProgressive = marker == JpegConstants.Markers.SOF2;
+ this.ProcessStartOfFrameMarker(remaining);
+ if (metadataOnly && this.isJfif)
+ {
+ return;
+ }
+
+ break;
+ case JpegConstants.Markers.DHT:
+ if (metadataOnly)
+ {
+ this.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineHuffmanTablesMarker(remaining);
+ }
+
+ break;
+ case JpegConstants.Markers.DQT:
+ if (metadataOnly)
+ {
+ this.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDqt(remaining);
+ }
+
+ break;
+ case JpegConstants.Markers.SOS:
+ if (metadataOnly)
+ {
+ return;
+ }
+
+ // when this is a progressive image this gets called a number of times
+ // need to know how many times this should be called in total.
+ this.ProcessStartOfScan(remaining);
+ if (!this.IsProgressive)
+ {
+ // if this is not a progressive image we can stop processing bytes as we now have the image data.
+ processBytes = false;
+ }
+
+ break;
+ case JpegConstants.Markers.DRI:
+ if (metadataOnly)
+ {
+ this.Skip(remaining);
+ }
+ else
+ {
+ this.ProcessDefineRestartIntervalMarker(remaining);
+ }
+
+ break;
+ case JpegConstants.Markers.APP0:
+ this.ProcessApplicationHeader(remaining);
+ break;
+ case JpegConstants.Markers.APP1:
+ this.ProcessApp1Marker(remaining, image);
+ break;
+ case JpegConstants.Markers.APP14:
+ this.ProcessApp14Marker(remaining);
+ break;
+ default:
+ if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
+ || marker == JpegConstants.Markers.COM)
+ {
+ this.Skip(remaining);
+ }
+ else if (marker < JpegConstants.Markers.SOF0)
+ {
+ // See Table B.1 "Marker code assignments".
+ throw new ImageFormatException("Unknown marker");
+ }
+ else
+ {
+ throw new ImageFormatException("Unknown marker");
+ }
+
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Process the blocks in into Jpeg image channels ( and )
+ ///
+ /// The pixel type
+ private void DecodeBlocksIntoJpegImageChannels()
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ JpegScanDecoder scanDecoder = default(JpegScanDecoder);
+ JpegScanDecoder.Init(&scanDecoder);
+
+ for (int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++)
+ {
+ scanDecoder.ComponentIndex = componentIndex;
+ DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex];
+ for (int i = 0; i < blockArray.Length; i++)
+ {
+ scanDecoder.LoadMemento(ref blockArray[i]);
+ scanDecoder.ProcessBlock(this);
+ }
+ }
+ }
+
+ ///
+ /// Convert the pixel data in and/or into pixels of
+ ///
+ /// The pixel type
+ /// The destination image
+ private void ConvertJpegPixelsToImagePixels(Image image)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ if (this.grayImage.IsInitialized)
+ {
+ this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image);
+ }
+ else if (this.ycbcrImage != null)
+ {
+ if (this.ComponentCount == 4)
+ {
+ if (!this.adobeTransformValid)
+ {
+ throw new ImageFormatException(
+ "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ }
+
+ // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+ // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
+ // TODO: YCbCrA?
+ if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck)
+ {
+ this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image);
+ }
+ else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
+ {
+ // Assume CMYK
+ this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image);
+ }
+
+ return;
+ }
+
+ if (this.ComponentCount == 3)
+ {
+ if (this.IsRGB())
+ {
+ this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image);
+ return;
+ }
+
+ this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image);
+ return;
+ }
+
+ throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces.");
+ }
+ else
+ {
+ throw new ImageFormatException("Missing SOS marker.");
+ }
+ }
+
///
/// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
///
@@ -1470,7 +1485,7 @@ namespace ImageSharp.Formats
{
int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor
* this.ComponentArray[i].VerticalFactor;
- this.DecodedBlocks[i] = DecodedBlockMemento.ArrayPool.Rent(size);
+ this.DecodedBlocks[i] = DecodedBlockMemento.RentArray(size);
}
}