mirror of https://github.com/SixLabors/ImageSharp
17 changed files with 712 additions and 364 deletions
@ -1,115 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Encapsulates a collection of <see cref="ImageFrame{T}"/> instances that make up an <see cref="Image{T}"/>.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
||||
public interface IImageFrameCollection<TPixel> : IEnumerable<ImageFrame<TPixel>> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Gets the number of frames.
|
|
||||
/// </summary>
|
|
||||
int Count { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the root frame.
|
|
||||
/// </summary>
|
|
||||
ImageFrame<TPixel> RootFrame { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the <see cref="ImageFrame{TPixel}"/> at the specified index.
|
|
||||
/// </summary>
|
|
||||
/// <value>
|
|
||||
/// The <see cref="ImageFrame{TPixel}"/>.
|
|
||||
/// </value>
|
|
||||
/// <param name="index">The index.</param>
|
|
||||
/// <returns>The <see cref="ImageFrame{TPixel}"/> at the specified index.</returns>
|
|
||||
ImageFrame<TPixel> this[int index] { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Creates an <see cref="Image{T}"/> with only the frame at the specified index
|
|
||||
/// with the same metadata as the original image.
|
|
||||
/// </summary>
|
|
||||
/// <param name="index">The zero-based index of the frame to clone.</param>
|
|
||||
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
|
|
||||
Image<TPixel> CloneFrame(int index); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Removes the frame at the specified index and creates a new image with only the removed frame
|
|
||||
/// with the same metadata as the original image.
|
|
||||
/// </summary>
|
|
||||
/// <param name="index">The zero-based index of the frame to export.</param>
|
|
||||
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
|
|
||||
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
|
|
||||
Image<TPixel> ExportFrame(int index); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Removes the frame at the specified index and frees all freeable resources associated with it.
|
|
||||
/// </summary>
|
|
||||
/// <param name="index">The zero-based index of the frame to remove.</param>
|
|
||||
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
|
|
||||
void RemoveFrame(int index); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Creates a new <seealso cref="ImageFrame{TPixel}"/> and appends it to the end of the collection.
|
|
||||
/// </summary>
|
|
||||
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
|
|
||||
ImageFrame<TPixel> CreateFrame(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Clones the <paramref name="source"/> frame and appends the clone to the end of the collection.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
|
|
||||
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
|
|
||||
ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the
|
|
||||
/// new frame at the end of the collection.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
|
|
||||
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
|
|
||||
ImageFrame<TPixel> AddFrame(TPixel[] source); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="IImageFrameCollection{TPixel}"/> at the specified <paramref name="index"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="index">The zero-based index to insert the frame at.</param>
|
|
||||
/// <param name="source">The <seealso cref="ImageFrame{TPixel}"/> to clone and insert into the <seealso cref="IImageFrameCollection{TPixel}"/>.</param>
|
|
||||
/// <exception cref="ArgumentException">Frame must have the same dimensions as the image.</exception>
|
|
||||
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
|
|
||||
ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="sourceIndex">The zero-based index of the frame to move.</param>
|
|
||||
/// <param name="destinationIndex">The index to move the frame to.</param>
|
|
||||
void MoveFrame(int sourceIndex, int destinationIndex); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="IImageFrameCollection{TPixel}"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to locate in the <seealso cref="IImageFrameCollection{TPixel}"/>.</param>
|
|
||||
/// <returns>The index of item if found in the list; otherwise, -1.</returns>
|
|
||||
int IndexOf(ImageFrame<TPixel> frame); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Determines whether the <seealso cref="IImageFrameCollection{TPixel}"/> contains the <paramref name="frame"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="frame">The frame.</param>
|
|
||||
/// <returns>
|
|
||||
/// <c>true</c> if the <seealso cref="IImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
|
|
||||
/// </returns>
|
|
||||
bool Contains(ImageFrame<TPixel> frame); |
|
||||
} |
|
||||
} |
|
||||
@ -1,92 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Drawing; |
|
||||
using SixLabors.Primitives; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Drawing |
|
||||
{ |
|
||||
using SixLabors.ImageSharp.Processing; |
|
||||
|
|
||||
public class BlendedShapes |
|
||||
{ |
|
||||
public static IEnumerable<object[]> modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))) |
|
||||
.Select(x => new object[] { x }); |
|
||||
|
|
||||
[Theory] |
|
||||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|
||||
public void DrawBlendedValues<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
using (var img = provider.GetImage()) |
|
||||
{ |
|
||||
var scaleX = (img.Width / 100); |
|
||||
var scaleY = (img.Height / 100); |
|
||||
img.Mutate(x => x |
|
||||
.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)) |
|
||||
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY) |
|
||||
)); |
|
||||
img.DebugSave(provider, new { mode }); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|
||||
public void DrawBlendedValues_transparent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
using (var img = provider.GetImage()) |
|
||||
{ |
|
||||
var scaleX = (img.Width / 100); |
|
||||
var scaleY = (img.Height / 100); |
|
||||
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|
||||
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); |
|
||||
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.Transparent, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
|
||||
img.DebugSave(provider, new { mode }); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|
||||
public void DrawBlendedValues_transparent50Percent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
using (var img = provider.GetImage()) |
|
||||
{ |
|
||||
var scaleX = (img.Width / 100); |
|
||||
var scaleY = (img.Height / 100); |
|
||||
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); |
|
||||
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); |
|
||||
var c = NamedColors<TPixel>.Red.ToVector4(); |
|
||||
c.W *= 0.5f; |
|
||||
TPixel pixel = default(TPixel); |
|
||||
pixel.PackFromVector4(c); |
|
||||
|
|
||||
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, pixel, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
|
||||
img.DebugSave(provider, new { mode }); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
[Theory] |
|
||||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|
||||
public void DrawBlendedValues_doldidEllips<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
using (var img = provider.GetImage()) |
|
||||
{ |
|
||||
var scaleX = (img.Width / 100); |
|
||||
var scaleY = (img.Height / 100); |
|
||||
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|
||||
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.Black, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
|
||||
img.DebugSave(provider, new { mode }); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,79 +1,164 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System.Numerics; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.ImageSharp.Processing; |
using SixLabors.ImageSharp.Processing; |
||||
using SixLabors.ImageSharp.Processing.Drawing; |
using SixLabors.ImageSharp.Processing.Drawing; |
||||
using SixLabors.ImageSharp.Processing.Overlays; |
using SixLabors.ImageSharp.Primitives; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
||||
|
using SixLabors.Shapes; |
||||
using Xunit; |
using Xunit; |
||||
|
// ReSharper disable InconsistentNaming
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Drawing |
namespace SixLabors.ImageSharp.Tests.Drawing |
||||
{ |
{ |
||||
public class FillSolidBrushTests : FileTestBase |
|
||||
|
|
||||
|
[GroupOutput("Drawing")] |
||||
|
public class FillSolidBrushTests |
||||
{ |
{ |
||||
[Fact] |
[Theory] |
||||
public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() |
[WithBlankImages(1, 1, PixelTypes.Rgba32)] |
||||
|
[WithBlankImages(7, 4, PixelTypes.Rgba32)] |
||||
|
[WithBlankImages(16, 7, PixelTypes.Rgba32)] |
||||
|
[WithBlankImages(33, 32, PixelTypes.Rgba32)] |
||||
|
[WithBlankImages(400, 500, PixelTypes.Rgba32)] |
||||
|
public void DoesNotDependOnSize<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); |
using (Image<TPixel> image = provider.GetImage()) |
||||
using (var image = new Image<Rgba32>(500, 500)) |
|
||||
{ |
{ |
||||
image.Mutate(x => x.Fill(Rgba32.HotPink)); |
TPixel color = NamedColors<TPixel>.HotPink; |
||||
image.Save($"{path}/DefaultBack.png"); |
image.Mutate(c => c.Fill(color)); |
||||
|
|
||||
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) |
|
||||
{ |
|
||||
Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); |
|
||||
|
|
||||
Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); |
image.DebugSave(provider, appendPixelTypeToFileName: false); |
||||
} |
image.ComparePixelBufferTo(color); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void ImageShouldBeFloodFilledWithColor() |
[WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] |
||||
|
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); |
using (Image<TPixel> image = provider.GetImage()) |
||||
using (var image = new Image<Rgba32>(500, 500)) |
|
||||
{ |
{ |
||||
image.Mutate(x => x |
TPixel color = NamedColors<TPixel>.HotPink; |
||||
.BackgroundColor(Rgba32.Blue) |
image.Mutate(c => c.Fill(color)); |
||||
.Fill(Rgba32.HotPink)); |
|
||||
image.Save($"{path}/Simple.png"); |
|
||||
|
|
||||
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) |
image.DebugSave(provider, appendSourceFileOrDescription: false); |
||||
{ |
image.ComparePixelBufferTo(color); |
||||
Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); |
|
||||
|
|
||||
Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void ImageShouldBeFloodFilledWithColorOpacity() |
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] |
||||
|
[WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] |
||||
|
public void WhenColorIsOpaque_OverridePreviousColor<TPixel>(TestImageProvider<TPixel> provider, string newColorName) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); |
using (Image<TPixel> image = provider.GetImage()) |
||||
using (var image = new Image<Rgba32>(500, 500)) |
|
||||
{ |
{ |
||||
var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); |
TPixel color = TestUtils.GetPixelOfNamedColor<TPixel>(newColorName); |
||||
|
image.Mutate(c => c.Fill(color)); |
||||
|
|
||||
|
image.DebugSave(provider, newColorName, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
||||
|
image.ComparePixelBufferTo(color); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static readonly TheoryData<bool, string, float, PixelBlenderMode, float> BlendData = |
||||
|
new TheoryData<bool, string, float, PixelBlenderMode, float>() |
||||
|
{ |
||||
|
{ false, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f }, |
||||
|
{ false, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f }, |
||||
|
{ false, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f }, |
||||
|
{ false, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f }, |
||||
|
|
||||
|
{ false, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f }, |
||||
|
{ false, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f }, |
||||
|
{ false, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f }, |
||||
|
{ false, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f }, |
||||
|
|
||||
|
{ false, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f }, |
||||
|
{ false, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f }, |
||||
|
{ false, "Green", 0.5f, PixelBlenderMode.Add, 0.3f }, |
||||
|
{ false, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f }, |
||||
|
|
||||
image.Mutate(x => x |
{ true, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f }, |
||||
.BackgroundColor(Rgba32.Blue) |
{ true, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f }, |
||||
.Fill(color)); |
{ true, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f }, |
||||
image.Save($"{path}/Opacity.png"); |
{ true, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f }, |
||||
|
|
||||
//shift background color towards forground color by the opacity amount
|
{ true, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f }, |
||||
var mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); |
{ true, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f }, |
||||
|
{ true, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f }, |
||||
|
{ true, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f }, |
||||
|
|
||||
|
{ true, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f }, |
||||
|
{ true, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f }, |
||||
|
{ true, "Green", 0.5f, PixelBlenderMode.Add, 0.3f }, |
||||
|
{ true, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f }, |
||||
|
}; |
||||
|
|
||||
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) |
[Theory] |
||||
|
[WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)] |
||||
|
public void BlendFillColorOverBackround<TPixel>( |
||||
|
TestImageProvider<TPixel> provider, |
||||
|
bool triggerFillRegion, |
||||
|
string newColorName, |
||||
|
float alpha, |
||||
|
PixelBlenderMode blenderMode, |
||||
|
float blendPercentage) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
var vec = TestUtils.GetPixelOfNamedColor<RgbaVector>(newColorName).ToVector4(); |
||||
|
vec.W = alpha; |
||||
|
|
||||
|
TPixel fillColor = default; |
||||
|
fillColor.PackFromVector4(vec); |
||||
|
|
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
TPixel bgColor = image[0, 0]; |
||||
|
|
||||
|
var options = new GraphicsOptions(false) |
||||
|
{ |
||||
|
BlenderMode = blenderMode, |
||||
|
BlendPercentage = blendPercentage |
||||
|
}; |
||||
|
|
||||
|
if (triggerFillRegion) |
||||
|
{ |
||||
|
var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16)); |
||||
|
|
||||
|
image.Mutate(c => c.Fill(options, new SolidBrush<TPixel>(fillColor), region)); |
||||
|
} |
||||
|
else |
||||
{ |
{ |
||||
Assert.Equal(mergedColor, sourcePixels[9, 9]); |
image.Mutate(c => c.Fill(options, new SolidBrush<TPixel>(fillColor))); |
||||
Assert.Equal(mergedColor, sourcePixels[199, 149]); |
|
||||
} |
} |
||||
|
|
||||
|
var testOutputDetails = new |
||||
|
{ |
||||
|
triggerFillRegion = triggerFillRegion, |
||||
|
newColorName = newColorName, |
||||
|
alpha = alpha, |
||||
|
blenderMode = blenderMode, |
||||
|
blendPercentage = blendPercentage |
||||
|
}; |
||||
|
|
||||
|
image.DebugSave( |
||||
|
provider, |
||||
|
testOutputDetails, |
||||
|
appendPixelTypeToFileName: false, |
||||
|
appendSourceFileOrDescription: false); |
||||
|
|
||||
|
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode); |
||||
|
TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage); |
||||
|
|
||||
|
image.ComparePixelBufferTo(expectedPixel); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,154 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
||||
|
using SixLabors.Primitives; |
||||
|
using Xunit; |
||||
|
|
||||
|
// ReSharper disable InconsistentNaming
|
||||
|
namespace SixLabors.ImageSharp.Tests.Drawing |
||||
|
{ |
||||
|
[GroupOutput("Drawing")] |
||||
|
public class SolidFillBlendedShapesTests |
||||
|
{ |
||||
|
public static IEnumerable<object[]> modes = |
||||
|
((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x }); |
||||
|
|
||||
|
[Theory] |
||||
|
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
||||
|
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>( |
||||
|
TestImageProvider<TPixel> provider, |
||||
|
PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> img = provider.GetImage()) |
||||
|
{ |
||||
|
int scaleX = img.Width / 100; |
||||
|
int scaleY = img.Height / 100; |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
NamedColors<TPixel>.DarkBlue, |
||||
|
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) |
||||
|
) |
||||
|
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, |
||||
|
NamedColors<TPixel>.HotPink, |
||||
|
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) |
||||
|
); |
||||
|
|
||||
|
VerifyImage(provider, mode, img); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
||||
|
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>( |
||||
|
TestImageProvider<TPixel> provider, |
||||
|
PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> img = provider.GetImage()) |
||||
|
{ |
||||
|
int scaleX = img.Width / 100; |
||||
|
int scaleY = img.Height / 100; |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
NamedColors<TPixel>.DarkBlue, |
||||
|
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
new GraphicsOptions(true) { BlenderMode = mode }, |
||||
|
NamedColors<TPixel>.HotPink, |
||||
|
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
new GraphicsOptions(true) { BlenderMode = mode }, |
||||
|
NamedColors<TPixel>.Transparent, |
||||
|
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
||||
|
); |
||||
|
|
||||
|
VerifyImage(provider, mode, img); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
||||
|
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>( |
||||
|
TestImageProvider<TPixel> provider, |
||||
|
PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> img = provider.GetImage()) |
||||
|
{ |
||||
|
int scaleX = (img.Width / 100); |
||||
|
int scaleY = (img.Height / 100); |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
NamedColors<TPixel>.DarkBlue, |
||||
|
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
new GraphicsOptions(true) { BlenderMode = mode }, |
||||
|
NamedColors<TPixel>.HotPink, |
||||
|
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); |
||||
|
var c = NamedColors<TPixel>.Red.ToVector4(); |
||||
|
c.W *= 0.5f; |
||||
|
var pixel = default(TPixel); |
||||
|
pixel.PackFromVector4(c); |
||||
|
|
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
new GraphicsOptions(true) { BlenderMode = mode }, |
||||
|
pixel, |
||||
|
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
||||
|
); |
||||
|
|
||||
|
VerifyImage(provider, mode, img); ; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
||||
|
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> img = provider.GetImage()) |
||||
|
{ |
||||
|
int scaleX = (img.Width / 100); |
||||
|
int scaleY = (img.Height / 100); |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
NamedColors<TPixel>.DarkBlue, |
||||
|
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
||||
|
img.Mutate( |
||||
|
x => x.Fill( |
||||
|
new GraphicsOptions(true) { BlenderMode = mode }, |
||||
|
NamedColors<TPixel>.Black, |
||||
|
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
||||
|
|
||||
|
VerifyImage(provider, mode, img); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void VerifyImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode, Image<TPixel> img) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
img.DebugSave( |
||||
|
provider, |
||||
|
new { mode }, |
||||
|
appendPixelTypeToFileName: false, |
||||
|
appendSourceFileOrDescription: false); |
||||
|
|
||||
|
var comparer = ImageComparer.TolerantPercentage(0.01f, 3); |
||||
|
img.CompareFirstFrameToReferenceOutput(comparer, |
||||
|
provider, |
||||
|
new { mode }, |
||||
|
appendPixelTypeToFileName: false, |
||||
|
appendSourceFileOrDescription: false); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue