缩放时在各个像素周围显示网格线

时间:2016-03-07 18:44:56

标签: c# wpf canvas zooming gridlines

我尝试在控件上绘制网格线的概念,并想知道我可能需要做些什么调整才能使其真正起作用。我在另一篇文章中找到了一些代码,可以在画布上绘制OnRender网格线。这是什么样的:

public class MyCanvas : Canvas
{
    public bool IsGridVisible = true;

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

        if (IsGridVisible)
        {
            // Draw GridLines
            Pen pen = new Pen(Brushes.Black, 1);
            pen.DashStyle = DashStyles.Solid;

            for (double x = 0; x < this.ActualWidth; x += 2)
            {
                dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight));
            }

            for (double y = 0; y < this.ActualHeight; y += 2)
            {
                dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y));
            }
        }
    }

    public MyCanvas()
    {
        DefaultStyleKey = typeof(MyCanvas);
    }
}

此部分:y += 2表示在绘制下一行之前要等待多少其他像素/点,但我不确定它是否正确。

这是xaml:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <ScrollViewer>
        <local:MyCanvas>
            <local:MyCanvas.LayoutTransform>
                <ScaleTransform ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
            </local:MyCanvas.LayoutTransform>
            <Image Canvas.Top="2" Canvas.Left="2"  Source="C:\Users\Me\Pictures\nyan-wallpaper2.jpg" Width="325" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
        </local:MyCanvas>
    </ScrollViewer>
    <Slider x:Name="Slider" Maximum="500" Grid.Row="1" Value="1"/>
</Grid>

以下是上述结果的截图。

enter image description here

正如您所看到的,网格线在缩放时会改变大小,并且线条本身不会围绕每个像素进行捕捉。我用红色突出显示了一个示例像素,以显示这些线应该与它们实际上的距离有多小。

我读到笔的粗细应该除以比例值,但是,我通过用Pen pen = new Pen(Brushes.Black, 1);替换Pen pen = new Pen(Brushes.Black, 1 / 3);并将MyCanvas的ScaleX和ScaleY设置为3来测试它。点,根本没有显示任何线条。

任何帮助都非常有价值!

1 个答案:

答案 0 :(得分:2)

让任何好奇的人都这样工作:

MainWindow.xaml.cs

namespace Test
{
    public class MyCanvas : Canvas
    {
        public bool IsGridVisible = false;

        #region Dependency Properties

        public static DependencyProperty ZoomValueProperty = DependencyProperty.Register("ZoomValue", typeof(double), typeof(MyCanvas), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnZoomValueChanged));
        public double ZoomValue
        {
            get
            {
                return (double)GetValue(ZoomValueProperty);
            }
            set
            {
                SetValue(ZoomValueProperty, value);
            }
        }
        private static void OnZoomValueChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
        {
        }

        #endregion

        protected override void OnRender(System.Windows.Media.DrawingContext dc)
        {
            base.OnRender(dc);
            IsGridVisible = ZoomValue > 4.75 ? true : false;
            if (IsGridVisible)
            {
                // Draw GridLines
                Pen pen = new Pen(Brushes.Black, 1 / ZoomValue);
                pen.DashStyle = DashStyles.Solid;

                for (double x = 0; x < this.ActualWidth; x += 1)
                {
                    dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight));
                }

