diff --git a/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf b/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf new file mode 100644 index 000000000..3423fb315 Binary files /dev/null and b/src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf differ diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs index 84ceff7cc..0ec2297d7 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs @@ -7,33 +7,80 @@ using System; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// - /// Provides information about the Adobe marker segment + /// Provides information about the Adobe marker segment. /// + /// See the included 5116.DCT.pdf file in the source for more information. internal struct AdobeMarker : IEquatable { /// - /// The DCT Encode Version + /// Gets the length of an adobe marker segment. /// - public short DCTEncodeVersion; + public const int Length = 12; /// - /// 0x0 : (none) + /// Initializes a new instance of the struct. + /// + /// The DCT encode version + /// The horizontal downsampling hint used for DCT encoding + /// The vertical downsampling hint used for DCT encoding + /// The color transform model used + private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1, byte colorTransform) + { + this.DCTEncodeVersion = dctEncodeVersion; + this.APP14Flags0 = app14Flags0; + this.APP14Flags1 = app14Flags1; + this.ColorTransform = colorTransform; + } + + /// + /// Gets the DCT Encode Version + /// + public short DCTEncodeVersion { get; } + + /// + /// Gets the horizontal downsampling hint used for DCT encoding + /// 0x0 : (none - Chop) /// Bit 15 : Encoded with Blend=1 downsampling /// - public short APP14Flags0; + public short APP14Flags0 { get; } /// - /// 0x0 : (none) + /// Gets the vertical downsampling hint used for DCT encoding + /// 0x0 : (none - Chop) + /// Bit 15 : Encoded with Blend=1 downsampling /// - public short APP14Flags1; + public short APP14Flags1 { get; } /// - /// Determines the colorspace transform + /// Gets the colorspace transform model used /// 00 : Unknown (RGB or CMYK) /// 01 : YCbCr /// 02 : YCCK /// - public byte ColorTransform; + public byte ColorTransform { get; } + + /// + /// Converts the specified byte array representation of an Adobe marker to its equivalent and + /// returns a value that indicates whether the conversion succeeded. + /// + /// The byte array containing metadata to parse + /// The marker to return. + public static bool TryParse(byte[] bytes, out AdobeMarker marker) + { + if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker)) + { + short dctEncodeVersion = (short)((bytes[5] << 8) | bytes[6]); + short app14Flags0 = (short)((bytes[7] << 8) | bytes[8]); + short app14Flags1 = (short)((bytes[9] << 8) | bytes[10]); + byte colorTransform = bytes[11]; + + marker = new AdobeMarker(dctEncodeVersion, app14Flags0, app14Flags1, colorTransform); + return true; + } + + marker = default(AdobeMarker); + return false; + } /// public bool Equals(AdobeMarker other) @@ -57,19 +104,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// public override int GetHashCode() - { - return GetHashCode(this); - } - - private static int GetHashCode(AdobeMarker marker) { return HashHelpers.Combine( - marker.DCTEncodeVersion.GetHashCode(), + this.DCTEncodeVersion.GetHashCode(), HashHelpers.Combine( - marker.APP14Flags0.GetHashCode(), + this.APP14Flags0.GetHashCode(), HashHelpers.Combine( - marker.APP14Flags1.GetHashCode(), - marker.ColorTransform.GetHashCode()))); + this.APP14Flags1.GetHashCode(), + this.ColorTransform.GetHashCode()))); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index d68a90155..fa890a2a5 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -67,14 +67,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private bool isExif; /// - /// Whether the image has an Adobe marker + /// Whether the image has an Adobe marker. + /// It's faster to check this than to use the equality operator on the struct /// private bool isAdobe; /// - /// The Adobe color transform value for determining what color space the image uses. + /// Contains information about the Adobe marker /// - private byte adobeColorTransform; + private AdobeMarker adobe; + + /// + /// Contains information about the JFIF marker + /// + private JFifMarker jFif; /// /// Initializes a new instance of the class. @@ -515,21 +521,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// The remaining bytes in the segment block. private void ProcessApp14Marker(int remaining) { - if (remaining < 12) + const int MarkerLength = AdobeMarker.Length; + if (remaining < MarkerLength) { // Skip the application header length this.InputProcessor.Skip(remaining); return; } - this.InputProcessor.ReadFull(this.Temp, 0, 12); - remaining -= 12; + this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength); + remaining -= MarkerLength; - if (ProfileResolver.IsProfile(this.Temp, ProfileResolver.AdobeMarker)) - { - this.isAdobe = true; - this.adobeColorTransform = this.Temp[11]; - } + this.isAdobe = AdobeMarker.TryParse(this.Temp, out this.adobe); if (remaining > 0) { @@ -750,19 +753,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort case 1: return JpegColorSpace.GrayScale; case 3: - if (!this.isAdobe || this.adobeColorTransform == OrigJpegConstants.Adobe.ColorTransformYCbCr) + if (!this.isAdobe || this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYCbCr) { return JpegColorSpace.YCbCr; } - if (this.adobeColorTransform == OrigJpegConstants.Adobe.ColorTransformUnknown) + if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformUnknown) { return JpegColorSpace.RGB; } break; case 4: - if (this.adobeColorTransform == OrigJpegConstants.Adobe.ColorTransformYcck) + if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYcck) { return JpegColorSpace.Ycck; } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 8ed1e3103..ad13098ba 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -525,32 +525,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// The remaining bytes in the segment block. private void ProcessApp14Marker(int remaining) { - if (remaining < 12) + const int MarkerLength = AdobeMarker.Length; + if (remaining < MarkerLength) { // Skip the application header length this.InputStream.Skip(remaining); return; } - this.InputStream.Read(this.temp, 0, 12); - remaining -= 12; + this.InputStream.Read(this.temp, 0, MarkerLength); + remaining -= MarkerLength; - bool isAdobe = this.temp[0] == PdfJsJpegConstants.Markers.Adobe.A && - this.temp[1] == PdfJsJpegConstants.Markers.Adobe.D && - this.temp[2] == PdfJsJpegConstants.Markers.Adobe.O && - this.temp[3] == PdfJsJpegConstants.Markers.Adobe.B && - this.temp[4] == PdfJsJpegConstants.Markers.Adobe.E; - - if (isAdobe) - { - this.adobe = new AdobeMarker - { - DCTEncodeVersion = (short)((this.temp[5] << 8) | this.temp[6]), - APP14Flags0 = (short)((this.temp[7] << 8) | this.temp[8]), - APP14Flags1 = (short)((this.temp[9] << 8) | this.temp[10]), - ColorTransform = this.temp[11] - }; - } + AdobeMarker.TryParse(this.temp, out this.adobe); if (remaining > 0) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs new file mode 100644 index 000000000..7d179b040 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + + using Xunit; + + public class AdobeMarkerTests + { + // Taken from actual test image + private readonly byte[] bytes = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x2 }; + + [Fact] + public void MarkerLengthIsCorrect() + { + Assert.Equal(12, AdobeMarker.Length); + } + + [Fact] + public void MarkerReturnsCorrectParsedValue() + { + bool isAdobe = AdobeMarker.TryParse(this.bytes, out var marker); + + Assert.True(isAdobe); + Assert.Equal(100, marker.DCTEncodeVersion); + Assert.Equal(0, marker.APP14Flags0); + Assert.Equal(0, marker.APP14Flags1); + Assert.Equal(OrigJpegConstants.Adobe.ColorTransformYcck, marker.ColorTransform); + } + + [Fact] + public void MarkerIgnoresIncorrectValue() + { + bool isAdobe = AdobeMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out var marker); + + Assert.False(isAdobe); + Assert.Equal(default(AdobeMarker), marker); + } + } +} \ No newline at end of file