From a8657d9bd1a284cd1733a75bb1fc6241a726cbcd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 23 Jan 2017 01:29:32 +0100 Subject: [PATCH] cleanup --- .../Components/Decoder/DecodedBlockMemento.cs | 51 +- .../Components/Decoder/JpegScanDecoder.cs | 21 +- .../JpegDecoderCore.cs | 517 +++++++++--------- 3 files changed, 325 insertions(+), 264 deletions(-) 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); } }