Browse Source

deleted all deprecated channel processing code

af/merge-core
Anton Firszov 9 years ago
parent
commit
cb713a3ade
  1. 108
      src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs
  2. 6
      src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs
  3. 42
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs
  5. 42
      src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs
  6. 122
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegPixelArea.cs
  7. 119
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs
  8. 107
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs
  9. 462
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  10. 4
      src/ImageSharp/Memory/BufferArea.cs
  11. 84
      tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs
  12. 6
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
  13. 4
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
  14. 2
      tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs
  15. 2
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs

108
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
{
/// <summary>
/// Various utilities for <see cref="SubsampleRatio"/> and <see cref="IJpegComponent"/>.
/// Various utilities for <see cref="IJpegComponent"/>.
/// </summary>
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<IJpegComponent> 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;
}
}
/// <summary>
/// Returns the height and width of the chroma components
/// TODO: Not needed by new JpegImagePostprocessor
/// </summary>
/// <param name="ratio">The subsampling ratio.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>The <see cref="Size"/> of the chrominance channel</returns>
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<IJpegComponent> 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;
}
}
}

6
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
{
/// <inheritdoc />
/// <summary>
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="IJpegComponent"/>-s.
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="T:SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.IJpegComponent" />-s.
/// </summary>
internal interface IRawJpegData
internal interface IRawJpegData : IDisposable
{
/// <summary>
/// Gets the image size in pixels.

42
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs → 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
{
/// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="Buffer{T}"/>-s into Jpeg image channels.
@ -36,22 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
postProcessor->pointers = new DataPointers(&postProcessor->data);
}
/// <summary>
/// Dequantize, perform the inverse DCT and store the blocks to the into the corresponding <see cref="OrigJpegPixelArea"/> instances.
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
/// <param name="component">The component</param>
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);
}
/// <summary>
/// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding <see cref="OrigJpegPixelArea"/> instance.
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/></param>
/// <param name="component">The <see cref="OrigComponent"/></param>
/// <param name="bx">The x index of the block in <see cref="OrigComponent.SpectralBlocks"/></param>
/// <param name="by">The y index of the block in <see cref="OrigComponent.SpectralBlocks"/></param>
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);
}
/// <summary>
/// Holds the "large" data blocks needed for computations.
/// </summary>
@ -140,7 +102,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 = UnzigData.Create();
return data;
}

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs

@ -8,7 +8,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Encapsulates the execution post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
/// Encapsulates the execution od post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
/// (1) Dequantization <br/>
/// (2) IDCT <br/>
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>

42
src/ImageSharp/Formats/Jpeg/Common/SubsampleRatio.cs

@ -1,42 +0,0 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
/// <summary>
/// Provides enumeration of the various available subsample ratios.
/// https://en.wikipedia.org/wiki/Chroma_subsampling
/// TODO: Not needed by new JpegImagePostprocessor
/// </summary>
internal enum SubsampleRatio
{
Undefined,
/// <summary>
/// 4:4:4
/// </summary>
Ratio444,
/// <summary>
/// 4:2:2
/// </summary>
Ratio422,
/// <summary>
/// 4:2:0
/// </summary>
Ratio420,
/// <summary>
/// 4:4:0
/// </summary>
Ratio440,
/// <summary>
/// 4:1:1
/// </summary>
Ratio411,
/// <summary>
/// 4:1:0
/// </summary>
Ratio410,
}
}

122
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegPixelArea.cs

@ -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
{
/// <summary>
/// Represents an area of a Jpeg subimage (channel)
/// </summary>
internal struct OrigJpegPixelArea
{
/// <summary>
/// Initializes a new instance of the <see cref="OrigJpegPixelArea" /> struct from existing data.
/// </summary>
/// <param name="pixels">The pixel buffer</param>
/// <param name="stride">The stride</param>
/// <param name="offset">The offset</param>
public OrigJpegPixelArea(Buffer2D<byte> pixels, int stride, int offset)
{
this.Stride = stride;
this.Pixels = pixels;
this.Offset = offset;
}
/// <summary>
/// Initializes a new instance of the <see cref="OrigJpegPixelArea" /> struct from existing buffer.
/// <see cref="Stride"/> will be set to <see cref="Buffer2D{T}.Width"/> of <paramref name="pixels"/> and <see cref="Offset"/> will be set to 0.
/// </summary>
/// <param name="pixels">The pixel buffer</param>
public OrigJpegPixelArea(Buffer2D<byte> pixels)
: this(pixels, pixels.Width, 0)
{
}
public OrigJpegPixelArea(Size size)
: this(Buffer2D<byte>.CreateClean(size))
{
}
/// <summary>
/// Gets the pixels buffer.
/// </summary>
public Buffer2D<byte> Pixels { get; private set; }
/// <summary>
/// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea))
/// </summary>
public bool IsInitialized => this.Pixels != null;
/// <summary>
/// Gets the stride.
/// </summary>
public int Stride { get; }
/// <summary>
/// Gets the offset.
/// </summary>
public int Offset { get; }
/// <summary>
/// Gets a <see cref="Span{T}" /> of bytes to the pixel area
/// </summary>
public Span<byte> Span => new Span<byte>(this.Pixels.Array, this.Offset);
/// <summary>
/// Returns the pixel at (x, y)
/// </summary>
/// <param name="x">The x index</param>
/// <param name="y">The y index</param>
/// <returns>The pixel value</returns>
public byte this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Pixels[(y * this.Stride) + x];
}
}
/// <summary>
/// Gets the subarea that belongs to the Block8x8 defined by block indices
/// </summary>
/// <param name="bx">The block X index</param>
/// <param name="by">The block Y index</param>
/// <returns>The subarea offseted by block indices</returns>
public OrigJpegPixelArea GetOffsetedSubAreaForBlock(int bx, int by)
{
int offset = this.Offset + (8 * ((by * this.Stride) + bx));
return new OrigJpegPixelArea(this.Pixels, this.Stride, offset);
}
/// <summary>
/// Gets the row offset at the given position
/// </summary>
/// <param name="y">The y-coordinate of the image.</param>
/// <returns>The <see cref="int" /></returns>
public int GetRowOffset(int y)
{
return this.Offset + (y * this.Stride);
}
/// <summary>
/// Load values to the pixel area from the given <see cref="Block8x8F" />.
/// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to
/// <see cref="byte" /> values
/// </summary>
/// <param name="block">The block holding the color values</param>
/// <param name="temp">Temporal block provided by the caller</param>
[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<byte>(this.Pixels.Array, this.Offset), this.Stride, temp);
}
}
}

119
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs

@ -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;
/// <summary>
/// Represents an image made up of three color components (luminance, blue chroma, red chroma)
/// </summary>
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
/// <summary>
/// Gets the luminance components channel as <see cref="OrigJpegPixelArea" />.
/// </summary>
public Buffer2D<byte> YChannel;
/// <summary>
/// Gets the blue chroma components channel as <see cref="OrigJpegPixelArea" />.
/// </summary>
public Buffer2D<byte> CbChannel;
/// <summary>
/// Gets an offseted <see cref="OrigJpegPixelArea" /> to the Cr channel
/// </summary>
public Buffer2D<byte> CrChannel;
#pragma warning restore SA1401
/// <summary>
/// Initializes a new instance of the <see cref="YCbCrImage" /> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="ratio">The ratio.</param>
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<byte>.CreateClean(width, height);
this.CbChannel = Buffer2D<byte>.CreateClean(cSize.Width, cSize.Height);
this.CrChannel = Buffer2D<byte>.CreateClean(cSize.Width, cSize.Height);
}
/// <summary>
/// Gets the Y slice index delta between vertically adjacent pixels.
/// </summary>
public int YStride { get; }
/// <summary>
/// Gets the red and blue chroma slice index delta between vertically adjacent pixels
/// that map to separate chroma samples.
/// </summary>
public int CStride { get; }
/// <summary>
/// Gets or sets the subsampling ratio.
/// </summary>
public SubsampleRatio Ratio { get; set; }
/// <summary>
/// Disposes the <see cref="YCbCrImage" /> returning rented arrays to the pools.
/// </summary>
public void Dispose()
{
this.YChannel.Dispose();
this.CbChannel.Dispose();
this.CrChannel.Dispose();
}
/// <summary>
/// Returns the offset of the first chroma component at the given row
/// </summary>
/// <param name="y">The row number.</param>
/// <returns>
/// The <see cref="int" />.
/// </returns>
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;
}
}
/// <summary>
/// Returns the offset of the first luminance component at the given row
/// </summary>
/// <param name="y">The row number.</param>
/// <returns>
/// The <see cref="int" />.
/// </returns>
public int GetRowYOffset(int y)
{
return y * this.YStride;
}
}
}

