Browse Source

Merge remote-tracking branch 'upstream/master' into feature/adaptiveHistogramEqualization

pull/673/head
James Jackson-South 7 years ago
parent
commit
04ece7c12b
  1. 37
      src/ImageSharp/Common/Helpers/Vector4Utils.cs
  2. 17
      src/ImageSharp/Common/Tuples/Vector4Pair.cs
  3. 2
      src/ImageSharp/Formats/Bmp/BmpCompression.cs
  4. 36
      src/ImageSharp/Formats/Bmp/BmpConstants.cs
  5. 2
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  6. 527
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  7. 50
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  8. 252
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  9. 48
      src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs
  10. 11
      src/ImageSharp/Formats/Bmp/BmpMetaData.cs
  11. 19
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  12. 19
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
  13. 8
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  14. 12
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
  15. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
  16. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
  17. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  18. 28
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  19. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
  20. 22
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs
  21. 45
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  22. 23
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  23. 7
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  24. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  25. 3
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  26. 19
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  27. 23
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  28. 459
      src/ImageSharp/Primitives/ColorMatrix.cs
  29. 6
      src/ImageSharp/Processing/FilterExtensions.cs
  30. 358
      src/ImageSharp/Processing/KnownFilterMatrices.cs
  31. 36
      src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
  32. 6
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs
  33. 109
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  34. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  35. 3
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  36. 6
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  37. 20
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  38. 10
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  39. 5
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  40. 6
      tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs
  41. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  42. 270
      tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs
  43. 8
      tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs
  44. 9
      tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs
  45. 20
      tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
  46. 43
      tests/ImageSharp.Tests/TestImages.cs
  47. 28
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  48. BIN
      tests/Images/Input/Bmp/issue735.bmp
  49. BIN
      tests/Images/Input/Bmp/pal4rle.bmp
  50. BIN
      tests/Images/Input/Bmp/pal8v4.bmp
  51. BIN
      tests/Images/Input/Bmp/pal8v5.bmp
  52. BIN
      tests/Images/Input/Bmp/rgb16-565.bmp
  53. BIN
      tests/Images/Input/Bmp/rgb16-565pal.bmp
  54. BIN
      tests/Images/Input/Bmp/rgb16bfdef.bmp
  55. BIN
      tests/Images/Input/Bmp/rgb24.bmp
  56. BIN
      tests/Images/Input/Bmp/rgb32bf.bmp
  57. BIN
      tests/Images/Input/Bmp/rgb32bfdef.bmp
  58. BIN
      tests/Images/Input/Bmp/rgba32-1010102.bmp
  59. BIN
      tests/Images/Input/Bmp/rgba32.bmp
  60. BIN
      tests/Images/Input/Bmp/rgba32h56.bmp
  61. BIN
      tests/Images/Input/Jpg/baseline/testorig12.jpg
  62. BIN
      tests/Images/Input/Jpg/issues/Issue797-InvalidImage.jpg
  63. BIN
      tests/Images/Input/Jpg/issues/Issue798-AccessViolationException.jpg

37
src/ImageSharp/Common/Helpers/Vector4Utils.cs

@ -5,6 +5,7 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp
{
@ -70,5 +71,41 @@ namespace SixLabors.ImageSharp
UnPremultiply(ref v);
}
}
/// <summary>
/// Transforms a vector by the given matrix.
/// </summary>
/// <param name="vector">The source vector.</param>
/// <param name="matrix">The transformation matrix.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Transform(ref Vector4 vector, ref ColorMatrix matrix)
{
float x = vector.X;
float y = vector.Y;
float z = vector.Z;
float w = vector.W;
vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51;
vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52;
vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53;
vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54;
}
/// <summary>
/// Bulk variant of <see cref="Transform(ref Vector4, ref ColorMatrix)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
/// <param name="matrix">The transformation matrix.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Transform(Span<Vector4> vectors, ref ColorMatrix matrix)
{
ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
for (int i = 0; i < vectors.Length; i++)
{
ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
Transform(ref v, ref matrix);
}
}
}
}

17
src/ImageSharp/Common/Tuples/Vector4Pair.cs

@ -37,12 +37,11 @@ namespace SixLabors.ImageSharp.Tuples
this.B += other.B;
}
/// <summary>
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4!
/// TODO: Move it somewhere else.
/// <summary>.
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscalePreAvx2()
internal void RoundAndDownscalePreAvx2(float downscaleFactor)
{
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A);
a = a.FastRound();
@ -50,8 +49,8 @@ namespace SixLabors.ImageSharp.Tuples
ref Vector<float> b = ref Unsafe.As<Vector4, Vector<float>>(ref this.B);
b = b.FastRound();
// Downscale by 1/255
var scale = new Vector4(1 / 255f);
// Downscale by 1/factor
var scale = new Vector4(1 / downscaleFactor);
this.A *= scale;
this.B *= scale;
}
@ -61,14 +60,14 @@ namespace SixLabors.ImageSharp.Tuples
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscaleAvx2()
internal void RoundAndDownscaleAvx2(float downscaleFactor)
{
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this);
Vector<float> v = self;
v = v.FastRound();
// Downscale by 1/255
v *= new Vector<float>(1 / 255f);
// Downscale by 1/factor
v *= new Vector<float>(1 / downscaleFactor);
self = v;
}

2
src/ImageSharp/Formats/Bmp/BmpCompression.cs

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// If the first byte is zero, the record has different meanings, depending
/// on the second byte. If the second byte is zero, it is the end of the row,
/// if it is one, it is the end of the image.
/// Not supported at the moment.
/// </summary>
RLE8 = 1,
@ -42,7 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Each image row has a multiple of four elements. If the
/// row has less elements, zeros will be added at the right side.
/// Not supported at the moment.
/// </summary>
BitFields = 3,

36
src/ImageSharp/Formats/Bmp/BmpConstants.cs

@ -19,5 +19,41 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The list of file extensions that equate to a bmp.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "bm", "bmp", "dip" };
/// <summary>
/// Valid magic bytes markers identifying a Bitmap file.
/// </summary>
internal static class TypeMarkers
{
/// <summary>
/// Single-image BMP file that may have been created under Windows or OS/2.
/// </summary>
public const int Bitmap = 0x4D42;
/// <summary>
/// OS/2 Bitmap Array.
/// </summary>
public const int BitmapArray = 0x4142;
/// <summary>
/// OS/2 Color Icon.
/// </summary>
public const int ColorIcon = 0x4943;
/// <summary>
/// OS/2 Color Pointer.
/// </summary>
public const int ColorPointer = 0x5043;
/// <summary>
/// OS/2 Icon.
/// </summary>
public const int Icon = 0x4349;
/// <summary>
/// OS/2 Pointer.
/// </summary>
public const int Pointer = 0x5450;
}
}
}

2
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -15,8 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <item>JPG</item>
/// <item>PNG</item>
/// <item>RLE4</item>
/// <item>RLE8</item>
/// <item>BitFields</item>
/// </list>
/// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps.

