1
votes

I'm trying to implement some WPF drawing sample to understand how it works. I can solve such task with C++ very quickly but I want to understand WPF means.

During the implementation of a task I faced with some strange problem: coordinates shift of mouse cursor responsible to pixels I can see on canvas.

First of all, my task is: load some picture from file; show it on Image component; allow to draw over image with mouse (like pencil tool); save changes to new file. Task is easy to implement.

Here is my code:

XAML:

<Window x:Class="MyPaint.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:MyPaint"
    xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
    Background="#FF000000" 
    mc:Ignorable="d"
    Title="Strange Paint" Height="503.542" Width="766.281" Icon="icons/paint.png">

<Grid >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="50"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid Grid.Column="0" Grid.Row="1" Width="Auto">
        <StackPanel HorizontalAlignment="Left" Width="Auto" Background="{x:Null}">
            <Button x:Name="arrowButton" Width="25" Height="25" HorizontalAlignment="Left" Click="ArrowButton_Click">
                <Image Source="icons/arrow.png"/>
            </Button>
            <Button x:Name="selectorButton" Width="25" Height="25" Click="SelectorButton_Click" HorizontalAlignment="Left">
                <Image Source="icons/select_selection_tool-128.png"/>
            </Button>
            <Button x:Name="clearButton" Width="25" Height="25" Click="ClearButton_Click" HorizontalAlignment="Left">
                <Image Source="icons/clear.png"/>
            </Button>
            <Button x:Name="pencilButton" Width="25" Height="25" Click="PencilButton_Click" HorizontalAlignment="Left">
                <Image Source="icons/pencil.png"/>
            </Button>
            <Button x:Name="fillButton" Width="25" Height="25" Click="FillButton_Click" HorizontalAlignment="Left">
                <Image Source="icons/fill.png"/>
            </Button>
            <xctk:ColorPicker Width="50"  Name="ClrPcker_Foreground" SelectedColorChanged="ClrPcker_Foreground_SelectedColorChanged">

            </xctk:ColorPicker>
        </StackPanel>
    </Grid>
    <Grid x:Name="drawingCanvas" Grid.Column="1" Grid.Row="1" MouseMove="paintImageCanvas_MouseMove"  MouseLeave="PaintImageCanvas_MouseLeave" MouseLeftButtonUp="PaintImageCanvas_MouseLeftButtonUp">
        <ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <Canvas x:Name="scrCanvas" Width="{Binding ActualWidth, ElementName=paintImageCanvas}" Height="{Binding ActualHeight, ElementName=paintImageCanvas}" >
                <Image x:Name="paintImageCanvas" HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="UniformToFill" MouseDown="paintImageCanvas_MouseDown" MouseMove="paintImageCanvas_MouseMove">

                </Image>
                <Rectangle x:Name="Rect" Stroke="DarkOrange" Visibility="Collapsed" Fill="#77EEEEEE"></Rectangle>
            </Canvas>
        </ScrollViewer>
    </Grid>

    <StackPanel Grid.Row="0">
        <Menu IsMainMenu="True" DockPanel.Dock="Top" Background="#FF000000">
            <MenuItem Header="_File" Foreground="White"  Background="#FF000000">
                <MenuItem x:Name="newMenuItem" Header="_New"  Background="#FF000000" Click="NewMenuItem_Click"/>
                <MenuItem x:Name="openMenuItem" Header="_Open" Click="openMenuItem_Click"  Background="#FF000000"/>
                <MenuItem Header="_Close"  Background="#FF000000"/>
                <MenuItem Header="_Save"  Background="#FF000000" Click="MenuItem_Click"/>
                <MenuItem x:Name="exitMenuItem" Header="_Exit" Click="exitMenuItem_Click"  Background="#FF000000"/>
            </MenuItem>
        </Menu>
        <StackPanel></StackPanel>
    </StackPanel>


</Grid>

Implementation of window class:

Class members:

Point currentPoint = new Point();

    ToolBoxTypes currentSelectedTool = ToolBoxTypes.Unknown;
    Color foregroundColor = Brushes.Black.Color;

    WriteableBitmap imageWriteableBitmap;

Constructor and initialization (init white canvas 1024x768):

        public MainWindow()
    {
        InitializeComponent();
        ClrPcker_Foreground.SelectedColor = foregroundColor;

        imageWriteableBitmap = BitmapFactory.New(1024, 768);
        paintImageCanvas.Source = imageWriteableBitmap;

        imageWriteableBitmap.Clear(Colors.White);
        int i = 0;

    }

Mouse down event (here I get the first point):

private void paintImageCanvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ButtonState == MouseButtonState.Pressed)
        {
            currentPoint = e.GetPosition(paintImageCanvas);
        }
        if (currentSelectedTool == ToolBoxTypes.PencilTool)
        {

        }
    }

Mouse move event (draw on canvas if pressed):

        private void paintImageCanvas_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if(currentSelectedTool == ToolBoxTypes.PencilTool)
            {
                int x1 = Convert.ToInt32(currentPoint.X);
                int y1 = Convert.ToInt32(currentPoint.Y);

                int x2 = Convert.ToInt32(e.GetPosition(paintImageCanvas).X);
                int y2 = Convert.ToInt32(e.GetPosition(paintImageCanvas).Y);

                Console.WriteLine("Mouse X: " + x2 + " Mouse Y: " + y2);
                imageWriteableBitmap.DrawLine(  x1, y1, x2, y2, foregroundColor );



                currentPoint = e.GetPosition(paintImageCanvas);
            }               
        }
    }

Ok. That's easy code.

And now, two usecases:

  1. On start and init I can see white canvas and can draw with mouse without any problem (cursor follows pixels):

usecase 1

  1. I loaded picture (size is 700x600) and got a problem, cursor has different place (can see a shift):

usecase 2

I think that problem is that canvas (Image) has different side than actual picture's side. I'm not sure.

Could you help me please to understand what is wrong and how to fix that?

Thanks.

1
Did you try to replace UniformToFill with None? UniformToFill actually allows the image to overflow in a way to fill all the visible area. With None it will not resize the image inside the Image control.Dmitry
Yeah. I tried: UniformToFill, Fill, None. Same result :(Victor
Ok. And do you know the DPI of your picture? Is it 96?Dmitry
picture has 72 dpiVictor
Hm.... interesting.... with 96 dpi it works like magic.Victor

1 Answers

0
votes

Thanks to Dmitry (see comments on my question) the reason of the problem has been found: DPI of source picture.

I changed my code of picture loading and it works fine:

private void openMenuItem_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Filter = "JPEG files (*.jpg)|*.jpg|PNG files (*.png)|*.png";
        if (openFileDialog.ShowDialog() == true)
        {
            BitmapImage image = new BitmapImage(new Uri(openFileDialog.FileName));                
            imageWriteableBitmap = new WriteableBitmap(image);
            double dpi = 96;
            int width = imageWriteableBitmap.PixelWidth;
            int height = imageWriteableBitmap.PixelHeight;

            int stride = width * 4; 
            byte[] pixelData = new byte[stride * height];
            imageWriteableBitmap.CopyPixels(pixelData, stride, 0);

            BitmapSource bmpSource = BitmapSource.Create(width, height, dpi, dpi, PixelFormats.Bgra32, null, pixelData, stride);

            imageWriteableBitmap = new WriteableBitmap(
                                        bmpSource.PixelWidth,
                                        bmpSource.PixelHeight,
                                        bmpSource.DpiX, bmpSource.DpiY,
                                        bmpSource.Format, null);
            imageWriteableBitmap.WritePixels(
              new Int32Rect(0, 0, bmpSource.PixelWidth, bmpSource.PixelHeight), pixelData, stride, 0);

            paintImageCanvas.Source = imageWriteableBitmap;


        }
    }