/// <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);
            }
        }
Beispiel #2
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);
            }
        }
Beispiel #4
0
        /// <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();
        }