527
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
@ -14,7 +16,7 @@ using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Performs the bmp decoding operation.
/// Performs the bitmap decoding operation.
/// </summary>
/// <remarks>
/// A useful decoding source example can be found at <see href="https://dxr.mozilla.org/mozilla-central/source/image/decoders/nsBMPDecoder.cpp"/>
@ -22,19 +24,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal sealed class BmpDecoderCore
{
/// <summary>
/// The mask for the red part of the color for 16 bit rgb bitmaps.
/// The default mask for the red part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16RMask = 0x7C00;
private const int DefaultRgb16RMask = 0x7C00;
/// <summary>
/// The mask for the green part of the color for 16 bit rgb bitmaps.
/// The default mask for the green part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16GMask = 0x3E0;
private const int DefaultRgb16GMask = 0x3E0;
/// <summary>
/// The mask for the blue part of the color for 16 bit rgb bitmaps.
/// The default mask for the blue part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16BMask = 0x1F;
private const int DefaultRgb16BMask = 0x1F;
/// <summary>
/// RLE8 flag value that indicates following byte has special meaning.
@ -62,10 +64,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private Stream stream;
/// <summary>
/// The metadata
/// The metadata.
/// </summary>
private ImageMetaData metaData;
/// <summary>
/// The bmp specific metadata.
/// </summary>
private BmpMetaData bmpMetaData;
/// <summary>
/// The file header containing general information.
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
@ -85,7 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options</param>
/// <param name="options">The options.</param>
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
{
this.configuration = configuration;
@ -119,7 +126,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpCompression.RGB:
if (this.infoHeader.BitsPerPixel == 32)
{
this.ReadRgb32(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
if (this.bmpMetaData.InfoHeaderType == BmpInfoHeaderType.WinVersion3)
{
this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else
{
this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
}
else if (this.infoHeader.BitsPerPixel == 24)
{
@ -143,9 +157,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
case BmpCompression.RLE8:
this.ReadRle8(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
case BmpCompression.RLE4:
this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
break;
case BmpCompression.BitFields:
this.ReadBitFields(pixels, inverted);
break;
default:
throw new NotSupportedException("Does not support this kind of bitmap files.");
}
@ -199,30 +220,66 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Performs final shifting from a 5bit value to an 8bit one.
/// Decodes a bitmap containing BITFIELDS Compression type. For each color channel, there will be bitmask
/// which will be used to determine which bits belong to that channel.
/// </summary>
/// <param name="value">The masked and shifted value</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadBitFields<TPixel>(Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(
pixels,
this.infoHeader.Width,
this.infoHeader.Height,
inverted,
this.infoHeader.RedMask,
this.infoHeader.GreenMask,
this.infoHeader.BlueMask);
}
else
{
this.ReadRgb32BitFields(
pixels,
this.infoHeader.Width,
this.infoHeader.Height,
inverted,
this.infoHeader.RedMask,
this.infoHeader.GreenMask,
this.infoHeader.BlueMask,
this.infoHeader.AlphaMask);
}
}
/// <summary>
/// Looks up color values and builds the image from de-compressed RLE8 data.
/// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data.
/// Compressed RLE8 stream is uncompressed by <see cref="UncompressRle8(int, Span{byte})"/>
/// Compressed RLE4 stream is uncompressed by <see cref="UncompressRle4(int, Span{byte})"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="compression">The compression type. Either RLE4 or RLE8.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle8<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
private void ReadRle<TPixel>(BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
using (Buffer2D<byte> buffer = this.memoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean))
{
this.UncompressRle8(width, buffer.GetSpan());
if (compression == BmpCompression.RLE8)
{
this.UncompressRle8(width, buffer.GetSpan());
}
else
{
this.UncompressRle4(width, buffer.GetSpan());
}
for (int y = 0; y < height; y++)
{
@ -240,12 +297,122 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Produce uncompressed bitmap data from RLE8 stream
/// Produce uncompressed bitmap data from a RLE4 stream.
/// </summary>
/// <remarks>
/// RLE4 is a 2-byte run-length encoding.
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, the first byte is the length of the run and second byte contains two color indexes.
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
private void UncompressRle4(int w, Span<byte> buffer)
{
#if NETCOREAPP2_1
Span<byte> cmd = stackalloc byte[2];
#else
byte[] cmd = new byte[2];
#endif
int count = 0;
while (count < buffer.Length)
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
throw new Exception("Failed to read 2 bytes from the stream");
}
if (cmd[0] == RleCommand)
{
switch (cmd[1])
{
case RleEndOfBitmap:
return;
case RleEndOfLine:
int extra = count % w;
if (extra > 0)
{
count += w - extra;
}
break;
case RleDelta:
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
count += (w * dy) + dx;
break;
default:
// If the second byte > 2, we are in 'absolute mode'.
// The second byte contains the number of color indexes that follow.
int max = cmd[1];
int bytesToRead = (max + 1) / 2;
byte[] run = new byte[bytesToRead];
this.stream.Read(run, 0, run.Length);
int idx = 0;
for (int i = 0; i < max; i++)
{
byte twoPixels = run[idx];
if (i % 2 == 0)
{
byte leftPixel = (byte)((twoPixels >> 4) & 0xF);
buffer[count++] = leftPixel;
}
else
{
byte rightPixel = (byte)(twoPixels & 0xF);
buffer[count++] = rightPixel;
idx++;
}
}
// Absolute mode data is aligned to two-byte word-boundary
int padding = bytesToRead & 1;
this.stream.Skip(padding);
break;
}
}
else
{
int max = cmd[0];
// The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
byte twoPixels = cmd[1];
byte rightPixel = (byte)(twoPixels & 0xF);
byte leftPixel = (byte)((twoPixels >> 4) & 0xF);
for (int idx = 0; idx < max; idx++)
{
if (idx % 2 == 0)
{
buffer[count] = leftPixel;
}
else
{
buffer[count] = rightPixel;
}
count++;
}
}
}
}
/// <summary>
/// Produce uncompressed bitmap data from a RLE8 stream.
/// </summary>
/// <remarks>
/// RLE8 is a 2-byte run-length encoding
/// <br/>If first byte is 0, the second byte may have special meaning
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run
/// RLE8 is a 2-byte run-length encoding.
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, the first byte is the length of the run and second byte is the color for the run.
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
@ -382,20 +549,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 16 bit color palette from the stream
/// Reads the 16 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
/// <param name="redMask">The bitmask for the red channel.</param>
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 2);
int stride = (width * 2) + padding;
TPixel color = default;
int rightShiftRedMask = CalculateRightShift((uint)redMask);
int rightShiftGreenMask = CalculateRightShift((uint)greenMask);
int rightShiftBlueMask = CalculateRightShift((uint)blueMask);
// Each color channel contains either 5 or 6 Bits values.
int redMaskBits = CountBits((uint)redMask);
int greenMaskBits = CountBits((uint)greenMask);
int blueMaskBits = CountBits((uint)blueMask);
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
{
for (int y = 0; y < height; y++)
@ -409,10 +588,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
short temp = BitConverter.ToInt16(buffer.Array, offset);
var rgb = new Rgb24(
GetBytesFrom5BitValue((temp & Rgb16RMask) >> 10),
GetBytesFrom5BitValue((temp & Rgb16GMask) >> 5),
GetBytesFrom5BitValue(temp & Rgb16BMask));
// Rescale values, so the values range from 0 to 255.
int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask);
int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask);
int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask);
var rgb = new Rgb24((byte)r, (byte)g, (byte)b);
color.FromRgb24(rgb);
pixelRow[x] = color;
@ -423,7 +603,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 24 bit color palette from the stream
/// Performs final shifting from a 5bit value to an 8bit one.
/// </summary>
/// <param name="value">The masked and shifted value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
/// <summary>
/// Performs final shifting from a 6bit value to an 8bit one.
/// </summary>
/// <param name="value">The masked and shifted value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4));
/// <summary>
/// Reads the 24 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
@ -452,14 +648,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 32 bit color palette from the stream
/// Reads the 32 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
private void ReadRgb32Fast<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
@ -480,6 +676,228 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
/// <summary>
/// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel.
/// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
using (IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width))
{
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
bgraRowSpan,
width);
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
if (hasAlpha)
{
break;
}
}
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
pixelSpan,
width);
}
return;
}
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
bgraRowSpan,
width);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
}
}
}
}
/// <summary>
/// Decode an 32 Bit Bitmap containing a bitmask for each color channel.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
/// <param name="redMask">The bitmask for the red channel.</param>
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
/// <param name="alphaMask">The bitmask for the alpha channel.</param>
private void ReadRgb32BitFields<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
int padding = CalculatePadding(width, 4);
int stride = (width * 4) + padding;
int rightShiftRedMask = CalculateRightShift((uint)redMask);
int rightShiftGreenMask = CalculateRightShift((uint)greenMask);
int rightShiftBlueMask = CalculateRightShift((uint)blueMask);
int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask);
int bitsRedMask = CountBits((uint)redMask);
int bitsGreenMask = CountBits((uint)greenMask);
int bitsBlueMask = CountBits((uint)blueMask);
int bitsAlphaMask = CountBits((uint)alphaMask);
float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask));
float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask));
float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask));
uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask);
float invMaxValueAlpha = 1.0f / maxValueAlpha;
bool unusualBitMask = false;
if (bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8)
{
unusualBitMask = true;
}
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
{
for (int y = 0; y < height; y++)
{
this.stream.Read(buffer.Array, 0, stride);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
{
uint temp = BitConverter.ToUInt32(buffer.Array, offset);
if (unusualBitMask)
{
uint r = (uint)(temp & redMask) >> rightShiftRedMask;
uint g = (uint)(temp & greenMask) >> rightShiftGreenMask;
uint b = (uint)(temp & blueMask) >> rightShiftBlueMask;
float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f;
var vector4 = new Vector4(
r * invMaxValueRed,
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromVector4(vector4);
}
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
}
}
}
}
/// <summary>
/// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right).
/// </summary>
/// <param name="n">The color bit mask.</param>
/// <returns>Number of bits to shift right.</returns>
private static int CalculateRightShift(uint n)
{
int count = 0;
while (n > 0)
{
if ((1 & n) == 0)
{
count++;
}
else
{
break;
}
n >>= 1;
}
return count;
}
/// <summary>
/// Counts none zero bits.
/// </summary>
/// <param name="n">A color mask.</param>
/// <returns>The none zero bits.</returns>
private static int CountBits(uint n)
{
int count = 0;
while (n != 0)
{
count++;
n &= n - 1;
}
return count;
}
/// <summary>
/// Reads the <see cref="BmpInfoHeader"/> from the stream.
/// </summary>
@ -508,20 +926,54 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// read the rest of the header
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
BmpInfoHeaderType inofHeaderType = BmpInfoHeaderType.WinVersion2;
if (headerSize == BmpInfoHeader.CoreSize)
{
// 12 bytes
inofHeaderType = BmpInfoHeaderType.WinVersion2;
this.infoHeader = BmpInfoHeader.ParseCore(buffer);
}
else if (headerSize == BmpInfoHeader.Os22ShortSize)
{
// 16 bytes
inofHeaderType = BmpInfoHeaderType.Os2Version2Short;
this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer);
}
else if (headerSize >= BmpInfoHeader.Size)
else if (headerSize == BmpInfoHeader.SizeV3)
{
// == 40 bytes
inofHeaderType = BmpInfoHeaderType.WinVersion3;
this.infoHeader = BmpInfoHeader.ParseV3(buffer);
// if the info header is BMP version 3 and the compression type is BITFIELDS,
// color masks for each color channel follow the info header.
if (this.infoHeader.Compression == BmpCompression.BitFields)
{
byte[] bitfieldsBuffer = new byte[12];
this.stream.Read(bitfieldsBuffer, 0, 12);
Span<byte> data = bitfieldsBuffer.AsSpan<byte>();
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4));
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
}
}
else if (headerSize == BmpInfoHeader.AdobeV3Size)
{
// == 52 bytes
inofHeaderType = BmpInfoHeaderType.AdobeVersion3;
this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false);
}
else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize)
{
// == 56 bytes
inofHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha;
this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true);
}
else if (headerSize >= BmpInfoHeader.SizeV4)
{
// >= 40 bytes
this.infoHeader = BmpInfoHeader.Parse(buffer);
// >= 108 bytes
inofHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5;
this.infoHeader = BmpInfoHeader.ParseV4(buffer);
}
else
{
@ -548,13 +1000,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.metaData = meta;
short bitsPerPixel = this.infoHeader.BitsPerPixel;
BmpMetaData bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
this.bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
this.bmpMetaData.InfoHeaderType = inofHeaderType;
// We can only encode at these bit rates so far.
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
{
bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
this.bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
// skip the remaining header because we can't read those parts

50
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -23,6 +23,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private int padding;
/// <summary>
/// The mask for the alpha channel of the color for a 32 bit rgba bitmaps.
/// </summary>
private const int Rgba32AlphaMask = 0xFF << 24;
/// <summary>
/// The mask for the red part of the color for a 32 bit rgba bitmaps.
/// </summary>
private const int Rgba32RedMask = 0xFF << 16;
/// <summary>
/// The mask for the green part of the color for a 32 bit rgba bitmaps.
/// </summary>
private const int Rgba32GreenMask = 0xFF << 8;
/// <summary>
/// The mask for the blue part of the color for a 32 bit rgba bitmaps.
/// </summary>
private const int Rgba32BlueMask = 0xFF;
private readonly MemoryAllocator memoryAllocator;
private Configuration configuration;
@ -92,8 +112,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
int infoHeaderSize = BmpInfoHeader.SizeV4;
var infoHeader = new BmpInfoHeader(
headerSize: BmpInfoHeader.Size,
headerSize: infoHeaderSize,
height: image.Height,
width: image.Width,
bitsPerPixel: bpp,
@ -102,26 +123,37 @@ namespace SixLabors.ImageSharp.Formats.Bmp
clrUsed: 0,
clrImportant: 0,
xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution);
yPelsPerMeter: vResolution)
{
RedMask = Rgba32RedMask,
GreenMask = Rgba32GreenMask,
BlueMask = Rgba32BlueMask,
Compression = BmpCompression.BitFields
};
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
{
infoHeader.AlphaMask = Rgba32AlphaMask;
}
var fileHeader = new BmpFileHeader(
type: 19778, // BM
fileSize: 54 + infoHeader.ImageSize,
type: BmpConstants.TypeMarkers.Bitmap,
fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize,
reserved: 0,
offset: 54);
offset: BmpFileHeader.Size + infoHeaderSize);
#if NETCOREAPP2_1
Span<byte> buffer = stackalloc byte[40];
Span<byte> buffer = stackalloc byte[infoHeaderSize];
#else
byte[] buffer = new byte[40];
byte[] buffer = new byte[infoHeaderSize];
#endif
fileHeader.WriteTo(buffer);
stream.Write(buffer, 0, BmpFileHeader.Size);
infoHeader.WriteTo(buffer);
infoHeader.WriteV4Header(buffer);
stream.Write(buffer, 0, 40);
stream.Write(buffer, 0, infoHeaderSize);
this.WriteImage(stream, image.Frames.RootFrame);

