Browse Source

Merge branch 'master' into icc-color-conversion

pull/1567/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
8b96d37164
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 47
      README.md
  2. 2
      shared-infrastructure
  3. 77
      src/ImageSharp/Common/Helpers/Numerics.cs
  4. 14
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  5. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  6. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  7. 187
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  8. 6
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  9. 78
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  10. 118
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  11. 49
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  12. 55
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  13. 76
      src/ImageSharp/Image.WrapMemory.cs
  14. 54
      src/ImageSharp/Memory/ByteMemoryOwner{T}.cs
  15. 75
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  16. 313
      tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs
  17. 229
      tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs
  18. 69
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

47
README.md

@ -1,4 +1,4 @@
<h1 align="center">
<h1 align="center">
<img src="https://github.com/SixLabors/Branding/raw/master/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/>
<br/>
@ -26,9 +26,16 @@ Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standa
## License
- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
- An alternative Commercial License can be purchased for projects and applications requiring support.
- An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
Please visit https://sixlabors.com/pricing for details.
## Support Six Labors
Support the efforts of the development of the Six Labors projects.
- [Purchase a Commercial Support License :heart:](https://sixlabors.com/pricing/)
- [Become a sponsor via GitHub Sponsors :heart:]( https://github.com/sponsors/SixLabors)
- [Become a sponsor via Open Collective :heart:](https://opencollective.com/sixlabors)
## Documentation
- [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started.
@ -57,7 +64,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!)
- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed
- Make sure you have [the .NET Core 3.1 SDK](https://www.microsoft.com/net/core#windows) installed
- Make sure you have [the .NET 5 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
@ -96,40 +103,6 @@ Please... Spread the word, contribute algorithms, submit performance improvement
- [Scott Williams](https://github.com/tocsoft)
- [Brian Popow](https://github.com/brianpopow)
## Sponsor Six Labors
Support the efforts of the development of the Six Labors projects. [[Become a sponsor :heart:](https://opencollective.com/sixlabors#sponsor)]
### Platinum Sponsors
Become a platinum sponsor with a monthly donation of $2000 (providing 32 hours of maintenance and development) and get 2 hours of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (large) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/avatar.svg?avatarHeight=192"></a>
### Gold Sponsors
Become a gold sponsor with a monthly donation of $1000 (providing 16 hours of maintenance and development) and get 1 hour of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (medium) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/avatar.svg?avatarHeight=156"></a>
### Silver Sponsors
Become a silver sponsor with a monthly donation of $500 (providing 8 hours of maintenance and development) and get your logo (medium) on our README on GitHub and the product pages of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/avatar.svg?avatarHeight=128"></a>
### Bronze Sponsors
Become a bronze sponsor with a monthly donation of $100 and get your logo (small) on our README on GitHub.
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/avatar.svg?avatarHeight=96"></a>

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 41fff7bf7ddb1d118898db1ddba43b95ba6ed0bb
Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506

77
src/ImageSharp/Common/Helpers/Numerics.cs

@ -748,5 +748,82 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1;
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Accumulates 8-bit integers into <paramref name="accumulator"/> by
/// widening them to 32-bit integers and performing four additions.
/// </summary>
/// <remarks>
/// <code>byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)</code>
/// is widened and added onto <paramref name="accumulator"/> as such:
/// <code>
/// accumulator += i32(1, 2, 3, 4);
/// accumulator += i32(5, 6, 7, 8);
/// accumulator += i32(9, 10, 11, 12);
/// accumulator += i32(13, 14, 15, 16);
/// </code>
/// </remarks>
/// <param name="accumulator">The accumulator destination.</param>
/// <param name="values">The values to accumulate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Accumulate(ref Vector<uint> accumulator, Vector<byte> values)
{
Vector.Widen(values, out Vector<ushort> shortLow, out Vector<ushort> shortHigh);
Vector.Widen(shortLow, out Vector<uint> intLow, out Vector<uint> intHigh);
accumulator += intLow;
accumulator += intHigh;
Vector.Widen(shortHigh, out intLow, out intHigh);
accumulator += intLow;
accumulator += intHigh;
}
/// <summary>
/// Reduces elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of all elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReduceSum(Vector128<int> accumulator)
{
if (Ssse3.IsSupported)
{
Vector128<int> hadd = Ssse3.HorizontalAdd(accumulator, accumulator);
Vector128<int> swapped = Sse2.Shuffle(hadd, 0x1);
Vector128<int> tmp = Sse2.Add(hadd, swapped);
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(tmp);
}
else
{
int sum = 0;
for (int i = 0; i < Vector128<int>.Count; i++)
{
sum += accumulator.GetElement(i);
}
return sum;
}
}
/// <summary>
/// Reduces even elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of even elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EvenReduceSum(Vector256<int> accumulator)
{
Vector128<int> vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(vsum);
}
#endif
}
}

14
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Bmp
@ -8,6 +8,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public enum BmpBitsPerPixel : short
{
/// <summary>
/// 1 bit per pixel.
/// </summary>
Pixel1 = 1,
/// <summary>
/// 4 bits per pixel.
/// </summary>
Pixel4 = 4,
/// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary>
@ -28,4 +38,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
Pixel32 = 32
}
}
}

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

@ -1303,15 +1303,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
short bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
// We can only encode at these bit rates so far (1 bit and 4 bit are still missing).
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
{
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
/// <summary>

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

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets or sets the quantizer for reducing the color count for 8-Bit images.
/// Defaults to OctreeQuantizer.
/// Defaults to Wu Quantizer.
/// </summary>
public IQuantizer Quantizer { get; set; }

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

@ -51,6 +51,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private const int ColorPaletteSize8Bit = 1024;
/// <summary>
/// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize4Bit = 64;
/// <summary>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize1Bit = 8;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
@ -74,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private readonly bool writeV4Header;
/// <summary>
/// The quantizer for reducing the color count for 8-Bit images.
/// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
/// </summary>
private readonly IQuantizer quantizer;
@ -88,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.quantizer = options.Quantizer ?? KnownQuantizers.Wu;
}
/// <summary>
@ -107,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
BmpMetadata bmpMetadata = metadata.GetBmpMetadata();
this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel;
this.bitsPerPixel ??= bmpMetadata.BitsPerPixel;
short bpp = (short)this.bitsPerPixel;
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
@ -166,7 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields;
}
int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0;
int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
{
colorPaletteSize = ColorPaletteSize1Bit;
}
var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap,
@ -224,6 +246,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image);
break;
case BmpBitsPerPixel.Pixel4:
this.Write4BitColor(stream, image);
break;
case BmpBitsPerPixel.Pixel1:
this.Write1BitColor(stream, image);
break;
}
}
@ -308,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -332,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -344,16 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
var quantizedColorBytes = quantizedColors.Length * 4;
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes)));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
for (int y = image.Height - 1; y >= 0; y--)
{
@ -368,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -404,5 +426,136 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
}
/// <summary>
/// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 16
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.GetPixelRowSpan(0);
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
pixelRowSpan = quantized.GetPixelRowSpan(y);
int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1;
for (int i = 0; i < endIdx; i += 2)
{
stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1]));
}
if (pixelRowSpan.Length % 2 != 0)
{
stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0));
}
for (int i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 2
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> quantizedPixelRow = quantized.GetPixelRowSpan(0);
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
quantizedPixelRow = quantized.GetPixelRowSpan(y);
int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8;
for (int i = 0; i < endIdx; i += 8)
{
Write1BitPalette(stream, i, i + 8, quantizedPixelRow);
}
if (quantizedPixelRow.Length % 8 != 0)
{
int startIdx = quantizedPixelRow.Length - 7;
endIdx = quantizedPixelRow.Length;
Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow);
}
for (int i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes the color palette to the stream. The color palette has 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="quantizedColorPalette">The color palette from the quantized image.</param>
/// <param name="colorPalette">A temporary byte span to write the color palette to.</param>
private void WriteColorPalette<TPixel>(Stream stream, ReadOnlySpan<TPixel> quantizedColorPalette, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
int quantizedColorBytes = quantizedColorPalette.Length * 4;
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes)));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
}
/// <summary>
/// Writes a 1-bit palette.
/// </summary>
/// <param name="stream">The stream to write the palette to.</param>
/// <param name="startIdx">The start index.</param>
/// <param name="endIdx">The end index.</param>
/// <param name="quantizedPixelRow">A quantized pixel row.</param>
private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan<byte> quantizedPixelRow)
{
int shift = 7;
byte indices = 0;
for (int j = startIdx; j < endIdx; j++)
{
indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift));
shift--;
}
stream.WriteByte(indices);
}
}
}

