From ed9bd16cd35d3c8da38cb80ac18d3cb413dcd341 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 16 Aug 2021 17:05:56 +0200 Subject: [PATCH] Split WriteApplicationHeader into WriteStartOfImage and WriteJfifApplicationHeader. Do not write WriteJfifApplicationHeader with RGB. --- .../Jpeg/Components/Encoder/QuantIndex.cs | 10 ++-- src/ImageSharp/Formats/Jpeg/JpegColorType.cs | 2 +- .../Formats/Jpeg/JpegEncoderCore.cs | 58 ++++++++++++------- .../Formats/Jpg/JpegEncoderTests.cs | 17 +++--- 4 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs index 5eee5dfde..f9d0fba57 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs @@ -1,21 +1,21 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// - /// Enumerates the quantization tables + /// Enumerates the quantization tables. /// internal enum QuantIndex { /// - /// The luminance quantization table index + /// The luminance quantization table index. /// Luminance = 0, /// - /// The chrominance quantization table index + /// The chrominance quantization table index. /// Chrominance = 1, } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs index d6a9542c3..c15038c23 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. - /// + /// /// Note: Not supported by the encoder. /// YCbCrRatio410 = 4, diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 546de74da..7f411ee53 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -111,7 +111,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); // Write the Start Of Image marker. - this.WriteApplicationHeader(metadata); + this.WriteStartOfImage(); + + // Do not write APP0 marker for RGB colorspace. + if (this.colorType != JpegColorType.Rgb) + { + this.WriteJfifApplicationHeader(metadata); + } // Write Exif, ICC and IPTC profiles this.WriteProfiles(metadata); @@ -212,52 +218,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Writes the application header containing the JFIF identifier plus extra data. + /// Write the start of image marker. /// - /// The image metadata. - private void WriteApplicationHeader(ImageMetadata meta) + private void WriteStartOfImage() { - // Write the start of image marker. Markers are always prefixed with 0xff. + // Markers are always prefixed with 0xff. this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[1] = JpegConstants.Markers.SOI; + this.outputStream.Write(this.buffer, 0, 2); + } + + /// + /// Writes the application header containing the JFIF identifier plus extra data. + /// + /// The image metadata. + private void WriteJfifApplicationHeader(ImageMetadata meta) + { // Write the JFIF headers - this.buffer[2] = JpegConstants.Markers.XFF; - this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker - this.buffer[4] = 0x00; - this.buffer[5] = 0x10; - this.buffer[6] = 0x4a; // J + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[2] = 0x00; + this.buffer[3] = 0x10; + this.buffer[4] = 0x4a; // J + this.buffer[5] = 0x46; // F + this.buffer[6] = 0x49; // I this.buffer[7] = 0x46; // F - this.buffer[8] = 0x49; // I - this.buffer[9] = 0x46; // F - this.buffer[10] = 0x00; // = "JFIF",'\0' - this.buffer[11] = 0x01; // versionhi - this.buffer[12] = 0x01; // versionlo + this.buffer[8] = 0x00; // = "JFIF",'\0' + this.buffer[9] = 0x01; // versionhi + this.buffer[10] = 0x01; // versionlo // Resolution. Big Endian - Span hResolution = this.buffer.AsSpan(14, 2); - Span vResolution = this.buffer.AsSpan(16, 2); + Span hResolution = this.buffer.AsSpan(12, 2); + Span vResolution = this.buffer.AsSpan(14, 2); if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) { // Scale down to PPI - this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits + this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); } else { // We can simply pass the value. - this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits + this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); } // No thumbnail - this.buffer[18] = 0x00; // Thumbnail width - this.buffer[19] = 0x00; // Thumbnail height + this.buffer[16] = 0x00; // Thumbnail width + this.buffer[17] = 0x00; // Thumbnail height - this.outputStream.Write(this.buffer, 0, 20); + this.outputStream.Write(this.buffer, 0, 18); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index a30a5611f..2bd2961de 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -162,22 +162,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] + [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); @@ -226,14 +227,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (quality < 50) { - tolerance *= 2.5f; + tolerance *= 4.5f; } else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420) { - tolerance *= 1.5f; + tolerance *= 2.0f; if (colorType == JpegColorType.YCbCrRatio420) { - tolerance *= 2f; + tolerance *= 2.0f; } }