252
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -16,11 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct BmpInfoHeader
{
/// <summary>
/// Defines the size of the BITMAPINFOHEADER data structure in the bitmap file.
/// </summary>
public const int Size = 40;
/// <summary>
/// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file.
/// </summary>
@ -31,10 +26,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public const int Os22ShortSize = 16;
/// <summary>
/// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file.
/// </summary>
public const int SizeV3 = 40;
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
/// </summary>
public const int AdobeV3Size = 52;
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it.
/// </summary>
public const int AdobeV3WithAlphaSize = 56;
/// <summary>
/// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file.
/// </summary>
public const int SizeV4 = 108;
/// <summary>
/// Defines the size of the biggest supported header data structure in the bitmap file.
/// </summary>
public const int MaxHeaderSize = Size;
public const int MaxHeaderSize = SizeV4;
/// <summary>
/// Defines the size of the <see cref="HeaderSize"/> field.
@ -52,7 +67,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int xPelsPerMeter = 0,
int yPelsPerMeter = 0,
int clrUsed = 0,
int clrImportant = 0)
int clrImportant = 0,
int redMask = 0,
int greenMask = 0,
int blueMask = 0,
int alphaMask = 0,
int csType = 0,
int redX = 0,
int redY = 0,
int redZ = 0,
int greenX = 0,
int greenY = 0,
int greenZ = 0,
int blueX = 0,
int blueY = 0,
int blueZ = 0,
int gammeRed = 0,
int gammeGreen = 0,
int gammeBlue = 0)
{
this.HeaderSize = headerSize;
this.Width = width;
@ -65,6 +97,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.YPelsPerMeter = yPelsPerMeter;
this.ClrUsed = clrUsed;
this.ClrImportant = clrImportant;
this.RedMask = redMask;
this.GreenMask = greenMask;
this.BlueMask = blueMask;
this.AlphaMask = alphaMask;
this.CsType = csType;
this.RedX = redX;
this.RedY = redY;
this.RedZ = redZ;
this.GreenX = greenX;
this.GreenY = greenY;
this.GreenZ = greenZ;
this.BlueX = blueX;
this.BlueY = blueY;
this.BlueZ = blueZ;
this.GammaRed = gammeRed;
this.GammaGreen = gammeGreen;
this.GammaBlue = gammeBlue;
}
/// <summary>
@ -130,23 +179,92 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public int ClrImportant { get; set; }
/// <summary>
/// Parses the full BITMAPINFOHEADER header (40 bytes).
/// Gets or sets red color mask. This is used with the BITFIELDS decoding.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>Parsed header</returns>
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx"/>
public static BmpInfoHeader Parse(ReadOnlySpan<byte> data)
{
if (data.Length != Size)
{
throw new ArgumentException(nameof(data), $"Must be {Size} bytes. Was {data.Length} bytes.");
}
public int RedMask { get; set; }
return MemoryMarshal.Cast<byte, BmpInfoHeader>(data)[0];
}
/// <summary>
/// Gets or sets green color mask. This is used with the BITFIELDS decoding.
/// </summary>
public int GreenMask { get; set; }
/// <summary>
/// Gets or sets blue color mask. This is used with the BITFIELDS decoding.
/// </summary>
public int BlueMask { get; set; }
/// <summary>
/// Gets or sets alpha color mask. This is not used yet.
/// </summary>
public int AlphaMask { get; set; }
/// <summary>
/// Gets or sets the Color space type. Not used yet.
/// </summary>
public int CsType { get; set; }
/// <summary>
/// Gets or sets the X coordinate of red endpoint. Not used yet.
/// </summary>
public int RedX { get; set; }
/// <summary>
/// Gets or sets the Y coordinate of red endpoint. Not used yet.
/// </summary>
public int RedY { get; set; }
/// <summary>
/// Gets or sets the Z coordinate of red endpoint. Not used yet.
/// </summary>
public int RedZ { get; set; }
/// <summary>
/// Gets or sets the X coordinate of green endpoint. Not used yet.
/// </summary>
public int GreenX { get; set; }
/// <summary>
/// Gets or sets the Y coordinate of green endpoint. Not used yet.
/// </summary>
public int GreenY { get; set; }
/// <summary>
/// Gets or sets the Z coordinate of green endpoint. Not used yet.
/// </summary>
public int GreenZ { get; set; }
/// <summary>
/// Gets or sets the X coordinate of blue endpoint. Not used yet.
/// </summary>
public int BlueX { get; set; }
/// <summary>
/// Parses the BITMAPCOREHEADER consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
/// Gets or sets the Y coordinate of blue endpoint. Not used yet.
/// </summary>
public int BlueY { get; set; }
/// <summary>
/// Gets or sets the Z coordinate of blue endpoint. Not used yet.
/// </summary>
public int BlueZ { get; set; }
/// <summary>
/// Gets or sets the Gamma red coordinate scale value. Not used yet.
/// </summary>
public int GammaRed { get; set; }
/// <summary>
/// Gets or sets the Gamma green coordinate scale value. Not used yet.
/// </summary>
public int GammaGreen { get; set; }
/// <summary>
/// Gets or sets the Gamma blue coordinate scale value. Not used yet.
/// </summary>
public int GammaBlue { get; set; }
/// <summary>
/// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>Parsed header</returns>
@ -163,7 +281,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height
/// are 4 bytes instead of 2.
/// are 4 bytes instead of 2, resulting in 16 bytes total.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>Parsed header</returns>
@ -178,7 +296,97 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
}
public unsafe void WriteTo(Span<byte> buffer)
/// <summary>
/// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
public static BmpInfoHeader ParseV3(ReadOnlySpan<byte> data)
{
return new BmpInfoHeader(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)));
}
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
/// 52 bytes without the alpha mask, 56 bytes with the alpha mask.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <param name="withAlpha">Indicates, if the alpha bitmask is present.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="https://forums.adobe.com/message/3272950#3272950"/>
public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan<byte> data, bool withAlpha = true)
{
return new BmpInfoHeader(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)),
redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)),
greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
}
/// <summary>
/// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes).
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
public static BmpInfoHeader ParseV4(ReadOnlySpan<byte> data)
{
if (data.Length != SizeV4)
{
throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes.");
}
return MemoryMarshal.Cast<byte, BmpInfoHeader>(data)[0];
}
/// <summary>
/// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes).
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
public void WriteV3Header(Span<byte> buffer)
{
buffer.Clear();
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), SizeV3);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height);
BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes);
BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant);
}
/// <summary>
/// Writes a complete Bitmap V4 header to a buffer.
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
public unsafe void WriteV4Header(Span<byte> buffer)
{
ref BmpInfoHeader dest = ref Unsafe.As<byte, BmpInfoHeader>(ref MemoryMarshal.GetReference(buffer));

48
src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs

@ -0,0 +1,48 @@
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header.
/// </summary>
public enum BmpInfoHeaderType
{
/// <summary>
/// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x).
/// </summary>
WinVersion2 = 12,
/// <summary>
/// Short variant of the OS/2 Version 2 bitmap header.
/// </summary>
Os2Version2Short = 16,
/// <summary>
/// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT).
/// </summary>
WinVersion3 = 40,
/// <summary>
/// Adobe variant of the BMP Version 3 header.
/// </summary>
AdobeVersion3 = 52,
/// <summary>
/// Adobe variant of the BMP Version 3 header with an alpha mask.
/// </summary>
AdobeVersion3WithAlpha = 56,
/// <summary>
/// BMP Version 2.x header (IBM OS/2 2.x).
/// </summary>
Os2Version2 = 64,
/// <summary>
/// BMP Version 4 header (Microsoft Windows 95).
/// </summary>
WinVersion4 = 108,
/// <summary>
/// BMP Version 5 header (Windows NT 5.0, 98 or later).
/// </summary>
WinVersion5 = 124,
}
}

11
src/ImageSharp/Formats/Bmp/BmpMetaData.cs

@ -19,7 +19,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Initializes a new instance of the <see cref="BmpMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel;
private BmpMetaData(BmpMetaData other)
{
this.BitsPerPixel = other.BitsPerPixel;
this.InfoHeaderType = other.InfoHeaderType;
}
/// <summary>
/// Gets or sets the bitmap info header type.
/// </summary>
public BmpInfoHeaderType InfoHeaderType { get; set; }
/// <summary>
/// Gets or sets the number of bits per pixel.

19
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -9,10 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128F);
/// <summary>
/// Transpose the block into the destination block.
/// </summary>
@ -94,10 +91,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// Level shift by +maximum/2, clip to [0, maximum]
/// </summary>
public void NormalizeColorsInplace()
public void NormalizeColorsInplace(float maximum)
{
Vector4 CMin4 = new Vector4(0F);
Vector4 CMax4 = new Vector4(maximum);
Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2));
this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4);
this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4);
this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4);
@ -120,10 +121,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void NormalizeColorsAndRoundInplaceAvx2()
public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
{
Vector<float> off = new Vector<float>(128f);
Vector<float> max = new Vector<float>(255F);
Vector<float> off = new Vector<float>((float)Math.Ceiling(maximum/2));
Vector<float> max = new Vector<float>(maximum);
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V0L);
row0 = NormalizeAndRound(row0, off, max);

19
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt

@ -11,6 +11,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -22,10 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128F);
/// <summary>
/// Transpose the block into the destination block.
/// </summary>
@ -59,10 +56,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// Level shift by +maximum/2, clip to [0, maximum]
/// </summary>
public void NormalizeColorsInplace()
public void NormalizeColorsInplace(float maximum)
{
Vector4 CMin4 = new Vector4(0F);
Vector4 CMax4 = new Vector4(maximum);
Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2));
<#
PushIndent(" ");
@ -83,10 +84,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void NormalizeColorsAndRoundInplaceAvx2()
public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
{
Vector<float> off = new Vector<float>(128f);
Vector<float> max = new Vector<float>(255F);
Vector<float> off = new Vector<float>((float)Math.Ceiling(maximum/2));
Vector<float> max = new Vector<float>(maximum);
<#
for (int i = 0; i < 8; i++)

8
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -467,17 +467,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
/// <summary>
/// Level shift by +128, clip to [0..255], and round all the values in the block.
/// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block.
/// </summary>
public void NormalizeColorsAndRoundInplace()
public void NormalizeColorsAndRoundInplace(float maximum)
{
if (SimdUtils.IsAvx2CompatibleArchitecture)
{
this.NormalizeColorsAndRoundInplaceAvx2();
this.NormalizeColorsAndRoundInplaceAvx2(maximum);
}
else
{
this.NormalizeColorsInplace();
this.NormalizeColorsInplace(maximum);
this.RoundInplace();
}
}

12
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs

@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromCmyk : JpegColorConverter
{
public FromCmyk()
: base(JpegColorSpace.Cmyk)
public FromCmyk(int precision)
: base(JpegColorSpace.Cmyk, precision)
{
}
@ -25,14 +25,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(
1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);
for (int i = 0; i < result.Length; i++)
{
float c = cVals[i];
float m = mVals[i];
float y = yVals[i];
float k = kVals[i] / 255F;
float k = kVals[i] / this.MaximumValue;
v.X = c * k;
v.Y = m * k;

10
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs

@ -12,14 +12,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromGrayscale : JpegColorConverter
{
public FromGrayscale()
: base(JpegColorSpace.Grayscale)
public FromGrayscale(int precision)
: base(JpegColorSpace.Grayscale, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(
1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);
ref float sBase = ref MemoryMarshal.GetReference(values.Component0);
ref Vector4 dBase = ref MemoryMarshal.GetReference(result);

10
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs

@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromRgb : JpegColorConverter
{
public FromRgb()
: base(JpegColorSpace.RGB)
public FromRgb(int precision)
: base(JpegColorSpace.RGB, precision)
{
}
@ -24,7 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(
1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);
for (int i = 0; i < result.Length; i++)
{

14
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -10,17 +10,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromYCbCrBasic : JpegColorConverter
{
public FromYCbCrBasic()
: base(JpegColorSpace.YCbCr)
public FromYCbCrBasic(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
ConvertCore(values, result);
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;
@ -29,13 +29,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F);
for (int i = 0; i < result.Length; i++)
{
float y = yVals[i];
float cb = cbVals[i] - 128F;
float cr = crVals[i] - 128F;
float cb = cbVals[i] - halfValue;
float cr = crVals[i] - halfValue;
v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);

28
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs

@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromYCbCrSimd : JpegColorConverter
{
public FromYCbCrSimd()
: base(JpegColorSpace.YCbCr)
public FromYCbCrSimd(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
@ -25,16 +25,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
int simdCount = result.Length - remainder;
if (simdCount > 0)
{
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount));
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue);
}
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder));
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue);
}
/// <summary>
/// SIMD convert using buffers of sizes divisible by 8.
/// </summary>
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!");
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector4(-128f);
var chromaOffset = new Vector4(-halfValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
// y = yVals[i];
Vector4Pair y = Unsafe.Add(ref yBase, i);
// cb = cbVals[i] - 128F;
// cb = cbVals[i] - halfValue);
Vector4Pair cb = Unsafe.Add(ref cbBase, i);
cb.AddInplace(chromaOffset);
// cr = crVals[i] - 128F;
// cr = crVals[i] - halfValue;
Vector4Pair cr = Unsafe.Add(ref crBase, i);
cr.AddInplace(chromaOffset);
@ -90,15 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
if (Vector<float>.Count == 4)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?)
r.RoundAndDownscalePreAvx2();
g.RoundAndDownscalePreAvx2();
b.RoundAndDownscalePreAvx2();
r.RoundAndDownscalePreAvx2(maxValue);
g.RoundAndDownscalePreAvx2(maxValue);
b.RoundAndDownscalePreAvx2(maxValue);
}
else if (SimdUtils.IsAvx2CompatibleArchitecture)
{
r.RoundAndDownscaleAvx2();
g.RoundAndDownscaleAvx2();
b.RoundAndDownscaleAvx2();
r.RoundAndDownscaleAvx2(maxValue);
g.RoundAndDownscaleAvx2(maxValue);
b.RoundAndDownscaleAvx2(maxValue);
}
else
{

14
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs

@ -15,8 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromYCbCrSimdAvx2 : JpegColorConverter
{
public FromYCbCrSimdAvx2()
: base(JpegColorSpace.YCbCr)
public FromYCbCrSimdAvx2(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
@ -28,16 +28,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
int simdCount = result.Length - remainder;
if (simdCount > 0)
{
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount));
ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue);
}
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder));
FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue);
}
/// <summary>
/// SIMD convert using buffers of sizes divisible by 8.
/// </summary>
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-128f);
var chromaOffset = new Vector<float>(-halfValue);
// Walking 8 elements at one step:
int n = result.Length / 8;
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / 255f);
var scale = new Vector<float>(1 / maxValue);
for (int i = 0; i < n; i++)
{

22
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs

@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal class FromYccK : JpegColorConverter
{
public FromYccK()
: base(JpegColorSpace.Ycck)
public FromYccK(int precision)
: base(JpegColorSpace.Ycck, precision)
{
}
@ -25,18 +25,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var scale = new Vector4(
1 / this.MaximumValue,
1 / this.MaximumValue,
1 / this.MaximumValue,
1F);
for (int i = 0; i < result.Length; i++)
{
float y = yVals[i];
float cb = cbVals[i] - 128F;
float cr = crVals[i] - 128F;
float k = kVals[i] / 255F;
float cb = cbVals[i] - this.HalfValue;
float cr = crVals[i] - this.HalfValue;
float k = kVals[i] / this.MaximumValue;
v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k;
v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.X = (this.MaximumValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (this.MaximumValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k;
v.Z = (this.MaximumValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.W = 1F;
v *= scale;

45
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -3,12 +3,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Tuples;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
@ -22,15 +20,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// </summary>
private static readonly JpegColorConverter[] Converters =
{
GetYCbCrConverter(), new FromYccK(), new FromCmyk(), new FromGrayscale(), new FromRgb()
// 8-bit converters
GetYCbCrConverter(8),
new FromYccK(8),
new FromCmyk(8),
new FromGrayscale(8),
new FromRgb(8),
// 12-bit converters
GetYCbCrConverter(12),
new FromYccK(12),
new FromCmyk(12),
new FromGrayscale(12),
new FromRgb(12),
};
/// <summary>
/// Initializes a new instance of the <see cref="JpegColorConverter"/> class.
/// </summary>
protected JpegColorConverter(JpegColorSpace colorSpace)
protected JpegColorConverter(JpegColorSpace colorSpace, int precision)
{
this.ColorSpace = colorSpace;
this.Precision = precision;
this.MaximumValue = (float)Math.Pow(2, precision) - 1;
this.HalfValue = (float)Math.Ceiling(this.MaximumValue / 2);
}
/// <summary>
@ -38,12 +51,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// </summary>
public JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the Precision of this converter in bits.
/// </summary>
public int Precision { get; }
/// <summary>
/// Gets the maximum value of a sample
/// </summary>
private float MaximumValue { get; }
/// <summary>
/// Gets the half of the maximum value of a sample
/// </summary>
private float HalfValue { get; }
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> corresponding to the given <see cref="JpegColorSpace"/>
/// </summary>
public static JpegColorConverter GetConverter(JpegColorSpace colorSpace)
public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision)
{
JpegColorConverter converter = Converters.FirstOrDefault(c => c.ColorSpace == colorSpace);
JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace
&& c.Precision == precision);
if (converter is null)
{
@ -63,8 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture.
/// </summary>
private static JpegColorConverter GetYCbCrConverter() =>
FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2() : new FromYCbCrSimd();
private static JpegColorConverter GetYCbCrConverter(int precision) =>
FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision);
/// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.

23
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -50,8 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="values">The huffman values</param>
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
const int Length = 257;
using (IMemoryOwner<short> huffcode = memoryAllocator.Allocate<short>(Length))
// We do some bounds checks in the code here to protect against AccessViolationExceptions
const int HuffCodeLength = 257;
const int MaxSizeLength = HuffCodeLength - 1;
using (IMemoryOwner<short> huffcode = memoryAllocator.Allocate<short>(HuffCodeLength))
{
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths);
@ -63,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (short i = 1; i < 17; i++)
{
byte length = Unsafe.Add(ref codeLengthsRef, i);
for (short j = 0; j < length; j++)
for (short j = 0; j < length && x < MaxSizeLength; j++)
{
Unsafe.Add(ref sizesRef, x++) = i;
}
@ -84,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
Unsafe.Add(ref valOffsetRef, k) = (int)(si - code);
if (Unsafe.Add(ref sizesRef, si) == k)
{
while (Unsafe.Add(ref sizesRef, si) == k)
while (Unsafe.Add(ref sizesRef, si) == k && si < HuffCodeLength)
{
Unsafe.Add(ref huffcodeRef, si++) = (short)code++;
}
@ -100,19 +102,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Generate non-spec lookup tables to speed up decoding.
const int FastBits = ScanDecoder.FastBits;
ref byte fastRef = ref this.Lookahead[0];
Unsafe.InitBlockUnaligned(ref fastRef, 0xFF, 1 << FastBits); // Flag for non-accelerated
ref byte lookaheadRef = ref this.Lookahead[0];
const uint MaxFastLength = 1 << FastBits;
Unsafe.InitBlockUnaligned(ref lookaheadRef, 0xFF, MaxFastLength); // Flag for non-accelerated
for (int i = 0; i < si; i++)
{
int size = Unsafe.Add(ref sizesRef, i);
if (size <= FastBits)
{
int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - size);
int m = 1 << (FastBits - size);
for (int l = 0; l < m; l++)
int huffCode = Unsafe.Add(ref huffcodeRef, i) << (FastBits - size);
int max = 1 << (FastBits - size);
for (int left = 0; left < max; left++)
{
Unsafe.Add(ref fastRef, c + l) = (byte)i;
Unsafe.Add(ref lookaheadRef, huffCode + left) = (byte)i;
}
}
}

7
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -29,10 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the number of bits used for precision.
/// </summary>
int Precision { get; }
/// <summary>
/// Gets the components.
/// </summary>
IEnumerable<IJpegComponent> Components { get; }
IJpegComponent[] Components { get; }
/// <summary>
/// Gets the quantization tables, in zigzag order.

15
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
@ -38,6 +39,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private Size subSamplingDivisors;
/// <summary>
/// Defines the maximum value derived from the bitdepth
/// </summary>
private int maximumValue;
/// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary>
@ -48,6 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]);
this.subSamplingDivisors = component.SubSamplingDivisors;
this.maximumValue = (int)Math.Pow(2, decoder.Precision) - 1;
this.SourceBlock = default;
this.WorkspaceBlock1 = default;
@ -58,14 +65,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
/// - Dequantize
/// - Applying IDCT
/// - Level shift by +128, clip to [0, 255]
/// - Level shift by +maximumValue/2, clip to [0, maximumValue]
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>
/// </summary>
/// <param name="sourceBlock">The source block.</param>
/// <param name="destArea">The destination buffer area.</param>
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
in BufferArea<float> destArea)
in BufferArea<float> destArea,
float maximumValue)
{
ref Block8x8F b = ref this.SourceBlock;
b.LoadFrom(ref sourceBlock);
@ -78,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
this.WorkspaceBlock1.NormalizeColorsAndRoundInplace();
this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue);
this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height);
}

3
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -78,6 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public void CopyBlocksToColorBuffer()
{
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component);
float maximumValue = (float)Math.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1;
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.blockAreaSize.Width,
this.blockAreaSize.Height);
blockPp.ProcessBlockColorsInto(ref block, destArea);
blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue);
}
}

