Hiện tại, Avalonia không cho phép kế thừa lớp Effect để ghi đè, vì hàm tạo của nó là internal. Thay vào đó, chúng ta sẽ triển khai một lớp GrayscaleImage để thực hiện hiệu ứng xám.
Lớp 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);
}
}
}
Code 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>
Code 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();
}
}
Kết quả chạy