How to optimize draw area in pixel art editor

给你一囗甜甜゛ 提交于 2020-02-02 06:26:15

问题


I have pixel art creator program, and I have rectangles on canvas that are one field (pixel?). And this is good solution on not huge amount of it (for example 128x128). if i want to create 1024x1024 rectangles on canvas this process is very long, ram usage is about 1-2 gb and after that program runs very slowly. How to optimize this, or create better solution?


回答1:


Using a Rectangle to represent each pixel is the wrong way to do this. As a FrameworkElement, every rectangle participates in layout and input hit testing. That approach is too heavy weight to be scalable. Abandon it now.

I would recommend drawing directly to a WriteableBitmap and using a custom surface to render the bitmap as the user draws.

Below is a minimum proof of concept that allows simple drawing in a single color. It requires the WriteableBitmapEx library, which is available from 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);
        }
    }
}

Just import it into your Xaml, preferably inside a 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>

Obviously, this is a far cry from being a fully-featured pixel art editor, but it's functional, and it's enough to get you on the right track. The difference in memory usage between editing a 128x128 image vs. 1024x1024 is about ~30mb. Fire it up and see it in action:

Hey, that was fun! Thanks for the diversion.




回答2:


Just to improve Mike Strobel solution to snap gridlines to device pixels.

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
}


来源:https://stackoverflow.com/questions/48447504/how-to-optimize-draw-area-in-pixel-art-editor

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!