Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use XOR mode when calling DrawRectangle method in C#

As the title says, I want to paint something with XOR mode because I want to clean it after a period of time.

I'm using C# (Window Form) with Visual Studio 2010.

Can anybody help me with this?

like image 883
Võ Quang Hòa Avatar asked Feb 21 '23 17:02

Võ Quang Hòa


2 Answers

I made a nice extension for .net:

public static class XorDrawing
{

    [DllImport("gdi32.dll", EntryPoint = "SetROP2", CallingConvention = CallingConvention.StdCall)]
    private extern static int SetROP2(IntPtr hdc, int fnDrawMode);

    [DllImport("gdi32.dll", EntryPoint = "MoveToEx", CallingConvention = CallingConvention.StdCall)]
    private extern static bool MoveToEx(IntPtr hdc, int x, int y, IntPtr lpPoint);

    [DllImport("gdi32.dll", EntryPoint = "LineTo", CallingConvention = CallingConvention.StdCall)]
    private extern static bool LineTo(IntPtr hdc, int x, int y);

    [DllImport("gdi32.dll", SetLastError = true)]
    static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
    public static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj);

    [DllImport("gdi32.dll")]
    static extern bool DeleteObject(IntPtr target);

    [DllImport("gdi32.dll")]
    static extern IntPtr CreatePen(PenStyle fnPenStyle, int nWidth, uint crColor);

    [DllImport("gdi32.dll")]
    static extern bool SetWorldTransform(IntPtr hdc, [In] ref XFORM lpXform);

    [DllImport("gdi32.dll")]
    public static extern int SetGraphicsMode(IntPtr hdc, int iMode);

    /// <summary>
    ///   The XFORM structure specifies a world-space to page-space transformation.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct XFORM
    {
        public float eM11;
        public float eM12;
        public float eM21;
        public float eM22;
        public float eDx;
        public float eDy;

        public XFORM(float eM11, float eM12, float eM21, float eM22, float eDx, float eDy)
        {
            this.eM11 = eM11;
            this.eM12 = eM12;
            this.eM21 = eM21;
            this.eM22 = eM22;
            this.eDx = eDx;
            this.eDy = eDy;
        }

        /// <summary>
        ///   Allows implicit converstion to a managed transformation matrix.
        /// </summary>
        public static implicit operator System.Drawing.Drawing2D.Matrix(XFORM xf)
        {
            return new System.Drawing.Drawing2D.Matrix(xf.eM11, xf.eM12, xf.eM21, xf.eM22, xf.eDx, xf.eDy);
        }

        /// <summary>
        ///   Allows implicit converstion from a managed transformation matrix.
        /// </summary>
        public static implicit operator XFORM(System.Drawing.Drawing2D.Matrix m)
        {
            float[] elems = m.Elements;
            return new XFORM(elems[0], elems[1], elems[2], elems[3], elems[4], elems[5]);
        }
    }

    public enum BinaryRasterOperations
    {
        R2_BLACK = 1,
        R2_NOTMERGEPEN = 2,
        R2_MASKNOTPEN = 3,
        R2_NOTCOPYPEN = 4,
        R2_MASKPENNOT = 5,
        R2_NOT = 6,
        R2_XORPEN = 7,
        R2_NOTMASKPEN = 8,
        R2_MASKPEN = 9,
        R2_NOTXORPEN = 10,
        R2_NOP = 11,
        R2_MERGENOTPEN = 12,
        R2_COPYPEN = 13,
        R2_MERGEPENNOT = 14,
        R2_MERGEPEN = 15,
        R2_WHITE = 16
    }

    private enum PenStyle : int
    {
        PS_SOLID = 0, //The pen is solid.
        PS_DASH = 1, //The pen is dashed.
        PS_DOT = 2, //The pen is dotted.
        PS_DASHDOT = 3, //The pen has alternating dashes and dots.
        PS_DASHDOTDOT = 4, //The pen has alternating dashes and double dots.
        PS_NULL = 5, //The pen is invisible.
        PS_INSIDEFRAME = 6,// Normally when the edge is drawn, it’s centred on the outer edge meaning that half the width of the pen is drawn
        // outside the shape’s edge, half is inside the shape’s edge. When PS_INSIDEFRAME is specified the edge is drawn 
        //completely inside the outer edge of the shape.
        PS_USERSTYLE = 7,
        PS_ALTERNATE = 8,
        PS_STYLE_MASK = 0x0000000F,

        PS_ENDCAP_ROUND = 0x00000000,
        PS_ENDCAP_SQUARE = 0x00000100,
        PS_ENDCAP_FLAT = 0x00000200,
        PS_ENDCAP_MASK = 0x00000F00,

        PS_JOIN_ROUND = 0x00000000,
        PS_JOIN_BEVEL = 0x00001000,
        PS_JOIN_MITER = 0x00002000,
        PS_JOIN_MASK = 0x0000F000,

        PS_COSMETIC = 0x00000000,
        PS_GEOMETRIC = 0x00010000,
        PS_TYPE_MASK = 0x000F0000
    };

    public enum GraphicsMode : int
    {   
        GM_COMPATIBLE = 1,
        GM_ADVANCED = 2,
    }

    private static IntPtr BeginDraw(System.Drawing.Bitmap bmp, System.Drawing.Graphics graphics, int x1, int y1, int x2, int y2, bool dash, out int oldRop, out IntPtr img, out IntPtr oldpen)
    {
        var gHdc = graphics.GetHdc();
        var hdc = CreateCompatibleDC(gHdc);
        graphics.ReleaseHdc(hdc);

        img = bmp.GetHbitmap();
        SelectObject(hdc, img);

        oldpen = IntPtr.Zero;
        if (dash)
        {
            var pen = CreatePen(PenStyle.PS_DASH, 1, 0);
            oldpen = SelectObject(hdc, pen);
        }
        oldRop = SetROP2(hdc, (int)BinaryRasterOperations.R2_NOTXORPEN); // Switch to inverted mode. (XOR)

        SetGraphicsMode(hdc, (int)GraphicsMode.GM_ADVANCED);
        XFORM transform = graphics.Transform;
        SetWorldTransform(hdc, ref transform);

        return hdc;
    }


    private static void FinishDraw(System.Drawing.Bitmap bmp, System.Drawing.Graphics graphics, IntPtr hdc, IntPtr oldpen, int oldRop, IntPtr img, bool dash)
    {
        SetROP2(hdc, oldRop);

        var transform = graphics.Transform;
        graphics.ResetTransform(); //in case there is transform
        var outBmp = System.Drawing.Image.FromHbitmap(img);
        //CopyChannel(bmp, outBmp, ChannelARGB.Alpha, ChannelARGB.Alpha);
        graphics.Clear(Color.Transparent);
        graphics.DrawImage(outBmp, 0, 0); //draw the xored image on the bitmap
        graphics.Transform = transform;

        if (dash) DeleteObject(SelectObject(hdc, oldpen)); //delete new pen (switch to oldpen)
        DeleteObject(img); // Delete the GDI bitmap (important).
        DeleteObject(hdc);
    }

    public static void DrawXorLine(this System.Drawing.Graphics graphics, System.Drawing.Bitmap bmp, int x1, int y1, int x2, int y2, bool dash = true)
    {
        int oldRop;
        IntPtr oldpen, img;
        var hdc = BeginDraw(bmp, graphics, x1, y1, x2, y2, dash, out oldRop, out img, out oldpen);

        MoveToEx(hdc, x1, y1, IntPtr.Zero);
        LineTo(hdc, x2, y2);

        FinishDraw(bmp, graphics, hdc, oldpen, oldRop, img, dash);
    }

    public static void DrawXorRectangle(this System.Drawing.Graphics graphics, System.Drawing.Bitmap bmp, int x1, int y1, int x2, int y2, bool dash = true)
    {
        int oldRop;
        IntPtr oldpen, img;
        var hdc = BeginDraw(bmp, graphics, x1, y1, x2, y2, dash, out oldRop, out img, out oldpen);

        MoveToEx(hdc, x1, y1, IntPtr.Zero); //clockwise
        LineTo(hdc, x2, y1);
        LineTo(hdc, x2, y2);
        LineTo(hdc, x1, y2);
        LineTo(hdc, x1, y1);

        FinishDraw(bmp, graphics, hdc, oldpen, oldRop, img, dash);
    }
}

