Browse Source

Merge branch 'master' into colorspace-transforms

af/merge-core
James Jackson-South 8 years ago
parent
commit
99050a3de4
  1. 2
      appveyor.yml
  2. 2
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs
  3. 94
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  4. 31
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  5. 38
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  6. 42
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  7. 32
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  8. 6
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  9. 4
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  10. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
  11. 2
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs
  12. 33
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  13. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
  14. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs
  15. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
  16. 10
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  17. 36
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  18. 73
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  19. 2
      src/ImageSharp/ImageFrame{TPixel}.cs
  20. 2
      src/ImageSharp/ImageSharp.csproj
  21. 21
      src/ImageSharp/Memory/AllocationOptions.cs
  22. 8
      src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs
  23. 13
      src/ImageSharp/Memory/MemoryAllocator.cs
  24. 47
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  25. 4
      src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs
  26. 14
      src/ImageSharp/MetaData/ImageMetaData.cs
  27. 31
      src/ImageSharp/MetaData/PixelResolutionUnit.cs
  28. 42
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  29. 4
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  30. 36
      src/ImageSharp/Processing/HistogramEqualizationExtension.cs
  31. 126
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
  32. 2
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  33. 14
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  34. 2
      src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
  35. 52
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  36. 49
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  37. 89
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  38. 36
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  39. 4
      tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs
  40. 88
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
  41. 65
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  42. 11
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  43. 45
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  44. 34
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  45. 41
      tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs
  46. 10
      tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs
  47. 8
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
  48. 14
      tests/ImageSharp.Tests/Memory/BufferTestSuite.cs
  49. 68
      tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs
  50. 22
      tests/ImageSharp.Tests/TestImages.cs
  51. 3
      tests/Images/Input/Gif/base_1x4.gif
  52. 3
      tests/Images/Input/Gif/base_4x1.gif
  53. 3
      tests/Images/Input/Jpg/baseline/ratio-1x1.jpg
  54. 3
      tests/Images/Input/Png/ratio-1x4.png
  55. 3
      tests/Images/Input/Png/ratio-4x1.png

2
appveyor.yml

@ -1,5 +1,5 @@
version: 1.0.0.{build}
image: Visual Studio 2017
image: Previous Visual Studio 2017
# prevent the double build when a branch has an active PR
skip_branch_with_pr: true

2
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs

@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
}
// take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it.
Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, true);
Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean);
using (IBuffer<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections))
using (IBuffer<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width))

94
src/ImageSharp/Common/Helpers/UnitConverter.cs

@ -0,0 +1,94 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
namespace SixLabors.ImageSharp.Common.Helpers
{
/// <summary>
/// Contains methods for converting values between unit scales.
/// </summary>
internal static class UnitConverter
{
/// <summary>
/// The number of centimeters in a meter.
/// 1 cm is equal to exactly 0.01 meters.
/// </summary>
private const double CmsInMeter = 1 / 0.01D;
/// <summary>
/// The number of centimeters in an inch.
/// 1 inch is equal to exactly 2.54 centimeters.
/// </summary>
private const double CmsInInch = 2.54D;
/// <summary>
/// The number of inches in a meter.
/// 1 inch is equal to exactly 0.0254 meters.
/// </summary>
private const double InchesInMeter = 1 / 0.0254D;
/// <summary>
/// Scales the value from centimeters to meters.
/// </summary>
/// <param name="x">The value to scale.</param>
/// <returns>The <see cref="double"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static double CmToMeter(double x) => x * CmsInMeter;
/// <summary>
/// Scales the value from meters to centimeters.
/// </summary>
/// <param name="x">The value to scale.</param>
/// <returns>The <see cref="double"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static double MeterToCm(double x) => x / CmsInMeter;
/// <summary>
/// Scales the value from meters to inches.
/// </summary>
/// <param name="x">The value to scale.</param>
/// <returns>The <see cref="double"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static double MeterToInch(double x) => x / InchesInMeter;
/// <summary>
/// Scales the value from inches to meters.
/// </summary>
/// <param name="x">The value to scale.</param>
/// <returns>The <see cref="double"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static double InchToMeter(double x) => x * InchesInMeter;
/// <summary>
/// Scales the value from centimeters to inches.
/// </summary>
/// <param name="x">The value to scale.</param>
/// <returns>The <see cref="double"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static double CmToInch(double x) => x / CmsInInch;
/// <summary>
/// Scales the value from inches to centimeters.
/// </summary>
/// <param name="x">The value to scale.</param>
/// <returns>The <see cref="double"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static double InchToCm(double x) => x * CmsInInch;
/// <summary>
/// Converts an <see cref="ExifTag.ResolutionUnit"/> to a <see cref="PixelResolutionUnit"/>.
/// </summary>
/// <param name="profile">The EXIF profile containing the value.</param>
/// <returns>The <see cref="PixelResolutionUnit"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile)
{
return profile.TryGetValue(ExifTag.ResolutionUnit, out ExifValue resolution)
? (PixelResolutionUnit)(byte)(((ushort)resolution.Value) - 1) // EXIF is 1, 2, 3
: default;
}
}
}

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

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -59,6 +60,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private Stream stream;
/// <summary>
/// The metadata
/// </summary>
private ImageMetaData metaData;
/// <summary>
/// The file header containing general information.
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
@ -103,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
var image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height);
var image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
@ -157,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public IImageInfo Identify(Stream stream)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, new ImageMetaData());
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metaData);
}
/// <summary>
@ -220,7 +226,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
TPixel color = default;
var rgba = new Rgba32(0, 0, 0, 255);
using (Buffer2D<byte> buffer = this.memoryAllocator.AllocateClean2D<byte>(width, height))
using (Buffer2D<byte> buffer = this.memoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean))
{
this.UncompressRle8(width, buffer.GetSpan());
@ -348,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding;
}
using (IManagedByteBuffer row = this.memoryAllocator.AllocateCleanManagedByteBuffer(arrayWidth + padding))
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean))
{
TPixel color = default;
var rgba = new Rgba32(0, 0, 0, 255);
@ -518,6 +524,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}.");
}
// Resolution is stored in PPM.
var meta = new ImageMetaData();
meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter;
if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0)
{
meta.HorizontalResolution = this.infoHeader.XPelsPerMeter;
meta.VerticalResolution = this.infoHeader.YPelsPerMeter;
}
else
{
// Convert default metadata values to PPM.
meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultHorizontalResolution));
meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultVerticalResolution));
}
this.metaData = meta;
// skip the remaining header because we can't read those parts
this.stream.Skip(skipAmount);
}

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

