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

问题描述 投票:2回答:2

我有像素艺术创作者程序,我在画布上有一个矩形(像素?)。这是很好的解决方案,不是很大的(例如128x128)。如果我想在画布上创建1024x1024矩形,这个过程非常长,ram的使用量大约是1-2 gb,之后程序运行得非常慢。如何优化这一点,或创造更好的解决方案?

c# wpf canvas draw
2个回答
5
投票

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

我建议直接绘制到WriteableBitmap并使用自定义曲面在用户绘制时渲染位图。

以下是允许简单绘制单一颜色的概念证明。它需要可从NuGet获得的WriteableBitmapEx库。

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

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


0
投票

只是为了改进Mike Strobel解决方案,将网格线捕捉到设备像素。

var d = -0.5d; // snap gridlines to device pixels

using (DrawingContext dc = _dv.RenderOpen())
{
    GuidelineSet guidelineSet = new GuidelineSet();
    guidelineSet.GuidelinesX.Add(0.5);
    guidelineSet.GuidelinesY.Add(0.5);
    dc.PushGuidelineSet(guidelineSet);

    // Draw grid
}
© www.soinside.com 2019 - 2024. All rights reserved.