c# - 如何在像素艺术编辑器中优化绘制区域

标签 c# wpf canvas draw

我有像素艺术创作程序,我在 Canvas 上有一个矩形(像素?)。这是一个很好的解决方案,但数量不多(例如 128x128)。如果我想在 Canvas 上创建 1024x1024 矩形,这个过程会很长,内存使用量约为 1-2 GB,之后程序运行非常缓慢。如何优化这个,或者创造更好的解决方案?

最佳答案

使用矩形 来表示每个像素是错误的做法。作为 FrameworkElement,每个矩形都参与布局和输入 HitTest 。这种方法太重了,无法扩展。 现在放弃它。

我建议直接绘制到 WriteableBitmap 并使用自定义表面在用户绘制时呈现位图。

以下是允许使用单一颜色进行简单绘图的最低概念证明。它需要 WriteableBitmapEx 库,该库可从 NuGet 获得。

public class PixelEditor : FrameworkElement
{
    private readonly Surface _surface;
    private readonly Visual _gridLines;

    public int PixelWidth { get; } = 128;
    public int PixelHeight { get; } = 128;
    public int Magnification { get; } = 10;

    public PixelEditor()
    {
        _surface = new Surface(this);
        _gridLines = CreateGridLines();

        Cursor = Cursors.Pen;

        AddVisualChild(_surface);
        AddVisualChild(_gridLines);
    }

    protected override int VisualChildrenCount => 2;

    protected override Visual GetVisualChild(int index)
    {
        return index == 0 ? _surface : _gridLines;
    }

    private void Draw()
    {
        var p = Mouse.GetPosition(_surface);
        var magnification = Magnification;
        var surfaceWidth = PixelWidth * magnification;
        var surfaceHeight = PixelHeight * magnification;

        if (p.X < 0 || p.X >= surfaceWidth || p.Y < 0 || p.Y >= surfaceHeight)
            return;

        _surface.SetColor(
            (int)(p.X / magnification),
            (int)(p.Y / magnification),
            Colors.DodgerBlue);

        _surface.InvalidateVisual();
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);

        if (e.LeftButton == MouseButtonState.Pressed && IsMouseCaptured)
            Draw();
    }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        base.OnMouseLeftButtonDown(e);
        CaptureMouse();
        Draw();
    }

    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        base.OnMouseLeftButtonUp(e);
        ReleaseMouseCapture();
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        var magnification = Magnification;
        var size = new Size(PixelWidth* magnification, PixelHeight * magnification);

        _surface.Measure(size);

        return size;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        _surface.Arrange(new Rect(finalSize));
        return finalSize;
    }

    private Visual CreateGridLines()
    {
        var dv = new DrawingVisual();
        var dc = dv.RenderOpen();

        var w = PixelWidth;
        var h = PixelHeight;
        var m = Magnification;
        var d = -0.5d; // snap gridlines to device pixels

        var pen = new Pen(new SolidColorBrush(Color.FromArgb(63, 63, 63, 63)), 1d);

        pen.Freeze();

        for (var x = 1; x < w; x++)
            dc.DrawLine(pen, new Point(x * m + d, 0), new Point(x * m + d, h * m));

        for (var y = 1; y < h; y++)
            dc.DrawLine(pen, new Point(0, y * m + d), new Point(w * m, y * m + d));

        dc.Close();

        return dv;
    }

    private sealed class Surface : FrameworkElement
    {
        private readonly PixelEditor _owner;
        private readonly WriteableBitmap _bitmap;

        public Surface(PixelEditor owner)
        {
            _owner = owner;
            _bitmap = BitmapFactory.New(owner.PixelWidth, owner.PixelHeight);
            _bitmap.Clear(Colors.White);
            RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.NearestNeighbor);
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            var magnification = _owner.Magnification;
            var width = _bitmap.PixelWidth * magnification;
            var height = _bitmap.PixelHeight * magnification;

            dc.DrawImage(_bitmap, new Rect(0, 0, width, height));
        }

        internal void SetColor(int x, int y, Color color)
        {
            _bitmap.SetPixel(x, y, color);
        }
    }
}

只需将它导入到您的 Xaml 中,最好是在 ScrollViewer 中:

<Window x:Class="WpfTest.PixelArtEditor"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfTest"
        Title="PixelArtEditor"
        Width="640"
        Height="480">
    <ScrollViewer HorizontalScrollBarVisibility="Auto"
                  VerticalScrollBarVisibility="Auto">
      <l:PixelEditor />
    </ScrollViewer>
</Window>

显然,这与功能完备的像素艺术编辑器相去甚远,但它的功能性很强,足以让您走上正轨。编辑 128x128 图像与编辑 1024x1024 图像之间的内存使用差异约为 30mb。启动它并查看它的运行情况:

Screenshot of Pixel Art Editor

嘿,这很有趣!感谢您的转移。

关于c# - 如何在像素艺术编辑器中优化绘制区域,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48447504/

相关文章:

c# - 如何在运行时克隆 Control 事件处理程序?

c# - .NET - 线程同步

c# - 无法检索切换状态为0或1

c# - StatusBar 中的 WPF ProgressBar 不会拉伸(stretch)

C# WPF MVVM 阻塞 UI 线程

javascript:在 Canvas 上绘制图像(背景)

javascript - 需要帮助使用此函数来使用 javascript 销毁 Canvas 上的项目

java - 在矩形内创建字符串

c# - ComAddin.Object.SomeMethod 在一个项目中有效,但在另一个项目中无效

c# - 为什么不能同时使用 2 个 NetworkManager?