6
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bool SupportTransparency { get; }
/// <summary>
/// Gets the quantizer for reducing the color count for 8-Bit images.
/// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images.
/// </summary>
IQuantizer Quantizer { get; }
}
}
}

78
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -5,6 +5,11 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -79,6 +84,79 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
Vector256<byte> allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
for (int xLeft = x - bytesPerPixel; x + Vector256<byte>.Count <= scanline.Length; xLeft += Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector256<byte> avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet);
Vector256<byte> res = Avx2.Subtract(scan, avg);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Sse2.IsSupported)
{
Vector128<sbyte> zero8 = Vector128<sbyte>.Zero;
Vector128<short> zero16 = Vector128<short>.Zero;
Vector128<int> sumAccumulator = Vector128<int>.Zero;
Vector128<byte> allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
for (int xLeft = x - bytesPerPixel; x + Vector128<byte>.Count <= scanline.Length; xLeft += Vector128<byte>.Count)
{
Vector128<byte> scan = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector128<byte> left = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector128<byte> above = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector128<byte> avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet);
Vector128<byte> res = Sse2.Subtract(scan, avg);
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector128<byte>.Count;
Vector128<sbyte> absRes;
if (Ssse3.IsSupported)
{
absRes = Ssse3.Abs(res.AsSByte()).AsSByte();
}
else
{
Vector128<sbyte> mask = Sse2.CompareGreaterThan(res.AsSByte(), zero8);
mask = Sse2.Xor(mask, allBitsSet.AsSByte());
absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask);
}
Vector128<short> loRes16 = Sse2.UnpackLow(absRes, zero8).AsInt16();
Vector128<short> hiRes16 = Sse2.UnpackHigh(absRes, zero8).AsInt16();
Vector128<int> loRes32 = Sse2.UnpackLow(loRes16, zero16).AsInt32();
Vector128<int> hiRes32 = Sse2.UnpackHigh(loRes16, zero16).AsInt32();
sumAccumulator = Sse2.Add(sumAccumulator, loRes32);
sumAccumulator = Sse2.Add(sumAccumulator, hiRes32);
loRes32 = Sse2.UnpackLow(hiRes16, zero16).AsInt32();
hiRes32 = Sse2.UnpackHigh(hiRes16, zero16).AsInt32();
sumAccumulator = Sse2.Add(sumAccumulator, loRes32);
sumAccumulator = Sse2.Add(sumAccumulator, hiRes32);
}
sum += Numerics.ReduceSum(sumAccumulator);
}
#endif
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);

