Browse Source

Merge branch 'master' into master

af/merge-core
Nikita Balabaev 9 years ago
committed by GitHub
parent
commit
3f14919608
  1. BIN
      src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf
  2. 117
      src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs
  3. 127
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs
  4. 59
      src/ImageSharp/Formats/Jpeg/Common/Decoder/ProfileResolver.cs
  5. 8
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs
  6. 11
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  7. 3
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs
  8. 37
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs
  9. 61
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs
  10. 297
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  11. 72
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsAdobe.cs
  12. 77
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJFif.cs
  13. 53
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  14. 22
      tests/ImageSharp.Tests/FileTestBase.cs
  15. 82
      tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs
  16. 95
      tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs
  17. 79
      tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs

BIN
src/ImageSharp/Formats/Jpeg/5116.DCT_Filter.pdf

Binary file not shown.

117
src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs

@ -0,0 +1,117 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Provides information about the Adobe marker segment.
/// </summary>
/// <remarks>See the included 5116.DCT.pdf file in the source for more information.</remarks>
internal struct AdobeMarker : IEquatable<AdobeMarker>
{
/// <summary>
/// Gets the length of an adobe marker segment.
/// </summary>
public const int Length = 12;
/// <summary>
/// Initializes a new instance of the <see cref="AdobeMarker"/> struct.
/// </summary>
/// <param name="dctEncodeVersion">The DCT encode version</param>
/// <param name="app14Flags0">The horizontal downsampling hint used for DCT encoding</param>
/// <param name="app14Flags1">The vertical downsampling hint used for DCT encoding</param>
/// <param name="colorTransform">The color transform model used</param>
private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1, byte colorTransform)
{
this.DCTEncodeVersion = dctEncodeVersion;
this.APP14Flags0 = app14Flags0;
this.APP14Flags1 = app14Flags1;
this.ColorTransform = colorTransform;
}
/// <summary>
/// Gets the DCT Encode Version
/// </summary>
public short DCTEncodeVersion { get; }
/// <summary>
/// Gets the horizontal downsampling hint used for DCT encoding
/// 0x0 : (none - Chop)
/// Bit 15 : Encoded with Blend=1 downsampling
/// </summary>
public short APP14Flags0 { get; }
/// <summary>
/// Gets the vertical downsampling hint used for DCT encoding
/// 0x0 : (none - Chop)
/// Bit 15 : Encoded with Blend=1 downsampling
/// </summary>
public short APP14Flags1 { get; }
/// <summary>
/// Gets the colorspace transform model used
/// 00 : Unknown (RGB or CMYK)
/// 01 : YCbCr
/// 02 : YCCK
/// </summary>
public byte ColorTransform { get; }
/// <summary>
/// Converts the specified byte array representation of an Adobe marker to its <see cref="AdobeMarker"/> equivalent and
/// returns a value that indicates whether the conversion succeeded.
/// </summary>
/// <param name="bytes">The byte array containing metadata to parse</param>
/// <param name="marker">The marker to return.</param>
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;
}
/// <inheritdoc/>
public bool Equals(AdobeMarker other)
{
return this.DCTEncodeVersion == other.DCTEncodeVersion
&& this.APP14Flags0 == other.APP14Flags0
&& this.APP14Flags1 == other.APP14Flags1
&& this.ColorTransform == other.ColorTransform;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is AdobeMarker && this.Equals((AdobeMarker)obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashHelpers.Combine(
this.DCTEncodeVersion.GetHashCode(),
HashHelpers.Combine(
this.APP14Flags0.GetHashCode(),
HashHelpers.Combine(
this.APP14Flags1.GetHashCode(),
this.ColorTransform.GetHashCode())));
}
}
}

127
src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs

@ -0,0 +1,127 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Provides information about the JFIF marker segment
/// TODO: Thumbnail?
/// </summary>
internal struct JFifMarker : IEquatable<JFifMarker>
{
/// <summary>
/// Gets the length of an JFIF marker segment.
/// </summary>
public const int Length = 13;
/// <summary>
/// Initializes a new instance of the <see cref="JFifMarker"/> struct.
/// </summary>
/// <param name="majorVersion">The major version</param>
/// <param name="minorVersion">The minor version</param>
/// <param name="densityUnits">The units for the density values</param>
/// <param name="xDensity">The horizontal pixel density</param>
/// <param name="yDensity">The veritcal pixel density</param>
private JFifMarker(byte majorVersion, byte minorVersion, byte densityUnits, short xDensity, short yDensity)
{
Guard.MustBeGreaterThan(xDensity, 0, nameof(xDensity));
Guard.MustBeGreaterThan(yDensity, 0, nameof(yDensity));
this.MajorVersion = majorVersion;
this.MinorVersion = minorVersion;
this.DensityUnits = densityUnits;
this.XDensity = xDensity;
this.YDensity = yDensity;
}
/// <summary>
/// Gets the major version
/// </summary>
public byte MajorVersion { get; }
/// <summary>
/// Gets the minor version
/// </summary>
public byte MinorVersion { get; }
/// <summary>
/// Gets the units for the following pixel density fields
/// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
/// 01 : Pixels per inch (2.54 cm)
/// 02 : Pixels per centimeter
/// </summary>
public byte DensityUnits { get; }
/// <summary>
/// Gets the horizontal pixel density. Must not be zero.
/// </summary>
public short XDensity { get; }
/// <summary>
/// Gets the vertical pixel density. Must not be zero.
/// </summary>
public short YDensity { get; }
/// <summary>
/// Converts the specified byte array representation of an JFIF marker to its <see cref="JFifMarker"/> equivalent and
/// returns a value that indicates whether the conversion succeeded.
/// </summary>
/// <param name="bytes">The byte array containing metadata to parse</param>
/// <param name="marker">The marker to return.</param>
public static bool TryParse(byte[] bytes, out JFifMarker marker)
{
if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker))
{
byte majorVersion = bytes[5];
byte minorVersion = bytes[6];
byte densityUnits = bytes[7];
short xDensity = (short)((bytes[8] << 8) | bytes[9]);
short yDensity = (short)((bytes[10] << 8) | bytes[11]);
if (xDensity > 0 && yDensity > 0)
{
marker = new JFifMarker(majorVersion, minorVersion, densityUnits, xDensity, yDensity);
return true;
}
}
marker = default(JFifMarker);
return false;
}
/// <inheritdoc/>
public bool Equals(JFifMarker other)
{
return this.MajorVersion == other.MajorVersion
&& this.MinorVersion == other.MinorVersion
&& this.DensityUnits == other.DensityUnits
&& this.XDensity == other.XDensity
&& this.YDensity == other.YDensity;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is JFifMarker && this.Equals((JFifMarker)obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashHelpers.Combine(
this.MajorVersion.GetHashCode(),
HashHelpers.Combine(
this.MinorVersion.GetHashCode(),
HashHelpers.Combine(
this.DensityUnits.GetHashCode(),
HashHelpers.Combine(this.XDensity, this.YDensity))));
}
}
}

59
src/ImageSharp/Formats/Jpeg/Common/Decoder/ProfileResolver.cs

@ -0,0 +1,59 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Provides methods for identifying metadata and color profiles within jpeg images.
/// </summary>
internal static class ProfileResolver
{
/// <summary>
/// Describes the EXIF specific markers
/// </summary>
public static readonly byte[] JFifMarker = ToAsciiBytes("JFIF\0");
/// <summary>
/// Describes the EXIF specific markers
/// </summary>
public static readonly byte[] IccMarker = ToAsciiBytes("ICC_PROFILE\0");
/// <summary>
/// Describes the ICC specific markers
/// </summary>
public static readonly byte[] ExifMarker = ToAsciiBytes("Exif\0\0");
/// <summary>
/// Describes Adobe specific markers <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe"/>
/// </summary>
public static readonly byte[] AdobeMarker = ToAsciiBytes("Adobe");
/// <summary>
/// Returns a value indicating whether the passed bytes are a match to the profile identifer
/// </summary>
/// <param name="bytesToCheck">The bytes to check</param>
/// <param name="profileIdentifier">The profile identifier</param>
/// <returns>The <see cref="bool"/></returns>
public static bool IsProfile(Span<byte> bytesToCheck, Span<byte> profileIdentifier)
{
return bytesToCheck.Length >= profileIdentifier.Length
&& bytesToCheck.Slice(0, profileIdentifier.Length).SequenceEqual(profileIdentifier);
}
// No Encoding.ASCII nor Linq.Select on NetStandard 1.1
private static byte[] ToAsciiBytes(string str)
{
int length = str.Length;
byte[] bytes = new byte[length];
char[] chars = str.ToCharArray();
for (int i = 0; i < length; i++)
{
bytes[i] = (byte)chars[i];
}
return bytes;
}
}
}

8
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs

@ -92,8 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReceiveExtend(int t, ref InputProcessor inputProcessor)
{
int x;
OrigDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out x);
OrigDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out int x);
errorCode.EnsureNoError();
return x;
}
@ -120,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.UnreadBits -= t;
this.Mask >>= t;
int s = 1 << t;
x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1));
x = (this.Accumulator >> this.UnreadBits) & (s - 1);
if (x < (s >> 1))
{
@ -132,8 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
private OrigDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor)
{
int c;
OrigDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out c);
OrigDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out int c);
if (errorCode != OrigDecoderErrorCode.NoError)
{

11
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs

@ -59,10 +59,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public static Bytes Create()
{
return new Bytes
{
Buffer = BytePool.Rent(BufferSize),
BufferAsInt = IntPool.Rent(BufferSize)
};
{
Buffer = BytePool.Rent(BufferSize),
BufferAsInt = IntPool.Rent(BufferSize)
};
}
/// <summary>
@ -148,8 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte(Stream inputStream)
{
byte result;
OrigDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out result);
OrigDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result);
errorCode.EnsureNoError();
return result;
}

3
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs

@ -3,7 +3,6 @@
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
@ -44,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <returns>The <see cref="ComputationData"/></returns>
public static ComputationData Create()
{
ComputationData data = default(ComputationData);
var data = default(ComputationData);
data.Unzig = ZigZag.CreateUnzigTable();
return data;
}

37
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs

@ -326,11 +326,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
zig++;
// Decode the DC coefficient, as specified in section F.2.2.1.
int value;
int huffmanIndex = (OrigHuffmanTree.DcTableIndex * OrigHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector;
decoder.InputProcessor.DecodeHuffmanUnsafe(
ref decoder.HuffmanTrees[huffmanIndex],
out value);
out int value);
if (!decoder.InputProcessor.CheckEOF())
{
return;
@ -341,8 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
throw new ImageFormatException("Excessive DC component");
}
int deltaDC;
decoder.InputProcessor.ReceiveExtendUnsafe(value, out deltaDC);
decoder.InputProcessor.ReceiveExtendUnsafe(value, out int deltaDC);
if (!decoder.InputProcessor.CheckEOFEnsureNoError())
{
return;
@ -364,8 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
// Decode the AC coefficients, as specified in section F.2.2.2.
for (; zig <= this.zigEnd; zig++)
{
int value;
decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value);
decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out int value);
if (decoder.InputProcessor.HasError)
{
return;
@ -381,8 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
break;
}
int ac;
decoder.InputProcessor.ReceiveExtendUnsafe(val1, out ac);
decoder.InputProcessor.ReceiveExtendUnsafe(val1, out int ac);
if (decoder.InputProcessor.HasError)
{
return;
@ -419,8 +415,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
private void DecodeEobRun(int count, ref InputProcessor processor)
{
int bitsResult;
processor.DecodeBitsUnsafe(count, out bitsResult);
processor.DecodeBitsUnsafe(count, out int bitsResult);
if (processor.LastErrorCode != OrigDecoderErrorCode.NoError)
{
return;
@ -429,16 +424,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.eobRun |= bitsResult;
}
/// <summary>
/// Gets the block index used to retieve blocks from in <see cref="OrigComponent.SpectralBlocks"/>
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
/// <returns>The index</returns>
private int GetBlockIndex(OrigJpegDecoderCore decoder)
{
return ((this.by * decoder.MCUCountX) * this.hi) + this.bx;
}
private void InitComponentScan(OrigJpegDecoderCore decoder, int i, ref OrigComponentScan currentComponentScan, ref int totalHv)
{
// Component selector.
@ -516,8 +501,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
throw new ImageFormatException("Invalid state for zig DC component");
}
bool bit;
bp.DecodeBitUnsafe(out bit);
bp.DecodeBitUnsafe(out bool bit);
if (!bp.CheckEOFEnsureNoError())
{
return;
@ -546,8 +530,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
bool done = false;
int z = 0;
int val;
bp.DecodeHuffmanUnsafe(ref h, out val);
bp.DecodeHuffmanUnsafe(ref h, out int val);
if (!bp.CheckEOF())
{
return;
@ -578,8 +561,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
case 1:
z = delta;
bool bit;
bp.DecodeBitUnsafe(out bit);
bp.DecodeBitUnsafe(out bool bit);
if (!bp.CheckEOFEnsureNoError())
{
return;
@ -650,8 +632,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
continue;
}
bool bit;
bp.DecodeBitUnsafe(out bit);
bp.DecodeBitUnsafe(out bool bit);
if (bp.HasError)
{
return int.MinValue;

61
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs

@ -25,67 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "jpg", "jpeg", "jfif" };
/// <summary>
/// Represents high detail chroma horizontal subsampling.
/// </summary>
public static readonly byte[] ChromaFourFourFourHorizontal = { 0x11, 0x11, 0x11 };
/// <summary>
/// Represents high detail chroma vertical subsampling.
/// </summary>
public static readonly byte[] ChromaFourFourFourVertical = { 0x11, 0x11, 0x11 };
/// <summary>
/// Represents medium detail chroma vertical subsampling.
/// </summary>
public static readonly byte[] ChromaFourTwoTwoVertical = { 0x11, 0x11, 0x11 };
/// <summary>
/// Represents low detail chroma vertical subsampling.
/// </summary>
public static readonly byte[] ChromaFourTwoZeroVertical = { 0x22, 0x11, 0x11 };
/// <summary>
/// Represents medium detail chroma horizontal subsampling.
/// </summary>
public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x22, 0x11, 0x11 };
/// <summary>
/// Represents low detail chroma horizontal subsampling.
/// </summary>
public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x22, 0x11, 0x11 };
/// <summary>
/// Describes component ids for start of frame components.
/// </summary>
internal static class Components
{
/// <summary>
/// The YCbCr luminance component id.
/// </summary>
public const byte Y = 1;
/// <summary>
/// The YCbCr chroma component id.
/// </summary>
public const byte Cb = 2;
/// <summary>
/// The YCbCr chroma component id.
/// </summary>
public const byte Cr = 3;
/// <summary>
/// The YIQ x coordinate component id.
/// </summary>
public const byte I = 4;
/// <summary>
/// The YIQ y coordinate component id.
/// </summary>
public const byte Q = 5;
}
/// <summary>
/// Describes common Jpeg markers
/// </summary>

297
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
@ -53,34 +52,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private readonly Configuration configuration;
/// <summary>
/// The App14 marker color-space
/// </summary>
private byte adobeTransform;
/// <summary>
/// Whether the image is in CMYK format with an App14 marker
/// Whether the image has a JFIF header
/// It's faster to check this than to use the equality operator on the struct
/// </summary>
private bool adobeTransformValid;
private bool isJFif;
/// <summary>
/// The horizontal resolution. Calculated if the image has a JFIF header.
/// Contains information about the JFIF marker
/// </summary>
private short horizontalResolution;
private JFifMarker jFif;
/// <summary>
/// Whether the image has a JFIF header
/// Whether the image has a EXIF header
/// </summary>
private bool isJfif;
private bool isExif;
/// <summary>
/// Whether the image has a EXIF header
/// Whether the image has an Adobe marker.
/// It's faster to check this than to use the equality operator on the struct
/// </summary>
private bool isExif;
private bool isAdobe;
/// <summary>
/// The vertical resolution. Calculated if the image has a JFIF header.
/// Contains information about the Adobe marker
/// </summary>
private short verticalResolution;
private AdobeMarker adobe;
/// <summary>
/// Initializes a new instance of the <see cref="OrigJpegDecoderCore" /> class.
@ -321,7 +317,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
case OrigJpegConstants.Markers.SOF2:
this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining);
if (metadataOnly && this.isJfif)
if (metadataOnly && this.isJFif)
{
return;
}
@ -345,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
else
{
this.ProcessDqt(remaining);
this.ProcessDefineQuantizationTablesMarker(remaining);
}
break;
@ -357,7 +353,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
// when this is a progressive image this gets called a number of times
// need to know how many times this should be called in total.
this.ProcessStartOfScan(remaining);
this.ProcessStartOfScanMarker(remaining);
if (this.InputProcessor.ReachedEOF || !this.IsProgressive)
{
// if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data.
@ -377,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
break;
case OrigJpegConstants.Markers.APP0:
this.ProcessApplicationHeader(remaining);
this.ProcessApplicationHeaderMarker(remaining);
break;
case OrigJpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining);
@ -411,33 +407,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InitDerivedMetaDataProperties();
}
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Missing SOF Marker
/// SOS has wrong length
/// </exception>
private void ProcessStartOfScan(int remaining)
{
var scan = default(OrigJpegScanDecoder);
OrigJpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default(Bits);
scan.DecodeBlocks(this);
}
/// <summary>
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary>
private void InitDerivedMetaDataProperties()
{
if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0)
{
this.MetaData.HorizontalResolution = this.horizontalResolution;
this.MetaData.VerticalResolution = this.verticalResolution;
}
else if (this.isExif)
if (this.isExif)
{
ExifValue horizontal = this.MetaData.ExifProfile.GetValue(ExifTag.XResolution);
ExifValue vertical = this.MetaData.ExifProfile.GetValue(ExifTag.YResolution);
@ -450,55 +425,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.MetaData.VerticalResolution = verticalValue;
}
}
}
/// <summary>
/// Returns a value indicating whether the image in an RGB image.
/// </summary>
/// <returns>
/// The <see cref="bool" />.
/// </returns>
private bool IsRGB()
{
if (this.isJfif)
{
return false;
}
if (this.adobeTransformValid && this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformUnknown)
else if (this.isJFif)
{
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
// says that 0 means Unknown (and in practice RGB) and 1 means YCbCr.
return true;
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
return this.Components[0].Identifier == 'R' && this.Components[1].Identifier == 'G'
&& this.Components[2].Identifier == 'B';
}
/// <summary>
/// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters.
/// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not
/// deleted by default when deleting all metadata because it may affect the appearance of the image.
/// Processes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="remaining">The remaining number of bytes in the stream.</param>
private void ProcessApp14Marker(int remaining)
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeaderMarker(int remaining)
{
if (remaining < 12)
if (remaining < 5)
{
this.InputProcessor.Skip(remaining);
return;
}
this.InputProcessor.ReadFull(this.Temp, 0, 12);
remaining -= 12;
const int MarkerLength = JFifMarker.Length;
this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength);
remaining -= MarkerLength;
if (this.Temp[0] == 'A' && this.Temp[1] == 'd' && this.Temp[2] == 'o' && this.Temp[3] == 'b'
&& this.Temp[4] == 'e')
{
this.adobeTransformValid = true;
this.adobeTransform = this.Temp[11];
}
this.isJFif = JFifMarker.TryParse(this.Temp, out this.jFif);
if (remaining > 0)
{
@ -521,8 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
&& profile[5] == '\0')
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{
this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile);
@ -545,11 +494,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
byte[] identifier = new byte[Icclength];
this.InputProcessor.ReadFull(identifier, 0, Icclength);
remaining -= Icclength; // we have read it by this point
remaining -= Icclength; // We have read it by this point
if (identifier[0] == 'I' && identifier[1] == 'C' && identifier[2] == 'C' && identifier[3] == '_'
&& identifier[4] == 'P' && identifier[5] == 'R' && identifier[6] == 'O' && identifier[7] == 'F'
&& identifier[8] == 'I' && identifier[9] == 'L' && identifier[10] == 'E' && identifier[11] == '\0')
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
@ -565,35 +512,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
else
{
// not an ICC profile we can handle read the remaining so we can carry on and ignore this.
// Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this.
this.InputProcessor.Skip(remaining);
}
}
/// <summary>
/// Processes the application header containing the JFIF identifier plus extra data.
/// Processes the application header containing the Adobe identifier
/// which stores image encoding information for DCT filters.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeader(int remaining)
private void ProcessApp14Marker(int remaining)
{
if (remaining < 5)
const int MarkerLength = AdobeMarker.Length;
if (remaining < MarkerLength)
{
// Skip the application header length
this.InputProcessor.Skip(remaining);
return;
}
this.InputProcessor.ReadFull(this.Temp, 0, 13);
remaining -= 13;
this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength);
remaining -= MarkerLength;
// TODO: We should be using constants for this.
this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F'
&& this.Temp[4] == '\x00';
if (this.isJfif)
{
this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8));
this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8));
}
this.isAdobe = AdobeMarker.TryParse(this.Temp, out this.adobe);
if (remaining > 0)
{
@ -601,58 +543,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
}
/// <summary>
/// Processes a Define Huffman Table marker, and initializes a huffman
/// struct from its contents. Specified in section B.2.4.2.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(int remaining)
{
while (remaining > 0)
{
if (remaining < 17)
{
throw new ImageFormatException("DHT has wrong length");
}
this.InputProcessor.ReadFull(this.Temp, 0, 17);
int tc = this.Temp[0] >> 4;
if (tc > OrigHuffmanTree.MaxTc)
{
throw new ImageFormatException("Bad Tc value");
}
int th = this.Temp[0] & 0x0f;
if (th > OrigHuffmanTree.MaxTh || (!this.IsProgressive && (th > 1)))
{
throw new ImageFormatException("Bad Th value");
}
int huffTreeIndex = (tc * OrigHuffmanTree.ThRowSize) + th;
this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(
ref this.InputProcessor,
this.Temp,
ref remaining);
}
}
/// <summary>
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
/// macroblocks
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineRestartIntervalMarker(int remaining)
{
if (remaining != 2)
{
throw new ImageFormatException("DRI has wrong length");
}
this.InputProcessor.ReadFull(this.Temp, 0, remaining);
this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1];
}
/// <summary>
/// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
/// </summary>
@ -660,7 +550,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <exception cref="ImageFormatException">
/// Thrown if the tables do not match the header
/// </exception>
private void ProcessDqt(int remaining)
private void ProcessDefineQuantizationTablesMarker(int remaining)
{
while (remaining > 0)
{
@ -791,38 +681,103 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.ColorSpace = this.DeduceJpegColorSpace();
}
/// <summary>
/// Processes a Define Huffman Table marker, and initializes a huffman
/// struct from its contents. Specified in section B.2.4.2.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(int remaining)
{
while (remaining > 0)
{
if (remaining < 17)
{
throw new ImageFormatException("DHT has wrong length");
}
this.InputProcessor.ReadFull(this.Temp, 0, 17);
int tc = this.Temp[0] >> 4;
if (tc > OrigHuffmanTree.MaxTc)
{
throw new ImageFormatException("Bad Tc value");
}
int th = this.Temp[0] & 0x0f;
if (th > OrigHuffmanTree.MaxTh || (!this.IsProgressive && (th > 1)))
{
throw new ImageFormatException("Bad Th value");
}
int huffTreeIndex = (tc * OrigHuffmanTree.ThRowSize) + th;
this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(
ref this.InputProcessor,
this.Temp,
ref remaining);
}
}
/// <summary>
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
/// macroblocks
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineRestartIntervalMarker(int remaining)
{
if (remaining != 2)
{
throw new ImageFormatException("DRI has wrong length");
}
this.InputProcessor.ReadFull(this.Temp, 0, remaining);
this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1];
}
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Missing SOF Marker
/// SOS has wrong length
/// </exception>
private void ProcessStartOfScanMarker(int remaining)
{
var scan = default(OrigJpegScanDecoder);
OrigJpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default(Bits);
scan.DecodeBlocks(this);
}
private JpegColorSpace DeduceJpegColorSpace()
{
switch (this.ComponentCount)
{
case 1: return JpegColorSpace.GrayScale;
case 3: return this.IsRGB() ? JpegColorSpace.RGB : JpegColorSpace.YCbCr;
case 4:
if (!this.adobeTransformValid)
case 1:
return JpegColorSpace.GrayScale;
case 3:
if (!this.isAdobe || this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYCbCr)
{
throw new ImageFormatException(
"Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
return JpegColorSpace.YCbCr;
}
// See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
// See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
// TODO: YCbCrA?
if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformYcck)
if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformUnknown)
{
return JpegColorSpace.Ycck;
return JpegColorSpace.RGB;
}
else if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformUnknown)
break;
case 4:
if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYcck)
{
// Assume CMYK
return JpegColorSpace.Cmyk;
return JpegColorSpace.Ycck;
}
goto default;
default:
throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces.");
return JpegColorSpace.Cmyk;
}
throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}."
+ "JpegDecoder only supports YCbCr, RGB, YccK, CMYK and Grayscale color spaces.");
}
private Image<TPixel> PostProcessIntoImage<TPixel>()

