diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs
index 001aee5a4f..20dca67706 100644
--- a/src/ImageSharp/Color/Color.Conversions.cs
+++ b/src/ImageSharp/Color/Color.Conversions.cs
@@ -117,6 +117,13 @@ namespace SixLabors.ImageSharp
/// The .
public static implicit operator Color(Bgr24 source) => new Color(source);
+ ///
+ /// Converts an to .
+ ///
+ /// The .
+ /// The .
+ public static explicit operator Color(Vector4 source) => new Color(source);
+
///
/// Converts a to .
///
@@ -158,5 +165,12 @@ namespace SixLabors.ImageSharp
/// The .
/// The .
public static implicit operator Bgr24(Color color) => color.data.ToBgr24();
+
+ ///
+ /// Converts a to .
+ ///
+ /// The .
+ /// The .
+ public static explicit operator Vector4(Color color) => color.data.ToVector4();
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index f2f8578d83..fe35033147 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -94,6 +94,18 @@ namespace SixLabors.ImageSharp
return new Color(rgba);
}
+ ///
+ /// Alters the alpha channel of the color, returning a new instance.
+ ///
+ /// The new value of alpha [0..1].
+ /// The color having it's alpha channel altered.
+ public Color WithAlpha(float alpha)
+ {
+ Vector4 v = (Vector4)this;
+ v.W = alpha;
+ return new Color(v);
+ }
+
///
/// Gets the hexadecimal representation of the color instance in rrggbbaa form.
///
diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs
index 729ef94e3c..e9e22ccdd9 100644
--- a/tests/ImageSharp.Tests/Color/ColorTests.cs
+++ b/tests/ImageSharp.Tests/Color/ColorTests.cs
@@ -13,6 +13,17 @@ namespace SixLabors.ImageSharp.Tests
{
public partial class ColorTests
{
+ [Fact]
+ public void WithAlpha()
+ {
+ Color c1 = Color.FromRgba(111, 222, 55, 255);
+ Color c2 = c1.WithAlpha(0.5f);
+
+ Rgba32 expected = new Rgba32(111, 222, 55, 128);
+
+ Assert.Equal(expected, (Rgba32)c2);
+ }
+
[Fact]
public void Equality_WhenTrue()
{
diff --git a/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
new file mode 100644
index 0000000000..43029e6957
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
@@ -0,0 +1,155 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.Shapes;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Drawing
+{
+ [GroupOutput("Drawing")]
+ public class FillPolygonTests
+ {
+ [Theory]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)]
+ public void FillPolygon_Solid(TestImageProvider provider, string colorName, float alpha, bool antialias)
+ where TPixel : struct, IPixel
+ {
+ SixLabors.Primitives.PointF[] simplePath =
+ {
+ new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)
+ };
+ Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
+
+ GraphicsOptions options = new GraphicsOptions(antialias);
+
+ string aa = antialias ? "" : "_NoAntialias";
+ FormattableString outputDetails = $"{colorName}_A{alpha}{aa}";
+
+ provider.RunValidatingProcessorTest(
+ c => c.FillPolygon(options, color.ToPixel(), simplePath),
+ outputDetails,
+ appendSourceFileOrDescription: false);
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)]
+ public void FillPolygon_Concave(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ var points = new SixLabors.Primitives.PointF[]
+ {
+ new Vector2(8, 8),
+ new Vector2(64, 8),
+ new Vector2(64, 64),
+ new Vector2(120, 64),
+ new Vector2(120, 120),
+ new Vector2(8, 120)
+ };
+
+ Color color = Color.LightGreen;
+
+ provider.RunValidatingProcessorTest(
+ c => c.FillPolygon(color.ToPixel(), points),
+ appendSourceFileOrDescription: false,
+ appendPixelTypeToFileName: false);
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)]
+ public void FillPolygon_Pattern(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ SixLabors.Primitives.PointF[] simplePath =
+ {
+ new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)
+ };
+ Color color = Color.Yellow;
+
+ var brush = Brushes.Horizontal(color.ToPixel());
+
+ provider.RunValidatingProcessorTest(
+ c => c.FillPolygon(brush, simplePath),
+ appendSourceFileOrDescription: false);
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)]
+ [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)]
+ public void FillPolygon_ImageBrush(TestImageProvider provider, string brushImageName)
+ where TPixel : struct, IPixel
+ {
+ SixLabors.Primitives.PointF[] simplePath =
+ {
+ new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200)
+ };
+
+ using (Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes))
+ {
+ var brush = new ImageBrush(brushImage);
+
+ provider.RunValidatingProcessorTest(
+ c => c.FillPolygon(brush, simplePath),
+ System.IO.Path.GetFileNameWithoutExtension(brushImageName),
+ appendSourceFileOrDescription: false);
+ }
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)]
+ public void Fill_RectangularPolygon(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ var polygon = new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140);
+ Color color = Color.White;
+
+ provider.RunValidatingProcessorTest(
+ c => c.Fill(color.ToPixel(), polygon),
+ appendSourceFileOrDescription: false);
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)]
+ public void Fill_RegularPolygon(TestImageProvider provider, int vertices, float radius, float angleDeg)
+ where TPixel : struct, IPixel
+ {
+ float angle = GeometryUtilities.DegreeToRadian(angleDeg);
+ var polygon = new RegularPolygon(100, 100, vertices, radius, angle);
+ Color color = Color.Yellow;
+
+ FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})";
+ provider.RunValidatingProcessorTest(
+ c => c.Fill(color.ToPixel(), polygon),
+ testOutput,
+ appendSourceFileOrDescription: false,
+ appendPixelTypeToFileName: false);
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)]
+ public void Fill_EllipsePolygon(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ var polygon = new EllipsePolygon(100, 100, 80, 120);
+ Color color = Color.Azure;
+
+ provider.RunValidatingProcessorTest(
+ c => c.Fill(color.ToPixel(), polygon),
+ appendSourceFileOrDescription: false,
+ appendPixelTypeToFileName: false);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs
index cb671a53d4..461ca700f7 100644
--- a/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTests.cs
@@ -6,9 +6,10 @@ using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
+
using Xunit;
-namespace SixLabors.ImageSharp.Tests
+namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class RecolorImageTests
diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
deleted file mode 100644
index 6a299aad7b..0000000000
--- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Numerics;
-
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
-using SixLabors.Shapes;
-
-using Xunit;
-
-namespace SixLabors.ImageSharp.Tests.Drawing
-{
- public class SolidPolygonTests : FileTestBase
- {
- [Fact]
- public void ImageShouldBeOverlayedByFilledPolygon()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
- SixLabors.Primitives.PointF[] simplePath = {
- new Vector2(10, 10),
- new Vector2(200, 150),
- new Vector2(50, 300)
- };
-
- using (var image = new Image(500, 500))
- {
- image.Mutate(x => x.FillPolygon(new GraphicsOptions(true), Rgba32.HotPink, simplePath));
- image.Save($"{path}/Simple.png");
-
- Buffer2D sourcePixels = image.GetRootFramePixelBuffer();
- Assert.Equal(Rgba32.HotPink, sourcePixels[81, 145]);
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledPolygonWithPattern()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
- var simplePath = new SixLabors.Primitives.PointF[] {
- new Vector2(10, 10),
- new Vector2(200, 150),
- new Vector2(50, 300)
- };
-
- using (var image = new Image(500, 500))
- {
- image.Mutate(
- x => x.FillPolygon(new GraphicsOptions(true), Brushes.Horizontal(Rgba32.HotPink), simplePath));
- image.Save($"{path}/Pattern.png");
-
- Buffer2D sourcePixels = image.GetRootFramePixelBuffer();
- Assert.Equal(Rgba32.HotPink, sourcePixels[81, 145]);
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledPolygonNoAntialias()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
- var simplePath = new SixLabors.Primitives.PointF[] {
- new Vector2(10, 10),
- new Vector2(200, 150),
- new Vector2(50, 300)
- };
-
- using (var image = new Image(500, 500))
- {
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(
- x => x.FillPolygon(
- new GraphicsOptions(false),
- Rgba32.HotPink,
- simplePath));
- image.Save($"{path}/Simple_NoAntialias.png");
-
- Buffer2D sourcePixels = image.GetRootFramePixelBuffer();
- Assert.True(Rgba32.HotPink == sourcePixels[11, 11], "[11, 11] wrong");
-
- Assert.True(Rgba32.HotPink == sourcePixels[199, 149], "[199, 149] wrong");
-
- Assert.True(Rgba32.HotPink == sourcePixels[50, 50], "[50, 50] wrong");
-
- Assert.True(Rgba32.Blue == sourcePixels[2, 2], "[2, 2] wrong");
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledPolygonImage()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
- var simplePath = new SixLabors.Primitives.PointF[] {
- new Vector2(10, 10),
- new Vector2(200, 150),
- new Vector2(50, 300)
- };
-
- using (Image brushImage = TestFile.Create(TestImages.Bmp.Car).CreateRgba32Image())
- using (var image = new Image(500, 500))
- {
- var brush = new ImageBrush(brushImage);
-
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(x => x.FillPolygon(brush, simplePath));
- image.Save($"{path}/Image.png");
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledPolygonOpacity()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
- var simplePath = new SixLabors.Primitives.PointF[] {
- new Vector2(10, 10),
- new Vector2(200, 150),
- new Vector2(50, 300)
- };
- var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
-
- using (var image = new Image(500, 500))
- {
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(x => x.FillPolygon(color, simplePath));
- image.Save($"{path}/Opacity.png");
-
- //shift background color towards forground color by the opacity amount
- var mergedColor = new Rgba32(
- Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
-
- Buffer2D sourcePixels = image.GetRootFramePixelBuffer();
- Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]);
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledRectangle()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
-
- using (var image = new Image(500, 500))
- {
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(
- x => x.Fill(
- Rgba32.HotPink,
- new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140)));
- image.Save($"{path}/Rectangle.png");
-
- Buffer2D sourcePixels = image.GetRootFramePixelBuffer();
- Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]);
-
- Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]);
-
- Assert.Equal(Rgba32.HotPink, sourcePixels[10, 50]);
-
- Assert.Equal(Rgba32.HotPink, sourcePixels[50, 50]);
-
- Assert.Equal(Rgba32.Blue, sourcePixels[2, 2]);
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledTriangle()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
-
- using (var image = new Image(100, 100))
- {
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(
- x => x.Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30)));
- image.Save($"{path}/Triangle.png");
-
- Buffer2D sourcePixels = image.GetRootFramePixelBuffer();
- Assert.Equal(Rgba32.Blue, sourcePixels[30, 65]);
-
- Assert.Equal(Rgba32.HotPink, sourcePixels[50, 50]);
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledSeptagon()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
-
- var config = Configuration.CreateDefaultInstance();
- config.MaxDegreeOfParallelism = 1;
- using (var image = new Image(config, 100, 100))
- {
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(x => x.Fill(Rgba32.HotPink,
- new RegularPolygon(50, 50, 7, 30, -(float)Math.PI)));
- image.Save($"{path}/Septagon.png");
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedByFilledEllipse()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
-
- var config = Configuration.CreateDefaultInstance();
- config.MaxDegreeOfParallelism = 1;
- using (var image = new Image(config, 100, 100))
- {
- image.Mutate(x => x.BackgroundColor(Rgba32.Blue));
- image.Mutate(x => x
- .Fill(Rgba32.HotPink, new EllipsePolygon(50, 50, 30, 50)
- .Rotate((float)(Math.PI / 3))));
- image.Save($"{path}/ellipse.png");
- }
- }
-
- [Fact]
- public void ImageShouldBeOverlayedBySquareWithCornerClipped()
- {
- string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons");
-
- var config = Configuration.CreateDefaultInstance();
- config.MaxDegreeOfParallelism = 1;
- using (var image = new Image(config, 200, 200))
- {
- image.Mutate(x => x
- .Fill(Rgba32.Blue)
- .FillPolygon(Rgba32.HotPink, new SixLabors.Primitives.PointF[]
- {
- new Vector2( 8, 8 ),
- new Vector2( 64, 8 ),
- new Vector2( 64, 64 ),
- new Vector2( 120, 64 ),
- new Vector2( 120, 120 ),
- new Vector2( 8, 120 )
- }));
- image.Save($"{path}/clipped-corner.png");
- }
- }
- }
-}
diff --git a/tests/Images/External b/tests/Images/External
index 5036d47ec2..0edeb078b9 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit 5036d47ec2393d90efb6f3864d828e2e4381947f
+Subproject commit 0edeb078b9d9f9c8be016ca514a3a625c4768bd6