Une lutte en essayant de faire la même chose que le mode XOR de Java en C #

introduction

Nous avons décidé de réimplémenter l'applet Java existant en C # sur le .Net Framework. Le programme original était une application de dessin à la truelle, et après avoir mené diverses recherches pour le portage, celui qui semblait être le plus gros goulot d'étranglement était le mode XOR. Notez ce que j'ai fait pour y parvenir en C #.

Qu'est-ce que le mode XOR?

Concernant XORMode, reportez-vous d'abord à "Qu'est-ce que setXORMode dans la classe Graphics de java" de Yahoo Chiebukuro. Vous ne savez probablement pas ce que c'est. Je n'ai pas vraiment compris non plus.

Dans un tel cas, il se limite à la compréhension du but. Par exemple, disons que vous tracez une ligne avec un programme de dessin utilisant une souris. En tant qu'opération utilisateur, vous devez cliquer sur la souris à la position où vous souhaitez commencer la ligne et lever la souris à la position où vous souhaitez terminer la ligne. Dans ce cas, que ce soit Java ou C #, je pense que l'implémentation sera la suivante.

1 Enregistrez les coordonnées cliquées (disons x0, y0) dans le gestionnaire d'événements du clic de souris. 2 Tracez une ligne reliant (x0, y0) aux coordonnées où la souris s'est déplacée avec le gestionnaire d'événements du déplacement de la souris. Il s'agit d'un état dans lequel l'utilisateur déplace la souris pour déterminer la position finale de la ligne, et une ligne temporaire est dessinée, car il n'a pas encore été déterminé où tracer la ligne. (Remarque) À ce stade, chaque fois que la souris est déplacée, la ligne dessinée lors du déplacement précédent de la souris est supprimée. 3 Utilisez le gestionnaire d'événements de la souris pour acquérir les coordonnées de la souris (disons x1, y1) et tracez une ligne reliant (x0, y0) et (x1, y1). Cela confirme le tracé de la ligne.

Ici, s'il n'y a pas de "processus pour effacer la ligne au moment du déplacement précédent de la souris" décrit dans (Note), la ligne augmentera chaque fois que la souris est déplacée comme indiqué ci-dessous. Devenir.

image.png

Maintenant, que dois-je faire avec "Effacer la ligne tracée lors du dernier déplacement de la souris"? Vous pourriez penser que vous pouvez écraser la même ligne que la dernière fois avec une couleur différente et une couleur d'arrière-plan (blanc dans ce cas). Bien sûr, cela efface la ligne précédente, mais cela écrase ce qui a été dessiné à l'origine sur l'objet graphique avec une ligne blanche avant de dessiner la ligne. Voici quelques lignes tracées de cette façon. Vous pouvez voir que les lignes tracées dans le passé sont pâles.

image.png

** C'est là qu'intervient le mode XOR. Si vous dessinez deux fois avec le même contenu (même couleur, même position, même forme, etc.) en mode XOR, il reviendra complètement à l'état d'origine, y compris le contenu que vous avez dessiné avant de dessiner la ligne. Il est également appelé "dessin inversé". ** **

En d'autres termes, on peut dire que c'est une technique indispensable pour dessiner des objets en mouvement en déplaçant la souris, en faisant glisser la souris, etc. (je ne le savais pas avant (rires)).

À propos, en regardant le programme Java original, chaque fois que je dessine un objet en mouvement, le processus d'écrasement de l'objet précédemment dessiné en mode XOR est répété de manière persistante.

Maintenant que vous avez une idée générale de ce qu'est XORMode, écrivons un enregistrement que nous avons eu du mal à réaliser en C #.

environnement

Cette fois, nous avons étudié dans l'environnement suivant.

Tout d'abord, uniquement avec l'API standard de .Net Framework

Tout d'abord, lorsque j'ai vérifié XORMode avec .Net Framework, j'ai trouvé Inverser la couleur d'affichage et dessiner une ligne. «En utilisant la méthode DrawReversibleLine de la classe ControlPaint, vous pouvez inverser la couleur d'affichage et tracer une ligne. Vous pouvez également dessiner un cadre avec la méthode DrawReversibleFrame de la classe ControlPaint et un carré rempli avec la méthode FillReversibleRectangle. . "c'est écrit comme ça.

Sur cette base, je l'ai implémenté comme suit. Puisqu'il est implémenté par le contrôle utilisateur pour le déplacer, il est nécessaire de le coller sur le formulaire. De plus, il est nécessaire de lier UserControl1_MouseDown à l'événement MouseDown, UserControl1_MouseMove à l'événement MouseMove et UserControl1_MouseUp à l'événement MouseUp dans Visual Studio.

Dans cette source, ControlPaint.DrawReversibleLine '' est utilisé pour dessiner une ligne, et ControlPaint.FillReversibleRectangle '' est utilisé pour dessiner un carré plein. Dans les deux cas, le fait est que lors de MouseMove, le traitement est décrit deux fois, l'un pour effacer le dessin précédent et l'autre pour ce dessin.

UserControl1.cs


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


namespace EditorSample
{
    public partial class UserControl1 : UserControl
    {

        /**
        *Couleur de dessin
        */
        Color foreC = Color.Black;
        /**
        *Couleur du dessin d'arrière-plan
        */
        Color backC = Color.White;

        //Enregistrer les événements de la souris
        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);	

            //Enregistrer la position du clic
            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();

            //-----------------------------
            //Bouge toi
            //-----------------------------

            /*Carré.Méthode standard nette*/
            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);

            //-----------------------------
            //dessin
            //-----------------------------
            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);
            }

            //Enregistrer la position actuelle
            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);
        }
    }
}

Les lignes et les carrés sont affichés sans aucun problème suite au mouvement de la souris. Cependant, ControlPaint ne fournit pas de méthodes pour les cercles et les polygones. Étant donné que la source à porter cette fois-ci comprend la méthode drawOval qui dessine un cercle et la méthode drawPolygon qui dessine un polygone dans l'objet Java Graphics, il s'est avéré qu'elle ne peut pas être réalisée telle quelle.

Prochaine étape

À la suite d'une enquête plus approfondie, https://github.com/EWSoftware/ImageMaps/blob/master/Source/WinForms/UnsafeNativeMethods.cs /WinForms/UnsafeNativeMethods.cs) a fourni un exemple d'appel de gdi32.dll pour réaliser un dessin inversé avec un cercle ou un polygone.

J'ai incorporé cette source dans le projet et l'ai implémentée comme suit. Ensuite, j'ai pu inverser les cercles et les polygones.

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
    {

        /**
        *Couleur de dessin
        */
        Color foreC = Color.Black;
        /**
        *Couleur du dessin d'arrière-plan
        */
        Color backC = Color.White;

        //Enregistrer les événements de la souris
        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);	

            //Enregistrer la position du clic
            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();

            //-----------------------------
            //Bouge toi
            //-----------------------------

            /*Méthode utilisant la bibliothèque de yens*/
            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));


            /*Méthode utilisant la bibliothèque de polygones*/
            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));

            //-----------------------------
            //dessin
            //-----------------------------
            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);
            }

            //Enregistrer la position actuelle
            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;
            }

        }
    }
}

Cependant, cette bibliothèque n'a pas de remplissage. Eh bien, je n'en avais pas besoin, donc je ne l'implémente tout simplement pas. Cependant, je n'ai pas d'autre choix que de m'en tenir à cette bibliothèque, donc je dois réussir à la remplir. J'ai continué à chercher sur Google avec autant de mots clés que je pouvais penser.

Pour le moment, le yen

Je ne me souviens pas comment je l'ai cherché, mais pour le cercle de précipitation, la couleur est à IntPtr oldBrush = SelectObject (hDC, GetStockObject (NULL_BRUSH))); `` `de la méthode` `ʻUnsafeNativeMethods.DrawReversibleCircle de la bibliothèque. Puisqu'il n'est pas spécifié, ici

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


 Il s'est avéré que si vous le remplacez par, il sera rempli avec la couleur de l'objet Color spécifié par backColor.

