/// <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> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); const ushort max = JpegConstants.MaxLength; if (image.Width >= max || image.Height >= max) { throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } this.outputStream = stream; ImageMetadata metadata = image.Metadata; // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. int qlty = (this.quality ?? metadata.GetJpegMetadata().Quality).Clamp(1, 100); this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); // Convert from a quality rating to a scaling factor. int scale; if (qlty < 50) { scale = 5000 / qlty; } else { scale = 200 - (qlty * 2); } // Initialize the quantization tables. InitQuantizationTable(0, scale, ref this.luminanceQuantTable); InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); // Compute number of components based on input image type. const int componentCount = 3; // Write the Start Of Image marker. this.WriteApplicationHeader(metadata); // Write Exif, ICC and IPTC profiles this.WriteProfiles(metadata); // Write the quantization tables. this.WriteDefineQuantizationTables(); // Write the image dimensions. this.WriteStartOfFrame(image.Width, image.Height, componentCount); // Write the Huffman tables. this.WriteDefineHuffmanTables(componentCount); // Write the image data. this.WriteStartOfScan(image); // Write the End Of Image marker. this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[1] = JpegConstants.Markers.EOI; stream.Write(this.buffer, 0, 2); stream.Flush(); }
/// <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(); }