diff --git a/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs
index 12c040fab3..aab65fcbc7 100644
--- a/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs
+++ b/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs
@@ -80,7 +80,7 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
StartPoint = new RelativePoint(x, y, RelativeUnit.Relative),
EndPoint = new RelativePoint(1 - x, 1 - y, RelativeUnit.Relative),
- GradientStops =
+ GradientStops = new[]
{
new GradientStop(startColor, 0),
new GradientStop(endColor, 1)
diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index 57393e5448..9c3830769b 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -181,7 +181,7 @@
-
+
diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs
index 9059df73d7..2ea3c78064 100644
--- a/src/Avalonia.Visuals/Media/GradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/GradientBrush.cs
@@ -7,28 +7,46 @@ using Avalonia.Metadata;
namespace Avalonia.Media
{
+ ///
+ /// Base class for brushes that draw with a gradient.
+ ///
public abstract class GradientBrush : Brush, IGradientBrush
{
+ ///
+ /// Defines the property.
+ ///
public static readonly StyledProperty SpreadMethodProperty =
AvaloniaProperty.Register(nameof(SpreadMethod));
- public static readonly StyledProperty> GradientStopsProperty =
- AvaloniaProperty.Register>(nameof(Opacity));
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty> GradientStopsProperty =
+ AvaloniaProperty.Register>(nameof(Opacity));
+ ///
+ /// Initializes a new instance of the class.
+ ///
public GradientBrush()
{
this.GradientStops = new List();
}
+ ///
+ /// Gets or sets the brush's spread method that defines how to draw a gradient that
+ /// doesn't fill the bounds of the destination control.
+ ///
public GradientSpreadMethod SpreadMethod
{
get { return GetValue(SpreadMethodProperty); }
set { SetValue(SpreadMethodProperty, value); }
}
- // TODO: We shouldn't be returning a concrete List<> here
+ ///
+ /// Gets or sets the brush's gradient stops.
+ ///
[Content]
- public List GradientStops
+ public IReadOnlyList GradientStops
{
get { return GetValue(GradientStopsProperty); }
set { SetValue(GradientStopsProperty, value); }
diff --git a/src/Avalonia.Visuals/Media/IGradientBrush.cs b/src/Avalonia.Visuals/Media/IGradientBrush.cs
index a9079ecfda..ce064c4a1f 100644
--- a/src/Avalonia.Visuals/Media/IGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/IGradientBrush.cs
@@ -3,7 +3,7 @@
namespace Avalonia.Media
{
///
- /// A brush that draws with a linear gradient.
+ /// A brush that draws with a gradient.
///
public interface IGradientBrush : IBrush
{
diff --git a/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs
index a9d902dabe..cadf53cc18 100644
--- a/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs
@@ -11,7 +11,8 @@
RelativePoint Center { get; }
///
- /// Gets the location of the two-dimensional focal point that defines the beginning of the gradient.
+ /// Gets the location of the two-dimensional focal point that defines the beginning of the
+ /// gradient.
///
RelativePoint GradientOrigin { get; }
diff --git a/src/Avalonia.Visuals/Media/ImageBush.cs b/src/Avalonia.Visuals/Media/ImageBrush.cs
similarity index 100%
rename from src/Avalonia.Visuals/Media/ImageBush.cs
rename to src/Avalonia.Visuals/Media/ImageBrush.cs
diff --git a/src/Avalonia.Visuals/Media/Imaging/IImageBrush.cs b/src/Avalonia.Visuals/Media/Imaging/IImageBrush.cs
new file mode 100644
index 0000000000..aaa481bd28
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Imaging/IImageBrush.cs
@@ -0,0 +1,15 @@
+using Avalonia.Media.Imaging;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Paints an area with an .
+ ///
+ public interface IImageBrush : ITileBrush
+ {
+ ///
+ /// Gets the image to draw.
+ ///
+ IBitmap Source { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs
index a78cd86afe..203b5767df 100644
--- a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs
@@ -44,7 +44,8 @@ namespace Avalonia.Media
}
///
- /// Gets or sets the location of the two-dimensional focal point that defines the beginning of the gradient.
+ /// Gets or sets the location of the two-dimensional focal point that defines the beginning
+ /// of the gradient.
///
public RelativePoint GradientOrigin
{
@@ -53,7 +54,8 @@ namespace Avalonia.Media
}
///
- /// Gets or sets the horizontal and vertical radius of the outermost circle of the radial gradient.
+ /// Gets or sets the horizontal and vertical radius of the outermost circle of the radial
+ /// gradient.
///
// TODO: This appears to always be relative so should use a RelativeSize struct or something.
public double Radius
diff --git a/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs b/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs
index 4e037ce210..94aa4e6b25 100644
--- a/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs
+++ b/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs
@@ -4,6 +4,7 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Utilities;
using Gdk;
+using Avalonia.Media;
using global::Cairo;
namespace Avalonia.Cairo.Media
diff --git a/src/Gtk/Avalonia.Cairo/Media/LinearGradientBrushImpl.cs b/src/Gtk/Avalonia.Cairo/Media/LinearGradientBrushImpl.cs
index 5354d4899a..1e82da4059 100644
--- a/src/Gtk/Avalonia.Cairo/Media/LinearGradientBrushImpl.cs
+++ b/src/Gtk/Avalonia.Cairo/Media/LinearGradientBrushImpl.cs
@@ -1,11 +1,12 @@
using System;
+using Avalonia.Media;
using global::Cairo;
namespace Avalonia.Cairo
{
public class LinearGradientBrushImpl : BrushImpl
{
- public LinearGradientBrushImpl(Avalonia.Media.ILinearGradientBrush brush, Size destinationSize)
+ public LinearGradientBrushImpl(ILinearGradientBrush brush, Size destinationSize)
{
var start = brush.StartPoint.ToPixels(destinationSize);
var end = brush.EndPoint.ToPixels(destinationSize);
diff --git a/src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs b/src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs
index 51d028b935..03385f8d86 100644
--- a/src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs
+++ b/src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs
@@ -1,11 +1,12 @@
using System;
+using Avalonia.Media;
using global::Cairo;
namespace Avalonia.Cairo
{
public class RadialGradientBrushImpl : BrushImpl
{
- public RadialGradientBrushImpl(Avalonia.Media.IRadialGradientBrush brush, Size destinationSize)
+ public RadialGradientBrushImpl(IRadialGradientBrush brush, Size destinationSize)
{
var center = brush.Center.ToPixels(destinationSize);
var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize);
diff --git a/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs b/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs
new file mode 100644
index 0000000000..eb78c9d2f3
--- /dev/null
+++ b/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs
@@ -0,0 +1,55 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Cairo;
+using Avalonia.Cairo.Media.Imaging;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.RenderHelpers;
+
+namespace Avalonia.Cairo.Media
+{
+ internal static class TileBrushes
+ {
+ public static SurfacePattern CreateTileBrush(ITileBrush brush, Size targetSize)
+ {
+ var helper = new TileBrushImplHelper(brush, targetSize);
+ if (!helper.IsValid)
+ return null;
+
+ using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height))
+ using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
+ {
+ helper.DrawIntermediate(ctx);
+
+ var result = new SurfacePattern(intermediate);
+
+ if ((brush.TileMode & TileMode.FlipXY) != 0)
+ {
+ // TODO: Currently always FlipXY as that's all cairo supports natively.
+ // Support separate FlipX and FlipY by drawing flipped images to intermediate
+ // surface.
+ result.Extend = Extend.Reflect;
+ }
+ else
+ {
+ result.Extend = Extend.Repeat;
+ }
+
+ if (brush.TileMode != TileMode.None)
+ {
+ var matrix = result.Matrix;
+ matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y);
+ result.Matrix = matrix;
+ }
+
+ return result;
+ }
+ }
+
+
+ }
+}
diff --git a/src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs b/src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs
new file mode 100644
index 0000000000..e820c50420
--- /dev/null
+++ b/src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs
@@ -0,0 +1,15 @@
+using System;
+using Avalonia.Media;
+using global::Cairo;
+
+namespace Avalonia.Cairo.Media
+{
+ public class VisualBrushImpl : BrushImpl
+ {
+ public VisualBrushImpl(IVisualBrush brush, Size destinationSize)
+ {
+ this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize);
+ }
+ }
+}
+
diff --git a/src/Shared/RenderHelpers/TileBrushImplHelper.cs b/src/Shared/RenderHelpers/TileBrushImplHelper.cs
index d1d3073be6..b6d09101c9 100644
--- a/src/Shared/RenderHelpers/TileBrushImplHelper.cs
+++ b/src/Shared/RenderHelpers/TileBrushImplHelper.cs
@@ -105,7 +105,7 @@ namespace Avalonia.RenderHelpers
///
- /// Calculates a translate based on a , a source and destination
+ /// Calculates a translate based on an , a source and destination
/// rectangle and a scale.
///
/// The brush.
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 26207c23d2..216f94dc3e 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -341,11 +341,11 @@ namespace Avalonia.Direct2D1.Media
/// The Direct2D brush wrapper.
public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
{
- var solidColorBrush = brush as Avalonia.Media.ISolidColorBrush;
- var linearGradientBrush = brush as Avalonia.Media.ILinearGradientBrush;
- var radialGradientBrush = brush as Avalonia.Media.IRadialGradientBrush;
- var imageBrush = brush as Avalonia.Media.IImageBrush;
- var visualBrush = brush as Avalonia.Media.IVisualBrush;
+ var solidColorBrush = brush as ISolidColorBrush;
+ var linearGradientBrush = brush as ILinearGradientBrush;
+ var radialGradientBrush = brush as IRadialGradientBrush;
+ var imageBrush = brush as IImageBrush;
+ var visualBrush = brush as IVisualBrush;
if (solidColorBrush != null)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
index 2eb585e6ef..5369f84f46 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
@@ -2,13 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
+using Avalonia.Media;
namespace Avalonia.Direct2D1.Media
{
public class LinearGradientBrushImpl : BrushImpl
{
public LinearGradientBrushImpl(
- Avalonia.Media.ILinearGradientBrush brush,
+ ILinearGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
index be972c0173..45932a4760 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
@@ -2,13 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
+using Avalonia.Media;
namespace Avalonia.Direct2D1.Media
{
public class RadialGradientBrushImpl : BrushImpl
{
public RadialGradientBrushImpl(
- Avalonia.Media.IRadialGradientBrush brush,
+ IRadialGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs
new file mode 100644
index 0000000000..b0c4a2c83b
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs
@@ -0,0 +1,78 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Media;
+using Avalonia.RenderHelpers;
+using SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+ public sealed class TileBrushImpl : BrushImpl
+ {
+ public TileBrushImpl(
+ ITileBrush brush,
+ SharpDX.Direct2D1.RenderTarget target,
+ Size targetSize)
+ {
+ var helper = new TileBrushImplHelper(brush, targetSize);
+ if (!helper.IsValid)
+ return;
+
+ using (var intermediate = new BitmapRenderTarget(target, CompatibleRenderTargetOptions.None, helper.IntermediateSize.ToSharpDX()))
+ {
+ using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
+ {
+ intermediate.Clear(null);
+ helper.DrawIntermediate(ctx);
+ }
+
+ PlatformBrush = new BitmapBrush(
+ target,
+ intermediate.Bitmap,
+ GetBitmapBrushProperties(brush),
+ GetBrushProperties(brush, helper.DestinationRect));
+ }
+ }
+
+ private static BrushProperties GetBrushProperties(ITileBrush brush, Rect destinationRect)
+ {
+ var tileTransform =
+ brush.TileMode != TileMode.None ?
+ Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) :
+ Matrix.Identity;
+
+ return new BrushProperties
+ {
+ Opacity = (float)brush.Opacity,
+ Transform = tileTransform.ToDirect2D(),
+ };
+ }
+
+ private static BitmapBrushProperties GetBitmapBrushProperties(ITileBrush brush)
+ {
+ var tileMode = brush.TileMode;
+
+ return new BitmapBrushProperties
+ {
+ ExtendModeX = GetExtendModeX(tileMode),
+ ExtendModeY = GetExtendModeY(tileMode),
+ };
+ }
+
+ private static ExtendMode GetExtendModeX(TileMode tileMode)
+ {
+ return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
+ }
+
+ private static ExtendMode GetExtendModeY(TileMode tileMode)
+ {
+ return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
+ }
+
+ public override void Dispose()
+ {
+ ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
+ base.Dispose();
+ }
+ }
+}
diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
index 776910ca5a..e7889672b9 100644
--- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
- GradientStops =
+ GradientStops = new[]
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
@@ -73,7 +73,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
- GradientStops =
+ GradientStops = new[]
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
index af4c17b328..383c8ee0f8 100644
--- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
@@ -40,7 +40,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
Background = new RadialGradientBrush
{
- GradientStops =
+ GradientStops = new[]
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }