Browse Source

Merge branch 'main' into fix-async-image-load

pull/2006/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
77e3986953
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 172
      src/ImageSharp/Compression/Zlib/Adler32.cs
  2. 22
      src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
  3. 5
      src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs
  4. 22
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  5. 18
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  6. 28
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  7. 5
      src/ImageSharp/Processing/ResizeOptions.cs
  8. 39
      tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
  9. 40
      tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs
  10. 2
      tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
  11. 10
      tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

172
src/ImageSharp/Compression/Zlib/Adler32.cs

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
@ -31,6 +32,8 @@ namespace SixLabors.ImageSharp.Compression.Zlib
#if SUPPORTS_RUNTIME_INTRINSICS
private const int MinBufferSize = 64;
private const int BlockSize = 1 << 5;
// The C# compiler emits this as a compile-time constant embedded in the PE file.
private static ReadOnlySpan<byte> Tap1Tap2 => new byte[]
{
@ -63,6 +66,11 @@ namespace SixLabors.ImageSharp.Compression.Zlib
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateAvx2(adler, buffer);
}
if (Ssse3.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateSse(adler, buffer);
@ -83,19 +91,15 @@ namespace SixLabors.ImageSharp.Compression.Zlib
uint s2 = (adler >> 16) & 0xFFFF;
// Process the data in blocks.
const int BLOCK_SIZE = 1 << 5;
uint length = (uint)buffer.Length;
uint blocks = length / BLOCK_SIZE;
length -= blocks * BLOCK_SIZE;
uint blocks = length / BlockSize;
length -= blocks * BlockSize;
int index = 0;
fixed (byte* bufferPtr = buffer)
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
fixed (byte* tapPtr = Tap1Tap2)
fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2))
{
index += (int)blocks * BLOCK_SIZE;
var localBufferPtr = bufferPtr;
byte* localBufferPtr = bufferPtr;
// _mm_setr_epi8 on x86
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
@ -105,7 +109,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
while (blocks > 0)
{
uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
uint n = NMAX / BlockSize; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
@ -138,7 +142,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
localBufferPtr += BLOCK_SIZE;
localBufferPtr += BlockSize;
}
while (--n > 0);
@ -164,45 +168,127 @@ namespace SixLabors.ImageSharp.Compression.Zlib
if (length > 0)
{
if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
return s1 | (s2 << 16);
}
}
}
if (s1 >= BASE)
{
s1 -= BASE;
}
// Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan<byte> buffer)
{
uint s1 = adler & 0xFFFF;
uint s2 = (adler >> 16) & 0xFFFF;
uint length = (uint)buffer.Length;
s2 %= BASE;
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
byte* localBufferPtr = bufferPtr;
Vector256<byte> zero = Vector256<byte>.Zero;
var dot3v = Vector256.Create((short)1);
var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
var vs1 = Vector256.CreateScalar(s1);
var vs2 = Vector256.CreateScalar(s2);
while (length >= 32)
{
int k = length < NMAX ? (int)length : (int)NMAX;
k -= k % 32;
length -= (uint)k;
Vector256<uint> vs10 = vs1;
Vector256<uint> vs3 = Vector256<uint>.Zero;
while (k >= 32)
{
// Load 32 input bytes.
Vector256<byte> block = Avx.LoadVector256(localBufferPtr);
// Sum of abs diff, resulting in 2 x int32's
Vector256<ushort> vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
vs1 = Avx2.Add(vs1, vs1sad.AsUInt32());
vs3 = Avx2.Add(vs3, vs10);
// sum 32 uint8s to 16 shorts.
Vector256<short> vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
// sum 16 shorts to 8 uint32s.
Vector256<int> vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
vs2 = Avx2.Add(vsum2.AsUInt32(), vs2);
vs10 = vs1;
localBufferPtr += BlockSize;
k -= 32;
}
return s1 | (s2 << 16);
// Defer the multiplication with 32 to outside of the loop.
vs3 = Avx2.ShiftLeftLogical(vs3, 5);
vs2 = Avx2.Add(vs2, vs3);
s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32());
s2 = (uint)Numerics.ReduceSum(vs2.AsInt32());
s1 %= BASE;
s2 %= BASE;
vs1 = Vector256.CreateScalar(s1);
vs2 = Vector256.CreateScalar(s2);
}
if (length > 0)
{
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
return s1 | (s2 << 16);
}
}
private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2)
{
if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
if (s1 >= BASE)
{
s1 -= BASE;
}
s2 %= BASE;
}
#endif

22
src/ImageSharp/Compression/Zlib/DeflaterEngine.cs