@ -3,6 +3,8 @@
using System;
using System.IO;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -50,6 +52,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel);
// Set Resolution.
ImageMetaData meta = image.MetaData;
int hResolution = 0;
int vResolution = 0;
if (meta.ResolutionUnits != PixelResolutionUnit.AspectRatio)
{
if (meta.HorizontalResolution > 0 && meta.VerticalResolution > 0)
{
switch (meta.ResolutionUnits)
{
case PixelResolutionUnit.PixelsPerInch:
hResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution));
vResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
hResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution));
vResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerMeter:
hResolution = (int)Math.Round(meta.HorizontalResolution);
vResolution = (int)Math.Round(meta.VerticalResolution);
break;
}
}
}
var infoHeader = new BmpInfoHeader(
headerSize: BmpInfoHeader.Size,
height: image.Height,
@ -58,7 +92,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
planes: 1,
imageSize: image.Height * bytesPerLine,
clrUsed: 0,
clrImportant: 0);
clrImportant: 0,
xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution);
var fileHeader = new BmpFileHeader(
type: 19778, // BM

42
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -321,11 +321,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (imageDescriptor.LocalColorTableFlag)
{
int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, true);
localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.Array, 0, length);
}
indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(imageDescriptor, indices.GetSpan());
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan());
@ -450,8 +450,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int index = Unsafe.Add(ref indicesRef, i);
if (this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
if (!this.graphicsControlExtension.TransparencyFlag
|| this.graphicsControlExtension.TransparencyIndex != index)
{
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
rgba.Rgb = colorTable[index];
@ -516,19 +516,47 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="stream">The stream containing image data. </param>
private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream)
{
this.metaData = new ImageMetaData();
this.stream = stream;
// Skip the identifier
this.stream.Skip(6);
this.ReadLogicalScreenDescriptor();
var meta = new ImageMetaData();
// The Pixel Aspect Ratio is defined to be the quotient of the pixel's
// width over its height. The value range in this field allows
// specification of the widest pixel of 4:1 to the tallest pixel of
// 1:4 in increments of 1/64th.
//
// Values : 0 - No aspect ratio information is given.
// 1..255 - Value used in the computation.
//
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
if (this.logicalScreenDescriptor.PixelAspectRatio > 0)
{
meta.ResolutionUnits = PixelResolutionUnit.AspectRatio;
float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F;
if (ratio > 1)
{
meta.HorizontalResolution = ratio;
meta.VerticalResolution = 1;
}
else
{
meta.VerticalResolution = 1 / ratio;
meta.HorizontalResolution = 1;
}
}
this.metaData = meta;
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, true);
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream
stream.Read(this.globalColorTable.Array, 0, globalColorTableLength);

32
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -226,11 +226,41 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1);
// The Pixel Aspect Ratio is defined to be the quotient of the pixel's
// width over its height. The value range in this field allows
// specification of the widest pixel of 4:1 to the tallest pixel of
// 1:4 in increments of 1/64th.
//
// Values : 0 - No aspect ratio information is given.
// 1..255 - Value used in the computation.
//
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
ImageMetaData meta = image.MetaData;
byte ratio = 0;
if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio)
{
double hr = meta.HorizontalResolution;
double vr = meta.VerticalResolution;
if (hr != vr)
{
if (hr > vr)
{
ratio = (byte)((hr * 64) - 15);
}
else
{
ratio = (byte)(((1 / vr) * 64) - 15);
}
}
}
var descriptor = new GifLogicalScreenDescriptor(
width: (ushort)image.Width,
height: (ushort)image.Height,
packed: packedValue,
backgroundColorIndex: unchecked((byte)transparencyIndex));
backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio);
descriptor.WriteTo(this.buffer);

6
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream = stream;
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, true);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, true);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, true);
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
}
/// <summary>

4
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -168,8 +168,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth)
{
this.initialCodeSize = Math.Max(2, colorDepth);
this.hashTable = memoryAllocator.Allocate<int>(HashSize, true);
this.codeTable = memoryAllocator.Allocate<int>(HashSize, true);
this.hashTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean);
this.codeTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean);
}
/// <summary>

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.MetaData;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
@ -28,10 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
Guard.MustBeGreaterThan(xDensity, 0, nameof(xDensity));
Guard.MustBeGreaterThan(yDensity, 0, nameof(yDensity));
Guard.MustBeBetweenOrEqualTo(densityUnits, 0, 2, nameof(densityUnits));
this.MajorVersion = majorVersion;
this.MinorVersion = minorVersion;
this.DensityUnits = densityUnits;
this.DensityUnits = (PixelResolutionUnit)densityUnits;
this.XDensity = xDensity;
this.YDensity = yDensity;
}
@ -52,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// 01 : Pixels per inch (2.54 cm)
/// 02 : Pixels per centimeter
/// </summary>
public byte DensityUnits { get; }
public PixelResolutionUnit DensityUnits { get; }
/// <summary>
/// Gets the horizontal pixel density. Must not be zero.

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs

@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
this.SpectralBlocks = memoryAllocator.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
this.SpectralBlocks = memoryAllocator.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, AllocationOptions.Clean);
}
/// <summary>