118
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -2,9 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -82,6 +88,53 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector256<byte>.Count <= scanline.Length; xLeft += Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector256<byte> upperLeft = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, xLeft));
Vector256<byte> res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft));
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> sumAccumulator = Vector<uint>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector<byte>.Count <= scanline.Length; xLeft += Vector<byte>.Count)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> left = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector<byte> upperLeft = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref prevBaseRef, xLeft));
Vector<byte> res = scan - PaethPredictor(left, above, upperLeft);
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector<byte>.Count;
Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)sumAccumulator[i];
}
}
#endif
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
@ -127,5 +180,70 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
return upperLeft;
}
#if SUPPORTS_RUNTIME_INTRINSICS
private static Vector256<byte> PaethPredictor(Vector256<byte> left, Vector256<byte> above, Vector256<byte> upleft)
{
Vector256<byte> zero = Vector256<byte>.Zero;
// Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left)
// to pa = abs(above - upleft). Same deal for pb.
// Using saturated subtraction, if the result is negative, the output is zero.
// If we subtract in both directions and `or` the results, only one can be
// non-zero, so we end up with the absolute value.
Vector256<byte> sac = Avx2.SubtractSaturate(above, upleft);
Vector256<byte> sbc = Avx2.SubtractSaturate(left, upleft);
Vector256<byte> pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac);
Vector256<byte> pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc);
// pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft).
// We've already calculated left - upleft and above - upleft in `sac` and `sbc`.
// If they are both negative or both positive, the absolute value of their
// sum can't possibly be less than `pa` or `pb`, so we'll never use the value.
// We make a mask that sets the value to 255 if they either both got
// saturated to zero or both didn't. Then we calculate the absolute value
// of their difference using saturated subtract and `or`, same as before,
// keeping the value only where the mask isn't set.
Vector256<byte> pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero));
Vector256<byte> pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb)));
// Finally, blend the values together. We start with `upleft` and overwrite on
// tied values so that the `left`, `above`, `upleft` precedence is preserved.
Vector256<byte> minbc = Avx2.Min(pc, pb);
Vector256<byte> resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb));
return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa));
}
private static Vector<byte> PaethPredictor(Vector<byte> left, Vector<byte> above, Vector<byte> upperLeft)
{
Vector.Widen(left, out Vector<ushort> a1, out Vector<ushort> a2);
Vector.Widen(above, out Vector<ushort> b1, out Vector<ushort> b2);
Vector.Widen(upperLeft, out Vector<ushort> c1, out Vector<ushort> c2);
Vector<short> p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1));
Vector<short> p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2));
return Vector.AsVectorByte(Vector.Narrow(p1, p2));
}
private static Vector<short> PaethPredictor(Vector<short> left, Vector<short> above, Vector<short> upperLeft)
{
Vector<short> p = left + above - upperLeft;
var pa = Vector.Abs(p - left);
var pb = Vector.Abs(p - above);
var pc = Vector.Abs(p - upperLeft);
var pa_pb = Vector.LessThanOrEqual(pa, pb);
var pa_pc = Vector.LessThanOrEqual(pa, pc);
var pb_pc = Vector.LessThanOrEqual(pb, pc);
return Vector.ConditionalSelect(
condition: Vector.BitwiseAnd(pa_pb, pa_pc),
left: left,
right: Vector.ConditionalSelect(
condition: pb_pc,
left: above,
right: upperLeft));
}
#endif
}
}