19
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -3,7 +3,6 @@
using System;
using System.Buffers;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -57,14 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.configuration = configuration;
this.RawJpeg = rawJpeg;
IJpegComponent c0 = rawJpeg.Components.First();
IJpegComponent c0 = rawJpeg.Components[0];
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryAllocator, this, c)).ToArray();
this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length];
for (int i = 0; i < rawJpeg.Components.Length; i++)
{
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]);
}
this.rgbaBuffer = memoryAllocator.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision);
}
/// <summary>
@ -152,7 +157,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);
Buffer2D<float>[] buffers = this.ComponentProcessors.Select(cp => cp.ColorBuffer).ToArray();
var buffers = new Buffer2D<float>[this.ComponentProcessors.Length];
for (int i = 0; i < this.ComponentProcessors.Length; i++)
{
buffers[i] = this.ComponentProcessors[i].ColorBuffer;
}
for (int yy = this.PixelRowCounter; yy < maxY; yy++)
{

23
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -3,7 +3,6 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// The only supported precision
/// </summary>
public const int SupportedPrecision = 8;
private readonly int[] supportedPrecisions = { 8, 12 };
/// <summary>
/// The global configuration
@ -137,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Gets the color depth, in number of bits per pixel.
/// </summary>
public int BitsPerPixel => this.ComponentCount * SupportedPrecision;
public int BitsPerPixel => this.ComponentCount * this.Frame.Precision;
/// <summary>
/// Gets the input stream.
@ -160,13 +159,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public JpegColorSpace ColorSpace { get; private set; }
/// <inheritdoc/>
public int Precision { get; private set; }
/// <summary>
/// Gets the components.
/// </summary>
public JpegComponent[] Components => this.Frame.Components;
/// <inheritdoc/>
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
IJpegComponent[] IRawJpegData.Components => this.Components;
/// <inheritdoc/>
public Block8x8F[] QuantizationTables { get; private set; }
@ -720,12 +722,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InputStream.Read(this.temp, 0, remaining);
// We only support 8-bit precision.
if (this.temp[0] != SupportedPrecision)
// We only support 8-bit and 12-bit precision.
if (!this.supportedPrecisions.Contains(this.temp[0]))
{
throw new ImageFormatException("Only 8-Bit precision supported.");
throw new ImageFormatException("Only 8-Bit and 12-Bit precision supported.");
}
this.Precision = this.temp[0];
this.Frame = new JpegFrame
{
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
@ -855,6 +859,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private void ProcessStartOfScanMarker()
{
if (this.Frame is null)
{
throw new ImageFormatException("No readable SOFn (Start Of Frame) marker found.");
}
int selectorsCount = this.InputStream.ReadByte();
for (int i = 0; i < selectorsCount; i++)
{

459
src/ImageSharp/Primitives/ColorMatrix.cs

@ -0,0 +1,459 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
#pragma warning disable SA1117 // Parameters should be on same line or separate lines
using System;
using System.Globalization;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Primitives
{
/// <summary>
/// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ColorMatrix : IEquatable<ColorMatrix>
{
/// <summary>
/// Value at row 1, column 1 of the matrix.
/// </summary>
public float M11;
/// <summary>
/// Value at row 1, column 2 of the matrix.
/// </summary>
public float M12;
/// <summary>
/// Value at row 1, column 3 of the matrix.
/// </summary>
public float M13;
/// <summary>
/// Value at row 1, column 4 of the matrix.
/// </summary>
public float M14;
/// <summary>
/// Value at row 2, column 1 of the matrix.
/// </summary>
public float M21;
/// <summary>
/// Value at row 2, column 2 of the matrix.
/// </summary>
public float M22;
/// <summary>
/// Value at row 2, column 3 of the matrix.
/// </summary>
public float M23;
/// <summary>
/// Value at row 2, column 4 of the matrix.
/// </summary>
public float M24;
/// <summary>
/// Value at row 3, column 1 of the matrix.
/// </summary>
public float M31;
/// <summary>
/// Value at row 3, column 2 of the matrix.
/// </summary>
public float M32;
/// <summary>
/// Value at row 3, column 3 of the matrix.
/// </summary>
public float M33;
/// <summary>
/// Value at row 3, column 4 of the matrix.
/// </summary>
public float M34;
/// <summary>
/// Value at row 4, column 1 of the matrix.
/// </summary>
public float M41;
/// <summary>
/// Value at row 4, column 2 of the matrix.
/// </summary>
public float M42;
/// <summary>
/// Value at row 4, column 3 of the matrix.
/// </summary>
public float M43;
/// <summary>
/// Value at row 4, column 4 of the matrix.
/// </summary>
public float M44;
/// <summary>
/// Value at row 5, column 1 of the matrix.
/// </summary>
public float M51;
/// <summary>
/// Value at row 5, column 2 of the matrix.
/// </summary>
public float M52;
/// <summary>
/// Value at row 5, column 3 of the matrix.
/// </summary>
public float M53;
/// <summary>
/// Value at row 5, column 4 of the matrix.
/// </summary>
public float M54;
/// <summary>
/// Initializes a new instance of the <see cref="ColorMatrix"/> struct.
/// </summary>
/// <param name="m11">The value at row 1, column 1 of the matrix.</param>
/// <param name="m12">The value at row 1, column 2 of the matrix.</param>
/// <param name="m13">The value at row 1, column 3 of the matrix.</param>
/// <param name="m14">The value at row 1, column 4 of the matrix.</param>
/// <param name="m21">The value at row 2, column 1 of the matrix.</param>
/// <param name="m22">The value at row 2, column 2 of the matrix.</param>
/// <param name="m23">The value at row 2, column 3 of the matrix.</param>
/// <param name="m24">The value at row 2, column 4 of the matrix.</param>
/// <param name="m31">The value at row 3, column 1 of the matrix.</param>
/// <param name="m32">The value at row 3, column 2 of the matrix.</param>
/// <param name="m33">The value at row 3, column 3 of the matrix.</param>
/// <param name="m34">The value at row 3, column 4 of the matrix.</param>
/// <param name="m41">The value at row 4, column 1 of the matrix.</param>
/// <param name="m42">The value at row 4, column 2 of the matrix.</param>
/// <param name="m43">The value at row 4, column 3 of the matrix.</param>
/// <param name="m44">The value at row 4, column 4 of the matrix.</param>
/// <param name="m51">The value at row 5, column 1 of the matrix.</param>
/// <param name="m52">The value at row 5, column 2 of the matrix.</param>
/// <param name="m53">The value at row 5, column 3 of the matrix.</param>
/// <param name="m54">The value at row 5, column 4 of the matrix.</param>
public ColorMatrix(float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44,
float m51, float m52, float m53, float m54)
{
this.M11 = m11;
this.M12 = m12;
this.M13 = m13;
this.M14 = m14;
this.M21 = m21;
this.M22 = m22;
this.M23 = m23;
this.M24 = m24;
this.M31 = m31;
this.M32 = m32;
this.M33 = m33;
this.M34 = m34;
this.M41 = m41;
this.M42 = m42;
this.M43 = m43;
this.M44 = m44;
this.M51 = m51;
this.M52 = m52;
this.M53 = m53;
this.M54 = m54;
}
/// <summary>
/// Gets the multiplicative identity matrix.
/// </summary>
public static ColorMatrix Identity { get; } =
new ColorMatrix(1F, 0F, 0F, 0F,
0F, 1F, 0F, 0F,
0F, 0F, 1F, 0F,
0F, 0F, 0F, 1F,
0F, 0F, 0F, 0F);
/// <summary>
/// Gets a value indicating whether the matrix is the identity matrix.
/// </summary>
public bool IsIdentity
{
get
{
// Check diagonal element first for early out.
return this.M11 == 1F && this.M22 == 1F && this.M33 == 1F && this.M44 == 1F
&& this.M12 == 0F && this.M13 == 0F && this.M14 == 0F
&& this.M21 == 0F && this.M23 == 0F && this.M24 == 0F
&& this.M31 == 0F && this.M32 == 0F && this.M34 == 0F
&& this.M41 == 0F && this.M42 == 0F && this.M43 == 0F
&& this.M51 == 0F && this.M52 == 0F && this.M53 == 0F && this.M54 == 0F;
}
}
/// <summary>
/// Adds two matrices together.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The resulting matrix.</returns>
public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2)
{
ColorMatrix m;
m.M11 = value1.M11 + value2.M11;
m.M12 = value1.M12 + value2.M12;
m.M13 = value1.M13 + value2.M13;
m.M14 = value1.M14 + value2.M14;
m.M21 = value1.M21 + value2.M21;
m.M22 = value1.M22 + value2.M22;
m.M23 = value1.M23 + value2.M23;
m.M24 = value1.M24 + value2.M24;
m.M31 = value1.M31 + value2.M31;
m.M32 = value1.M32 + value2.M32;
m.M33 = value1.M33 + value2.M33;
m.M34 = value1.M34 + value2.M34;
m.M41 = value1.M41 + value2.M41;
m.M42 = value1.M42 + value2.M42;
m.M43 = value1.M43 + value2.M43;
m.M44 = value1.M44 + value2.M44;
m.M51 = value1.M51 + value2.M51;
m.M52 = value1.M52 + value2.M52;
m.M53 = value1.M53 + value2.M53;
m.M54 = value1.M54 + value2.M54;
return m;
}
/// <summary>
/// Subtracts the second matrix from the first.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The result of the subtraction.</returns>
public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2)
{
ColorMatrix m;
m.M11 = value1.M11 - value2.M11;
m.M12 = value1.M12 - value2.M12;
m.M13 = value1.M13 - value2.M13;
m.M14 = value1.M14 - value2.M14;
m.M21 = value1.M21 - value2.M21;
m.M22 = value1.M22 - value2.M22;
m.M23 = value1.M23 - value2.M23;
m.M24 = value1.M24 - value2.M24;
m.M31 = value1.M31 - value2.M31;
m.M32 = value1.M32 - value2.M32;
m.M33 = value1.M33 - value2.M33;
m.M34 = value1.M34 - value2.M34;
m.M41 = value1.M41 - value2.M41;
m.M42 = value1.M42 - value2.M42;
m.M43 = value1.M43 - value2.M43;
m.M44 = value1.M44 - value2.M44;
m.M51 = value1.M51 - value2.M51;
m.M52 = value1.M52 - value2.M52;
m.M53 = value1.M53 - value2.M53;
m.M54 = value1.M54 - value2.M54;
return m;
}
/// <summary>
/// Returns a new matrix with the negated elements of the given matrix.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static unsafe ColorMatrix operator -(ColorMatrix value)
{
ColorMatrix m;
m.M11 = -value.M11;
m.M12 = -value.M12;
m.M13 = -value.M13;
m.M14 = -value.M14;
m.M21 = -value.M21;
m.M22 = -value.M22;
m.M23 = -value.M23;
m.M24 = -value.M24;
m.M31 = -value.M31;
m.M32 = -value.M32;
m.M33 = -value.M33;
m.M34 = -value.M34;
m.M41 = -value.M41;
m.M42 = -value.M42;
m.M43 = -value.M43;
m.M44 = -value.M44;
m.M51 = -value.M51;
m.M52 = -value.M52;
m.M53 = -value.M53;
m.M54 = -value.M54;
return m;
}
/// <summary>
/// Multiplies a matrix by another matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The result of the multiplication.</returns>
public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2)
{
ColorMatrix m;
// First row
m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41);
m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42);
m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43);
m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44);
// Second row
m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41);
m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42);
m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43);
m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44);
// Third row
m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41);
m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42);
m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43);
m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44);
// Fourth row
m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41);
m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42);
m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43);
m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44);
// Fifth row
m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51;
m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52;
m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53;
m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54;
return m;
}
/// <summary>
/// Multiplies a matrix by a scalar value.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling factor.</param>
/// <returns>The scaled matrix.</returns>
public static ColorMatrix operator *(ColorMatrix value1, float value2)
{
ColorMatrix m;
m.M11 = value1.M11 * value2;
m.M12 = value1.M12 * value2;
m.M13 = value1.M13 * value2;
m.M14 = value1.M14 * value2;
m.M21 = value1.M21 * value2;
m.M22 = value1.M22 * value2;
m.M23 = value1.M23 * value2;
m.M24 = value1.M24 * value2;
m.M31 = value1.M31 * value2;
m.M32 = value1.M32 * value2;
m.M33 = value1.M33 * value2;
m.M34 = value1.M34 * value2;
m.M41 = value1.M41 * value2;
m.M42 = value1.M42 * value2;
m.M43 = value1.M43 * value2;
m.M44 = value1.M44 * value2;
m.M51 = value1.M51 * value2;
m.M52 = value1.M52 * value2;
m.M53 = value1.M53 * value2;
m.M54 = value1.M54 * value2;
return m;
}
/// <summary>
/// Returns a boolean indicating whether the given two matrices are equal.
/// </summary>
/// <param name="value1">The first matrix to compare.</param>
/// <param name="value2">The second matrix to compare.</param>
/// <returns>True if the given matrices are equal; False otherwise.</returns>
public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2);
/// <summary>
/// Returns a boolean indicating whether the given two matrices are not equal.
/// </summary>
/// <param name="value1">The first matrix to compare.</param>
/// <param name="value2">The second matrix to compare.</param>
/// <returns>True if the given matrices are equal; False otherwise.</returns>
public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2);
/// <inheritdoc/>
public override bool Equals(object obj) => obj is ColorMatrix matrix && this.Equals(matrix);
/// <inheritdoc/>
public bool Equals(ColorMatrix other) =>
this.M11 == other.M11
&& this.M12 == other.M12
&& this.M13 == other.M13
&& this.M14 == other.M14
&& this.M21 == other.M21
&& this.M22 == other.M22
&& this.M23 == other.M23
&& this.M24 == other.M24
&& this.M31 == other.M31
&& this.M32 == other.M32
&& this.M33 == other.M33
&& this.M34 == other.M34
&& this.M41 == other.M41
&& this.M42 == other.M42
&& this.M43 == other.M43
&& this.M44 == other.M44
&& this.M51 == other.M51
&& this.M52 == other.M52
&& this.M53 == other.M53
&& this.M54 == other.M54;
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = default;
hash.Add(this.M11);
hash.Add(this.M12);
hash.Add(this.M13);
hash.Add(this.M14);
hash.Add(this.M21);
hash.Add(this.M22);
hash.Add(this.M23);
hash.Add(this.M24);
hash.Add(this.M31);
hash.Add(this.M32);
hash.Add(this.M33);
hash.Add(this.M34);
hash.Add(this.M41);
hash.Add(this.M42);
hash.Add(this.M43);
hash.Add(this.M44);
hash.Add(this.M51);
hash.Add(this.M52);
hash.Add(this.M53);
hash.Add(this.M54);
return hash.ToHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
CultureInfo ci = CultureInfo.CurrentCulture;
return string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}",
this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci),
this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci),
this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci),
this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci),
this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci));
}
}
}

6
src/ImageSharp/Processing/FilterExtensions.cs

@ -1,8 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param>
/// <param name="matrix">The filter color matrix</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Filter<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix)
public static IImageProcessingContext<TPixel> Filter<TPixel>(this IImageProcessingContext<TPixel> source, ColorMatrix matrix)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new FilterProcessor<TPixel>(matrix));
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Filter<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, Rectangle rectangle)
public static IImageProcessingContext<TPixel> Filter<TPixel>(this IImageProcessingContext<TPixel> source, ColorMatrix matrix, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new FilterProcessor<TPixel>(matrix), rectangle);
}