72
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsAdobe.cs

@ -1,72 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
/// Provides information about the Adobe marker segment
/// </summary>
internal struct PdfJsAdobe : IEquatable<PdfJsAdobe>
{
/// <summary>
/// The DCT Encode Version
/// </summary>
public short DCTEncodeVersion;
/// <summary>
/// 0x0 : (none)
/// Bit 15 : Encoded with Blend=1 downsampling
/// </summary>
public short APP14Flags0;
/// <summary>
/// 0x0 : (none)
/// </summary>
public short APP14Flags1;
/// <summary>
/// Determines the colorspace transform
/// 00 : Unknown (RGB or CMYK)
/// 01 : YCbCr
/// 02 : YCCK
/// </summary>
public byte ColorTransform;
/// <inheritdoc/>
public bool Equals(PdfJsAdobe other)
{
return this.DCTEncodeVersion == other.DCTEncodeVersion
&& this.APP14Flags0 == other.APP14Flags0
&& this.APP14Flags1 == other.APP14Flags1
&& this.ColorTransform == other.ColorTransform;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is PdfJsAdobe && this.Equals((PdfJsAdobe)obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
// TODO: Merge and use HashCodeHelpers
int hashCode = this.DCTEncodeVersion.GetHashCode();
hashCode = (hashCode * 397) ^ this.APP14Flags0.GetHashCode();
hashCode = (hashCode * 397) ^ this.APP14Flags1.GetHashCode();
hashCode = (hashCode * 397) ^ this.ColorTransform.GetHashCode();
return hashCode;
}
}
}
}

77
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJFif.cs

@ -1,77 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
/// Provides information about the JFIF marker segment
/// TODO: Thumbnail?
/// </summary>
internal struct PdfJsJFif : IEquatable<PdfJsJFif>
{
/// <summary>
/// The major version
/// </summary>
public byte MajorVersion;
/// <summary>
/// The minor version
/// </summary>
public byte MinorVersion;
/// <summary>
/// Units for the following pixel density fields
/// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
/// 01 : Pixels per inch (2.54 cm)
/// 02 : Pixels per centimeter
/// </summary>
public byte DensityUnits;
/// <summary>
/// Horizontal pixel density. Must not be zero.
/// </summary>
public short XDensity;
/// <summary>
/// Vertical pixel density. Must not be zero.
/// </summary>
public short YDensity;
/// <inheritdoc/>
public bool Equals(PdfJsJFif other)
{
return this.MajorVersion == other.MajorVersion
&& this.MinorVersion == other.MinorVersion
&& this.DensityUnits == other.DensityUnits
&& this.XDensity == other.XDensity
&& this.YDensity == other.YDensity;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is PdfJsJFif && this.Equals((PdfJsJFif)obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.MajorVersion.GetHashCode();
hashCode = (hashCode * 397) ^ this.MinorVersion.GetHashCode();
hashCode = (hashCode * 397) ^ this.DensityUnits.GetHashCode();
hashCode = (hashCode * 397) ^ this.XDensity.GetHashCode();
hashCode = (hashCode * 397) ^ this.YDensity.GetHashCode();
return hashCode;
}
}
}
}

