Fighting notes when trying to do the same thing as Java's XOR Mode in C #

Introduction

We decided to reimplement the existing Java applet in C # on the .Net Framework. The original program was a trowel drawing app, and after conducting various researches for porting, the one that seemed to be the biggest bottleneck was XOR Mode. Make a note of what I did to achieve this in C #.

What is XOR Mode?

Regarding XORMode, first of all, please refer to "What is setXORMode in the Graphics class of java" of Yahoo Answers. You probably don't know what it is. I didn't really understand either.

In such a case, it is limited to understanding from the purpose. For example, let's say you draw a line with a drawing program that uses a mouse. As a user operation, you should click the mouse at the position where you want to start the line and mouse up at the position where you want to end the line. In that case, whether it is Java or C #, I think that the implementation will be as follows.

1 Record the clicked coordinates (let's say x0, y0) with the mouse click event handler. 2 Draw a line connecting the coordinates moved by the mouse and (x0, y0) with the event handler of the mouse move. This is a state in which the user is moving the mouse to determine the end position of the line, and a temporary line is being drawn, as it has not yet been determined where to draw the line. (Note) At this time, every time the mouse is moved, the line drawn during the previous mouse move is deleted. 3 Use the mouse-up event handler to get the mouse-up coordinates (let's say x1, y1) and draw a line connecting (x0, y0) and (x1, y1). This confirms the drawing of the line.

Here, if there is no "process to erase the line at the time of the previous mouse move" described in (Note), the line will increase every time the mouse is moved as shown below. Become.

image.png

Now, what should I do with "erasing the line drawn during the last mouse move"? You might think that you can overwrite the same line as last time with a different color and a background color (white in this case). Sure, that erases the previous line, but it overwrites what was originally drawn on the graphic object with a white line before drawing the line. Below are some lines drawn this way. You can see that the lines drawn in the past are faint.

image.png

** That's where XOR Mode comes in. If you draw twice with the same content (same color, same position, same shape, etc.) in XOR Mode, it will completely return to the original state, including the content drawn before drawing the line. It is also called "reverse drawing". ** **

