/// <summary> /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's, /// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips). /// </summary> /// <param name="tableBytes">The table bytes.</param> /// <param name="huffmanScanDecoder">The scan decoder.</param> public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder) { this.Metadata = new ImageMetadata(); this.QuantizationTables = new Block8x8F[4]; this.scanDecoder = huffmanScanDecoder; using var ms = new MemoryStream(tableBytes); using var stream = new BufferedReadStream(this.Configuration, ms); // Check for the Start Of Image marker. stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } // Read next marker. stream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { if (!fileMarker.Invalid) { // Get the marker length. int remaining = this.ReadUint16(stream) - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOI: break; case JpegConstants.Markers.RST0: case JpegConstants.Markers.RST7: break; case JpegConstants.Markers.DHT: this.ProcessDefineHuffmanTablesMarker(stream, remaining); break; case JpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(stream, remaining); break; case JpegConstants.Markers.DRI: this.ProcessDefineRestartIntervalMarker(stream, remaining); break; case JpegConstants.Markers.EOI: return; } } // Read next marker. stream.Read(this.markerBuffer, 0, 2); fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); } }
/// <inheritdoc/> public Image <TPixel> Decode <TPixel>(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); try { return(decoder.Decode <TPixel>(stream)); } catch (InvalidMemoryOperationException ex) { (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); JpegThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); // Not reachable, as the previous statement will throw a exception. return(null); } }
/// <summary> /// Parses the input stream for file markers. /// </summary> /// <param name="stream">The input stream.</param> /// <param name="scanDecoder">Scan decoder used exclusively to decode SOS marker.</param> /// <param name="cancellationToken">The token to monitor cancellation.</param> internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken) { bool metadataOnly = scanDecoder == null; this.scanDecoder = scanDecoder; this.Metadata = new ImageMetadata(); // Check for the Start Of Image marker. stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } stream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); this.QuantizationTables ??= new Block8x8F[4]; // Break only when we discover a valid EOI marker. // https://github.com/SixLabors/ImageSharp/issues/695 while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { cancellationToken.ThrowIfCancellationRequested(); if (!fileMarker.Invalid) { // Get the marker length. int remaining = this.ReadUint16(stream) - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); break; case JpegConstants.Markers.SOF5: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential sequential DCT is not supported."); break; case JpegConstants.Markers.SOF6: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential progressive DCT is not supported."); break; case JpegConstants.Markers.SOF3: case JpegConstants.Markers.SOF7: JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported."); break; case JpegConstants.Markers.SOF9: case JpegConstants.Markers.SOF10: case JpegConstants.Markers.SOF11: case JpegConstants.Markers.SOF13: case JpegConstants.Markers.SOF14: case JpegConstants.Markers.SOF15: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported."); break; case JpegConstants.Markers.SOS: if (!metadataOnly) { this.ProcessStartOfScanMarker(stream, remaining); 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 JpegConstants.Markers.DHT: if (metadataOnly) { stream.Skip(remaining); } else { this.ProcessDefineHuffmanTablesMarker(stream, remaining); } break; case JpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(stream, remaining); break; case JpegConstants.Markers.DRI: if (metadataOnly) { stream.Skip(remaining); } else { this.ProcessDefineRestartIntervalMarker(stream, remaining); } break; case JpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(stream, remaining); break; case JpegConstants.Markers.APP1: this.ProcessApp1Marker(stream, remaining); break; case JpegConstants.Markers.APP2: this.ProcessApp2Marker(stream, remaining); break; case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP4: case JpegConstants.Markers.APP5: case JpegConstants.Markers.APP6: case JpegConstants.Markers.APP7: case JpegConstants.Markers.APP8: case JpegConstants.Markers.APP9: case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: stream.Skip(remaining); break; case JpegConstants.Markers.APP13: this.ProcessApp13Marker(stream, remaining); break; case JpegConstants.Markers.APP14: this.ProcessApp14Marker(stream, remaining); break; case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: stream.Skip(remaining); break; case JpegConstants.Markers.DAC: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported."); break; } } // Read on. fileMarker = FindNextFileMarker(this.markerBuffer, stream); } }
/// <summary> /// Parses the input stream for file markers /// </summary> /// <param name="stream">The input 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 = new DoubleBufferedStreamReader(this.Configuration.MemoryAllocator, stream); // Check for the Start Of Image marker. this.InputStream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } this.InputStream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2); this.QuantizationTables = new Block8x8F[4]; // Only assign what we need if (!metadataOnly) { const int maxTables = 4; this.dcHuffmanTables = new HuffmanTable[maxTables]; this.acHuffmanTables = new HuffmanTable[maxTables]; } // Break only when we discover a valid EOI marker. // https://github.com/SixLabors/ImageSharp/issues/695 while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { if (!fileMarker.Invalid) { // Get the marker length int remaining = this.ReadUint16() - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: this.ProcessStartOfFrameMarker(remaining, fileMarker, metadataOnly); break; case JpegConstants.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 JpegConstants.Markers.DHT: if (metadataOnly) { this.InputStream.Skip(remaining); } else { this.ProcessDefineHuffmanTablesMarker(remaining); } break; case JpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(remaining); break; case JpegConstants.Markers.DRI: if (metadataOnly) { this.InputStream.Skip(remaining); } else { this.ProcessDefineRestartIntervalMarker(remaining); } break; case JpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(remaining); break; case JpegConstants.Markers.APP1: this.ProcessApp1Marker(remaining); break; case JpegConstants.Markers.APP2: this.ProcessApp2Marker(remaining); break; case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP4: case JpegConstants.Markers.APP5: case JpegConstants.Markers.APP6: case JpegConstants.Markers.APP7: case JpegConstants.Markers.APP8: case JpegConstants.Markers.APP9: case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: this.InputStream.Skip(remaining); break; case JpegConstants.Markers.APP13: this.ProcessApp13Marker(remaining); break; case JpegConstants.Markers.APP14: this.ProcessApp14Marker(remaining); break; case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: this.InputStream.Skip(remaining); break; } } // Read on. fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream); } }
/// <summary> /// Encode writes the image to the jpeg baseline format with the given options. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The image to write from.</param> /// <param name="stream">The stream to write to.</param> /// <param name="cancellationToken">The token to request cancellation.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength) { JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); } cancellationToken.ThrowIfCancellationRequested(); this.outputStream = stream; ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); // If the color type was not specified by the user, preserve the color type of the input image. if (!this.colorType.HasValue) { this.colorType = GetFallbackColorType(image); } // Compute number of components based on color type in options. int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; ReadOnlySpan <byte> componentIds = this.GetComponentIds(); // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that // Initialize the quantization tables. this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); // Write the Start Of Image marker. this.WriteStartOfImage(); // Do not write APP0 marker for RGB colorspace. if (this.colorType != JpegColorType.Rgb) { this.WriteJfifApplicationHeader(metadata); } // Write Exif, XMP, ICC and IPTC profiles this.WriteProfiles(metadata); if (this.colorType == JpegColorType.Rgb) { // Write App14 marker to indicate RGB color space. this.WriteApp14Marker(); } // Write the quantization tables. this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); // Write the image dimensions. this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds); // Write the Huffman tables. this.WriteDefineHuffmanTables(componentCount); // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); // Write the scan compressed data. switch (this.colorType) { case JpegColorType.YCbCrRatio444: new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); break; case JpegColorType.YCbCrRatio420: new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); break; case JpegColorType.Luminance: new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); break; case JpegColorType.Rgb: new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken); break; default: // all other non-supported color types are checked at the start of this method break; } // Write the End Of Image marker. this.WriteEndOfImageMarker(); stream.Flush(); }