53
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
@ -53,12 +54,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Contains information about the JFIF marker
/// </summary>
private PdfJsJFif jFif;
private JFifMarker jFif;
/// <summary>
/// Contains information about the Adobe marker
/// </summary>
private PdfJsAdobe adobe;
private AdobeMarker adobe;
/// <summary>
/// Initializes static members of the <see cref="PdfJsJpegDecoderCore"/> class.
@ -345,7 +346,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (this.NumberOfComponents == 3)
{
if (this.adobe.Equals(default(PdfJsAdobe)) || this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYCbCr)
if (this.adobe.Equals(default(AdobeMarker)) || this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYCbCr)
{
this.FillYCbCrImage(image);
}
@ -409,26 +410,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
return;
}
this.InputStream.Read(this.temp, 0, 13);
remaining -= 13;
this.InputStream.Read(this.temp, 0, JFifMarker.Length);
remaining -= JFifMarker.Length;
bool isJfif = this.temp[0] == PdfJsJpegConstants.Markers.JFif.J &&
this.temp[1] == PdfJsJpegConstants.Markers.JFif.F &&
this.temp[2] == PdfJsJpegConstants.Markers.JFif.I &&
this.temp[3] == PdfJsJpegConstants.Markers.JFif.F &&
this.temp[4] == PdfJsJpegConstants.Markers.JFif.Null;
if (isJfif)
{
this.jFif = new PdfJsJFif
{
MajorVersion = this.temp[5],
MinorVersion = this.temp[6],
DensityUnits = this.temp[7],
XDensity = (short)((this.temp[8] << 8) | this.temp[9]),
YDensity = (short)((this.temp[10] << 8) | this.temp[11])
};
}
JFifMarker.TryParse(this.temp, out this.jFif);
// TODO: thumbnail
if (remaining > 0)
@ -524,32 +509,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="remaining">The remaining bytes in the segment block.</param>
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 PdfJsAdobe
{
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)
{

22
tests/ImageSharp.Tests/FileTestBase.cs

@ -70,17 +70,17 @@ namespace SixLabors.ImageSharp.Tests
protected static readonly List<TestFile> Files = new List<TestFile>
{
TestFile.Create(TestImages.Jpeg.Baseline.Calliphora),
// TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Bad.MissingEOF), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.Bad.BadEOF), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only
//TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only
TestFile.Create(TestImages.Bmp.Car),
// TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Bmp.CoreHeader), // Perf: Enable for local testing only

82
tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs

@ -0,0 +1,82 @@
// 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 };
// Altered components
private readonly byte[] bytes2 = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x1, 0x1, 0x1 };
[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);
}
[Fact]
public void MarkerEqualityIsCorrect()
{
AdobeMarker.TryParse(this.bytes, out var marker);
AdobeMarker.TryParse(this.bytes, out var marker2);
Assert.True(marker.Equals(marker2));
}
[Fact]
public void MarkerInEqualityIsCorrect()
{
AdobeMarker.TryParse(this.bytes, out var marker);
AdobeMarker.TryParse(this.bytes2, out var marker2);
Assert.False(marker.Equals(marker2));
}
[Fact]
public void MarkerHashCodeIsReplicable()
{
AdobeMarker.TryParse(this.bytes, out var marker);
AdobeMarker.TryParse(this.bytes, out var marker2);
Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode()));
}
[Fact]
public void MarkerHashCodeIsUnique()
{
AdobeMarker.TryParse(this.bytes, out var marker);
AdobeMarker.TryParse(this.bytes2, out var marker2);
Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode()));
}
}
}

