目前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();        
    }
}

运行效果

image

 

posted on 2025-09-15 12:13  dalgleish  阅读(26)  评论(0)    收藏  举报