49
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -2,9 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -64,6 +70,49 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector256<byte>.Count <= scanline.Length; xLeft += Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> prev = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> res = Avx2.Subtract(scan, prev);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> sumAccumulator = Vector<uint>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector<byte>.Count <= scanline.Length; xLeft += Vector<byte>.Count)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> prev = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector<byte> res = scan - prev;
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector<byte>.Count;
Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)sumAccumulator[i];
}
}
#endif
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);

55
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -1,10 +1,16 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -57,7 +63,52 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
// Up(x) = Raw(x) - Prior(x)
resultBaseRef = 2;
for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
int x = 0;
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
for (; x + Vector256<byte>.Count <= scanline.Length;)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector256<byte> res = Avx2.Subtract(scan, above);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> sumAccumulator = Vector<uint>.Zero;
for (; x + Vector<byte>.Count <= scanline.Length;)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector<byte> res = scan - above;
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector<byte>.Count;
Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)sumAccumulator[i];
}
}
#endif
for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);

76
src/ImageSharp/Image.WrapMemory.cs

@ -303,6 +303,82 @@ namespace SixLabors.ImageSharp
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(Configuration.Default, byteMemory, width, height);
/// <summary>
/// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="byteMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="byteMemoryOwner"/>.
/// It will be disposed together with the result image.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="byteMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/></param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The metadata is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
IMemoryOwner<byte> byteMemoryOwner,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
var pixelMemoryOwner = new ByteMemoryOwner<TPixel>(byteMemoryOwner);
Guard.IsTrue(pixelMemoryOwner.Memory.Length >= (long)width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size");
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
}
/// <summary>
/// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="byteMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="byteMemoryOwner"/>.
/// It will be disposed together with the result image.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="byteMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
IMemoryOwner<byte> byteMemoryOwner,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(configuration, byteMemoryOwner, width, height, new ImageMetadata());
/// <summary>
/// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="byteMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="byteMemoryOwner"/>.
/// It will be disposed together with the result image.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="byteMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
public static Image<TPixel> WrapMemory<TPixel>(
IMemoryOwner<byte> byteMemoryOwner,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(Configuration.Default, byteMemoryOwner, width, height);
/// <summary>
/// <para>
/// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as

54
src/ImageSharp/Memory/ByteMemoryOwner{T}.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// A custom <see cref="IMemoryOwner{T}"/> that can wrap <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instances
/// and cast them to be <see cref="IMemoryOwner{T}"/> for any arbitrary unmanaged <typeparamref name="T"/> value type.
/// </summary>
/// <typeparam name="T">The value type to use when casting the wrapped <see cref="IMemoryOwner{T}"/> instance.</typeparam>
internal sealed class ByteMemoryOwner<T> : IMemoryOwner<T>
where T : unmanaged
{
private readonly IMemoryOwner<byte> memoryOwner;
private readonly ByteMemoryManager<T> memoryManager;
private bool disposedValue;
/// <summary>
/// Initializes a new instance of the <see cref="ByteMemoryOwner{T}"/> class.
/// </summary>
/// <param name="memoryOwner">The <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance to wrap.</param>
public ByteMemoryOwner(IMemoryOwner<byte> memoryOwner)
{
this.memoryOwner = memoryOwner;
this.memoryManager = new ByteMemoryManager<T>(memoryOwner.Memory);
}
/// <inheritdoc/>
public Memory<T> Memory => this.memoryManager.Memory;
private void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
this.memoryOwner.Dispose();
}
this.disposedValue = true;
}
}
/// <inheritdoc/>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: true);
}
}
}

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