95
tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs

@ -0,0 +1,95 @@
// 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 JFifMarkerTests
{
// Taken from actual test image
private readonly byte[] bytes = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x60, 0x0, 0x60, 0x0 };
// Altered components
private readonly byte[] bytes2 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x48, 0x0, 0x48, 0x0 };
// Incorrect density values. Zero is invalid.
private readonly byte[] bytes3 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0 };
[Fact]
public void MarkerLengthIsCorrect()
{
Assert.Equal(13, JFifMarker.Length);
}
[Fact]
public void MarkerReturnsCorrectParsedValue()
{
bool isJFif = JFifMarker.TryParse(this.bytes, out var marker);
Assert.True(isJFif);
Assert.Equal(1, marker.MajorVersion);
Assert.Equal(1, marker.MinorVersion);
Assert.Equal(1, marker.DensityUnits);
Assert.Equal(96, marker.XDensity);
Assert.Equal(96, marker.YDensity);
}
[Fact]
public void MarkerIgnoresIncorrectValue()
{
bool isJFif = JFifMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out var marker);
Assert.False(isJFif);
Assert.Equal(default(JFifMarker), marker);
}
[Fact]
public void MarkerIgnoresCorrectHeaderButInvalidDensities()
{
bool isJFif = JFifMarker.TryParse(this.bytes3, out var marker);
Assert.False(isJFif);
Assert.Equal(default(JFifMarker), marker);
}
[Fact]
public void MarkerEqualityIsCorrect()
{
JFifMarker.TryParse(this.bytes, out var marker);
JFifMarker.TryParse(this.bytes, out var marker2);
Assert.True(marker.Equals(marker2));
}
[Fact]
public void MarkerInEqualityIsCorrect()
{
JFifMarker.TryParse(this.bytes, out var marker);
JFifMarker.TryParse(this.bytes2, out var marker2);
Assert.False(marker.Equals(marker2));
}
[Fact]
public void MarkerHashCodeIsReplicable()
{
JFifMarker.TryParse(this.bytes, out var marker);
JFifMarker.TryParse(this.bytes, out var marker2);
Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode()));
}
[Fact]
public void MarkerHashCodeIsUnique()
{
JFifMarker.TryParse(this.bytes, out var marker);
JFifMarker.TryParse(this.bytes2, out var marker2);
Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode()));
}
}
}

79
tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs

@ -0,0 +1,79 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using Xunit;
public class ProfileResolverTests
{
private static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0");
private static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0");
private static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0");
private static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe");
[Fact]
public void ProfileResolverHasCorrectJFifMarker()
{
Assert.Equal(JFifMarker, ProfileResolver.JFifMarker);
}
[Fact]
public void ProfileResolverHasCorrectExifMarker()
{
Assert.Equal(ExifMarker, ProfileResolver.ExifMarker);
}
[Fact]
public void ProfileResolverHasCorrectIccMarker()
{
Assert.Equal(IccMarker, ProfileResolver.IccMarker);
}
[Fact]
public void ProfileResolverHasCorrectAdobeMarker()
{
Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker);
}
[Fact]
public void ProfileResolverCanResolveJFifMarker()
{
Assert.True(ProfileResolver.IsProfile(JFifMarker, ProfileResolver.JFifMarker));
}
[Fact]
public void ProfileResolverCanResolveExifMarker()
{
Assert.True(ProfileResolver.IsProfile(ExifMarker, ProfileResolver.ExifMarker));
}
[Fact]
public void ProfileResolverCanResolveIccMarker()
{
Assert.True(ProfileResolver.IsProfile(IccMarker, ProfileResolver.IccMarker));
}
[Fact]
public void ProfileResolverCanResolveAdobeMarker()
{
Assert.True(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.AdobeMarker));
}
[Fact]
public void ProfileResolverCorrectlyReportsNonMarker()
{
Assert.False(ProfileResolver.IsProfile(IccMarker, ProfileResolver.AdobeMarker));
}
[Fact]
public void ProfileResolverCanHandleIncorrectLength()
{
Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker));
}
}
}
Loading…
Cancel
Save