Usage

g.DrawXorRectangle(bmp, outRect.Left, outRect.Top, outRect.Left + outRect.Width, outRect.Top + outRect.Height);

Or

g.DrawXorLine(bmp, x1, y1, x2, y2);
like image 57
EliSherer Avatar answered Mar 25 '23 05:03

EliSherer


You can use Windows API functions. I wrapped the imports in the static class Win32.

public static class Win32
{
    [DllImport("gdi32.dll", EntryPoint = "SetROP2", CallingConvention = CallingConvention.StdCall)]
    public extern static int SetROP2(IntPtr hdc, int fnDrawMode);

    [DllImport("user32.dll", EntryPoint = "GetDC", CallingConvention = CallingConvention.StdCall)]
    public extern static IntPtr GetDC(IntPtr hWnd);

    [DllImport("user32.dll", EntryPoint = "ReleaseDC", CallingConvention = CallingConvention.StdCall)]
    public extern static IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll", EntryPoint = "MoveToEx", CallingConvention = CallingConvention.StdCall)]
    public extern static bool MoveToEx(IntPtr hdc, int x, int y, IntPtr lpPoint);

    [DllImport("gdi32.dll", EntryPoint = "LineTo", CallingConvention = CallingConvention.StdCall)]
    public extern static bool LineTo(IntPtr hdc, int x, int y);

    public const int R2_NOT = 6;  // Inverted drawing mode

}

Using these definitions you can draw like this

IntPtr hdc = Win32.GetDC(IntPtr.Zero); // Get device context.
Win32.SetROP2(hdc, Win32.R2_NOT); // Switch to inverted mode. (XOR)
Win32.MoveToEx(hdc, x1, y1, IntPtr.Zero);
Win32.LineTo(hdc, x2, y2);
Win32.ReleaseDC(IntPtr.Zero, hdc); // Release device context.

Note that the standard drawing functions provided by .NET through the Graphics object do not work in inverted mode. You must use the functions of the API, shown here with MoveToEx and LineTo as an example.


I extracted these examples from my Code Project article Drag-and-Drop ListBox.

like image 31
Olivier Jacot-Descombes Avatar answered Mar 25 '23 06:03

Olivier Jacot-Descombes