358
src/ImageSharp/Processing/KnownFilterMatrices.cs

@ -2,19 +2,28 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Primitives;
// Many of these matrices are tranlated from Chromium project where
// SkScalar[] is memory-mapped to a row-major matrix.
// The following translates to our column-major form:
//
// | 0| 1| 2| 3| 4| |0|5|10|15| |M11|M12|M13|M14|
// | 5| 6| 7| 8| 9| |1|6|11|16| |M21|M22|M23|M24|
// |10|11|12|13|14| = |2|7|12|17| = |M31|M32|M33|M34|
// |15|16|17|18|19| |3|8|13|18| |M41|M42|M43|M44|
// |4|9|14|19| |M51|M52|M53|M54|
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// A collection of known <see cref="Matrix4x4"/> values for composing filters
/// A collection of known <see cref="ColorMatrix"/> values for composing filters
/// </summary>
public static class KnownFilterMatrices
{
/// <summary>
/// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness
/// </summary>
public static Matrix4x4 AchromatomalyFilter { get; } = new Matrix4x4
public static ColorMatrix AchromatomalyFilter { get; } = new ColorMatrix
{
M11 = .618F,
M12 = .163F,
@ -31,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets a filter recreating Achromatopsia (Monochrome) color blindness.
/// </summary>
public static Matrix4x4 AchromatopsiaFilter { get; } = new Matrix4x4
public static ColorMatrix AchromatopsiaFilter { get; } = new ColorMatrix
{
M11 = .299F,
M12 = .299F,
@ -42,97 +51,97 @@ namespace SixLabors.ImageSharp.Processing
M31 = .114F,
M32 = .114F,
M33 = .114F,
M44 = 1
M44 = 1F
};
/// <summary>
/// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness.
/// </summary>
public static Matrix4x4 DeuteranomalyFilter { get; } = new Matrix4x4
public static ColorMatrix DeuteranomalyFilter { get; } = new ColorMatrix
{
M11 = 0.8F,
M12 = 0.258F,
M21 = 0.2F,
M22 = 0.742F,
M23 = 0.142F,
M33 = 0.858F,
M44 = 1
M11 = .8F,
M12 = .258F,
M21 = .2F,
M22 = .742F,
M23 = .142F,
M33 = .858F,
M44 = 1F
};
/// <summary>
/// Gets a filter recreating Deuteranopia (Green-Blind) color blindness.
/// </summary>
public static Matrix4x4 DeuteranopiaFilter { get; } = new Matrix4x4
public static ColorMatrix DeuteranopiaFilter { get; } = new ColorMatrix
{
M11 = 0.625F,
M12 = 0.7F,
M21 = 0.375F,
M22 = 0.3F,
M23 = 0.3F,
M33 = 0.7F,
M44 = 1
M11 = .625F,
M12 = .7F,
M21 = .375F,
M22 = .3F,
M23 = .3F,
M33 = .7F,
M44 = 1F
};
/// <summary>
/// Gets a filter recreating Protanomaly (Red-Weak) color blindness.
/// </summary>
public static Matrix4x4 ProtanomalyFilter { get; } = new Matrix4x4
public static ColorMatrix ProtanomalyFilter { get; } = new ColorMatrix
{
M11 = 0.817F,
M12 = 0.333F,
M21 = 0.183F,
M22 = 0.667F,
M23 = 0.125F,
M33 = 0.875F,
M44 = 1
M11 = .817F,
M12 = .333F,
M21 = .183F,
M22 = .667F,
M23 = .125F,
M33 = .875F,
M44 = 1F
};
/// <summary>
/// Gets a filter recreating Protanopia (Red-Blind) color blindness.
/// </summary>
public static Matrix4x4 ProtanopiaFilter { get; } = new Matrix4x4
public static ColorMatrix ProtanopiaFilter { get; } = new ColorMatrix
{
M11 = 0.567F,
M12 = 0.558F,
M21 = 0.433F,
M22 = 0.442F,
M23 = 0.242F,
M33 = 0.758F,
M44 = 1
M11 = .567F,
M12 = .558F,
M21 = .433F,
M22 = .442F,
M23 = .242F,
M33 = .758F,
M44 = 1F
};
/// <summary>
/// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness.
/// </summary>
public static Matrix4x4 TritanomalyFilter { get; } = new Matrix4x4
public static ColorMatrix TritanomalyFilter { get; } = new ColorMatrix
{
M11 = 0.967F,
M21 = 0.33F,
M22 = 0.733F,
M23 = 0.183F,
M32 = 0.267F,
M33 = 0.817F,
M44 = 1
M11 = .967F,
M21 = .33F,
M22 = .733F,
M23 = .183F,
M32 = .267F,
M33 = .817F,
M44 = 1F
};
/// <summary>
/// Gets a filter recreating Tritanopia (Blue-Blind) color blindness.
/// </summary>
public static Matrix4x4 TritanopiaFilter { get; } = new Matrix4x4
public static ColorMatrix TritanopiaFilter { get; } = new ColorMatrix
{
M11 = 0.95F,
M21 = 0.05F,
M22 = 0.433F,
M23 = 0.475F,
M32 = 0.567F,
M33 = 0.525F,
M44 = 1
M11 = .95F,
M21 = .05F,
M22 = .433F,
M23 = .475F,
M32 = .567F,
M33 = .525F,
M44 = 1F
};
/// <summary>
/// Gets an approximated black and white filter
/// </summary>
public static Matrix4x4 BlackWhiteFilter { get; } = new Matrix4x4()
public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix()
{
M11 = 1.5F,
M12 = 1.5F,
@ -143,24 +152,24 @@ namespace SixLabors.ImageSharp.Processing
M31 = 1.5F,
M32 = 1.5F,
M33 = 1.5F,
M41 = -1F,
M42 = -1F,
M43 = -1F,
M44 = 1
M44 = 1F,
M51 = -1F,
M52 = -1F,
M53 = -1F,
};
/// <summary>
/// Gets a filter recreating an old Kodachrome camera effect.
/// </summary>
public static Matrix4x4 KodachromeFilter { get; } = new Matrix4x4
public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix
{
M11 = 0.7297023F,
M22 = 0.6109577F,
M33 = 0.597218F,
M41 = 0.105F,
M42 = 0.145F,
M43 = 0.155F,
M44 = 1
M11 = .7297023F,
M22 = .6109577F,
M33 = .597218F,
M44 = 1F,
M51 = .105F,
M52 = .145F,
M53 = .155F,
}
* CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F);
@ -168,15 +177,15 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets a filter recreating an old Lomograph camera effect.
/// </summary>
public static Matrix4x4 LomographFilter { get; } = new Matrix4x4
public static ColorMatrix LomographFilter { get; } = new ColorMatrix
{
M11 = 1.5F,
M22 = 1.45F,
M33 = 1.16F,
M41 = -.1F,
M42 = -.02F,
M43 = -.07F,
M44 = 1
M44 = 1F,
M51 = -.1F,
M52 = -.02F,
M53 = -.07F,
}
* CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F);
@ -184,21 +193,21 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets a filter recreating an old Polaroid camera effect.
/// </summary>
public static Matrix4x4 PolaroidFilter { get; } = new Matrix4x4
public static ColorMatrix PolaroidFilter { get; } = new ColorMatrix
{
M11 = 1.538F,
M12 = -0.062F,
M13 = -0.262F,
M21 = -0.022F,
M12 = -.062F,
M13 = -.262F,
M21 = -.022F,
M22 = 1.578F,
M23 = -0.022F,
M23 = -.022F,
M31 = .216F,
M32 = -.16F,
M33 = 1.5831F,
M41 = 0.02F,
M42 = -0.05F,
M43 = -0.05F,
M44 = 1
M44 = 1F,
M51 = .02F,
M52 = -.05F,
M53 = -.05F
};
/// <summary>
@ -209,18 +218,18 @@ namespace SixLabors.ImageSharp.Processing
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results.
/// </remarks>
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateBrightnessFilter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateBrightnessFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
return new Matrix4x4
return new ColorMatrix
{
M11 = amount,
M22 = amount,
M33 = amount,
M44 = 1
M44 = 1F
};
}
@ -232,23 +241,23 @@ namespace SixLabors.ImageSharp.Processing
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast.
/// </remarks>
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateContrastFilter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateContrastFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
float contrast = (-.5F * amount) + .5F;
return new Matrix4x4
return new ColorMatrix
{
M11 = amount,
M22 = amount,
M33 = amount,
M41 = contrast,
M42 = contrast,
M43 = contrast,
M44 = 1
M44 = 1F,
M51 = contrast,
M52 = contrast,
M53 = contrast
};
}
@ -257,26 +266,27 @@ namespace SixLabors.ImageSharp.Processing
/// <see href="https://en.wikipedia.org/wiki/Luma_%28video%29#Rec._601_luma_versus_Rec._709_luma_coefficients"/>
/// </summary>
/// <param name="amount">The proportion of the conversion. Must be between 0 and 1.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateGrayscaleBt601Filter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateGrayscaleBt601Filter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount));
amount = 1F - amount;
// https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
return new Matrix4x4
{
M11 = .299F + (.701F * amount),
M12 = .299F - (.299F * amount),
M13 = .299F - (.299F * amount),
M21 = .587F - (.587F * amount),
M22 = .587F + (.413F * amount),
M23 = .587F - (.587F * amount),
M31 = .114F - (.114F * amount),
M32 = .114F - (.114F * amount),
M33 = .114F + (.886F * amount),
M44 = 1
};
ColorMatrix m = default;
m.M11 = .299F + (.701F * amount);
m.M21 = .587F - (.587F * amount);
m.M31 = 1F - (m.M11 + m.M21);
m.M12 = .299F - (.299F * amount);
m.M22 = .587F + (.2848F * amount);
m.M32 = 1F - (m.M12 + m.M22);
m.M13 = .299F - (.299F * amount);
m.M23 = .587F - (.587F * amount);
m.M33 = 1F - (m.M13 + m.M23);
m.M44 = 1F;
return m;
}
/// <summary>
@ -284,34 +294,36 @@ namespace SixLabors.ImageSharp.Processing
/// <see href="https://en.wikipedia.org/wiki/Rec._709#Luma_coefficients"/>
/// </summary>
/// <param name="amount">The proportion of the conversion. Must be between 0 and 1.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateGrayscaleBt709Filter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateGrayscaleBt709Filter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount));
amount = 1F - amount;
// https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
return new Matrix4x4
{
M11 = .2126F + (.7874F * amount),
M12 = .2126F - (.2126F * amount),
M13 = .2126F - (.2126F * amount),
M21 = .7152F - (.7152F * amount),
M22 = .7152F + (.2848F * amount),
M23 = .7152F - (.7152F * amount),
M31 = .0722F - (.0722F * amount),
M32 = .0722F - (.0722F * amount),
M33 = .0722F + (.9278F * amount),
M44 = 1
};
ColorMatrix m = default;
m.M11 = .2126F + (.7874F * amount);
m.M21 = .7152F - (.7152F * amount);
m.M31 = 1F - (m.M11 + m.M21);
m.M12 = .2126F - (.2126F * amount);
m.M22 = .7152F + (.2848F * amount);
m.M32 = 1F - (m.M12 + m.M22);
m.M13 = .2126F - (.2126F * amount);
m.M23 = .7152F - (.7152F * amount);
m.M33 = 1F - (m.M13 + m.M23);
m.M44 = 1F;
return m;
}
/// <summary>
/// Create a hue filter matrix using the given angle in degrees.
/// </summary>
/// <param name="degrees">The angle of rotation in degrees.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateHueFilter(float degrees)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateHueFilter(float degrees)
{
// Wrap the angle round at 360.
degrees %= 360;
@ -329,18 +341,20 @@ namespace SixLabors.ImageSharp.Processing
// The matrix is set up to preserve the luminance of the image.
// See http://graficaobscura.com/matrix/index.html
// Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx
return new Matrix4x4
return new ColorMatrix
{
M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F),
M12 = .213F - (cosRadian * .213F) - (sinRadian * 0.143F),
M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F),
M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F),
M22 = .715F + (cosRadian * .285F) + (sinRadian * 0.140F),
M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F),
M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F),
M32 = .072F - (cosRadian * .072F) - (sinRadian * 0.283F),
M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F),
M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F),
M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F),
M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F),
M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F),
M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F),
M44 = 1
M44 = 1F
};
}
@ -348,23 +362,23 @@ namespace SixLabors.ImageSharp.Processing
/// Create an invert filter matrix using the given amount.
/// </summary>
/// <param name="amount">The proportion of the conversion. Must be between 0 and 1.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateInvertFilter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateInvertFilter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
float invert = 1F - (2F * amount);
return new Matrix4x4
return new ColorMatrix
{
M11 = invert,
M22 = invert,
M33 = invert,
M41 = amount,
M42 = amount,
M43 = amount,
M44 = 1
M44 = 1F,
M51 = amount,
M52 = amount,
M53 = amount,
};
}
@ -372,17 +386,17 @@ namespace SixLabors.ImageSharp.Processing
/// Create an opacity filter matrix using the given amount.
/// </summary>
/// <param name="amount">The proportion of the conversion. Must be between 0 and 1.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateOpacityFilter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateOpacityFilter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
return new Matrix4x4
return new ColorMatrix
{
M11 = 1,
M22 = 1,
M33 = 1,
M11 = 1F,
M22 = 1F,
M33 = 1F,
M44 = amount
};
}
@ -395,25 +409,27 @@ namespace SixLabors.ImageSharp.Processing
/// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results
/// </remarks>
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateSaturateFilter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateSaturateFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
return new Matrix4x4
{
M11 = .213F + (.787F * amount),
M12 = .213F - (.213F * amount),
M13 = .213F - (.213F * amount),
M21 = .715F - (.715F * amount),
M22 = .715F + (.285F * amount),
M23 = .715F - (.715F * amount),
M31 = 1F - ((.213F + (.787F * amount)) + (.715F - (.715F * amount))),
M32 = 1F - ((.213F - (.213F * amount)) + (.715F + (.285F * amount))),
M33 = 1F - ((.213F - (.213F * amount)) + (.715F - (.715F * amount))),
M44 = 1
};
ColorMatrix m = default;
m.M11 = .213F + (.787F * amount);
m.M21 = .715F - (.715F * amount);
m.M31 = 1F - (m.M11 + m.M21);
m.M12 = .213F - (.213F * amount);
m.M22 = .715F + (.285F * amount);
m.M32 = 1F - (m.M12 + m.M22);
m.M13 = .213F - (.213F * amount);
m.M23 = .715F - (.715F * amount);
m.M33 = 1F - (m.M13 + m.M23);
m.M44 = 1F;
return m;
}
/// <summary>
@ -421,25 +437,27 @@ namespace SixLabors.ImageSharp.Processing
/// The formula used matches the svg specification. <see href="http://www.w3.org/TR/filter-effects/#sepiaEquivalent"/>
/// </summary>
/// <param name="amount">The proportion of the conversion. Must be between 0 and 1.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 CreateSepiaFilter(float amount)
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateSepiaFilter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
amount = 1F - amount;
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
return new Matrix4x4
return new ColorMatrix
{
M11 = .393F + (.607F * amount),
M12 = .349F - (.349F * amount),
M13 = .272F - (.272F * amount),
M21 = .769F - (.769F * amount),
M22 = .686F + (.314F * amount),
M23 = .534F - (.534F * amount),
M31 = .189F - (.189F * amount),
M12 = .349F - (.349F * amount),
M22 = .686F + (.314F * amount),
M32 = .168F - (.168F * amount),
M13 = .272F - (.272F * amount),
M23 = .534F - (.534F * amount),
M33 = .131F + (.869F * amount),
M44 = 1
M44 = 1F
};
}
}

36
src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs

@ -3,16 +3,16 @@
using System;
using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
/// <summary>
/// Provides methods that accept a <see cref="Matrix4x4"/> matrix to apply free-form filters to images.
/// Provides methods that accept a <see cref="ColorMatrix"/> matrix to apply free-form filters to images.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class FilterProcessor<TPixel> : ImageProcessor<TPixel>
@ -22,38 +22,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// Initializes a new instance of the <see cref="FilterProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The matrix used to apply the image filter</param>
public FilterProcessor(Matrix4x4 matrix)
{
this.Matrix = matrix;
}
public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix;
/// <summary>
/// Gets the <see cref="Matrix4x4"/> used to apply the image filter.
/// Gets the <see cref="ColorMatrix"/> used to apply the image filter.
/// </summary>
public Matrix4x4 Matrix { get; }
public ColorMatrix Matrix { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startX = interest.X;
Matrix4x4 matrix = this.Matrix;
ColorMatrix matrix = this.Matrix;
ParallelHelper.IterateRows(
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
interest,
configuration,
rows =>
(rows, vectorBuffer) =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = interest.X; x < interest.Right; x++)
{
ref TPixel pixel = ref row[x];
var vector = Vector4.Transform(pixel.ToVector4(), matrix);
pixel.FromVector4(vector);
}
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length);
PixelOperations<TPixel>.Instance.ToVector4(configuration, rowSpan, vectorSpan);
Vector4Utils.Transform(vectorSpan, ref matrix);
PixelOperations<TPixel>.Instance.FromVector4(configuration, vectorSpan, rowSpan);
}
});
}

6
tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output);
JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F);
}
[Benchmark]
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output);
JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output, 255F, 128F);
}
[Benchmark]
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output);
JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F);
}
private static Buffer2D<float>[] CreateRandomValues(

109
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -4,6 +4,9 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
// ReSharper disable InconsistentNaming
@ -19,12 +22,14 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] AllBmpFiles = All;
public static readonly string[] BitfieldsBmpFiles = BitFields;
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
{ TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
};
[Theory]
@ -34,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "bmp");
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
@ -44,17 +49,47 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[WithFile(F, CommonNonDefaultPixelTypes)]
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
[WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "bmp");
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(Bit32Rgba, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
}
[Theory]
[WithFile(Rgba321010102, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3,
// which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
// The total difference without the alpha channel is still: 0.0204%
// Exporting the image as PNG with GIMP yields to the same result as the imagesharp implementation.
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
}
[Theory]
[WithFile(WinBmpv2, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
@ -62,7 +97,67 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "png");
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(WinBmpv3, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(Rgba32bf56, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
}
[Theory]
[WithFile(WinBmpv4, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(WinBmpv5, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(F, CommonNonDefaultPixelTypes)]
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
@ -74,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "png");
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}

2
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests
{
{ TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
{ TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
};
public static readonly TheoryData<string, BmpBitsPerPixel> BmpBitsPerPixelFiles =

3
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -57,8 +57,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<Rgba32> image = file.CreateImage())
{
var encoder = new PngEncoder { Quantizer = new WuQuantizer(KnownDiffusers.JarvisJudiceNinke, 256), ColorType = PngColorType.Palette };
image.Save($"{path}/{file.FileName}.png", encoder);
image.Save($"{path}/{file.FileName}");
}
}
}

6
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.PrintLinearData(input);
Block8x8F dest = block;
dest.NormalizeColorsInplace();
dest.NormalizeColorsInplace(255);
float[] array = new float[64];
dest.CopyTo(array);
@ -253,11 +253,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Block8x8F source = CreateRandomFloatBlock(-200, 200, seed);
Block8x8F expected = source;
expected.NormalizeColorsInplace();
expected.NormalizeColorsInplace(255);
expected.RoundInplace();
Block8x8F actual = source;
actual.NormalizeColorsAndRoundInplaceAvx2();
actual.NormalizeColorsAndRoundInplaceAvx2(255);
this.Output.WriteLine(expected.ToString());
this.Output.WriteLine(actual.ToString());

20
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateRgbToYCbCrConversion(
new JpegColorConverter.FromYCbCrBasic(),
new JpegColorConverter.FromYCbCrBasic(8),
3,
inputBufferLength,
resultBufferLength,
@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed);
var result = new Vector4[size];
JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result);
JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result, 255, 128);
for (int i = 0; i < size; i++)
{
@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed)
{
ValidateRgbToYCbCrConversion(
new JpegColorConverter.FromYCbCrSimd(),
new JpegColorConverter.FromYCbCrSimd(8),
3,
inputBufferLength,
resultBufferLength,
@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
//JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s);
ValidateRgbToYCbCrConversion(
new JpegColorConverter.FromYCbCrSimdAvx2(),
new JpegColorConverter.FromYCbCrSimdAvx2(8),
3,
inputBufferLength,
resultBufferLength,
@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1);
var result = new Vector4[count];
JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic();
JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd(8) : new JpegColorConverter.FromYCbCrBasic(8);
// Warm up:
converter.ConvertToRgba(values, result);
@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[MemberData(nameof(CommonConversionData))]
public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed)
{
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[MemberData(nameof(CommonConversionData))]
public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed)
{
var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck);
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck, 8);
JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
@ -308,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int seed)
{
ValidateRgbToYCbCrConversion(
JpegColorConverter.GetConverter(colorSpace),
JpegColorConverter.GetConverter(colorSpace,8),
componentCount,
inputBufferLength,
resultBufferLength,

10
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs

@ -42,5 +42,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// TODO: We need a public ImageDecoderException class in ImageSharp!
Assert.ThrowsAny<Exception>(() => provider.GetImage(JpegDecoder));
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.InvalidJpegThrowsWrongException797, PixelTypes.Rgba32)]
public void LoadingImage_InvalidTagLength_ShouldThrow<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> => Assert.Throws<ImageFormatException>(() => provider.GetImage());
[Theory]
[WithFile(TestImages.Jpeg.Issues.AccessViolationException798, PixelTypes.Rgba32)]
public void LoadingImage_BadHuffman_ShouldNotThrow<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> => Assert.NotNull(provider.GetImage());
}
}

5
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -28,7 +28,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.ExifResizeOutOfRange696,
TestImages.Jpeg.Issues.InvalidAPP0721,
TestImages.Jpeg.Issues.ExifGetString750Load,
TestImages.Jpeg.Issues.ExifGetString750Transform
TestImages.Jpeg.Issues.ExifGetString750Transform,
// High depth images
TestImages.Jpeg.Baseline.Testorig12bit,
};
public static string[] ProgressiveTestJpegs =

6
tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs

@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers
{
public class Vector4UtilsTests
{
private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f);
[Theory]
[InlineData(0)]
[InlineData(1)]
@ -23,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Vector4Utils.Premultiply(source);
Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
Assert.Equal(expected, source, this.ApproximateFloatComparer);
}
[Theory]
@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Vector4Utils.UnPremultiply(source);
Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
Assert.Equal(expected, source, this.ApproximateFloatComparer);
}
}
}

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -27,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.9.0.1" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.9.2" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.console" Version="2.3.1" />

270
tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs

@ -0,0 +1,270 @@
using System;
using System.Globalization;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Primitives
{
public class ColorMatrixTests
{
private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f);
[Fact]
public void ColorMatrixIdentityIsCorrect()
{
ColorMatrix val = default;
val.M11 = val.M22 = val.M33 = val.M44 = 1F;
Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer);
}
[Fact]
public void ColorMatrixCanDetectIdentity()
{
ColorMatrix m = ColorMatrix.Identity;
Assert.True(m.IsIdentity);
m.M12 = 1F;
Assert.False(m.IsIdentity);
}
[Fact]
public void ColorMatrixEquality()
{
ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F);
ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F);
object obj = m2;
Assert.True(m.Equals(obj));
Assert.True(m.Equals(m2));
Assert.True(m == m2);
Assert.False(m != m2);
}
[Fact]
public void ColorMatrixMultiply()
{
ColorMatrix value1 = this.CreateAllTwos();
ColorMatrix value2 = this.CreateAllThrees();
ColorMatrix m;
// First row
m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41);
m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42);
m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43);
m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44);
// Second row
m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41);
m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42);
m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43);
m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44);
// Third row
m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41);
m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42);
m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43);
m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44);
// Fourth row
m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41);
m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42);
m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43);
m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44);
// Fifth row
m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51;
m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52;
m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53;
m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54;
Assert.Equal(m, value1 * value2, this.ApproximateFloatComparer);
}
[Fact]
public void ColorMatrixMultiplyScalar()
{
ColorMatrix m = this.CreateAllTwos();
Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer);
}
[Fact]
public void ColorMatrixSubtract()
{
ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos();
Assert.Equal(this.CreateAllThrees(), m);
}
[Fact]
public void ColorMatrixNegate()
{
ColorMatrix m = this.CreateAllOnes() * -1F;
Assert.Equal(m, -this.CreateAllOnes());
}
[Fact]
public void ColorMatrixAdd()
{
ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos();
Assert.Equal(this.CreateAllThrees(), m);
}
[Fact]
public void ColorMatrixHashCode()
{
#if NETCOREAPP2_1
ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F);
HashCode hash = default;
hash.Add(m.M11);
hash.Add(m.M12);
hash.Add(m.M13);
hash.Add(m.M14);
hash.Add(m.M21);
hash.Add(m.M22);
hash.Add(m.M23);
hash.Add(m.M24);
hash.Add(m.M31);
hash.Add(m.M32);
hash.Add(m.M33);
hash.Add(m.M34);
hash.Add(m.M41);
hash.Add(m.M42);
hash.Add(m.M43);
hash.Add(m.M44);
hash.Add(m.M51);
hash.Add(m.M52);
hash.Add(m.M53);
hash.Add(m.M54);
Assert.Equal(hash.ToHashCode(), m.GetHashCode());
#endif
}
[Fact]
public void ColorMatrixToString()
{
ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F);
CultureInfo ci = CultureInfo.CurrentCulture;
string expected = string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}",
m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci),
m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci),
m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci),
m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci),
m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci));
Assert.Equal(expected, m.ToString());
}
private ColorMatrix CreateAllOnes()
{
return new ColorMatrix
{
M11 = 1F,
M12 = 1F,
M13 = 1F,
M14 = 1F,
M21 = 1F,
M22 = 1F,
M23 = 1F,
M24 = 1F,
M31 = 1F,
M32 = 1F,
M33 = 1F,
M34 = 1F,
M41 = 1F,
M42 = 1F,
M43 = 1F,
M44 = 1F,
M51 = 1F,
M52 = 1F,
M53 = 1F,
M54 = 1F
};
}
private ColorMatrix CreateAllTwos()
{
return new ColorMatrix
{
M11 = 2F,
M12 = 2F,
M13 = 2F,
M14 = 2F,
M21 = 2F,
M22 = 2F,
M23 = 2F,
M24 = 2F,
M31 = 2F,
M32 = 2F,
M33 = 2F,
M34 = 2F,
M41 = 2F,
M42 = 2F,
M43 = 2F,
M44 = 2F,
M51 = 2F,
M52 = 2F,
M53 = 2F,
M54 = 2F
};
}
private ColorMatrix CreateAllThrees()
{
return new ColorMatrix
{
M11 = 3F,
M12 = 3F,
M13 = 3F,
M14 = 3F,
M21 = 3F,
M22 = 3F,
M23 = 3F,
M24 = 3F,
M31 = 3F,
M32 = 3F,
M33 = 3F,
M34 = 3F,
M41 = 3F,
M42 = 3F,
M43 = 3F,
M44 = 3F,
M51 = 3F,
M52 = 3F,
M53 = 3F,
M54 = 3F
};
}
private ColorMatrix CreateAllFours()
{
return new ColorMatrix
{
M11 = 4F,
M12 = 4F,
M13 = 4F,
M14 = 4F,
M21 = 4F,
M22 = 4F,
M23 = 4F,
M24 = 4F,
M31 = 4F,
M32 = 4F,
M33 = 4F,
M34 = 4F,
M41 = 4F,
M42 = 4F,
M43 = 4F,
M44 = 4F,
M51 = 4F,
M52 = 4F,
M53 = 4F,
M54 = 4F
};
}
}
}

8
tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs

@ -8,10 +8,13 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects
{
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Filters")]
public class BrightnessTest
{
private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F);
public static readonly TheoryData<float> BrightnessValues
= new TheoryData<float>
{
@ -22,9 +25,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects
[Theory]
[WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)]
public void ApplyBrightnessFilter<TPixel>(TestImageProvider<TPixel> provider, float value)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value);
}
where TPixel : struct, IPixel<TPixel> => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer);
}
}

9
tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs

@ -2,17 +2,19 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
{
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Filters")]
public class ColorBlindnessTest
{
private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F);
public static readonly TheoryData<ColorBlindnessMode> ColorBlindnessFilters
= new TheoryData<ColorBlindnessMode>
{
@ -29,9 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
[Theory]
[WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)]
public void ApplyColorBlindnessFilter<TPixel>(TestImageProvider<TPixel> provider, ColorBlindnessMode colorBlindness)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString());
}
where TPixel : struct, IPixel<TPixel> => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer);
}
}

20
tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs

@ -1,16 +1,13 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
{
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
[GroupOutput("Filters")]
@ -25,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
public void ApplyFilter<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
Matrix4x4 m = CreateCombinedTestFilterMatrix();
ColorMatrix m = CreateCombinedTestFilterMatrix();
provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer);
}
@ -35,18 +32,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
public void ApplyFilterInBox<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
Matrix4x4 m = CreateCombinedTestFilterMatrix();
ColorMatrix m = CreateCombinedTestFilterMatrix();
provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer);
}
private static Matrix4x4 CreateCombinedTestFilterMatrix()
private static ColorMatrix CreateCombinedTestFilterMatrix()
{
Matrix4x4 brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F);
Matrix4x4 hue = KnownFilterMatrices.CreateHueFilter(180F);
Matrix4x4 saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F);
Matrix4x4 m = brightness * hue * saturation;
return m;
ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F);
ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F);
ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F);
return brightness * hue * saturation;
}
}

43
tests/ImageSharp.Tests/TestImages.cs

@ -136,12 +136,14 @@ namespace SixLabors.ImageSharp.Tests
public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg";
public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg";
public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg";
public const string Testorig12bit = "Jpg/baseline/testorig12.jpg";
public static readonly string[] All =
{
Cmyk, Ycck, Exif, Floorplan,
Calliphora, Turtle, GammaDalaiLamaGray,
Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1
Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444,
Ratio1x1, Testorig12bit
};
}
@ -166,6 +168,8 @@ namespace SixLabors.ImageSharp.Tests
public const string OrderedInterleavedProgressive723C = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg";
public const string ExifGetString750Transform = "Jpg/issues/issue750-exif-tranform.jpg";
public const string ExifGetString750Load = "Jpg/issues/issue750-exif-load.jpg";
public const string InvalidJpegThrowsWrongException797 = "Jpg/issues/Issue797-InvalidImage.jpg";
public const string AccessViolationException798 = "Jpg/issues/Issue798-AccessViolationException.jpg";
}
public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray();
@ -192,7 +196,8 @@ namespace SixLabors.ImageSharp.Tests
public const string NegHeight = "Bmp/neg_height.bmp";
public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp";
public const string V5Header = "Bmp/BITMAPV5HEADER.bmp";
public const string RLE = "Bmp/RunLengthEncoded.bmp";
public const string RLE8 = "Bmp/RunLengthEncoded.bmp";
public const string RLE4 = "Bmp/pal4rle.bmp";
public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp";
public const string Bit1 = "Bmp/pal1.bmp";
public const string Bit1Pal1 = "Bmp/pal1p1.bmp";
@ -202,19 +207,46 @@ namespace SixLabors.ImageSharp.Tests
public const string Bit16 = "Bmp/test16.bmp";
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";
public const string Bit32Rgb = "Bmp/rgb32.bmp";
public const string Bit32Rgba = "Bmp/rgba32.bmp";
// Note: This format can be called OS/2 BMPv1, or Windows BMPv2
public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp";
public const string WinBmpv3 = "Bmp/rgb24.bmp";
public const string WinBmpv4 = "Bmp/pal8v4.bmp";
public const string WinBmpv5 = "Bmp/pal8v5.bmp";
public const string Bit8Palette4 = "Bmp/pal8-0.bmp";
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";
// Bitmap images with compression type BITFIELDS
public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp";
public const string Rgb32bf = "Bmp/rgb32bf.bmp";
public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp";
public const string Rgb16565 = "Bmp/rgb16-565.bmp";
public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp";
public const string Issue735 = "Bmp/issue735.bmp";
public const string Rgba32bf56 = "Bmp/rgba32h56.bmp";
public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp";
public static readonly string[] BitFields
= {
Rgb32bfdef,
Rgb32bf,
Rgb16565,
Rgb16bfdef,
Rgb16565pal,
Issue735,
};
public static readonly string[] All
= {
Car,
F,
NegHeight,
CoreHeader,
V5Header, RLE,
V5Header,
RLE4,
RLE8,
RLEInverted,
Bit1,
Bit1Pal1,
@ -222,7 +254,8 @@ namespace SixLabors.ImageSharp.Tests
Bit8,
Bit8Inverted,
Bit16,
Bit16Inverted
Bit16Inverted,
Bit32Rgb
};
}

28
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Numerics;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Tests
{
@ -11,8 +12,9 @@ namespace SixLabors.ImageSharp.Tests
/// </summary>
internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector2>,
IEqualityComparer<Vector4>,
IEqualityComparer<Vector2>
IEqualityComparer<ColorMatrix>
{
private readonly float Epsilon;
@ -20,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests
/// Initializes a new instance of the <see cref="ApproximateFloatComparer"/> class.
/// </summary>
/// <param name="epsilon">The comparison error difference epsilon to use.</param>
public ApproximateFloatComparer(float epsilon = 1f) => this.Epsilon = epsilon;
public ApproximateFloatComparer(float epsilon = 1F) => this.Epsilon = epsilon;
/// <inheritdoc/>
public bool Equals(float x, float y)
@ -34,17 +36,29 @@ namespace SixLabors.ImageSharp.Tests
public int GetHashCode(float obj) => obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 a, Vector4 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
/// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
public int GetHashCode(Vector2 obj) => obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
/// <inheritdoc/>
public bool Equals(Vector2 a, Vector2 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y);
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
public int GetHashCode(Vector2 obj)
/// <inheritdoc/>
public bool Equals(ColorMatrix x, ColorMatrix y)
{
throw new System.NotImplementedException();
return
this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
&& this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24)
&& this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34)
&& this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44)
&& this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54);
}
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();
}
}

BIN
tests/Images/Input/Bmp/issue735.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
tests/Images/Input/Bmp/pal4rle.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
tests/Images/Input/Bmp/pal8v4.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
tests/Images/Input/Bmp/pal8v5.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
tests/Images/Input/Bmp/rgb16-565.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
tests/Images/Input/Bmp/rgb16-565pal.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
tests/Images/Input/Bmp/rgb16bfdef.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
tests/Images/Input/Bmp/rgb24.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
tests/Images/Input/Bmp/rgb32bf.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
tests/Images/Input/Bmp/rgb32bfdef.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
tests/Images/Input/Bmp/rgba32-1010102.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
tests/Images/Input/Bmp/rgba32.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
tests/Images/Input/Bmp/rgba32h56.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
tests/Images/Input/Jpg/baseline/testorig12.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
tests/Images/Input/Jpg/issues/Issue797-InvalidImage.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

BIN
tests/Images/Input/Jpg/issues/Issue798-AccessViolationException.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Loading…
Cancel
Save