# Dernier polygone difficile
 Le dernier défi est le remplissage du polygone. La bibliothèque `` DrawReversiblePolygon '' dessinait plusieurs fois Line pour créer un polygone. Avec cela, il semble difficile de remplir le contenu en principe. Ainsi, lorsque j'ai cherché à nouveau sur Google, http://wisdom.sakura.ne.jp/system/winapi/win32/win29.html
 Il existe une API native de Don Pisha appelée `` Polygon '', et il semble qu'elle puisse être remplie en définissant SetPolyFillMode et en l'appelant. J'ai donc essayé ce qui suit.

 Tout d'abord, j'ai modifié la source de la bibliothèque et ajouté un paramètre pour importer Polygon et SetPolyFillMode. Nous avons également défini la structure de l'API POINT pour donner des coordonnées polygonales à la fonction Polygone.

```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);

Et j'ai implémenté la méthode FillReversiblePolygon suivante. Les points sont l'endroit où vous demandez de tout remplir avec SetPolyFillMode (hDC, 2);, et les points de la liste .Net sont convertis en tableau POINT API et l'argument de la fonction Polygon. Je le mets à.

        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);
        }

Maintenant, l'ensemble est prêt. Appelons-le à partir du contrôle utilisateur. Modifions l'appel en `` UnsafeNativeMethods.DrawReversiblePolygon '' dans la source précédente comme suit. Couleur: le noir indique la couleur de remplissage.

            /*Méthode utilisant la bibliothèque de polygones*/
            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);

Ensuite, le polygone a également été affiché sans aucun problème en suivant la souris dans l'état d'être rempli de noir. Hmmm, c'est enfin fini.

en conclusion

Il a fallu beaucoup de temps pour faire une enquête technique sur l'affichage des objets entre les mouvements de la souris, ce qui n'est pas la partie principale du processus de dessin, mais c'est là tout le développement.

Maintenant que votre plus grande préoccupation a été résolue, prenons le plat principal du portage du processus de dessin pour divers objets.

Post-scriptum (2020/1/5)

Dans la méthode UnsafeNativeMethods.DrawReversiblePolygon, le processus de suppression du pinceau créé par CreateSolidBrush avec DeleteObject n'était pas inclus. Si vous ne le faites pas, vous risquez de manquer de ressources graphiques si vous l'utilisez pendant une longue période.

Les références

Recommended Posts

Une lutte en essayant de faire la même chose que le mode XOR de Java en C #
Implémenter la même fonction que C, système C ++ ("cls"); en Java
Que faire lorsque les modifications du servlet ne sont pas reflétées
Il doit être identique à l'exemple lors de la connexion à Twitter, mais une erreur se produit jusqu'à la résolution
La première chose à faire lorsque vous voulez être satisfait d'Heroku sur GitHub avec Eclipse sur Java
Que faire lorsque le préfixe c n'est pas lié dans JSP
Remarques sur la marche à suivre lorsqu'une exception WebView ClassNotFoundException se produit dans JavaFX 12
Que faire lorsque la valeur devient nulle dans le second getSubmittedValue () dans JSF Validator
La raison pour laquelle la brume persiste lorsque l'on essaie de considérer l'orientation de l'objet comme "anglaise"
Une histoire embarrassante qui a été traitée comme le même jour en essayant de comparer les dates du 31/03 et du 01/04 [Java / Calendar]
Lorsque Eclipse ne parvient pas à démarrer le serveur
Que faire si IllegalStateException se produit dans PlayFramework
[React.useRef] Que faire lorsque le dernier état ne peut pas être référencé dans l'écouteur d'événements
Que faire lorsque «Échec du chargement de la bibliothèque partagée JNI» s'affiche dans Eclipse