In other words, it can be said that it is an indispensable technique for drawing moving objects by moving the mouse, dragging the mouse, etc. (I didn't know it before (laughs)).

By the way, looking at the original Java program, every time I draw a moving object, the process of overwriting the previously drawn object in XOR Mode is persistently repeated.

Now that you have a general idea of what XORMode is, let's write a record that we struggled to achieve in C #.

environment

This time, we investigated in the following environment.

First, only with the standard API of .Net Framework

First, when I checked for XORMode with .Net Framework, I found Invert the display color and draw a line. "By using the DrawReversibleLine method of the ControlPaint class, you can invert the display color and draw a line. You can also draw a frame with the DrawReversibleFrame method of the ControlPaint class and a square filled with the FillReversibleRectangle method. ."it is written like this.

Based on this, I implemented it as follows. Since it is implemented by user control to move it, it is necessary to paste it on the form. In addition, it is necessary to link UserControl1_MouseDown to the MouseDown event, UserControl1_MouseMove to the MouseMove event, and UserControl1_MouseUp to the MouseUp event in Visual Studio.

In this source, `ControlPaint.DrawReversibleLine``` is used to draw a line, and `ControlPaint.FillReversibleRectangle``` is used to draw a rectangle. In both cases, the point is that during MouseMove, the processing is described twice, one for erasing the previous drawing and the other for this drawing.

UserControl1.cs


using System;
using System.Drawing;
using System.Windows.Forms;


namespace EditorSample
{
    public partial class UserControl1 : UserControl
    {

        /**
        *Drawing color
        */
        Color foreC = Color.Black;
        /**
        *Background drawing color
        */
        Color backC = Color.White;

        //Recording mouse events
        int x0, y0, x1, y1;

        enum Status
        {
            None,
            Draw,
        }

        Status status = Status.None;

        public UserControl1()
        {
            InitializeComponent();
        }

        private void UserControl1_Load(object sender, EventArgs e)
        {
            x0 = y0 = x1 = y1 = -1;
        }

        private void UserControl1_MouseDown(object sender, MouseEventArgs e)
        {
            Console.WriteLine("mouseDown" + "(" + e.X + "," + e.Y);	

            //Record click position
            x0 = x1 = e.X;
            y0 = y1 = e.Y;
            status = Status.Draw;
        }

        private void UserControl1_MouseMove(object sender, MouseEventArgs e)
        {
            Console.WriteLine("mouseMove" + "(" + e.X + "," + e.Y);
            Graphics g = CreateGraphics();

            //-----------------------------
            //Move
            //-----------------------------

            /*Rectangle.Net standard method*/
            if (x1 > 0)
            {
                ControlPaint.FillReversibleRectangle(new Rectangle(this.PointToScreen(new Point(x1, y1)), new Size(10, 10)), foreC);
            }
            ControlPaint.FillReversibleRectangle(new Rectangle(this.PointToScreen(new Point(e.X, e.Y)), new Size(10, 10)), foreC);

            //-----------------------------
            //drawing
            //-----------------------------
            if (status == Status.Draw)
            {
                Console.WriteLine("mouseDrug" + "(" + e.X + "," + e.Y);
                Pen pen = new Pen(foreC);

                ControlPaint.DrawReversibleLine(this.PointToScreen(new Point(x1, y1)), this.PointToScreen(new Point(x0, y0)), foreC);
                ControlPaint.DrawReversibleLine(this.PointToScreen(new Point(e.X, e.Y)), this.PointToScreen(new Point(x0, y0)), foreC);
            }

            //Record current position
            x1 = e.X;
            y1 = e.Y;
        }

        private void UserControl1_MouseUp(object sender, MouseEventArgs e)
        {
            Console.WriteLine("mouseUp" + "(" + e.X + "," + e.Y);

            if (status == Status.Draw)
            {
                Graphics g = CreateGraphics();
                Pen pen = new Pen(foreC);
                g.DrawLine(pen, new Point(e.X, e.Y), new Point(x0, y0));
                status = Status.None;
            }
            
            ControlPaint.FillReversibleRectangle(new Rectangle(this.PointToScreen(new Point(e.X, e.Y)), new Size(10, 10)), foreC);
        }
    }
}

Both the line and the rectangle are displayed without any problem following the movement of the mouse. However, ControlPaint does not provide methods for circles and polygons. Since the source to be ported this time includes the drawOval method that draws a circle and the drawPolygon method that draws a polygon in the Java Graphics object, it turned out that it cannot be realized as it is.

Next move

As a result of further investigation, https://github.com/EWSoftware/ImageMaps/blob/master/Source/WinForms/UnsafeNativeMethods.cs /WinForms/UnsafeNativeMethods.cs) provided an example of calling gdi32.dll to realize reverse drawing with a circle or polygon.

I took this source into the project and implemented it as follows. Then, I was able to draw the circles and polygons in reverse.

UserControl01.cs


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using EWSoftware.ImageMaps;

namespace EditorSample
{
    public partial class UserControl1 : UserControl
    {

        /**
        *Drawing color
        */
        Color foreC = Color.Black;
        /**
        *Background drawing color
        */
        Color backC = Color.White;

        //Recording mouse events
        int x0, y0, x1, y1;

        enum Status
        {
            None,
            Draw,
        }

        Status status = Status.None;

        public UserControl1()
        {
            InitializeComponent();
        }

        private void UserControl1_Load(object sender, EventArgs e)
        {
            x0 = y0 = x1 = y1 = -1;
        }

        private void UserControl1_MouseDown(object sender, MouseEventArgs e)
        {
            Console.WriteLine("mouseDown" + "(" + e.X + "," + e.Y);	

            //Record click position
            x0 = x1 = e.X;
            y0 = y1 = e.Y;
            status = Status.Draw;
        }

        private void UserControl1_MouseMove(object sender, MouseEventArgs e)
        {
            Console.WriteLine("mouseMove" + "(" + e.X + "," + e.Y);
            Graphics g = CreateGraphics();

            //-----------------------------
            //Move
            //-----------------------------

            /*Method using the yen library*/
            if (x1 > 0)
            {
                UnsafeNativeMethods.DrawReversibleCircle(g, new Point(x1, y1), 10, new Point(0, 0));
            }
            UnsafeNativeMethods.DrawReversibleCircle(g, new Point(e.X, e.Y), 10, new Point(0, 0));


            /*Method using polygon library*/
            if (x1 > 0) { 
                List<Point> list_old = new List<Point>();
                list_old.Add(new Point(x1, y1));
                list_old.Add(new Point(x1 + 20, y1 - 10));
                list_old.Add(new Point(x1 + 20, y1 + 10));
                UnsafeNativeMethods.DrawReversiblePolygon(g, list_old, true, new Point(0, 0));
            }
            List<Point> list_new = new List<Point>();
            list_new.Add(new Point(e.X, e.Y));
            list_new.Add(new Point(e.X+20, e.Y-10));
            list_new.Add(new Point(e.X+20, e.Y+10));
            UnsafeNativeMethods.DrawReversiblePolygon(g, list_new, true, new Point(0, 0));

            //-----------------------------
            //drawing
            //-----------------------------
            if (status == Status.Draw)
            {
                Console.WriteLine("mouseDrug" + "(" + e.X + "," + e.Y);
                Pen pen = new Pen(foreC);

                ControlPaint.DrawReversibleLine(this.PointToScreen(new Point(x1, y1)), this.PointToScreen(new Point(x0, y0)), foreC);
                ControlPaint.DrawReversibleLine(this.PointToScreen(new Point(e.X, e.Y)), this.PointToScreen(new Point(x0, y0)), foreC);
            }

            //Record current position
            x1 = e.X;
            y1 = e.Y;
    }

        private void UserControl1_MouseUp(object sender, MouseEventArgs e)
        {
            Console.WriteLine("mouseUp" + "(" + e.X + "," + e.Y);

            if (status == Status.Draw)
            {
                Graphics g = CreateGraphics();
                Pen pen = new Pen(foreC);
                g.DrawLine(pen, new Point(e.X, e.Y), new Point(x0, y0));
                status = Status.None;
            }

        }
    }
}

However, this library doesn't have a fill. Well, I didn't need it, so I'm just not implementing it. That said, I have no choice but to stick to this library, so I have to manage to fill it. I continued to search on Google with as many keywords as I could think of.

For the time being, the yen

I don't remember how I searched for it, but for the rush circle, the color is at IntPtr oldBrush = SelectObject (hDC, GetStockObject (NULL_BRUSH));` `` in the UnsafeNativeMethods.DrawReversibleCircle``` method of the library. Since it is not specified, here

IntPtr oldBrush = SelectObject(hDC, CreateSolidBrush(ColorTranslator.ToWin32(backColor)));


 It turned out that if you replace it with, it will be filled with the color of the Color object specified by backColor.

# Last Difficult Polygon
 The final challenge is the polygon fill. The library `` `DrawReversiblePolygon``` was drawing a line several times to create a polygon. With this, it seems difficult to fill the contents in principle. So, when I searched on Google again, http://wisdom.sakura.ne.jp/system/winapi/win32/win29.html
 There is a native API of Don Pisha called `` `Polygon```, and it seems that it can be filled by setting SetPolyFillMode and calling it. So I tried the following.

 First, I modified the source of the library and added a setting to import Polygon and SetPolyFillMode. We also defined a POINT API structure to give polygonal coordinates to the Polygon function.

```csharp
        public struct POINTAPI
        {
            public int x;
            public int y;
        }

        [DllImport("gdi32.dll")]
        public static extern int Polygon(IntPtr hDC, ref POINTAPI lpPoint, int nCount);

        [DllImport("gdi32.dll")]C
        public static extern int SetPolyFillMode(IntPtr hdc, int nPolyFillMode);

And I implemented the following FillReversiblePolygon method. The points are the place where you are instructing to fill all with ``` SetPolyFillMode (hDC, 2);` ``, and the points in the .Net List are converted to the POINTAPI array and the argument of the Polygon function I'm setting it to.

        internal static void FillReversiblePolygon(Graphics g, List<Point> points, Point offset, Color backColor)
        {

            IntPtr hDC = g.GetHdc();
            SetPolyFillMode(hDC, 2);

            IntPtr pen = CreatePen(PS_SOLID, 1, ColorTranslator.ToWin32(Color.Black));
            IntPtr brush = CreateSolidBrush(ColorTranslator.ToWin32(backColor));

            int oldROP = SetROP2(hDC, R2_NOTXORPEN);
            IntPtr oldBrush = SelectObject(hDC, brush);
            IntPtr oldPen = SelectObject(hDC, pen);

            SetBkColor(hDC, ColorTranslator.ToWin32(Color.White));


            POINTAPI[] pointsArray = new POINTAPI[points.Count];
            for (int i = 0; i < points.Count; i++)
            {
                pointsArray[i].x = points[i].X;
                pointsArray[i].y = points[i].Y;
            }
            Polygon(hDC, ref pointsArray[0], Enumerable.Count(points));

            SelectObject(hDC, oldPen);
            SelectObject(hDC, oldBrush);
            SetROP2(hDC, oldROP);
            DeleteObject(pen);
            DeleteObject(brush);
            g.ReleaseHdc(hDC);
        }

Now, the set is ready. Let's call it from the user control. Let's change the call of UnsafeNativeMethods.DrawReversiblePolygon in the previous source as follows. Color.Black indicates the fill color.

            /*Method using polygon library*/
            if (x1 > 0) { 
                List<Point> list_old = new List<Point>();
                list_old.Add(new Point(x1, y1));
                list_old.Add(new Point(x1 + 20, y1 - 10));
                list_old.Add(new Point(x1 + 20, y1 + 10));
                UnsafeNativeMethods.FillReversiblePolygon(g, list_old, new Point(e.X, 0), Color.Black);
            }

            List<Point> list_new = new List<Point>();
            list_new.Add(new Point(e.X, e.Y));
            list_new.Add(new Point(e.X+20, e.Y-10));
            list_new.Add(new Point(e.X+20, e.Y+10));
            UnsafeNativeMethods.FillReversiblePolygon(g, list_new, new Point(e.X, 0), Color.Black);

Then, the polygon was also displayed without any problem following the mouse in the state of being filled in black. Hmmm, it's finally over.

in conclusion

It took a lot of time to do a technical investigation of the display of objects between mouse moves, which is not the main part of the drawing process, but that's what development is all about.

Now that your biggest concern has been cleared, let's say you have a main dish of porting the drawing process for various objects.

Postscript (2020/1/5)

In the UnsafeNativeMethods.DrawReversiblePolygon method, the process to delete the Brush created by CreateSolidBrush with DeleteObject was not included. If you don't do this, you risk running out of graphics resources if you use it for a long time.

References

Recommended Posts

Fighting notes when trying to do the same thing as Java's XOR Mode in C #
Implement the same function as C, C ++ system ("cls"); in Java
What to do when the changes in the Servlet are not reflected
It should be the same as the sample when logging in to Twitter, but an error occurs ~ resolution
The first thing to do when you want to be happy with Heroku on GitHub with Eclipse in Java
What to do if the prefix c is not bound in JSP
Notes on what to do when a WebView ClassNotFoundException occurs in JavaFX 12
What to do when the value becomes null in the second getSubmittedValue () in JSF Validator
What to do when rails db: seed does not reflect in the database
The reason why haze remains when trying to regard object orientation as "English"
An embarrassing story that was treated as the same day when trying to compare dates on 3/31 and 4/1 [Java / Calendar]
When the server fails to start in Eclipse
What to do when IllegalStateException occurs in PlayFramework
[React.useRef] What to do when the latest state cannot be referenced in the event listener
What to do when "Fail to load the JNI shared library" is displayed in Eclipse