@ -13,7 +13,6 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
using Xunit.Abstractions;
using static SixLabors.ImageSharp.Tests.TestImages.Bmp;
@ -41,14 +40,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public static readonly TheoryData<string, BmpBitsPerPixel> BmpBitsPerPixelFiles =
new TheoryData<string, BmpBitsPerPixel>
{
{ Bit1, BmpBitsPerPixel.Pixel1 },
{ Bit4, BmpBitsPerPixel.Pixel4 },
{ Bit8, BmpBitsPerPixel.Pixel8 },
{ Rgb16, BmpBitsPerPixel.Pixel16 },
{ Car, BmpBitsPerPixel.Pixel24 },
{ Bit32Rgb, BmpBitsPerPixel.Pixel32 }
};
public BmpEncoderTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
@ -175,6 +174,62 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
bitsPerPixel,
supportTransparency: false);
[Theory]
[WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)]
public void Encode_4Bit_WithV3Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows.
if (TestEnvironment.IsWindows)
{
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
}
}
[Theory]
[WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)]
public void Encode_4Bit_WithV4Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows.
if (TestEnvironment.IsWindows)
{
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
}
}
[Theory]
[WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
public void Encode_1Bit_WithV3Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows.
if (TestEnvironment.IsWindows)
{
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
}
}
[Theory]
[WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
public void Encode_1Bit_WithV4Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows.
if (TestEnvironment.IsWindows)
{
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
}
}
[Theory]
[WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)]
public void Encode_8BitGray_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
@ -271,7 +326,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
private static void TestBmpEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel,
bool supportTransparency = true,
bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header.
IQuantizer quantizer = null,
ImageComparer customComparer = null)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -283,7 +339,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
image.Mutate(c => c.MakeOpaque());
}
var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency };
var encoder = new BmpEncoder
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Wu
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer);

313
tests/ImageSharp.Tests/Formats/Png/PngFilterTests.cs

@ -0,0 +1,313 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Uncomment this to turn unit tests into benchmarks:
// #define BENCHMARKING
using System;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Tests.Formats.Png.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
[Trait("Format", "Png")]
public partial class PngFilterTests : MeasureFixture
{
#if BENCHMARKING
public const int Times = 1000000;
#else
public const int Times = 1;
#endif
public PngFilterTests(ITestOutputHelper output)
: base(output)
{
}
public const int Size = 64;
[Fact]
public void Average()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void AverageSse2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3);
}
[Fact]
public void AverageSsse3()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Fact]
public void AverageAvx2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Average, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void Paeth()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Paeth, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void PaethAvx2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Paeth, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void PaethVector()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Paeth, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Fact]
public void Up()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Up, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void UpAvx2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Up, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void UpVector()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Up, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Fact]
public void Sub()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Sub, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
}
[Fact]
public void SubAvx2()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Sub, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll);
}
[Fact]
public void SubVector()
{
static void RunTest()
{
var data = new TestData(PngFilterMethod.Sub, Size);
data.TestFilter();
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
public class TestData
{
private readonly PngFilterMethod filter;
private readonly int bpp;
private readonly byte[] previousScanline;
private readonly byte[] scanline;
private readonly byte[] expectedResult;
private readonly int expectedSum;
private readonly byte[] resultBuffer;
public TestData(PngFilterMethod filter, int size, int bpp = 4)
{
this.filter = filter;
this.bpp = bpp;
this.previousScanline = new byte[size * size * bpp];
this.scanline = new byte[size * size * bpp];
this.expectedResult = new byte[1 + (size * size * bpp)];
this.resultBuffer = new byte[1 + (size * size * bpp)];
var rng = new Random(12345678);
byte[] tmp = new byte[6];
for (int i = 0; i < this.previousScanline.Length; i += bpp)
{
rng.NextBytes(tmp);
this.previousScanline[i + 0] = tmp[0];
this.previousScanline[i + 1] = tmp[1];
this.previousScanline[i + 2] = tmp[2];
this.previousScanline[i + 3] = 255;
this.scanline[i + 0] = tmp[3];
this.scanline[i + 1] = tmp[4];
this.scanline[i + 2] = tmp[5];
this.scanline[i + 3] = 255;
}
switch (this.filter)
{
case PngFilterMethod.Sub:
ReferenceImplementations.EncodeSubFilter(
this.scanline, this.expectedResult, this.bpp, out this.expectedSum);
break;
case PngFilterMethod.Up:
ReferenceImplementations.EncodeUpFilter(
this.previousScanline, this.scanline, this.expectedResult, out this.expectedSum);
break;
case PngFilterMethod.Average:
ReferenceImplementations.EncodeAverageFilter(
this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum);
break;
case PngFilterMethod.Paeth:
ReferenceImplementations.EncodePaethFilter(
this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum);
break;
case PngFilterMethod.None:
case PngFilterMethod.Adaptive:
default:
throw new InvalidOperationException();
}
}
public void TestFilter()
{
int sum;
switch (this.filter)
{
case PngFilterMethod.Sub:
SubFilter.Encode(this.scanline, this.resultBuffer, this.bpp, out sum);
break;
case PngFilterMethod.Up:
UpFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, out sum);
break;
case PngFilterMethod.Average:
AverageFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum);
break;
case PngFilterMethod.Paeth:
PaethFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum);
break;
case PngFilterMethod.None:
case PngFilterMethod.Adaptive:
default:
throw new InvalidOperationException();
}
Assert.Equal(this.expectedSum, sum);
Assert.Equal(this.expectedResult, this.resultBuffer);
}
}
}
}

229
tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs

@ -0,0 +1,229 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Png.Utils
{
/// <summary>
/// This class contains reference implementations to produce verification data for unit tests
/// </summary>
internal static partial class ReferenceImplementations
{
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodePaethFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
resultBaseRef = 4;
int x = 0;
for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - PaethPredictor(0, above, 0));
sum += Numerics.Abs(unchecked((sbyte)res));
}
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref prevBaseRef, x);
byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - PaethPredictor(left, above, upperLeft));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 4;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeSubFilter(Span<byte> scanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Sub(x) = Raw(x) - Raw(x-bpp)
resultBaseRef = 1;
int x = 0;
for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = scan;
sum += Numerics.Abs(unchecked((sbyte)res));
}
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte prev = Unsafe.Add(ref scanBaseRef, xLeft);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - prev);
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 1;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeUpFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Up(x) = Raw(x) - Prior(x)
resultBaseRef = 2;
int x = 0;
for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - above);
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 2;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeAverageFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
ref byte resultBaseRef = ref MemoryMarshal.GetReference(result);
sum = 0;
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
resultBaseRef = 3;
int x = 0;
for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - (above >> 1));
sum += Numerics.Abs(unchecked((sbyte)res));
}
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, xLeft);
byte above = Unsafe.Add(ref prevBaseRef, x);
++x;
ref byte res = ref Unsafe.Add(ref resultBaseRef, x);
res = (byte)(scan - Average(left, above));
sum += Numerics.Abs(unchecked((sbyte)res));
}
sum -= 3;
}
/// <summary>
/// Calculates the average value of two bytes
/// </summary>
/// <param name="left">The left byte</param>
/// <param name="above">The above byte</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Average(byte left, byte above) => (left + above) >> 1;
/// <summary>
/// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses
/// as predictor the neighboring pixel closest to the computed value.
/// </summary>
/// <param name="left">The left neighbor pixel.</param>
/// <param name="above">The above neighbor pixel.</param>
/// <param name="upperLeft">The upper left neighbor pixel.</param>
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte PaethPredictor(byte left, byte above, byte upperLeft)
{
int p = left + above - upperLeft;
int pa = Numerics.Abs(p - left);
int pb = Numerics.Abs(p - above);
int pc = Numerics.Abs(p - upperLeft);
if (pa <= pb && pa <= pc)
{
return left;
}
if (pb <= pc)
{
return above;
}
return upperLeft;
}
}
}

69
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -380,11 +380,11 @@ namespace SixLabors.ImageSharp.Tests
private class TestMemoryOwner<T> : IMemoryOwner<T>
{
public bool Disposed { get; private set; }
public Memory<T> Memory { get; set; }
public void Dispose()
{
}
public void Dispose() => this.Disposed = true;
}
[Theory]
@ -410,7 +410,68 @@ namespace SixLabors.ImageSharp.Tests
var array = new Rgba32[size];
var memory = new TestMemoryOwner<Rgba32> { Memory = array };
Image.WrapMemory(memory, height, width);
using (var img = Image.WrapMemory<Rgba32>(memory, width, height))
{
Assert.Equal(width, img.Width);
Assert.Equal(height, img.Height);
for (int i = 0; i < height; ++i)
{
var arrayIndex = width * i;
Span<Rgba32> rowSpan = img.GetPixelRowSpan(i);
ref Rgba32 r0 = ref rowSpan[0];
ref Rgba32 r1 = ref array[arrayIndex];
Assert.True(Unsafe.AreSame(ref r0, ref r1));
}
}
Assert.True(memory.Disposed);
}
[Theory]
[InlineData(0, 5, 5)]
[InlineData(20, 5, 5)]
[InlineData(1023, 32, 32)]
public void WrapMemory_IMemoryOwnerOfByte_InvalidSize(int size, int height, int width)
{
var array = new byte[size * Unsafe.SizeOf<Rgba32>()];
var memory = new TestMemoryOwner<byte> { Memory = array };
Assert.Throws<ArgumentException>(() => Image.WrapMemory<Rgba32>(memory, height, width));
}
[Theory]
[InlineData(25, 5, 5)]
[InlineData(26, 5, 5)]
[InlineData(2, 1, 1)]
[InlineData(1024, 32, 32)]
[InlineData(2048, 32, 32)]
public void WrapMemory_IMemoryOwnerOfByte_ValidSize(int size, int height, int width)
{
var pixelSize = Unsafe.SizeOf<Rgba32>();
var array = new byte[size * pixelSize];
var memory = new TestMemoryOwner<byte> { Memory = array };
using (var img = Image.WrapMemory<Rgba32>(memory, width, height))
{
Assert.Equal(width, img.Width);
Assert.Equal(height, img.Height);
for (int i = 0; i < height; ++i)
{
var arrayIndex = pixelSize * width * i;
Span<Rgba32> rowSpan = img.GetPixelRowSpan(i);
ref Rgba32 r0 = ref rowSpan[0];
ref Rgba32 r1 = ref Unsafe.As<byte, Rgba32>(ref array[arrayIndex]);
Assert.True(Unsafe.AreSame(ref r0, ref r1));
}
}
Assert.True(memory.Disposed);
}
[Theory]

Loading…
Cancel
Save