33
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
@ -210,7 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int componentCount = 3;
// Write the Start Of Image marker.
this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution);
this.WriteApplicationHeader(image.MetaData);
this.WriteProfiles(image);
@ -425,9 +427,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="horizontalResolution">The resolution of the image in the x- direction.</param>
/// <param name="verticalResolution">The resolution of the image in the y- direction.</param>
private void WriteApplicationHeader(short horizontalResolution, short verticalResolution)
/// <param name="meta">The image meta data.</param>
private void WriteApplicationHeader(ImageMetaData meta)
{
// Write the start of image marker. Markers are always prefixed with with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF;
@ -445,13 +446,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[10] = 0x00; // = "JFIF",'\0'
this.buffer[11] = 0x01; // versionhi
this.buffer[12] = 0x01; // versionlo
this.buffer[13] = 0x01; // xyunits as dpi
// Resolution. Big Endian
this.buffer[14] = (byte)(horizontalResolution >> 8);
this.buffer[15] = (byte)horizontalResolution;
this.buffer[16] = (byte)(verticalResolution >> 8);
this.buffer[17] = (byte)verticalResolution;
Span<byte> hResolution = this.buffer.AsSpan(14, 2);
Span<byte> vResolution = this.buffer.AsSpan(16, 2);
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
{
// Scale down to PPI
this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
}
else
{
// We can simply pass the value.
this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution));
}
// No thumbnail
this.buffer[18] = 0x00; // Thumbnail width

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
this.stream = stream;
this.length = (int)stream.Length;
this.managedBuffer = memoryAllocator.AllocateCleanManagedByteBuffer(ChunkLength);
this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean);
this.bufferChunk = this.managedBuffer.Array;
}

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="memoryAllocator">The memory allocator used to allocate memory for image processing operations.</param>
public FastACTables(MemoryAllocator memoryAllocator)
{
this.tables = memoryAllocator.AllocateClean2D<short>(512, 4);
this.tables = memoryAllocator.Allocate2D<short>(512, 4, AllocationOptions.Clean);
}
/// <summary>

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs

@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
this.SpectralBlocks = this.memoryAllocator.AllocateClean2D<Block8x8>(blocksPerColumnForMcu, blocksPerLineForMcu + 1);
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(blocksPerColumnForMcu, blocksPerLineForMcu + 1, AllocationOptions.Clean);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

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

@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
@ -412,6 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
this.MetaData.ResolutionUnits = this.jFif.DensityUnits;
}
else if (this.isExif)
{
@ -427,6 +428,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
this.MetaData.HorizontalResolution = horizontalValue;
this.MetaData.VerticalResolution = verticalValue;
this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile);
}
}
@ -707,7 +709,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(int remaining)
{
using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(256))
using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
{
ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan());
for (int i = 2; i < remaining;)
@ -715,7 +717,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
byte huffmanTableSpec = (byte)this.InputStream.ReadByte();
this.InputStream.Read(huffmanData.Array, 0, 16);
using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(17))
using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean))
{
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan());
int codeLengthSum = 0;
@ -725,7 +727,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1);
}
using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(256))
using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
{
this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum);

36
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -10,6 +10,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.MetaData;
@ -233,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.Array);
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
if (image == null)
@ -307,7 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.Array);
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
@ -369,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
buffer = this.MemoryAllocator.AllocateCleanManagedByteBuffer(bytesPerScanline * 8 / bits);
buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
byte[] result = buffer.Array;
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
@ -396,9 +397,26 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The data containing physical data.</param>
private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan<byte> data)
{
// 39.3700787 = inches in a meter.
metadata.HorizontalResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)) / 39.3700787d;
metadata.VerticalResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)) / 39.3700787d;
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
// Pixels per unit, X axis: 4 bytes (unsigned integer)
// Pixels per unit, Y axis: 4 bytes (unsigned integer)
// Unit specifier: 1 byte
//
// The following values are legal for the unit specifier:
// 0: unit is unknown
// 1: unit is the meter
//
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4));
int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4));
byte unit = data[8];
metadata.ResolutionUnits = unit == byte.MinValue
? PixelResolutionUnit.AspectRatio
: PixelResolutionUnit.PixelsPerMeter;
metadata.HorizontalResolution = hResolution;
metadata.VerticalResolution = vResolution;
}
/// <summary>
@ -419,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline = this.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.scanline = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.previousScanline = this.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -1427,7 +1445,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private IManagedByteBuffer ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(length);
IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.currentStream.Read(buffer.Array, 0, length);

73
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -6,8 +6,10 @@ using System.Buffers.Binary;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.Memory;
@ -598,19 +600,52 @@ namespace SixLabors.ImageSharp.Formats.Png
private void WritePhysicalChunk<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
if (image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0)
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
// Pixels per unit, X axis: 4 bytes (unsigned integer)
// Pixels per unit, Y axis: 4 bytes (unsigned integer)
// Unit specifier: 1 byte
//
// The following values are legal for the unit specifier:
// 0: unit is unknown
// 1: unit is the meter
//
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
ImageMetaData meta = image.MetaData;
Span<byte> hResolution = this.chunkDataBuffer.AsSpan(0, 4);
Span<byte> vResolution = this.chunkDataBuffer.AsSpan(4, 4);
switch (meta.ResolutionUnits)
{
// 39.3700787 = inches in a meter.
int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D);
int dpmY = (int)Math.Round(image.MetaData.VerticalResolution * 39.3700787D);
case PixelResolutionUnit.AspectRatio:
BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), dpmX);
BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), dpmY);
this.chunkDataBuffer[8] = 0;
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerInch:
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
this.chunkDataBuffer[8] = 1;
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)));
break;
default:
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
this.chunkDataBuffer[8] = 1; // Per meter
BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution));
break;
}
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
}
/// <summary>
@ -643,9 +678,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerScanline = this.width * this.bytesPerPixel;
int resultLength = this.bytesPerScanline + 1;
this.previousScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.rawScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.result = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.rawScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.result = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
switch (this.pngFilterMethod)
{
@ -654,29 +689,29 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngFilterMethod.Sub:
this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Up:
this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Average:
this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Paeth:
this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Adaptive:
this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
}

2
src/ImageSharp/ImageFrame{TPixel}.cs

@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp
this.configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height, false);
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height);
this.MetaData = metaData;
this.Clear(configuration.ParallelOptions, backgroundColor);
}

2
src/ImageSharp/ImageSharp.csproj

@ -29,7 +29,7 @@
<DebugType Condition="$(codecov) == ''">portable</DebugType>
<DebugSymbols>True</DebugSymbols>
<Features>IOperation</Features>
<LangVersion>7.3</LangVersion>
<LangVersion>Latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />

21
src/ImageSharp/Memory/AllocationOptions.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.Memory
{
/// <summary>
/// Options for allocating buffers.
/// </summary>
public enum AllocationOptions
{
/// <summary>
/// Indicates that the buffer should just be allocated.
/// </summary>
None,
/// <summary>
/// Indicates that the allocated buffer should be cleaned following allocation.
/// </summary>
Clean
}
}

8
src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs

@ -89,7 +89,7 @@ namespace SixLabors.Memory
}
/// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, bool clear)
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options)
{
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
@ -98,7 +98,7 @@ namespace SixLabors.Memory
byte[] byteArray = pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteArray, length, pool);
if (clear)
if (options == AllocationOptions.Clean)
{
buffer.Clear();
}
@ -107,13 +107,13 @@ namespace SixLabors.Memory
}
/// <inheritdoc />
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options)
{
ArrayPool<byte> pool = this.GetArrayPool(length);
byte[] byteArray = pool.Rent(length);
var buffer = new ManagedByteBuffer(byteArray, length, pool);
if (clear)
if (options == AllocationOptions.Clean)
{
buffer.Clear();
}

13
src/ImageSharp/Memory/MemoryAllocator.cs

@ -9,23 +9,22 @@ namespace SixLabors.Memory
public abstract class MemoryAllocator
{
/// <summary>
/// Allocates an <see cref="IBuffer{T}"/> of size <paramref name="length"/>, optionally
/// clearing the buffer before it gets returned.
/// Allocates an <see cref="IBuffer{T}"/> of size <paramref name="length"/>.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="length">Size of the buffer to allocate</param>
/// <param name="clear">True to clear the backing memory of the buffer</param>
/// <param name="options">The allocation options.</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
internal abstract IBuffer<T> Allocate<T>(int length, bool clear)
internal abstract IBuffer<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
where T : struct;
/// <summary>
/// Allocates an <see cref="IManagedByteBuffer"/>
/// Allocates an <see cref="IManagedByteBuffer"/>.
/// </summary>
/// <param name="length">The requested buffer length</param>
/// <param name="clear">A value indicating whether to clean the buffer</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear);
internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None);
/// <summary>
/// Releases all retained resources not being in use.

47
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -7,56 +7,17 @@ namespace SixLabors.Memory
/// </summary>
internal static class MemoryAllocatorExtensions
{
/// <summary>
/// Allocates a <see cref="IBuffer{T}"/> of size <paramref name="length"/>.
/// Note: Depending on the implementation, the buffer may not cleared before
/// returning, so it may contain data from an earlier use.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/></param>
/// <param name="length">Size of the buffer to allocate</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
public static IBuffer<T> Allocate<T>(this MemoryAllocator memoryAllocator, int length)
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height, AllocationOptions options = AllocationOptions.None)
where T : struct
{
return memoryAllocator.Allocate<T>(length, false);
}
public static IBuffer<T> AllocateClean<T>(this MemoryAllocator memoryAllocator, int length)
where T : struct
{
return memoryAllocator.Allocate<T>(length, true);
}
public static IManagedByteBuffer AllocateManagedByteBuffer(this MemoryAllocator memoryAllocator, int length)
{
return memoryAllocator.AllocateManagedByteBuffer(length, false);
}
public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryAllocator memoryAllocator, int length)
{
return memoryAllocator.AllocateManagedByteBuffer(length, true);
}
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height, bool clear)
where T : struct
{
IBuffer<T> buffer = memoryAllocator.Allocate<T>(width * height, clear);
IBuffer<T> buffer = memoryAllocator.Allocate<T>(width * height, options);
return new Buffer2D<T>(buffer, width, height);
}
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, Size size)
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, false);
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height)
where T : struct =>
Allocate2D<T>(memoryAllocator, width, height, false);
public static Buffer2D<T> AllocateClean2D<T>(this MemoryAllocator memoryAllocator, int width, int height)
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, Size size, AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, width, height, true);
Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);
/// <summary>
/// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea)

4
src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs

@ -6,12 +6,12 @@
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{
/// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, bool clear)
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options)
{
return new BasicArrayBuffer<T>(new T[length]);
}
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options)
{
return new BasicByteBuffer(new byte[length]);
}

14
src/ImageSharp/MetaData/ImageMetaData.cs

@ -14,13 +14,13 @@ namespace SixLabors.ImageSharp.MetaData
{
/// <summary>
/// The default horizontal resolution value (dots per inch) in x direction.
/// <remarks>The default value is 96 dots per inch.</remarks>
/// <remarks>The default value is 96 <see cref="PixelResolutionUnit.PixelsPerInch"/>.</remarks>
/// </summary>
public const double DefaultHorizontalResolution = 96;
/// <summary>
/// The default vertical resolution value (dots per inch) in y direction.
/// <remarks>The default value is 96 dots per inch.</remarks>
/// <remarks>The default value is 96 <see cref="PixelResolutionUnit.PixelsPerInch"/>.</remarks>
/// </summary>
public const double DefaultVerticalResolution = 96;
@ -47,6 +47,7 @@ namespace SixLabors.ImageSharp.MetaData
{
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.ResolutionUnits = other.ResolutionUnits;
this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties)
@ -99,6 +100,15 @@ namespace SixLabors.ImageSharp.MetaData
}
}
/// <summary>
/// Gets or sets unit of measure used when reporting resolution.
/// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
/// 01 : Pixels per inch (2.54 cm)
/// 02 : Pixels per centimeter
/// 03 : Pixels per meter
/// </summary>
public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch;
/// <summary>
/// Gets or sets the Exif profile.
/// </summary>

31
src/ImageSharp/MetaData/PixelResolutionUnit.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.MetaData
{
/// <summary>
/// Provides enumeration of available pixel density units.
/// </summary>
public enum PixelResolutionUnit : byte
{
/// <summary>
/// No units; width:height pixel aspect ratio.
/// </summary>
AspectRatio = 0,
/// <summary>
/// Pixels per inch (2.54 cm).
/// </summary>
PixelsPerInch = 1,
/// <summary>
/// Pixels per centimeter.
/// </summary>
PixelsPerCentimeter = 2,
/// <summary>
/// Pixels per meter (100 cm).
/// </summary>
PixelsPerMeter = 3
}
}

42
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -278,7 +278,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -395,7 +395,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -434,7 +434,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -473,7 +473,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -512,7 +512,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -551,7 +551,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -590,7 +590,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -668,7 +668,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -707,7 +707,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -746,7 +746,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -785,7 +785,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -824,7 +824,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);

4
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{
using System;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
/// <summary>
@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3, false))
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);

36
src/ImageSharp/Processing/HistogramEqualizationExtension.cs

@ -0,0 +1,36 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Normalization;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extension that allow the adjustment of the contrast of an image via its histogram.
/// </summary>
public static class HistogramEqualizationExtension
{
/// <summary>
/// Equalizes the histogram of an image to increases the global contrast using 65536 luminance levels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> HistogramEqualization(source, 65536);
/// <summary>
/// Equalizes the histogram of an image to increases the global contrast.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> HistogramEqualization<TPixel>(this IImageProcessingContext<TPixel> source, int luminanceLevels)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new HistogramEqualizationProcessor<TPixel>(luminanceLevels));
}
}

126
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs

@ -0,0 +1,126 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
/// <summary>
/// Applies a global histogram equalization to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class HistogramEqualizationProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="HistogramEqualizationProcessor{TPixel}"/> class.
/// </summary>
/// <param name="luminanceLevels">The number of different luminance levels. Typical values are 256 for 8-bit grayscale images
/// or 65536 for 16-bit grayscale images.</param>
public HistogramEqualizationProcessor(int luminanceLevels)
{
Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels));
this.LuminanceLevels = luminanceLevels;
}
/// <summary>
/// Gets the number of luminance levels.
/// </summary>
public int LuminanceLevels { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
int numberOfPixels = source.Width * source.Height;
Span<TPixel> pixels = source.GetPixelSpan();
// Build the histogram of the grayscale levels.
using (IBuffer<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IBuffer<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
{
Span<int> histogram = histogramBuffer.GetSpan();
for (int i = 0; i < pixels.Length; i++)
{
TPixel sourcePixel = pixels[i];
int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels);
histogram[luminance]++;
}
// Calculate the cumulative distribution function, which will map each input pixel to a new value.
Span<int> cdf = cdfBuffer.GetSpan();
int cdfMin = this.CalculateCdf(cdf, histogram);
// Apply the cdf to each pixel of the image
float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin;
for (int i = 0; i < pixels.Length; i++)
{
TPixel sourcePixel = pixels[i];
int luminance = this.GetLuminance(sourcePixel, this.LuminanceLevels);
float luminanceEqualized = cdf[luminance] / numberOfPixelsMinusCdfMin;
pixels[i].PackFromVector4(new Vector4(luminanceEqualized));
}
}
}
/// <summary>
/// Calculates the cumulative distribution function.
/// </summary>
/// <param name="cdf">The array holding the cdf.</param>
/// <param name="histogram">The histogram of the input image.</param>
/// <returns>The first none zero value of the cdf.</returns>
private int CalculateCdf(Span<int> cdf, Span<int> histogram)
{
// Calculate the cumulative histogram
int histSum = 0;
for (int i = 0; i < histogram.Length; i++)
{
histSum += histogram[i];
cdf[i] = histSum;
}
// Get the first none zero value of the cumulative histogram
int cdfMin = 0;
for (int i = 0; i < histogram.Length; i++)
{
if (cdf[i] != 0)
{
cdfMin = cdf[i];
break;
}
}
// Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop
for (int i = 0; i < histogram.Length; i++)
{
cdf[i] = Math.Max(0, cdf[i] - cdfMin);
}
return cdfMin;
}
/// <summary>
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <param name="sourcePixel">The pixel to get the luminance from</param>
/// <param name="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
[MethodImpl(InliningOptions.ShortMethod)]
private int GetLuminance(TPixel sourcePixel, int luminanceLevels)
{
// Convert to grayscale using ITU-R Recommendation BT.709
var vector = sourcePixel.ToVector4();
int luminance = Convert.ToInt32(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1));
return luminance;
}
}
}

2
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Width = width;
this.Height = height;
this.Palette = palette;
this.pixels = memoryAllocator.AllocateCleanManagedByteBuffer(width * height);
this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean);
}
/// <summary>

14
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -139,13 +139,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
try
{
this.vwt = memoryAllocator.AllocateClean<long>(TableLength);
this.vmr = memoryAllocator.AllocateClean<long>(TableLength);
this.vmg = memoryAllocator.AllocateClean<long>(TableLength);
this.vmb = memoryAllocator.AllocateClean<long>(TableLength);
this.vma = memoryAllocator.AllocateClean<long>(TableLength);
this.m2 = memoryAllocator.AllocateClean<float>(TableLength);
this.tag = memoryAllocator.AllocateClean<byte>(TableLength);
this.vwt = memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vmr = memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vmg = memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vmb = memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.vma = memoryAllocator.Allocate<long>(TableLength, AllocationOptions.Clean);
this.m2 = memoryAllocator.Allocate<float>(TableLength, AllocationOptions.Clean);
this.tag = memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
return base.QuantizeFrame(image);
}

2
src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="destinationSize">The size of the destination window</param>
public WeightsBuffer(MemoryAllocator memoryAllocator, int sourceSize, int destinationSize)
{
this.dataBuffer = memoryAllocator.Allocate2D<float>(sourceSize, destinationSize, true);
this.dataBuffer = memoryAllocator.Allocate2D<float>(sourceSize, destinationSize, AllocationOptions.Clean);
this.Weights = new WeightsWindow[destinationSize];
}

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

