Skip to content

Commit 9441362

Browse files
leocbGillibald
andauthored
Add missing Blend Modes and expose BlendMode on Image Control (#17903)
* Add missing Bitmap Blend Modes supported by Skia * Expose Blend Mode on Image Control * Fix image render options "push" not being disposed Fix image blendMode change not triggering a re-render * Add Image Blend Tests * Remove Modulate Blend mode * Add Composition Blend Modes Tests --------- Co-authored-by: Benedikt Stebner <[email protected]>
1 parent 8b93dd1 commit 9441362

File tree

69 files changed

+266
-32
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+266
-32
lines changed

src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Avalonia.Media.Imaging
22
{
3+
// TODO12 split the enum into two: composite mode and blend mode. (And rename Blending to Blend at the same time).
34
/// <summary>
45
/// Controls the way the bitmaps are drawn together.
56
/// </summary>
@@ -54,6 +55,66 @@ public enum BitmapBlendingMode : byte
5455
/// <summary>
5556
/// Display the sum of the source image and destination image.
5657
/// </summary>
57-
Plus
58+
Plus,
59+
/// <summary>
60+
/// Multiplies the complements of the backdrop and source color values, then complements the result.
61+
/// </summary>
62+
Screen,
63+
/// <summary>
64+
/// Multiplies or screens the colors, depending on the backdrop color value.
65+
/// </summary>
66+
Overlay,
67+
/// <summary>
68+
/// Selects the darker of the backdrop and source colors.
69+
/// </summary>
70+
Darken,
71+
/// <summary>
72+
/// Selects the lighter of the backdrop and source colors.
73+
/// </summary>
74+
Lighten,
75+
/// <summary>
76+
/// Darkens the backdrop color to reflect the source color.
77+
/// </summary>
78+
ColorDodge,
79+
/// <summary>
80+
/// Multiplies or screens the colors, depending on the source color value.
81+
/// </summary>
82+
ColorBurn,
83+
/// <summary>
84+
/// Darkens or lightens the colors, depending on the source color value.
85+
/// </summary>
86+
HardLight,
87+
/// <summary>
88+
/// Subtracts the darker of the two constituent colors from the lighter color.
89+
/// </summary>
90+
SoftLight,
91+
/// <summary>
92+
/// Produces an effect similar to that of the Difference mode but lower in contrast.
93+
/// </summary>
94+
Difference,
95+
/// <summary>
96+
/// The source color is multiplied by the destination color and replaces the destination
97+
/// </summary>
98+
Exclusion,
99+
/// <summary>
100+
/// Creates a color with the hue of the source color and the saturation and luminosity of the backdrop color.
101+
/// </summary>
102+
Multiply,
103+
/// <summary>
104+
/// Creates a color with the hue of the source color and the saturation and luminosity of the backdrop color.
105+
/// </summary>
106+
Hue,
107+
/// <summary>
108+
/// Creates a color with the saturation of the source color and the hue and luminosity of the backdrop color.
109+
/// </summary>
110+
Saturation,
111+
/// <summary>
112+
/// Creates a color with the hue and saturation of the source color and the luminosity of the backdrop color.
113+
/// </summary>
114+
Color,
115+
/// <summary>
116+
/// Creates a color with the luminosity of the source color and the hue and saturation of the backdrop color.
117+
/// </summary>
118+
Luminosity
58119
}
59120
}

src/Avalonia.Controls/Image.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ public class Image : Control
1717
/// </summary>
1818
public static readonly StyledProperty<IImage?> SourceProperty =
1919
AvaloniaProperty.Register<Image, IImage?>(nameof(Source));
20+
21+
/// <summary>
22+
/// Defines the <see cref="BlendMode"/> property.
23+
/// </summary>
24+
public static readonly StyledProperty<BitmapBlendingMode> BlendModeProperty =
25+
AvaloniaProperty.Register<Image, BitmapBlendingMode>(nameof(BlendMode));
2026

2127
/// <summary>
2228
/// Defines the <see cref="Stretch"/> property.
@@ -34,7 +40,7 @@ public class Image : Control
3440

3541
static Image()
3642
{
37-
AffectsRender<Image>(SourceProperty, StretchProperty, StretchDirectionProperty);
43+
AffectsRender<Image>(SourceProperty, StretchProperty, StretchDirectionProperty, BlendModeProperty);
3844
AffectsMeasure<Image>(SourceProperty, StretchProperty, StretchDirectionProperty);
3945
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<Image>(AutomationControlType.Image);
4046
}
@@ -49,6 +55,15 @@ public IImage? Source
4955
set => SetValue(SourceProperty, value);
5056
}
5157

58+
/// <summary>
59+
/// Gets or sets the blend mode for the image.
60+
/// </summary>
61+
public BitmapBlendingMode BlendMode
62+
{
63+
get => GetValue(BlendModeProperty);
64+
set => SetValue(BlendModeProperty, value);
65+
}
66+
5267
/// <summary>
5368
/// Gets or sets a value controlling how the image will be stretched.
5469
/// </summary>
@@ -91,7 +106,10 @@ public sealed override void Render(DrawingContext context)
91106
Rect sourceRect = new Rect(sourceSize)
92107
.CenterRect(new Rect(destRect.Size / scale));
93108

94-
context.DrawImage(source, sourceRect, destRect);
109+
using (context.PushRenderOptions(RenderOptions with { BitmapBlendingMode = BlendMode }))
110+
{
111+
context.DrawImage(source, sourceRect, destRect);
112+
}
95113
}
96114
}
97115

src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,38 @@ public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode int
2929

3030
public static SKBlendMode ToSKBlendMode(this BitmapBlendingMode blendingMode)
3131
{
32-
switch (blendingMode)
32+
return blendingMode switch
3333
{
34-
case BitmapBlendingMode.Unspecified:
35-
case BitmapBlendingMode.SourceOver:
36-
return SKBlendMode.SrcOver;
37-
case BitmapBlendingMode.Source:
38-
return SKBlendMode.Src;
39-
case BitmapBlendingMode.SourceIn:
40-
return SKBlendMode.SrcIn;
41-
case BitmapBlendingMode.SourceOut:
42-
return SKBlendMode.SrcOut;
43-
case BitmapBlendingMode.SourceAtop:
44-
return SKBlendMode.SrcATop;
45-
case BitmapBlendingMode.Destination:
46-
return SKBlendMode.Dst;
47-
case BitmapBlendingMode.DestinationIn:
48-
return SKBlendMode.DstIn;
49-
case BitmapBlendingMode.DestinationOut:
50-
return SKBlendMode.DstOut;
51-
case BitmapBlendingMode.DestinationOver:
52-
return SKBlendMode.DstOver;
53-
case BitmapBlendingMode.DestinationAtop:
54-
return SKBlendMode.DstATop;
55-
case BitmapBlendingMode.Xor:
56-
return SKBlendMode.Xor;
57-
case BitmapBlendingMode.Plus:
58-
return SKBlendMode.Plus;
59-
default:
60-
throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null);
61-
}
34+
BitmapBlendingMode.Unspecified => SKBlendMode.SrcOver,
35+
BitmapBlendingMode.SourceOver => SKBlendMode.SrcOver,
36+
BitmapBlendingMode.Source => SKBlendMode.Src,
37+
BitmapBlendingMode.SourceIn => SKBlendMode.SrcIn,
38+
BitmapBlendingMode.SourceOut => SKBlendMode.SrcOut,
39+
BitmapBlendingMode.SourceAtop => SKBlendMode.SrcATop,
40+
BitmapBlendingMode.Destination => SKBlendMode.Dst,
41+
BitmapBlendingMode.DestinationIn => SKBlendMode.DstIn,
42+
BitmapBlendingMode.DestinationOut => SKBlendMode.DstOut,
43+
BitmapBlendingMode.DestinationOver => SKBlendMode.DstOver,
44+
BitmapBlendingMode.DestinationAtop => SKBlendMode.DstATop,
45+
BitmapBlendingMode.Xor => SKBlendMode.Xor,
46+
BitmapBlendingMode.Plus => SKBlendMode.Plus,
47+
BitmapBlendingMode.Screen => SKBlendMode.Screen,
48+
BitmapBlendingMode.Overlay => SKBlendMode.Overlay,
49+
BitmapBlendingMode.Darken => SKBlendMode.Darken,
50+
BitmapBlendingMode.Lighten => SKBlendMode.Lighten,
51+
BitmapBlendingMode.ColorDodge => SKBlendMode.ColorDodge,
52+
BitmapBlendingMode.ColorBurn => SKBlendMode.ColorBurn,
53+
BitmapBlendingMode.HardLight => SKBlendMode.HardLight,
54+
BitmapBlendingMode.SoftLight => SKBlendMode.SoftLight,
55+
BitmapBlendingMode.Difference => SKBlendMode.Difference,
56+
BitmapBlendingMode.Exclusion => SKBlendMode.Exclusion,
57+
BitmapBlendingMode.Multiply => SKBlendMode.Multiply,
58+
BitmapBlendingMode.Hue => SKBlendMode.Hue,
59+
BitmapBlendingMode.Saturation => SKBlendMode.Saturation,
60+
BitmapBlendingMode.Color => SKBlendMode.Color,
61+
BitmapBlendingMode.Luminosity => SKBlendMode.Luminosity,
62+
_ => throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null)
63+
};
6264
}
6365

