目前Avalonia无法继承Effect类重写,因为构造函数是internal。我们重写一个GrayscaleImage实现灰化。
GrayscaleImage类
public class GrayscaleImage : Control{public static readonly StyledProperty<IImage?> SourceProperty =AvaloniaProperty.Register<GrayscaleImage, IImage?>(nameof(Source));public static readonly StyledProperty<bool> IsGrayscaleProperty =AvaloniaProperty.Register<GrayscaleImage, bool>(nameof(IsGrayscale), true);public static readonly StyledProperty<Stretch> StretchProperty =AvaloniaProperty.Register<GrayscaleImage, Stretch>(nameof(Stretch), Stretch.Uniform);public IImage? Source{get => GetValue(SourceProperty);set => SetValue(SourceProperty, value);}public bool IsGrayscale{get => GetValue(IsGrayscaleProperty);set => SetValue(IsGrayscaleProperty, value);}public Stretch Stretch{get => GetValue(StretchProperty);set => SetValue(StretchProperty, value);}static GrayscaleImage(){AffectsRender<GrayscaleImage>(IsGrayscaleProperty, SourceProperty, StretchProperty);AffectsMeasure<GrayscaleImage>(SourceProperty);}protected override Size MeasureOverride(Size availableSize){if (Source is Bitmap bitmap){var sourceSize = new Size(bitmap.PixelSize.Width, bitmap.PixelSize.Height);if (double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height))return sourceSize;if (Stretch == Stretch.None)return sourceSize;return CalculateStretchedSize(sourceSize, availableSize, Stretch);}return base.MeasureOverride(availableSize);}private Size CalculateStretchedSize(Size sourceSize, Size availableSize, Stretch stretch){double scaleX = 1.0;double scaleY = 1.0;bool isConstrainedWidth = !double.IsInfinity(availableSize.Width);bool isConstrainedHeight = !double.IsInfinity(availableSize.Height);if ((isConstrainedWidth || isConstrainedHeight) && sourceSize.Width > 0 && sourceSize.Height > 0){scaleX = isConstrainedWidth ? availableSize.Width / sourceSize.Width : scaleX;scaleY = isConstrainedHeight ? availableSize.Height / sourceSize.Height : scaleY;if (stretch == Stretch.Uniform){scaleX = scaleY = Math.Min(scaleX, scaleY);}else if (stretch == Stretch.UniformToFill){scaleX = scaleY = Math.Max(scaleX, scaleY);}return new Size(sourceSize.Width * scaleX, sourceSize.Height * scaleY);}return sourceSize;}public override void Render(DrawingContext context){base.Render(context);if (Source is not Bitmap bitmap || Bounds.Width <= 0 || Bounds.Height <= 0)return;context.Custom(new GrayscaleDrawOperation(new Rect(Bounds.Size), bitmap, IsGrayscale, Stretch));}private sealed class GrayscaleDrawOperation : ICustomDrawOperation{private readonly Rect BoundsRect;private readonly Bitmap BitmapSource;private readonly bool ApplyGrayscale;private readonly Stretch StretchMode;public GrayscaleDrawOperation(Rect boundsRect, Bitmap bitmap, bool applyGrayscale, Stretch stretch){BoundsRect = boundsRect;BitmapSource = bitmap;ApplyGrayscale = applyGrayscale;StretchMode = stretch;}public Rect Bounds => BoundsRect;public void Dispose(){}public bool HitTest(Point point) => BoundsRect.Contains(point);public bool Equals(ICustomDrawOperation? other){return other is GrayscaleDrawOperation op &&op.BitmapSource == BitmapSource &&op.BoundsRect == BoundsRect &&op.ApplyGrayscale == ApplyGrayscale &&op.StretchMode == StretchMode;}public void Render(ImmediateDrawingContext context){var leaseFeature = context.TryGetFeature<ISkiaSharpApiLeaseFeature>();if (leaseFeature is null)return;using var lease = leaseFeature.Lease();var canvas = lease.SkCanvas;using var skImage = ToSkImage(BitmapSource);using var paint = new SKPaint();if (ApplyGrayscale){float[] colorMatrix = {0.299f, 0.587f, 0.114f, 0, 0,0.299f, 0.587f, 0.114f, 0, 0,0.299f, 0.587f, 0.114f, 0, 0,0, 0, 0, 1, 0};paint.ColorFilter = SKColorFilter.CreateColorMatrix(colorMatrix);}var sourceSize = new SKSize(skImage.Width, skImage.Height);var destRect = CalculateDestinationRect(BoundsRect, sourceSize, StretchMode);canvas.Save();canvas.DrawImage(skImage, destRect, paint);canvas.Restore();}private SKRect CalculateDestinationRect(Rect bounds, SKSize sourceSize, Stretch stretch){double sourceWidth = sourceSize.Width;double sourceHeight = sourceSize.Height;double destWidth = bounds.Width;double destHeight = bounds.Height;if (stretch == Stretch.None){return new SKRect(0, 0, (float)sourceWidth, (float)sourceHeight);}if (stretch == Stretch.Fill){return new SKRect(0, 0, (float)destWidth, (float)destHeight);}double scaleX = destWidth / sourceWidth;double scaleY = destHeight / sourceHeight;if (stretch == Stretch.Uniform){double scale = Math.Min(scaleX, scaleY);double scaledWidth = sourceWidth * scale;double scaledHeight = sourceHeight * scale;double x = (destWidth - scaledWidth) / 2;double y = (destHeight - scaledHeight) / 2;return new SKRect((float)x, (float)y, (float)(x + scaledWidth), (float)(y + scaledHeight));}if (stretch == Stretch.UniformToFill){double scale = Math.Max(scaleX, scaleY);double scaledWidth = sourceWidth * scale;double scaledHeight = sourceHeight * scale;double x = (destWidth - scaledWidth) / 2;double y = (destHeight - scaledHeight) / 2;return new SKRect((float)x, (float)y, (float)(x + scaledWidth), (float)(y + scaledHeight));}return new SKRect(0, 0, (float)destWidth, (float)destHeight);}private static SKImage ToSkImage(Bitmap bitmap){using var memoryStream = new MemoryStream();bitmap.Save(memoryStream);memoryStream.Position = 0;return SKImage.FromEncodedData(memoryStream);}}}
CustomPixelShader.axaml代码
<Window xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"Height="386" Width="268"x:Class="AvaloniaUI.CustomPixelShader"Title="CustomPixelShader"><StackPanel><GrayscaleImage Margin="5" Source="avares://AvaloniaUI/Resources/Images/harpsichord.jpg" IsGrayscale="{Binding #chkEffect.IsChecked}"></GrayscaleImage><CheckBox Name="chkEffect" Margin="5" Content="Effect enabled" IsChecked="True"></CheckBox></StackPanel> </Window>
CustomPixelShader.axaml.cs代码
using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Media;namespace AvaloniaUI;public partial class CustomPixelShader : Window {public CustomPixelShader(){InitializeComponent(); } }
运行效果