@ -483,18 +483,21 @@ namespace SixLabors.ImageSharp.Compression.Zlib
int niceLength = Math.Min(this.niceLength, this.lookahead);
int matchStrt = this.matchStart;
this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1);
int matchLength = this.matchLen;
matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1);
this.matchLen = matchLength;
if (scan + matchLength > scanMax)
if (scan > scanMax - matchLength)
{
return false;
}
int scanEndPosition = scan + matchLength;
byte* pinnedWindow = this.pinnedWindowPointer;
int scanStart = this.strstart;
byte scanEnd1 = pinnedWindow[scan + matchLength - 1];
byte scanEnd = pinnedWindow[scan + matchLength];
byte scanEnd1 = pinnedWindow[scanEndPosition - 1];
byte scanEnd = pinnedWindow[scanEndPosition];
// Do not waste too much time if we already have a good match:
if (matchLength >= this.goodLength)
@ -508,8 +511,9 @@ namespace SixLabors.ImageSharp.Compression.Zlib
match = curMatch;
scan = scanStart;
if (pinnedWindow[match + matchLength] != scanEnd
|| pinnedWindow[match + matchLength - 1] != scanEnd1
int matchEndPosition = match + matchLength;
if (pinnedWindow[matchEndPosition] != scanEnd
|| pinnedWindow[matchEndPosition - 1] != scanEnd1
|| pinnedWindow[match] != pinnedWindow[scan]
|| pinnedWindow[++match] != pinnedWindow[++scan])
{
@ -685,6 +689,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD;
while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
{
if (this.lookahead == 0)
@ -695,7 +700,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
if (this.strstart > windowLen)
{
// slide window, as FindLongestMatch needs this.
// This should only happen when flushing and the window
@ -766,6 +771,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD;
while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
{
if (this.lookahead == 0)
@ -783,7 +789,7 @@ namespace SixLabors.ImageSharp.Compression.Zlib
return false;
}
if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
if (this.strstart >= windowLen)
{
// slide window, as FindLongestMatch needs this.
// This should only happen when flushing and the window

5
src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
@ -34,9 +34,10 @@ namespace SixLabors.ImageSharp.Processing
Size = new Size(width, height),
Mode = ResizeMode.BoxPad,
Sampler = KnownResamplers.NearestNeighbor,
PadColor = color
};
return color.Equals(default) ? source.Resize(options) : source.Resize(options).BackgroundColor(color);
return source.Resize(options);
}
}
}

22
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -176,9 +176,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Rgba32 rgba = default;
color.ToRgba32(ref rgba);
int r = rgba.R >> (8 - IndexBits);
int g = rgba.G >> (8 - IndexBits);
int b = rgba.B >> (8 - IndexBits);
const int shift = 8 - IndexBits;
int r = rgba.R >> shift;
int g = rgba.G >> shift;
int b = rgba.B >> shift;
int a = rgba.A >> (8 - IndexAlphaBits);
ReadOnlySpan<byte> tagSpan = this.tagsOwner.GetSpan();
@ -413,6 +414,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Span<Moment> momentSpan = this.momentsOwner.GetSpan();
Span<Moment> volumeSpan = volume.GetSpan();
Span<Moment> areaSpan = area.GetSpan();
const int indexBits2 = IndexBits * 2;
const int indexAndAlphaBits = IndexBits + IndexAlphaBits;
const int indexBitsAndAlphaBits1 = IndexBits + IndexAlphaBits + 1;
int baseIndex = GetPaletteIndex(1, 0, 0, 0);
for (int r = 1; r < IndexCount; r++)
@ -421,9 +425,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// immediate outer loop. See https://github.com/dotnet/runtime/issues/61420
// To ensure the calculation doesn't happen repeatedly, hoist some of the calculations
// in the form of ind1* manually.
int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) +
(r << (IndexBits + IndexAlphaBits + 1)) +
(r << (IndexBits * 2)) +
int ind1R = (r << (indexBits2 + IndexAlphaBits)) +
(r << indexBitsAndAlphaBits1) +
(r << indexBits2) +
(r << (IndexBits + 1)) +
r;
@ -432,7 +436,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int g = 1; g < IndexCount; g++)
{
int ind1G = ind1R +
(g << (IndexBits + IndexAlphaBits)) +
(g << indexAndAlphaBits) +
(g << IndexBits) +
g;
int r_g = r + g;
@ -446,7 +450,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
b;
Moment line = default;
int bIndexAlphaOffset = b * IndexAlphaCount;
for (int a = 1; a < IndexAlphaCount; a++)
{
int ind1 = ind1B + a;
@ -455,7 +459,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
areaSpan[a] += line;
int inv = (b * IndexAlphaCount) + a;
int inv = bIndexAlphaOffset + a;
volumeSpan[inv] += areaSpan[a];
int ind2 = ind1 - baseIndex;

18
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

@ -21,19 +21,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);
this.Sampler = options.Sampler;
this.Options = options;
this.DestinationWidth = size.Width;
this.DestinationHeight = size.Height;
this.DestinationRectangle = rectangle;
this.Compand = options.Compand;
this.PremultiplyAlpha = options.PremultiplyAlpha;
}
/// <summary>
/// Gets the sampler to perform the resize operation.
/// </summary>
public IResampler Sampler { get; }
/// <summary>
/// Gets the destination width.
/// </summary>
@ -50,14 +43,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Rectangle DestinationRectangle { get; }
/// <summary>
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
/// </summary>
public bool Compand { get; }
/// <summary>
/// Gets a value indicating whether to premultiply the alpha (if it exists) during the resize operation.
/// Gets the resize options.
/// </summary>
public bool PremultiplyAlpha { get; }
public ResizeOptions Options { get; }
/// <inheritdoc />
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)

28
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -17,12 +16,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>, IResamplingTransformImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ResizeOptions options;
private readonly int destinationWidth;
private readonly int destinationHeight;
private readonly IResampler resampler;
private readonly Rectangle destinationRectangle;
private readonly bool compand;
private readonly bool premultiplyAlpha;
private Image<TPixel> destination;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
@ -31,13 +29,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.destinationWidth = definition.DestinationWidth;
this.destinationHeight = definition.DestinationHeight;
this.destinationRectangle = definition.DestinationRectangle;
this.resampler = definition.Sampler;
this.premultiplyAlpha = definition.PremultiplyAlpha;
this.compand = definition.Compand;
this.options = definition.Options;
this.resampler = definition.Options.Sampler;
}
/// <inheritdoc/>
protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight);
protected override Size GetDestinationSize() => new(this.destinationWidth, this.destinationHeight);
/// <inheritdoc/>
protected override void BeforeImageApply(Image<TPixel> destination)
@ -62,8 +59,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Image<TPixel> destination = this.destination;
Rectangle sourceRectangle = this.SourceRectangle;
Rectangle destinationRectangle = this.destinationRectangle;
bool compand = this.compand;
bool premultiplyAlpha = this.premultiplyAlpha;
bool compand = this.options.Compand;
bool premultiplyAlpha = this.options.PremultiplyAlpha;
bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad)
&& this.options.PadColor != default;
TPixel fillColor = this.options.PadColor.ToPixel<TPixel>();
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
@ -91,6 +91,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
if (shouldFill)
{
destinationFrame.Clear(fillColor);
}
ApplyNNResizeFrameTransform(
configuration,
sourceFrame,
@ -123,6 +128,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
if (shouldFill)
{
destinationFrame.Clear(fillColor);
}
ApplyResizeFrameTransform(
configuration,
sourceFrame,

5
src/ImageSharp/Processing/ResizeOptions.cs

@ -51,5 +51,10 @@ namespace SixLabors.ImageSharp.Processing
/// the alpha (if it exists) during the resize operation.
/// </summary>
public bool PremultiplyAlpha { get; set; } = true;
/// <summary>
/// Gets or sets the color to use as a background when padding an image.
/// </summary>
public Color PadColor { get; set; }
}
}

39
tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs

@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
@ -15,10 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void ReturnsCorrectWhenEmpty(uint input)
{
Assert.Equal(input, Adler32.Calculate(input, default));
}
public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default));
[Theory]
[InlineData(0)]
@ -28,24 +26,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length);
private static void CalculateAdlerAndCompareToReference(int length)
{
var data = GetBuffer(length);
// arrange
byte[] data = GetBuffer(length);
var adler = new SharpAdler32();
adler.Update(data);
long expected = adler.Value;
// act
long actual = Adler32.Calculate(data);
// assert
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
var data = new byte[length];
byte[] data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll);
[Fact]
public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
[Fact]
public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic);
private static void RunCalculateAdlerTest()
{
int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 };
for (int i = 0; i < testData.Length; i++)
{
CalculateAdlerAndCompareToReference(testData[i]);
}
}
#endif
}
}

40
tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs

@ -20,41 +20,37 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public void ImageShouldPad<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50));
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage();
image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50));
image.DebugSave(provider);
// Check pixels are empty
for (int y = 0; y < 25; y++)
// Check pixels are empty
for (int y = 0; y < 25; y++)
{
for (int x = 0; x < 25; x++)
{
for (int x = 0; x < 25; x++)
{
Assert.Equal(default, image[x, y]);
}
Assert.Equal(default, image[x, y]);
}
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)]
public void ImageShouldPadWithBackgroundColor<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var color = Color.Red;
Color color = Color.Red;
TPixel expected = color.ToPixel<TPixel>();
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color));
image.DebugSave(provider);
using Image<TPixel> image = provider.GetImage();
image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color));
image.DebugSave(provider);
// Check pixels are filled
for (int y = 0; y < 25; y++)
// Check pixels are filled
for (int y = 0; y < 25; y++)
{
for (int x = 0; x < 25; x++)
{
for (int x = 0; x < 25; x++)
{
Assert.Equal(expected, image[x, y]);
}
Assert.Equal(expected, image[x, y]);
}
}
}

2
tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(sampler, resizeProcessor.Options.Sampler);
}
}
}

10
tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(sampler, resizeProcessor.Options.Sampler);
}
[Fact]
@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
Assert.Equal(sampler, resizeProcessor.Options.Sampler);
Assert.Equal(compand, resizeProcessor.Options.Compand);
}
[Fact]
@ -78,8 +78,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
Assert.Equal(sampler, resizeProcessor.Options.Sampler);
Assert.Equal(compand, resizeProcessor.Options.Compand);
// Ensure options are not altered.
Assert.Equal(width, resizeOptions.Size.Width);

Loading…
Cancel
Save