目前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();
}
}
运行效果

浙公网安备 33010602011771号