@ -10,17 +10,25 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests
{
using SixLabors.ImageSharp.MetaData;
using static TestImages.Bmp;
public class BmpDecoderTests
{
public const PixelTypes CommonNonDefaultPixelTypes =
PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;
public static readonly string[] AllBmpFiles =
{
Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted
};
{
Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted
};
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 }
};
[Theory]
[WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)]
@ -64,5 +72,39 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new BmpDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
{
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new BmpDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}

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

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
@ -12,11 +14,19 @@ namespace SixLabors.ImageSharp.Tests
public class BmpEncoderTests : FileTestBase
{
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
new TheoryData<BmpBitsPerPixel>
{
BmpBitsPerPixel.Pixel24,
BmpBitsPerPixel.Pixel32
};
new TheoryData<BmpBitsPerPixel>
{
BmpBitsPerPixel.Pixel24,
BmpBitsPerPixel.Pixel32
};
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 }
};
public BmpEncoderTests(ITestOutputHelper output)
{
@ -25,6 +35,31 @@ namespace SixLabors.ImageSharp.Tests
private ITestOutputHelper Output { get; }
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var options = new BmpEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateImage())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetaData meta = output.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}
[Theory]
[WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
public void Encode_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
@ -44,10 +79,10 @@ namespace SixLabors.ImageSharp.Tests
{
TestBmpEncoderCore(provider, bitsPerPixel);
}
private static void TestBmpEncoderCore<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel>
{
{
using (Image<TPixel> image = provider.GetImage())
{
// there is no alpha in bmp!

89
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -4,7 +4,6 @@
using System.Text;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
using Xunit;
using System.IO;
using SixLabors.ImageSharp.Advanced;
@ -13,7 +12,7 @@ using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
using System.Collections.Generic;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
public class GifDecoderTests
@ -21,31 +20,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;
public static readonly string[] MultiFrameTestFiles =
{
TestImages.Gif.Giphy, TestImages.Gif.Kumin
};
{
TestImages.Gif.Giphy, TestImages.Gif.Kumin
};
public static readonly string[] BasicVerificationFiles =
{
TestImages.Gif.Cheers,
TestImages.Gif.Rings,
{
TestImages.Gif.Cheers,
TestImages.Gif.Rings,
// previously DecodeBadApplicationExtensionLength:
TestImages.Gif.Issues.BadAppExtLength,
TestImages.Gif.Issues.BadAppExtLength_2,
// previously DecodeBadApplicationExtensionLength:
TestImages.Gif.Issues.BadAppExtLength,
TestImages.Gif.Issues.BadAppExtLength_2,
// previously DecodeBadDescriptorDimensionsLength:
TestImages.Gif.Issues.BadDescriptorWidth
};
// previously DecodeBadDescriptorDimensionsLength:
TestImages.Gif.Issues.BadDescriptorWidth
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Gif.Rings, (int)ImageMetaData.DefaultHorizontalResolution, (int)ImageMetaData.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch},
{ TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
{ TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
};
private static readonly Dictionary<string, int> BasicVerificationFrameCount =
new Dictionary<string, int>
{
[TestImages.Gif.Cheers] = 93,
[TestImages.Gif.Issues.BadDescriptorWidth] = 36,
};
new Dictionary<string, int>
{
[TestImages.Gif.Cheers] = 93,
[TestImages.Gif.Issues.BadDescriptorWidth] = 36,
};
public static readonly string[] BadAppExtFiles = { TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2 };
public static readonly string[] BadAppExtFiles =
{
TestImages.Gif.Issues.BadAppExtLength,
TestImages.Gif.Issues.BadAppExtLength_2
};
[Theory]
[WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)]
@ -59,6 +70,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
{
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
[Theory]
[WithFile(TestImages.Gif.Trans, TestPixelTypes)]
public void GifDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
@ -88,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider);
}
}
[Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
@ -169,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[InlineData(TestImages.Gif.Trans, 8)]
public void DetectPixelSize(string imagePath, int expectedPixelSize)
{
TestFile testFile = TestFile.Create(imagePath);
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);

36
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -17,6 +17,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.001F);
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Gif.Rings, (int)ImageMetaData.DefaultHorizontalResolution, (int)ImageMetaData.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch},
{ TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
{ TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
};
[Theory]
[WithTestPatternImages(100, 100, TestPixelTypes)]
public void EncodeGeneratedPatterns<TPixel>(TestImageProvider<TPixel> provider)
@ -43,6 +51,34 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var options = new GifEncoder()
{
IgnoreMetadata = false
};
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateImage())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetaData meta = output.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}
[Fact]
public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten()
{

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.MetaData;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.True(isJFif);
Assert.Equal(1, marker.MajorVersion);
Assert.Equal(1, marker.MinorVersion);
Assert.Equal(1, marker.DensityUnits);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits);
Assert.Equal(96, marker.XDensity);
Assert.Equal(96, marker.YDensity);
}

88
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs

@ -15,30 +15,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.MetaData;
public partial class JpegDecoderTests
{
// TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct.
// I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton)
public static readonly TheoryData<bool, string, int, bool, bool> MetaDataTestData =
new TheoryData<bool, string, int, bool, bool>
{
{ false, TestImages.Jpeg.Progressive.Progress, 24, false, false },
{ false, TestImages.Jpeg.Progressive.Fb, 24, false, true },
{ false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true },
{ false, TestImages.Jpeg.Baseline.Ycck, 32, true, true },
{ false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false },
{ false, TestImages.Jpeg.Baseline.Snake, 24, true, true },
{ false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false },
{ true, TestImages.Jpeg.Progressive.Progress, 24, false, false },
{ true, TestImages.Jpeg.Progressive.Fb, 24, false, true },
{ true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true },
{ true, TestImages.Jpeg.Baseline.Ycck, 32, true, true },
{ true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false },
{ true, TestImages.Jpeg.Baseline.Snake, 24, true, true },
{ true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false },
};
new TheoryData<bool, string, int, bool, bool>
{
{ false, TestImages.Jpeg.Progressive.Progress, 24, false, false },
{ false, TestImages.Jpeg.Progressive.Fb, 24, false, true },
{ false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true },
{ false, TestImages.Jpeg.Baseline.Ycck, 32, true, true },
{ false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false },
{ false, TestImages.Jpeg.Baseline.Snake, 24, true, true },
{ false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false },
{ true, TestImages.Jpeg.Progressive.Progress, 24, false, false },
{ true, TestImages.Jpeg.Progressive.Fb, 24, false, true },
{ true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true },
{ true, TestImages.Jpeg.Baseline.Ycck, 32, true, true },
{ true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false },
{ true, TestImages.Jpeg.Baseline.Snake, 24, true, true },
{ true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false },
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio},
{ TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch},
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
[Theory]
[MemberData(nameof(MetaDataTestData))]
@ -76,14 +85,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
iccProfilePresent);
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
{
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = useIdentify
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream)
: decoder.Decode<Rgba32>(Configuration.Default, stream);
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream)
: decoder.Decode<Rgba32>(Configuration.Default, stream);
test(imageInfo);
}
}
@ -141,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
});
}
[Theory]
[InlineData(false)]
[InlineData(true)]
@ -166,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]

65
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -3,6 +3,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -14,16 +15,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public class JpegEncoderTests
{
public static readonly TheoryData<JpegSubsample, int> BitsPerPixel_Quality =
new TheoryData<JpegSubsample, int>
{
{ JpegSubsample.Ratio420, 40 },
{ JpegSubsample.Ratio420, 60 },
{ JpegSubsample.Ratio420, 100 },
new TheoryData<JpegSubsample, int>
{
{ JpegSubsample.Ratio420, 40 },
{ JpegSubsample.Ratio420, 60 },
{ JpegSubsample.Ratio420, 100 },
{ JpegSubsample.Ratio444, 40 },
{ JpegSubsample.Ratio444, 60 },
{ JpegSubsample.Ratio444, 100 },
};
{ JpegSubsample.Ratio444, 40 },
{ JpegSubsample.Ratio444, 60 },
{ JpegSubsample.Ratio444, 100 },
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio},
{ TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch},
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
@ -82,10 +91,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
image.Mutate(c => c.MakeOpaque());
var encoder = new JpegEncoder()
{
Subsample = subsample,
Quality = quality
};
{
Subsample = subsample,
Quality = quality
};
string info = $"{subsample}-Q{quality}";
ImageComparer comparer = GetComparer(quality, subsample);
@ -93,7 +102,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png");
}
}
[Theory]
[InlineData(false)]
@ -104,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
IgnoreMetadata = ignoreMetaData
};
using (Image<Rgba32> input = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage())
{
using (var memStream = new MemoryStream())
@ -126,7 +134,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
}
[Fact]
public void Quality_0_And_1_Are_Identical()
{
@ -172,5 +180,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray());
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var options = new JpegEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateImage())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetaData meta = output.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}
}
}

11
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -77,8 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[Theory]
[InlineData((uint)PngChunkType.Gamma)] // gAMA
[InlineData((uint)PngChunkType.PaletteAlpha)] // tRNS
[InlineData(
(uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks.
[InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks.
//[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this
public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType)
{
@ -112,9 +111,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
private static void WriteChunk(MemoryStream memStream, string chunkName)
{
memStream.Write(new byte[] { 0, 0, 0, 1 }, 0, 4);
memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4);
memStream.Write(new byte[] { 0, 0, 0, 0, 0 }, 0, 5);
// Needs a minimum length of 9 for pHYs chunk.
memStream.Write(new byte[] { 0, 0, 0, 9 }, 0, 4);
memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); // 4 bytes chunk header
memStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 9); // 9 bytes of chunk data
memStream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Junk Crc
}
private static void WriteDataChunk(MemoryStream memStream)

45
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -8,6 +8,7 @@ using System.IO;
using System.Text;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -18,8 +19,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public partial class PngDecoderTests
{
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32;
public static readonly string[] CommonTestImages =
{
@ -67,6 +66,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
TestImages.Png.GrayTrns16BitInterlaced
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter},
{ TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
{ TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
};
[Theory]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
public void Decode<TPixel>(TestImageProvider<TPixel> provider)
@ -218,5 +225,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
{
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new PngDecoder();
IImageInfo image = decoder.Identify(Configuration.Default, stream);
ImageMetaData meta = image.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}

34
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -7,6 +7,7 @@ using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -61,6 +62,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
80, 100, 120, 230
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter},
{ TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio},
{ TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
};
[Theory]
[WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)]
@ -256,5 +265,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(expected, data);
}
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var options = new PngEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateImage())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
ImageMetaData meta = output.MetaData;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
}
}
}
}
}

41
tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Common.Helpers;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
{
public class UnitConverterHelperTests
{
[Fact]
public void InchToFromMeter()
{
const double expected = 96D;
double actual = UnitConverter.InchToMeter(expected);
actual = UnitConverter.MeterToInch(actual);
Assert.Equal(expected, actual, 15);
}
[Fact]
public void InchToFromCm()
{
const double expected = 96D;
double actual = UnitConverter.InchToCm(expected);
actual = UnitConverter.CmToInch(actual);
Assert.Equal(expected, actual, 15);
}
[Fact]
public void CmToFromMeter()
{
const double expected = 96D;
double actual = UnitConverter.CmToMeter(expected);
actual = UnitConverter.MeterToCm(actual);
Assert.Equal(expected, actual, 15);
}
}
}

10
tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs

@ -126,18 +126,18 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean)
[InlineData(AllocationOptions.None)]
[InlineData(AllocationOptions.Clean)]
public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options)
{
using (IBuffer<int> firstAlloc = this.MemoryAllocator.Allocate<int>(42))
{
firstAlloc.GetSpan().Fill(666);
}
using (IBuffer<int> secondAlloc = this.MemoryAllocator.Allocate<int>(42, clean))
using (IBuffer<int> secondAlloc = this.MemoryAllocator.Allocate<int>(42, options))
{
int expected = clean ? 0 : 666;
int expected = options == AllocationOptions.Clean ? 0 : 666;
Assert.Equal(expected, secondAlloc.GetSpan()[0]);
}
}

8
tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

@ -34,11 +34,11 @@ namespace SixLabors.ImageSharp.Tests.Memory
private class MockMemoryAllocator : MemoryAllocator
{
internal override IBuffer<T> Allocate<T>(int length, bool clear)
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options)
{
var array = new T[length + 42];
if (!clear)
if (options == AllocationOptions.None)
{
Span<byte> data = MemoryMarshal.Cast<T, byte>(array.AsSpan());
for (int i = 0; i < data.Length; i++)
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
return new BasicArrayBuffer<T>(array, length);
}
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options)
{
throw new NotImplementedException();
}
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
[Fact]
public void CreateClean()
{
using (Buffer2D<int> buffer = this.MemoryAllocator.Allocate2D<int>(42, 42, true))
using (Buffer2D<int> buffer = this.MemoryAllocator.Allocate2D<int>(42, 42, AllocationOptions.Clean))
{
Span<int> span = buffer.GetSpan();
for (int j = 0; j < span.Length; j++)

14
tests/ImageSharp.Tests/Memory/BufferTestSuite.cs

@ -124,12 +124,12 @@ namespace SixLabors.ImageSharp.Tests.Memory
this.TestCanAllocateCleanBuffer<CustomStruct>(desiredLength);
}
private IBuffer<T> Allocate<T>(int desiredLength, bool clean, bool managedByteBuffer)
private IBuffer<T> Allocate<T>(int desiredLength, AllocationOptions options, bool managedByteBuffer)
where T : struct
{
if (managedByteBuffer)
{
if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, clean) is IBuffer<T> buffer))
if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IBuffer<T> buffer))
{
throw new InvalidOperationException("typeof(T) != typeof(byte)");
}
@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
return buffer;
}
return this.MemoryAllocator.Allocate<T>(desiredLength, clean);
return this.MemoryAllocator.Allocate<T>(desiredLength, options);
}
private void TestCanAllocateCleanBuffer<T>(int desiredLength, bool testManagedByteBuffer = false)
@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
for (int i = 0; i < 10; i++)
{
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, true, testManagedByteBuffer))
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.Clean, testManagedByteBuffer))
{
Assert.True(buffer.GetSpan().SequenceEqual(expected));
}
@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
private void TestSpanPropertyIsAlwaysTheSame<T>(int desiredLength, bool testManagedByteBuffer = false)
where T : struct
{
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, false, testManagedByteBuffer))
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer))
{
ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan());
ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan());
@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
private void TestWriteAndReadElements<T>(int desiredLength, Func<int, T> getExpectedValue, bool testManagedByteBuffer = false)
where T : struct
{
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, false, testManagedByteBuffer))
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer))
{
T[] expectedVals = new T[buffer.Length()];
@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
var dummy = default(T);
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, false, testManagedByteBuffer))
using (IBuffer<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer))
{
Assert.ThrowsAny<Exception>(
() =>

68
tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Normalization
{
public class HistogramEqualizationTests
{
[Theory]
[InlineData(256)]
[InlineData(65536)]
public void HistogramEqualizationTest(int luminanceLevels)
{
// Arrange
byte[] pixels = new byte[]
{
52, 55, 61, 59, 70, 61, 76, 61,
62, 59, 55, 104, 94, 85, 59, 71,
63, 65, 66, 113, 144, 104, 63, 72,
64, 70, 70, 126, 154, 109, 71, 69,
67, 73, 68, 106, 122, 88, 68, 68,
68, 79, 60, 79, 77, 66, 58, 75,
69, 85, 64, 58, 55, 61, 65, 83,
70, 87, 69, 68, 65, 73, 78, 90
};
var image = new Image<Rgba32>(8, 8);
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
byte luminance = pixels[y * 8 + x];
image[x, y] = new Rgba32(luminance, luminance, luminance);
}
}
byte[] expected = new byte[]
{
0, 12, 53, 32, 146, 53, 174, 53,
57, 32, 12, 227, 219, 202, 32, 154,
65, 85, 93, 239, 251, 227, 65, 158,
73, 146, 146, 247, 255, 235, 154, 130,
97, 166, 117, 231, 243, 210, 117, 117,
117, 190, 36, 190, 178, 93, 20, 170,
130, 202, 73, 20, 12, 53, 85, 194,
146, 206, 130, 117, 85, 166, 182, 215
};
// Act
image.Mutate(x => x.HistogramEqualization(luminanceLevels));
// Assert
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
Rgba32 actual = image[x, y];
Assert.Equal(expected[y * 8 + x], actual.R);
Assert.Equal(expected[y * 8 + x], actual.G);
Assert.Equal(expected[y * 8 + x], actual.B);
}
}
}
}
}

22
tests/ImageSharp.Tests/TestImages.cs

@ -65,6 +65,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png";
public const string Banner8Index = "Png/banner8-index.png";
public const string Ratio1x4 = "Png/ratio-1x4.png";
public const string Ratio4x1 = "Png/ratio-4x1.png";
public static class Bad
{
// Odd chunk lengths
@ -79,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests
Powerpoint, SplashInterlaced, Interlaced,
Filter0, Filter1, Filter2, Filter3, Filter4,
FilterVar, VimImage1, VimImage2, VersioningImage1,
VersioningImage2
VersioningImage2, Ratio4x1, Ratio1x4
};
}
@ -124,13 +127,14 @@ namespace SixLabors.ImageSharp.Tests
public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg";
public const string Testorig420 = "Jpg/baseline/testorig.jpg";
public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg";
public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg";
public static readonly string[] All =
{
Cmyk, Ycck, Exif, Floorplan,
Calliphora, Turtle, GammaDalaiLamaGray,
Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444,
};
{
Cmyk, Ycck, Exif, Floorplan,
Calliphora, Turtle, GammaDalaiLamaGray,
Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1
};
}
public static class Issues
@ -176,15 +180,17 @@ namespace SixLabors.ImageSharp.Tests
public const string Cheers = "Gif/cheers.gif";
public const string Trans = "Gif/trans.gif";
public const string Kumin = "Gif/kumin.gif";
public const string Ratio4x1 = "Gif/base_4x1.gif";
public const string Ratio1x4 = "Gif/base_1x4.gif";
public class Issues
public static class Issues
{
public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif";
public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif";
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif";
}
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin };
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Ratio4x1, Ratio1x4 };
}
}
}

3
tests/Images/Input/Gif/base_1x4.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56e2409223f140145db2ba405f5562451dc0fa9d4274830fb02bd78d42552162
size 1620

3
tests/Images/Input/Gif/base_4x1.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24fd7e9dd3c6516ddab7336a30efc5901754b6d43a9d989c6fbb3e06d8944c80
size 1620

3
tests/Images/Input/Jpg/baseline/ratio-1x1.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1e9410c16bc61f08bbc18ae0e0b13181c9c4f57c66e82a7c6e593a57f40756d7
size 34674

3
tests/Images/Input/Png/ratio-1x4.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:438018f19c85e582cb586ac7cca2220008ecb7fc70ce50e8f2a76b494c128a20
size 404

3
tests/Images/Input/Png/ratio-4x1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a068eaf1f7040490e08eda3259befb6689849dd0ff8bb4cc03c705d117cb2b9f
size 344
Loading…
Cancel
Save