107
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs

@ -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
{
/// <summary>
/// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace.
/// Methods to build the tables are based on libjpeg implementation.
/// </summary>
internal unsafe struct YCbCrToRgbTables
{
/// <summary>
/// The red red-chrominance table
/// </summary>
public fixed int CrRTable[256];
/// <summary>
/// The blue blue-chrominance table
/// </summary>
public fixed int CbBTable[256];
/// <summary>
/// The green red-chrominance table
/// </summary>
public fixed int CrGTable[256];
/// <summary>
/// The green blue-chrominance table
/// </summary>
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);
/// <summary>
/// Initializes the YCbCr tables
/// </summary>
/// <returns>The intialized <see cref="YCbCrToRgbTables"/></returns>
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;
}
/// <summary>
/// Optimized method to pack bytes to the image from the YCbCr color space.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="tables">The reference to the tables instance.</param>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Pack<TPixel>(ref TPixel packed, YCbCrToRgbTables* tables, byte y, byte cb, byte cr)
where TPixel : struct, IPixel<TPixel>
{
// 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;
}
}
}

462
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
{
/// <inheritdoc />
/// <summary>
/// Performs the jpeg decoding operation.
/// </summary>
internal sealed unsafe class OrigJpegDecoderCore : IDisposable, IRawJpegData
internal sealed unsafe class OrigJpegDecoderCore : IRawJpegData
{
/// <summary>
/// The maximum number of color components
@ -43,11 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public InputProcessor InputProcessor;
#pragma warning restore SA401
/// <summary>
/// Lookup tables for converting YCbCr to Rgb
/// </summary>
private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create();
/// <summary>
/// The global configuration
/// </summary>
@ -63,16 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
private bool adobeTransformValid;
/// <summary>
/// The black image to decode to.
/// </summary>
private OrigJpegPixelArea blackImage;
/// <summary>
/// A grayscale image to decode to.
/// </summary>
private OrigJpegPixelArea grayImage;
/// <summary>
/// The horizontal resolution. Calculated if the image has a JFIF header.
/// </summary>
@ -93,11 +77,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
private short verticalResolution;
/// <summary>
/// The full color image to decode to.
/// </summary>
private YCbCrImage ycbcrImage;
/// <summary>
/// Initializes a new instance of the <see cref="OrigJpegDecoderCore" /> class.
/// </summary>
@ -112,11 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.Temp = new byte[2 * Block8x8F.Size];
}
/// <summary>
/// Gets the <see cref="Common.SubsampleRatio"/> ratio.
/// </summary>
public SubsampleRatio SubsampleRatio { get; private set; }
/// <inheritdoc />
public JpegColorSpace ColorSpace { get; private set; }
/// <summary>
@ -138,13 +113,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
public byte[] Temp { get; }
/// <inheritdoc />
public Size ImageSizeInPixels { get; private set; }
public Size ImageSizeInMCU { get; private set; }
/// <summary>
/// Gets the number of color components within the image.
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
public Size ImageSizeInMCU { get; private set; }
/// <inheritdoc />
public int ComponentCount { get; private set; }
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
@ -192,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; private set; }
public bool IgnoreMetadata { get; }
/// <summary>
/// Gets the <see cref="ImageMetaData"/> 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<TPixel>();
#else
return this.PostProcessIntoImage<TPixel>();
#endif
}
/// <inheritdoc />
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();
}
/// <summary>
/// Gets the <see cref="OrigJpegPixelArea"/> representing the channel at a given component index
/// </summary>
/// <param name="compIndex">The component index</param>
/// <returns>The <see cref="OrigJpegPixelArea"/> of the channel</returns>
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");
}
}
}
/// <summary>
@ -452,6 +391,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
break;
}
}
this.InitDerivedMetaDataProperties();
}
/// <summary>
@ -471,297 +412,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
/// <summary>
/// Process the blocks in <see cref="OrigComponent.SpectralBlocks"/> into Jpeg image channels (<see cref="YCbCrImage"/> and <see cref="OrigJpegPixelArea"/>)
/// <see cref="OrigComponent.SpectralBlocks"/> 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 <see cref="OrigJpegPixelArea"/>-s afterwards.
/// </summary>
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);
});
}
/// <summary>
/// Convert the pixel data in <see cref="YCbCrImage"/> and/or <see cref="OrigJpegPixelArea"/> into pixels of <see cref="Image{TPixel}"/>
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <returns>The decoded image.</returns>
private Image<TPixel> ConvertJpegPixelsToImagePixels<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var image = new Image<TPixel>(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.");
}
}
/// <summary>
/// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to assign the resolution to.</param>
private void AssignResolution<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
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;
}
}
}
/// <summary>
/// Converts the image from the original CMYK image pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image.</param>
private void ConvertFromCmyk<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
int scale = this.Components[0].HorizontalSamplingFactor / this.Components[1].HorizontalSamplingFactor;
using (PixelAccessor<TPixel> 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);
}
/// <summary>
/// Converts the image from the original grayscale image pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image.</param>
private void ConvertFromGrayScale<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
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);
}
/// <summary>
/// Converts the image from the original RBG image pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image.</param>
private void ConvertFromRGB<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
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);
}
/// <summary>
/// Converts the image from the original YCbCr image pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image.</param>
private void ConvertFromYCbCr<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
int scale = this.Components[0].HorizontalSamplingFactor / this.Components[1].HorizontalSamplingFactor;
using (PixelAccessor<TPixel> 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);
}
/// <summary>
/// Converts the image from the original YCCK image pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image.</param>
private void ConvertFromYcck<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
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);
}
/// <summary>
/// Returns a value indicating whether the image in an RGB image.
/// </summary>
@ -786,99 +460,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
&& this.Components[2].Identifier == 'B';
}
/// <summary>
/// Initializes the image channels.
/// </summary>
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]);
}
}
}
/// <summary>
/// Optimized method to pack bytes to the image from the CMYK color space.
/// This is faster than implicit casting as it avoids double packing.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="c">The cyan component.</param>
/// <param name="m">The magenta component.</param>
/// <param name="y">The yellow component.</param>
/// <param name="xx">The x-position within the image.</param>
/// <param name="yy">The y-position within the image.</param>
private void PackCmyk<TPixel>(ref TPixel packed, byte c, byte m, byte y, int xx, int yy)
where TPixel : struct, IPixel<TPixel>
{
// 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));
}
/// <summary>
/// Optimized method to pack bytes to the image from the YCCK color space.
/// This is faster than implicit casting as it avoids double packing.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
/// <param name="xx">The x-position within the image.</param>
/// <param name="yy">The y-position within the image.</param>
private void PackYcck<TPixel>(ref TPixel packed, byte y, byte cb, byte cr, int xx, int yy)
where TPixel : struct, IPixel<TPixel>
{
// 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));
}
/// <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
@ -913,7 +494,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// Processes the App1 marker retrieving any stored metadata
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="metadata">The image.</param>
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()

4
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;

84
tests/ImageSharp.Tests/Formats/Jpg/ComponentUtilsTests.cs

@ -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}");
}
}
}

6
tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs → 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<ImageSimilarityReport> reports)
: base("Images are not similar enough!" + StringifyReports(reports))
public ImageDifferenceIsOverThresholdException(IEnumerable<ImageSimilarityReport> reports)
: base("Image difference is over threshold!" + StringifyReports(reports))
{
this.Reports = reports.ToArray();
}

4
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs

@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
IEnumerable<ImageSimilarityReport> 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);
}
}
}

2
tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs

@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests
var comparer = ImageComparer.Tolerant();
ImagePixelsAreDifferentException ex = Assert.ThrowsAny<ImagePixelsAreDifferentException>(
ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny<ImageDifferenceIsOverThresholdException>(
() =>
{
comparer.VerifySimilarity(image, clone);

2
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs

@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests
{
ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1);
Assert.ThrowsAny<ImagePixelsAreDifferentException>(
Assert.ThrowsAny<ImageDifferenceIsOverThresholdException>(
() =>
{
image.CompareToOriginal(provider, ImageComparer.Exact);

Loading…
Cancel
Save