/// <summary> /// Initializes a new instance of the <see cref="OrigJpegDecoderCore" /> class. /// </summary> /// <param name="configuration">The configuration.</param> /// <param name="options">The options.</param> public OrigJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) { 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]; }
/// <summary> /// Read metadata from stream and read the blocks in the scans into <see cref="OrigComponent.SpectralBlocks"/>. /// </summary> /// <param name="stream">The stream</param> /// <param name="metadataOnly">Whether to decode metadata only.</param> public void ParseStream(Stream stream, bool metadataOnly = false) { this.MetaData = new ImageMetaData(); 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); if (this.Temp[0] != OrigJpegConstants.Markers.XFF || this.Temp[1] != OrigJpegConstants.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.InputProcessor.ReadFull(this.Temp, 0, 2); if (this.InputProcessor.ReachedEOF) { // We've reached the end of the stream. processBytes = false; } 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.InputProcessor.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'". this.InputProcessor.ReadByteUnsafe(out marker); if (this.InputProcessor.ReachedEOF) { // We've reached the end of the stream. processBytes = false; break; } } // End Of Image. if (marker == OrigJpegConstants.Markers.EOI) { break; } if (marker >= OrigJpegConstants.Markers.RST0 && marker <= OrigJpegConstants.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.InputProcessor.ReadFullUnsafe(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 OrigJpegConstants.Markers.SOF0: case OrigJpegConstants.Markers.SOF1: case OrigJpegConstants.Markers.SOF2: this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2; this.ProcessStartOfFrameMarker(remaining, metadataOnly); break; case OrigJpegConstants.Markers.DHT: if (metadataOnly) { this.InputProcessor.Skip(remaining); } else { this.ProcessDefineHuffmanTablesMarker(remaining); } break; case OrigJpegConstants.Markers.DQT: if (metadataOnly) { this.InputProcessor.Skip(remaining); } else { this.ProcessDefineQuantizationTablesMarker(remaining); } break; case OrigJpegConstants.Markers.SOS: if (!metadataOnly) { 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; } } 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. processBytes = false; } break; case OrigJpegConstants.Markers.DRI: if (metadataOnly) { this.InputProcessor.Skip(remaining); } else { this.ProcessDefineRestartIntervalMarker(remaining); } break; case OrigJpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(remaining); break; case OrigJpegConstants.Markers.APP1: this.ProcessApp1Marker(remaining); break; case OrigJpegConstants.Markers.APP2: this.ProcessApp2Marker(remaining); break; case OrigJpegConstants.Markers.APP14: this.ProcessApp14Marker(remaining); break; default: if ((marker >= OrigJpegConstants.Markers.APP0 && marker <= OrigJpegConstants.Markers.APP15) || marker == OrigJpegConstants.Markers.COM) { this.InputProcessor.Skip(remaining); } break; } } this.InitDerivedMetaDataProperties(); }