diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs index a051df809d..d513401864 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; -using System.Linq; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// - /// Various utilities for and . + /// Various utilities for . /// internal static class ComponentUtils { @@ -13,105 +9,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { return ref component.SpectralBlocks[bx, by]; } - - public static SubsampleRatio GetSubsampleRatio(int horizontalRatio, int verticalRatio) - { - switch ((horizontalRatio << 4) | verticalRatio) - { - case 0x11: - return SubsampleRatio.Ratio444; - case 0x12: - return SubsampleRatio.Ratio440; - case 0x21: - return SubsampleRatio.Ratio422; - case 0x22: - return SubsampleRatio.Ratio420; - case 0x41: - return SubsampleRatio.Ratio411; - case 0x42: - return SubsampleRatio.Ratio410; - } - - return SubsampleRatio.Ratio444; - } - - // https://en.wikipedia.org/wiki/Chroma_subsampling - public static SubsampleRatio GetSubsampleRatio(IEnumerable components) - { - IJpegComponent[] componentArray = components.ToArray(); - if (componentArray.Length == 3) - { - Size s0 = componentArray[0].SamplingFactors; - Size ratio = s0.DivideBy(componentArray[1].SamplingFactors); - - return GetSubsampleRatio(ratio.Width, ratio.Height); - } - else - { - return SubsampleRatio.Undefined; - } - } - - /// - /// Returns the height and width of the chroma components - /// TODO: Not needed by new JpegImagePostprocessor - /// - /// The subsampling ratio. - /// The width. - /// The height. - /// The of the chrominance channel - public static Size CalculateChrominanceSize(this SubsampleRatio ratio, int width, int height) - { - (int divX, int divY) = ratio.GetChrominanceSubSampling(); - var size = new Size(width, height); - return size.DivideRoundUp(divX, divY); - } - - - - // TODO: Not needed by new JpegImagePostprocessor - public static (int divX, int divY) GetChrominanceSubSampling(this SubsampleRatio ratio) - { - switch (ratio) - { - case SubsampleRatio.Ratio422: return (2, 1); - case SubsampleRatio.Ratio420: return (2, 2); - case SubsampleRatio.Ratio440: return (1, 2); - case SubsampleRatio.Ratio411: return (4, 1); - case SubsampleRatio.Ratio410: return (4, 2); - default: return (1, 1); - } - } - - public static bool IsChromaComponent(this IJpegComponent component) => - component.Index > 0 && component.Index < 3; - - // TODO: Not needed by new JpegImagePostprocessor - public static Size[] CalculateJpegChannelSizes(IEnumerable components, SubsampleRatio ratio) - { - IJpegComponent[] c = components.ToArray(); - Size[] sizes = new Size[c.Length]; - - Size s0 = c[0].SizeInBlocks * 8; - sizes[0] = s0; - - if (c.Length > 1) - { - Size chromaSize = ratio.CalculateChrominanceSize(s0.Width, s0.Height); - sizes[1] = chromaSize; - - if (c.Length > 2) - { - sizes[2] = chromaSize; - } - } - - if (c.Length > 3) - { - sizes[3] = s0; - } - - return sizes; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs index 0e4f953f3d..3873656a4e 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs @@ -1,13 +1,15 @@ +using System; using System.Collections.Generic; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { + /// /// - /// Represents decompressed, unprocessed jpeg data with spectral space -s. + /// Represents decompressed, unprocessed jpeg data with spectral space -s. /// - internal interface IRawJpegData + internal interface IRawJpegData : IDisposable { /// /// Gets the image size in pixels. diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs similarity index 72% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs rename to src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 29149a186c..4c47017530 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -1,14 +1,11 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; using SixLabors.ImageSharp.Memory; using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. @@ -36,22 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder postProcessor->pointers = new DataPointers(&postProcessor->data); } - /// - /// Dequantize, perform the inverse DCT and store the blocks to the into the corresponding instances. - /// - /// The instance - /// The component - public void ProcessAllBlocks(OrigJpegDecoderCore decoder, IJpegComponent component) - { - for (int by = 0; by < component.SizeInBlocks.Height; by++) - { - for (int bx = 0; bx < component.SizeInBlocks.Width; bx++) - { - this.ProcessBlockColors(decoder, component, bx, by); - } - } - } - public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock) { this.data.SourceBlock = sourceBlock.AsFloatBlock(); @@ -84,25 +65,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height); } - /// - /// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding instance. - /// - /// The - /// The - /// The x index of the block in - /// The y index of the block in - private void ProcessBlockColors(OrigJpegDecoderCore decoder, IJpegComponent component, int bx, int by) - { - ref Block8x8 sourceBlock = ref component.GetBlockReference(bx, @by); - - this.QuantizeAndTransform(decoder, component, ref sourceBlock); - - OrigJpegPixelArea destChannel = decoder.GetDestinationChannel(component.Index); - OrigJpegPixelArea destArea = destChannel.GetOffsetedSubAreaForBlock(bx, by); - destArea.LoadColorsFrom(this.pointers.WorkspaceBlock1, this.pointers.WorkspaceBlock2); - } - - /// /// Holds the "large" data blocks needed for computations. /// @@ -140,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The public static ComputationData Create() { - ComputationData data = default(ComputationData); + var data = default(ComputationData); data.Unzig = UnzigData.Create(); return data; } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs index ae1180a52f..2b583bdbb5 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs @@ -8,7 +8,7 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// - /// Encapsulates the execution post-processing algorithms to be applied on a to produce a valid :
+ /// Encapsulates the execution od post-processing algorithms to be applied on a to produce a valid :
/// (1) Dequantization
/// (2) IDCT
/// (3) Color conversion form one of the -s into a buffer of RGBA values
diff --git a/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs b/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs deleted file mode 100644 index 235c2352a3..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace SixLabors.ImageSharp.Formats.Jpeg.Common -{ - /// - /// Provides enumeration of the various available subsample ratios. - /// https://en.wikipedia.org/wiki/Chroma_subsampling - /// TODO: Not needed by new JpegImagePostprocessor - /// - internal enum SubsampleRatio - { - Undefined, - - /// - /// 4:4:4 - /// - Ratio444, - - /// - /// 4:2:2 - /// - Ratio422, - - /// - /// 4:2:0 - /// - Ratio420, - - /// - /// 4:4:0 - /// - Ratio440, - - /// - /// 4:1:1 - /// - Ratio411, - - /// - /// 4:1:0 - /// - Ratio410, - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegPixelArea.cs deleted file mode 100644 index 91b9b3a101..0000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegPixelArea.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// Represents an area of a Jpeg subimage (channel) - /// - internal struct OrigJpegPixelArea - { - /// - /// Initializes a new instance of the struct from existing data. - /// - /// The pixel buffer - /// The stride - /// The offset - public OrigJpegPixelArea(Buffer2D pixels, int stride, int offset) - { - this.Stride = stride; - this.Pixels = pixels; - this.Offset = offset; - } - - /// - /// Initializes a new instance of the struct from existing buffer. - /// will be set to of and will be set to 0. - /// - /// The pixel buffer - public OrigJpegPixelArea(Buffer2D pixels) - : this(pixels, pixels.Width, 0) - { - } - - public OrigJpegPixelArea(Size size) - : this(Buffer2D.CreateClean(size)) - { - } - - /// - /// Gets the pixels buffer. - /// - public Buffer2D Pixels { get; private set; } - - /// - /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea)) - /// - public bool IsInitialized => this.Pixels != null; - - /// - /// Gets the stride. - /// - public int Stride { get; } - - /// - /// Gets the offset. - /// - public int Offset { get; } - - /// - /// Gets a of bytes to the pixel area - /// - public Span Span => new Span(this.Pixels.Array, this.Offset); - - /// - /// Returns the pixel at (x, y) - /// - /// The x index - /// The y index - /// The pixel value - public byte this[int x, int y] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return this.Pixels[(y * this.Stride) + x]; - } - } - - /// - /// Gets the subarea that belongs to the Block8x8 defined by block indices - /// - /// The block X index - /// The block Y index - /// The subarea offseted by block indices - public OrigJpegPixelArea GetOffsetedSubAreaForBlock(int bx, int by) - { - int offset = this.Offset + (8 * ((by * this.Stride) + bx)); - return new OrigJpegPixelArea(this.Pixels, this.Stride, offset); - } - - /// - /// Gets the row offset at the given position - /// - /// The y-coordinate of the image. - /// The - public int GetRowOffset(int y) - { - return this.Offset + (y * this.Stride); - } - - /// - /// Load values to the pixel area from the given . - /// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to - /// values - /// - /// The block holding the color values - /// Temporal block provided by the caller - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp) - { - // Level shift by +128, clip to [0, 255], and write to dst. - block->CopyColorsTo(new Span(this.Pixels.Array, this.Offset), this.Stride, temp); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs deleted file mode 100644 index c56d2d3417..0000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - using SixLabors.ImageSharp.Formats.Jpeg.Common; - using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; - - /// - /// Represents an image made up of three color components (luminance, blue chroma, red chroma) - /// - internal class YCbCrImage : IDisposable - { - // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P -#pragma warning disable SA1401 // FieldsMustBePrivate - /// - /// Gets the luminance components channel as . - /// - public Buffer2D YChannel; - - /// - /// Gets the blue chroma components channel as . - /// - public Buffer2D CbChannel; - - /// - /// Gets an offseted to the Cr channel - /// - public Buffer2D CrChannel; -#pragma warning restore SA1401 - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The ratio. - public YCbCrImage(int width, int height, SubsampleRatio ratio) - { - Size cSize = ratio.CalculateChrominanceSize(width, height); - - this.Ratio = ratio; - this.YStride = width; - this.CStride = cSize.Width; - - this.YChannel = Buffer2D.CreateClean(width, height); - this.CbChannel = Buffer2D.CreateClean(cSize.Width, cSize.Height); - this.CrChannel = Buffer2D.CreateClean(cSize.Width, cSize.Height); - } - - /// - /// Gets the Y slice index delta between vertically adjacent pixels. - /// - public int YStride { get; } - - /// - /// Gets the red and blue chroma slice index delta between vertically adjacent pixels - /// that map to separate chroma samples. - /// - public int CStride { get; } - - /// - /// Gets or sets the subsampling ratio. - /// - public SubsampleRatio Ratio { get; set; } - - /// - /// Disposes the returning rented arrays to the pools. - /// - public void Dispose() - { - this.YChannel.Dispose(); - this.CbChannel.Dispose(); - this.CrChannel.Dispose(); - } - - /// - /// Returns the offset of the first chroma component at the given row - /// - /// The row number. - /// - /// The . - /// - public int GetRowCOffset(int y) - { - switch (this.Ratio) - { - case SubsampleRatio.Ratio422: - return y * this.CStride; - case SubsampleRatio.Ratio420: - return (y / 2) * this.CStride; - case SubsampleRatio.Ratio440: - return (y / 2) * this.CStride; - case SubsampleRatio.Ratio411: - return y * this.CStride; - case SubsampleRatio.Ratio410: - return (y / 2) * this.CStride; - default: - return y * this.CStride; - } - } - - /// - /// Returns the offset of the first luminance component at the given row - /// - /// The row number. - /// - /// The . - /// - public int GetRowYOffset(int y) - { - return y * this.YStride; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs deleted file mode 100644 index fe0cd6fc08..0000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace. - /// Methods to build the tables are based on libjpeg implementation. - /// - internal unsafe struct YCbCrToRgbTables - { - /// - /// The red red-chrominance table - /// - public fixed int CrRTable[256]; - - /// - /// The blue blue-chrominance table - /// - public fixed int CbBTable[256]; - - /// - /// The green red-chrominance table - /// - public fixed int CrGTable[256]; - - /// - /// The green blue-chrominance table - /// - public fixed int CbGTable[256]; - - // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. - private const int ScaleBits = 16; - - private const int Half = 1 << (ScaleBits - 1); - - /// - /// Initializes the YCbCr tables - /// - /// The intialized - public static YCbCrToRgbTables Create() - { - YCbCrToRgbTables tables = default(YCbCrToRgbTables); - - for (int i = 0, x = -128; i <= 255; i++, x++) - { - // i is the actual input pixel value, in the range 0..255 - // The Cb or Cr value we are thinking of is x = i - 128 - // Cr=>R value is nearest int to 1.402 * x - tables.CrRTable[i] = RightShift((Fix(1.402F) * x) + Half); - - // Cb=>B value is nearest int to 1.772 * x - tables.CbBTable[i] = RightShift((Fix(1.772F) * x) + Half); - - // Cr=>G value is scaled-up -0.714136286 - tables.CrGTable[i] = (-Fix(0.714136286F)) * x; - - // Cb => G value is scaled - up - 0.344136286 * x - // We also add in Half so that need not do it in inner loop - tables.CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half; - } - - return tables; - } - - /// - /// Optimized method to pack bytes to the image from the YCbCr color space. - /// - /// The pixel format. - /// The packed pixel. - /// The reference to the tables instance. - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Pack(ref TPixel packed, YCbCrToRgbTables* tables, byte y, byte cb, byte cr) - where TPixel : struct, IPixel - { - // float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - byte r = (byte)(y + tables->CrRTable[cr]).Clamp(0, 255); - - // float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - // The values for the G calculation are left scaled up, since we must add them together before rounding. - byte g = (byte)(y + RightShift(tables->CbGTable[cb] + tables->CrGTable[cr])).Clamp(0, 255); - - // float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - byte b = (byte)(y + tables->CbBTable[cb]).Clamp(0, 255); - - packed.PackFromRgba32(new Rgba32(r, g, b, 255)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Fix(float x) - { - return (int)((x * (1L << ScaleBits)) + 0.5F); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RightShift(int x) - { - return x >> ScaleBits; - } - } -} \ 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 e8e8fe06e5..d37ec91490 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; @@ -18,10 +16,11 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { + /// /// /// Performs the jpeg decoding operation. /// - internal sealed unsafe class OrigJpegDecoderCore : IDisposable, IRawJpegData + internal sealed unsafe class OrigJpegDecoderCore : IRawJpegData { /// /// The maximum number of color components @@ -43,11 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort public InputProcessor InputProcessor; #pragma warning restore SA401 - /// - /// Lookup tables for converting YCbCr to Rgb - /// - private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create(); - /// /// The global configuration /// @@ -63,16 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// private bool adobeTransformValid; - /// - /// The black image to decode to. - /// - private OrigJpegPixelArea blackImage; - - /// - /// A grayscale image to decode to. - /// - private OrigJpegPixelArea grayImage; - /// /// The horizontal resolution. Calculated if the image has a JFIF header. /// @@ -93,11 +77,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort ///
private short verticalResolution; - /// - /// The full color image to decode to. - /// - private YCbCrImage ycbcrImage; - /// /// Initializes a new instance of the class. /// @@ -112,11 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.Temp = new byte[2 * Block8x8F.Size]; } - /// - /// Gets the ratio. - /// - public SubsampleRatio SubsampleRatio { get; private set; } - + /// public JpegColorSpace ColorSpace { get; private set; } /// @@ -138,13 +113,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// public byte[] Temp { get; } + /// public Size ImageSizeInPixels { get; private set; } - public Size ImageSizeInMCU { get; private set; } - /// - /// Gets the number of color components within the image. + /// Gets the number of MCU blocks in the image as . /// + public Size ImageSizeInMCU { get; private set; } + + /// public int ComponentCount { get; private set; } IEnumerable IRawJpegData.Components => this.Components; @@ -192,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public bool IgnoreMetadata { get; private set; } + public bool IgnoreMetadata { get; } /// /// Gets the decoded by this decoder instance. @@ -211,15 +188,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { this.ParseStream(stream); -#if OLDCODE - this.ProcessBlocksIntoJpegImageChannels(); - - return this.ConvertJpegPixelsToImagePixels(); -#else return this.PostProcessIntoImage(); -#endif } - + /// public void Dispose() { @@ -236,39 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } } - this.ycbcrImage?.Dispose(); this.InputProcessor.Dispose(); - this.grayImage.Pixels?.Dispose(); - this.blackImage.Pixels?.Dispose(); - } - - /// - /// Gets the representing the channel at a given component index - /// - /// The component index - /// The of the channel - public OrigJpegPixelArea GetDestinationChannel(int compIndex) - { - if (this.ComponentCount == 1) - { - return this.grayImage; - } - else - { - switch (compIndex) - { - case 0: - return new OrigJpegPixelArea(this.ycbcrImage.YChannel); - case 1: - return new OrigJpegPixelArea(this.ycbcrImage.CbChannel); - case 2: - return new OrigJpegPixelArea(this.ycbcrImage.CrChannel); - case 3: - return this.blackImage; - default: - throw new ImageFormatException("Too many components"); - } - } } /// @@ -452,6 +391,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort break; } } + + this.InitDerivedMetaDataProperties(); } /// @@ -471,297 +412,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } /// - /// Process the blocks in into Jpeg image channels ( and ) - /// are in a "raw" frequency-domain form. We need to apply IDCT, dequantization and unzigging to transform them into color-space blocks. - /// We can copy these blocks into -s afterwards. - /// - private void ProcessBlocksIntoJpegImageChannels() - { - this.InitJpegImageChannels(); - - Parallel.For( - 0, - this.ComponentCount, - componentIndex => - { - var postProcessor = default(JpegBlockPostProcessor); - JpegBlockPostProcessor.Init(&postProcessor); - IJpegComponent component = this.Components[componentIndex]; - postProcessor.ProcessAllBlocks(this, component); - }); - } - - /// - /// Convert the pixel data in and/or into pixels of + /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// - /// The pixel type - /// The decoded image. - private Image ConvertJpegPixelsToImagePixels() - where TPixel : struct, IPixel - { - var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); - - if (this.grayImage.IsInitialized) - { - this.ConvertFromGrayScale(image); - return image; - } - else if (this.ycbcrImage != null) - { - if (this.ComponentCount == 4) - { - if (!this.adobeTransformValid) - { - throw new ImageFormatException( - "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); - } - - // 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) - { - this.ConvertFromYcck(image); - } - else if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformUnknown) - { - // Assume CMYK - this.ConvertFromCmyk(image); - } - - return image; - } - - if (this.ComponentCount == 3) - { - if (this.IsRGB()) - { - this.ConvertFromRGB(image); - return image; - } - - this.ConvertFromYCbCr(image); - return image; - } - - throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); - } - else - { - throw new ImageFormatException("Missing SOS marker."); - } - } - - /// - /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. - /// - /// The pixel format. - /// The image to assign the resolution to. - private void AssignResolution(Image image) - where TPixel : struct, IPixel + private void InitDerivedMetaDataProperties() { if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) { - image.MetaData.HorizontalResolution = this.horizontalResolution; - image.MetaData.VerticalResolution = this.verticalResolution; + this.MetaData.HorizontalResolution = this.horizontalResolution; + this.MetaData.VerticalResolution = this.verticalResolution; } else if (this.isExif) { - ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); - ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution); + ExifValue horizontal = this.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + ExifValue vertical = this.MetaData.ExifProfile.GetValue(ExifTag.YResolution); double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; if (horizontalValue > 0 && verticalValue > 0) { - image.MetaData.HorizontalResolution = horizontalValue; - image.MetaData.VerticalResolution = verticalValue; + this.MetaData.HorizontalResolution = horizontalValue; + this.MetaData.VerticalResolution = verticalValue; } } } - /// - /// Converts the image from the original CMYK image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromCmyk(Image image) - where TPixel : struct, IPixel - { - int scale = this.Components[0].HorizontalSamplingFactor / this.Components[1].HorizontalSamplingFactor; - - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - image.Height, - y => - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < image.Width; x++) - { - byte cyan = this.ycbcrImage.YChannel[yo + x]; - byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)]; - - TPixel packed = default(TPixel); - this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); - pixels[x, y] = packed; - } - }); - } - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original grayscale image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromGrayScale(Image image) - where TPixel : struct, IPixel - { - Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - ref TPixel pixelRowBaseRef = ref image.GetPixelReference(0, y); - - int yoff = this.grayImage.GetRowOffset(y); - - for (int x = 0; x < image.Width; x++) - { - byte rgb = this.grayImage.Pixels[yoff + x]; - ref TPixel pixel = ref Unsafe.Add(ref pixelRowBaseRef, x); - pixel.PackFromRgba32(new Rgba32(rgb, rgb, rgb, 255)); - } - }); - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original RBG image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromRGB(Image image) - where TPixel : struct, IPixel - { - int scale = this.Components[0].HorizontalSamplingFactor / this.Components[1].HorizontalSamplingFactor; - - Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - ref TPixel pixelRowBaseRef = ref image.GetPixelReference(0, y); - - Rgba32 rgba = new Rgba32(0, 0, 0, 255); - - for (int x = 0; x < image.Width; x++) - { - rgba.R = this.ycbcrImage.YChannel[yo + x]; - rgba.G = this.ycbcrImage.CbChannel[co + (x / scale)]; - rgba.B = this.ycbcrImage.CrChannel[co + (x / scale)]; - - ref TPixel pixel = ref Unsafe.Add(ref pixelRowBaseRef, x); - pixel.PackFromRgba32(rgba); - } - }); - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original YCbCr image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromYCbCr(Image image) - where TPixel : struct, IPixel - { - int scale = this.Components[0].HorizontalSamplingFactor / this.Components[1].HorizontalSamplingFactor; - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - // TODO. This Parallel loop doesn't give us the boost it should. - ref byte ycRef = ref this.ycbcrImage.YChannel[0]; - ref byte cbRef = ref this.ycbcrImage.CbChannel[0]; - ref byte crRef = ref this.ycbcrImage.CrChannel[0]; - fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables) - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < image.Width; x++) - { - int cOff = co + (x / scale); - byte yy = Unsafe.Add(ref ycRef, yo + x); - byte cb = Unsafe.Add(ref cbRef, cOff); - byte cr = Unsafe.Add(ref crRef, cOff); - - TPixel packed = default(TPixel); - YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr); - pixels[x, y] = packed; - } - } - }); - } - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original YCCK image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromYcck(Image image) - where TPixel : struct, IPixel - { - int scale = this.Components[0].HorizontalSamplingFactor / this.Components[1].HorizontalSamplingFactor; - - Parallel.For( - 0, - image.Height, - y => - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - ref TPixel pixelRowBaseRef = ref image.GetPixelReference(0, y); - - for (int x = 0; x < image.Width; x++) - { - byte yy = this.ycbcrImage.YChannel[yo + x]; - byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; - - ref TPixel pixel = ref Unsafe.Add(ref pixelRowBaseRef, x); - this.PackYcck(ref pixel, yy, cb, cr, x, y); - } - }); - - this.AssignResolution(image); - } - /// /// Returns a value indicating whether the image in an RGB image. /// @@ -786,99 +460,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort && this.Components[2].Identifier == 'B'; } - /// - /// Initializes the image channels. - /// - private void InitJpegImageChannels() - { - Size[] sizes = ComponentUtils.CalculateJpegChannelSizes(this.Components, this.SubsampleRatio); - - if (this.ComponentCount == 1) - { - this.grayImage = new OrigJpegPixelArea(sizes[0]); - } - else - { - Size size = sizes[0]; - - this.ycbcrImage = new YCbCrImage(size.Width, size.Height, this.SubsampleRatio); - - if (this.ComponentCount == 4) - { - this.blackImage = new OrigJpegPixelArea(sizes[3]); - } - } - } - - /// - /// Optimized method to pack bytes to the image from the CMYK color space. - /// This is faster than implicit casting as it avoids double packing. - /// - /// The pixel format. - /// The packed pixel. - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The x-position within the image. - /// The y-position within the image. - private void PackCmyk(ref TPixel packed, byte c, byte m, byte y, int xx, int yy) - where TPixel : struct, IPixel - { - // Get keyline - float keyline = (255 - this.blackImage[xx, yy]) / 255F; - - // Convert back to RGB. CMY are not inverted - byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - - packed.PackFromRgba32(new Rgba32(r, g, b)); - } - - /// - /// Optimized method to pack bytes to the image from the YCCK color space. - /// This is faster than implicit casting as it avoids double packing. - /// - /// The pixel format. - /// The packed pixel. - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - /// The x-position within the image. - /// The y-position within the image. - private void PackYcck(ref TPixel packed, byte y, byte cb, byte cr, int xx, int yy) - where TPixel : struct, IPixel - { - // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get - // CMY, and patch in the original K. The RGB to CMY inversion cancels - // out the 'Adobe inversion' described in the applyBlack doc comment - // above, so in practice, only the fourth channel (black) is inverted. - int ccb = cb - 128; - int ccr = cr - 128; - - // Speed up the algorithm by removing floating point calculation - // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result - int r0 = 91881 * ccr; // (1.402F * 65536) + .5F - int g0 = 22554 * ccb; // (0.34414F * 65536) + .5F - int g1 = 46802 * ccr; // (0.71414F * 65536) + .5F - int b0 = 116130 * ccb; // (1.772F * 65536) + .5F - - // First convert from YCbCr to CMY - float cyan = (y + (r0 >> 16)).Clamp(0, 255) / 255F; - float magenta = (byte)(y - (g0 >> 16) - (g1 >> 16)).Clamp(0, 255) / 255F; - float yellow = (byte)(y + (b0 >> 16)).Clamp(0, 255) / 255F; - - // Get keyline - float keyline = (255 - this.blackImage[xx, yy]) / 255F; - - // Convert back to RGB - byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255); - byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255); - byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255); - - packed.PackFromRgba32(new Rgba32(r, g, b)); - } - /// /// 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 @@ -913,7 +494,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// Processes the App1 marker retrieving any stored metadata /// /// The remaining bytes in the segment block. - /// The image. private void ProcessApp1Marker(int remaining) { if (remaining < 6 || this.IgnoreMetadata) @@ -1193,8 +773,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } this.ColorSpace = this.DeduceJpegColorSpace(); - - this.SubsampleRatio = ComponentUtils.GetSubsampleRatio(this.Components); } private JpegColorSpace DeduceJpegColorSpace() diff --git a/src/ImageSharp/Memory/BufferArea.cs b/src/ImageSharp/Memory/BufferArea.cs index 67dddd77cc..0e14d1eacf 100644 --- a/src/ImageSharp/Memory/BufferArea.cs +++ b/src/ImageSharp/Memory/BufferArea.cs @@ -18,8 +18,8 @@ namespace SixLabors.ImageSharp.Memory { Guard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); Guard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); - Guard.MustBeLessThan(rectangle.Width, destinationBuffer.Width, nameof(rectangle)); - Guard.MustBeLessThan(rectangle.Height, destinationBuffer.Height, nameof(rectangle)); + Guard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle)); + Guard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle)); this.DestinationBuffer = destinationBuffer; this.Rectangle = rectangle; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs deleted file mode 100644 index c5f3dcc73f..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -// ReSharper disable InconsistentNaming - -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; -using SixLabors.Primitives; - -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - public class ComponentUtilsTests - { - public ComponentUtilsTests(ITestOutputHelper output) - { - this.Output = output; - } - - private ITestOutputHelper Output { get; } - - [Theory] - [InlineData(SubsampleRatio.Ratio410, 4, 2)] - [InlineData(SubsampleRatio.Ratio411, 4, 1)] - [InlineData(SubsampleRatio.Ratio420, 2, 2)] - [InlineData(SubsampleRatio.Ratio422, 2, 1)] - [InlineData(SubsampleRatio.Ratio440, 1, 2)] - [InlineData(SubsampleRatio.Ratio444, 1, 1)] - internal void CalculateChrominanceSize(SubsampleRatio ratio, int expectedDivX, int expectedDivY) - { - //this.Output.WriteLine($"RATIO: {ratio}"); - Size size = ratio.CalculateChrominanceSize(400, 400); - //this.Output.WriteLine($"Ch Size: {size}"); - - Assert.Equal(new Size(400 / expectedDivX, 400 / expectedDivY), size); - } - - [Theory] - [InlineData(SubsampleRatio.Ratio410, 4, 2)] - [InlineData(SubsampleRatio.Ratio411, 4, 1)] - [InlineData(SubsampleRatio.Ratio420, 2, 2)] - [InlineData(SubsampleRatio.Ratio422, 2, 1)] - [InlineData(SubsampleRatio.Ratio440, 1, 2)] - [InlineData(SubsampleRatio.Ratio444, 1, 1)] - [InlineData(SubsampleRatio.Undefined, 1, 1)] - internal void GetChrominanceSubSampling(SubsampleRatio ratio, int expectedDivX, int expectedDivY) - { - (int divX, int divY) = ratio.GetChrominanceSubSampling(); - - Assert.Equal(expectedDivX, divX); - Assert.Equal(expectedDivY, divY); - } - - [Theory] - [InlineData(SubsampleRatio.Ratio410, 4)] - [InlineData(SubsampleRatio.Ratio411, 4)] - [InlineData(SubsampleRatio.Ratio420, 2)] - [InlineData(SubsampleRatio.Ratio422, 2)] - [InlineData(SubsampleRatio.Ratio440, 1)] - [InlineData(SubsampleRatio.Ratio444, 1)] - internal void Create(SubsampleRatio ratio, int expectedCStrideDiv) - { - this.Output.WriteLine($"RATIO: {ratio}"); - - YCbCrImage img = new YCbCrImage(400, 400, ratio); - - //this.PrintChannel("Y", img.YChannel); - //this.PrintChannel("Cb", img.CbChannel); - //this.PrintChannel("Cr", img.CrChannel); - - Assert.Equal(400, img.YChannel.Width); - Assert.Equal(img.CbChannel.Width, 400 / expectedCStrideDiv); - Assert.Equal(img.CrChannel.Width, 400 / expectedCStrideDiv); - } - - private void PrintChannel(string name, OrigJpegPixelArea channel) - { - this.Output.WriteLine($"{name}: Stride={channel.Stride}"); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs similarity index 74% rename from tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs rename to tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 56bf6e90b2..e5f031b504 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -5,12 +5,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison using System.Linq; using System.Text; - public class ImagePixelsAreDifferentException : ImagesSimilarityException + public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException { public ImageSimilarityReport[] Reports { get; } - public ImagePixelsAreDifferentException(IEnumerable reports) - : base("Images are not similar enough!" + StringifyReports(reports)) + public ImageDifferenceIsOverThresholdException(IEnumerable reports) + : base("Image difference is over threshold!" + StringifyReports(reports)) { this.Reports = reports.ToArray(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index 74f46a869a..4fabd60761 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison IEnumerable reports = comparer.CompareImages(expected, actual); if (reports.Any()) { - throw new ImagePixelsAreDifferentException(reports); + throw new ImageDifferenceIsOverThresholdException(reports); } } @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison if (cleanedReports.Any()) { - throw new ImagePixelsAreDifferentException(cleanedReports); + throw new ImageDifferenceIsOverThresholdException(cleanedReports); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 29acabdc4f..f131f51f21 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests var comparer = ImageComparer.Tolerant(); - ImagePixelsAreDifferentException ex = Assert.ThrowsAny( + ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( () => { comparer.VerifySimilarity(image, clone); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs index 34300c56e3..45ac2d6cca 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests { ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1); - Assert.ThrowsAny( + Assert.ThrowsAny( () => { image.CompareToOriginal(provider, ImageComparer.Exact);