6466
public static SKPoint ToSKPoint(this Point p)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.IO;
2+
using System.Runtime.CompilerServices;
3+
using System.Threading.Tasks;
4+
using Avalonia.Controls;
5+
using Avalonia.Media;
6+
using Avalonia.Media.Imaging;
7+
using Xunit;
8+
9+
#if AVALONIA_SKIA
10+
namespace Avalonia.Skia.RenderTests
11+
#else
12+
namespace Avalonia.Direct2D1.RenderTests.Controls
13+
#endif
14+
{
15+
public class ImageBlendTests : TestBase
16+
{
17+
private readonly Bitmap _bitmapBase;
18+
private readonly Bitmap _bitmapOver;
19+
20+
public ImageBlendTests()
21+
: base(@"Controls\Image\blend")
22+
{
23+
_bitmapBase = new Bitmap(Path.Combine(OutputPath, "Cat.jpg"));
24+
_bitmapOver = new Bitmap(Path.Combine(OutputPath, "ColourShading - by Stib.png"));
25+
}
26+
27+
[Fact]
28+
public async Task Image_Blend_Nothing() => await TestBlendMode(BitmapBlendingMode.Unspecified);
29+
[Fact]
30+
public async Task Image_Blend_Plus() => await TestBlendMode(BitmapBlendingMode.Plus);
31+
[Fact]
32+
public async Task Image_Blend_Screen() => await TestBlendMode(BitmapBlendingMode.Screen);
33+
[Fact]
34+
public async Task Image_Blend_Overlay() => await TestBlendMode(BitmapBlendingMode.Overlay);
35+
[Fact]
36+
public async Task Image_Blend_Darken() => await TestBlendMode(BitmapBlendingMode.Darken);
37+
[Fact]
38+
public async Task Image_Blend_Lighten() => await TestBlendMode(BitmapBlendingMode.Lighten);
39+
[Fact]
40+
public async Task Image_Blend_ColorDodge() => await TestBlendMode(BitmapBlendingMode.ColorDodge);
41+
[Fact]
42+
public async Task Image_Blend_ColorBurn() => await TestBlendMode(BitmapBlendingMode.ColorBurn);
43+
[Fact]
44+
public async Task Image_Blend_HardLight() => await TestBlendMode(BitmapBlendingMode.HardLight);
45+
[Fact]
46+
public async Task Image_Blend_SoftLight() => await TestBlendMode(BitmapBlendingMode.SoftLight);
47+
[Fact]
48+
public async Task Image_Blend_Difference() => await TestBlendMode(BitmapBlendingMode.Difference);
49+
[Fact]
50+
public async Task Image_Blend_Exclusion() => await TestBlendMode(BitmapBlendingMode.Exclusion);
51+
[Fact]
52+
public async Task Image_Blend_Multiply() => await TestBlendMode(BitmapBlendingMode.Multiply);
53+
[Fact]
54+
public async Task Image_Blend_Hue() => await TestBlendMode(BitmapBlendingMode.Hue);
55+
[Fact]
56+
public async Task Image_Blend_Saturation() => await TestBlendMode(BitmapBlendingMode.Saturation);
57+
[Fact]
58+
public async Task Image_Blend_Color() => await TestBlendMode(BitmapBlendingMode.Color);
59+
[Fact]
60+
public async Task Image_Blend_Luminosity() => await TestBlendMode(BitmapBlendingMode.Luminosity);
61+
62+
private async Task TestBlendMode(BitmapBlendingMode blendMode, [CallerMemberName] string testName = "")
63+
{
64+
var panel = new Panel();
65+
panel.Children.Add(new Image() { Source = _bitmapBase });
66+
panel.Children.Add(new Image() { Source = _bitmapOver, BlendMode = blendMode });
67+
68+
var target = new Decorator
69+
{
70+
Width = 512,
71+
Height = 512,
72+
Child = new Border
73+
{
74+
Background = Brushes.Red,
75+
Child = panel
76+
}
77+
};
78+
79+
await RenderToFile(target,testName);
80+
CompareImages(testName);
81+
}
82+
}
83+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.IO;
2+
using System.Runtime.CompilerServices;
3+
using System.Threading.Tasks;
4+
using Avalonia.Controls;
5+
using Avalonia.Media;
6+
using Avalonia.Media.Imaging;
7+
using Xunit;
8+
9+
#if AVALONIA_SKIA
10+
namespace Avalonia.Skia.RenderTests
11+
#else
12+
namespace Avalonia.Direct2D1.RenderTests.Controls
13+
#endif
14+
{
15+
public class ImageCompositionTests : TestBase
16+
{
17+
private readonly Bitmap _bitmapA;
18+
private readonly Bitmap _bitmapB;
19+
20+
public ImageCompositionTests()
21+
: base(@"Controls\Image\composition")
22+
{
23+
_bitmapA = new Bitmap(Path.Combine(OutputPath, "A.png"));
24+
_bitmapB = new Bitmap(Path.Combine(OutputPath, "B.png"));
25+
}
26+
[Fact]
27+
public async Task Image_Blend_SourceOver() => await TestCompositeMode(BitmapBlendingMode.SourceOver);
28+
[Fact]
29+
public async Task Image_Blend_Source() => await TestCompositeMode(BitmapBlendingMode.Source);
30+
[Fact]
31+
public async Task Image_Blend_SourceIn() => await TestCompositeMode(BitmapBlendingMode.SourceIn);
32+
[Fact]
33+
public async Task Image_Blend_SourceOut() => await TestCompositeMode(BitmapBlendingMode.SourceOut);
34+
[Fact]
35+
public async Task Image_Blend_SourceAtop() => await TestCompositeMode(BitmapBlendingMode.SourceAtop);
36+
[Fact]
37+
public async Task Image_Blend_Destination() => await TestCompositeMode(BitmapBlendingMode.Destination);
38+
[Fact]
39+
public async Task Image_Blend_DestinationIn() => await TestCompositeMode(BitmapBlendingMode.DestinationIn);
40+
[Fact]
41+
public async Task Image_Blend_DestinationOut() => await TestCompositeMode(BitmapBlendingMode.DestinationOut);
42+
[Fact]
43+
public async Task Image_Blend_DestinationOver() => await TestCompositeMode(BitmapBlendingMode.DestinationOver);
44+
[Fact]
45+
public async Task Image_Blend_DestinationAtop() => await TestCompositeMode(BitmapBlendingMode.DestinationAtop);
46+
[Fact]
47+
public async Task Image_Blend_Xor() => await TestCompositeMode(BitmapBlendingMode.Xor);
48+
49+
private async Task TestCompositeMode(BitmapBlendingMode blendMode, [CallerMemberName] string testName = "")
50+
{
51+
var panel = new Panel();
52+
panel.Children.Add(new Image() { Source = _bitmapA });
53+
panel.Children.Add(new Image() { Source = _bitmapB, BlendMode = blendMode });
54+
55+
var target = new Decorator
56+
{
57+
Width = 512,
58+
Height = 512,
59+
Child = new Border
60+
{
61+
Background = Brushes.Transparent,
62+
Child = panel
63+
}
64+
};
65+
66+
await RenderToFile(target,testName);
67+
CompareImages(testName);
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)