【问题标题】:How to optimize draw area in pixel art editor如何优化像素艺术编辑器中的绘图区域
【发布时间】:2018-07-04 23:24:38
【问题描述】:

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

【问题讨论】:

    标签: c# wpf canvas draw


    【解决方案1】:

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

    我建议直接绘制到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。启动它并查看它的实际效果:

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

    【讨论】:

    • 这很好但是,我不想要绘图方法。你能寄给我只创建像素吗?如果可以在一个脚本中实现?
    • 什么意思,你不想要一个“绘图”方法?该示例的要点是它在WriteableBitmap 中维护像素,而不是在一个充满Rectangle 对象的画布中,并且表面绘制了该位图的放大版本。如何设置这些像素取决于您。我用来放置像素的方法只是一个例子。
    • 我看不懂那段代码,我想在其他脚本中创建 Draw()。我只想创建这个区域来绘制。我有选择工具的按钮。例如填充或选择颜色。如果不可能,就说吧。
    • Draw 方法只是在用户按下或拖动鼠标的地方放置一个彩色像素。这不是示例中有意义的部分。您可以根据需要在位图中放置您想要的任何像素。您问如何优化内存管理,这就是我向您展示的内容:如何将像素艺术数据存储在 WriteableBitmap 中并将其显示在绘图表面中。绘图表面的工作方式取决于您。重要的部分是像素数据存储在位图中。有很多方法可以在位图中放置像素。请参阅WriteableBitmapEx 文档。
    【解决方案2】:

    只是为了改进 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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-23
      • 2012-12-29
      • 1970-01-01
      相关资源
      最近更新 更多