                for (double y = 0; y < this.ActualHeight; y += 1)
                {
                    dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y));
                }
            }
        }

        public MyCanvas()
        {
            DefaultStyleKey = typeof(MyCanvas);
        }
    }

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private WriteableBitmap bitmap = new WriteableBitmap(500, 500, 96d, 96d, PixelFormats.Bgr24, null);

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            int size = 1;

            Random rnd = new Random(DateTime.Now.Millisecond);
            bitmap.Lock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests.

            for (int y = 0; y < 500; y++)
            {
                for (int x = 0; x < 500; x++)
                {
                    byte colR = (byte)rnd.Next(256);
                    byte colG = (byte)rnd.Next(256);
                    byte colB = (byte)rnd.Next(256);

                    DrawRectangle(bitmap, size * x, size * y, size, size, Color.FromRgb(colR, colG, colB));
                }
            }

            bitmap.Unlock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests.
            Image.Source = bitmap; // This should be done only once
        }

        public void DrawRectangle(WriteableBitmap writeableBitmap, int left, int top, int width, int height, Color color)
        {
            // Compute the pixel's color
            int colorData = color.R << 16; // R
            colorData |= color.G << 8; // G
            colorData |= color.B << 0; // B
            int bpp = writeableBitmap.Format.BitsPerPixel / 8;

            unsafe
            {
                for (int y = 0; y < height; y++)
                {
                    // Get a pointer to the back buffer
                    int pBackBuffer = (int)writeableBitmap.BackBuffer;

                    // Find the address of the pixel to draw
                    pBackBuffer += (top + y) * writeableBitmap.BackBufferStride;
                    pBackBuffer += left * bpp;

                    for (int x = 0; x < width; x++)
                    {
                        // Assign the color data to the pixel
                        *((int*)pBackBuffer) = colorData;

                        // Increment the address of the pixel to draw
                        pBackBuffer += bpp;
                    }
                }
            }

            writeableBitmap.AddDirtyRect(new Int32Rect(left, top, width, height));
        }
    }
}

MainWindow.xaml

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        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"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        Title="MainWindow" 
        Height="Auto" 
        Width="Auto"
        WindowStartupLocation="CenterScreen"
        WindowState="Maximized">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <ScrollViewer>
            <local:MyCanvas ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}">
                <local:MyCanvas.LayoutTransform>
                    <ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
                </local:MyCanvas.LayoutTransform>
                <Image Canvas.Top="1" Canvas.Left="1" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
            </local:MyCanvas>
        </ScrollViewer>
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Slider x:Name="Slider" Maximum="100" Minimum="0.5" Value="1" Width="200"/>
            <Button Click="Button_Click" Content="Click Me!"/>
        </StackPanel>
    </Grid>
</Window>

我们生成带有随机彩色像素的位图,然后仅在近距离放大时渲染网格线。在性能方面,这实际上比预期更好。不过,我应该注意,如果你试图缩放到50%以下,应用程序会崩溃。不确定网格线是以微小尺寸绘制(IsGridVisible = true,其中ZoomValue&lt; 0.5)还是生成位图是一个问题。无论哪种方式,欢呼!

<强>更新

没有意识到网格线仍然落后于画布的内容。尚未找到解决方案......

更新2

替换:

<local:MyCanvas ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}">
    <local:MyCanvas.LayoutTransform>
        <ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
        </local:MyCanvas.LayoutTransform>
        <Image Canvas.Top="1" Canvas.Left="1" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</local:MyCanvas>

使用:

<Grid>
    <Canvas>
        <Canvas.LayoutTransform>
            <ScaleTransform ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
        </Canvas.LayoutTransform>
        <Image Canvas.Top="5" Canvas.Left="5" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
    </Canvas>
    <local:MyGrid ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}">
        <local:MyGrid.LayoutTransform>
            <ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
        </local:MyGrid.LayoutTransform>
    </local:MyGrid>
</Grid>

我相信性能的另一个提升,因为我们使用更简单的控件来显示网格线,此外,网格线可以放置在所需控件的下方或上方。

更新3

我决定发布我的最新解决方案,效率明显提高,可以在XAML中完成:

<Grid>
    <Grid.Background>
        <DrawingBrush Viewport="0,0,5,5" ViewportUnits="Absolute" TileMode="Tile">
            <DrawingBrush.Drawing>
                <DrawingGroup>
                    <DrawingGroup.Children>
                        <GeometryDrawing Geometry="M-.5,0 L50,0 M0,10 L50,10 M0,20 L50,20 M0,30 L50,30 M0,40 L50,40 M0,0 L0,50 M10,0 L10,50 M20,0 L20,50 M30,0 L30,50 M40,0 L40,50">
                            <GeometryDrawing.Pen>
                                <Pen Thickness="1" Brush="Black" />
                            </GeometryDrawing.Pen>
                        </GeometryDrawing>
                    </DrawingGroup.Children>
                </DrawingGroup>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </Grid.Background>
</Grid>