0
votes

I am nearly there with this ... :)

I have implemented my own double buffer ...

So I create a bitmap:

if (_Bitmap != null) _Bitmap.Dispose();
if (_Graphics != null) _Graphics.Dispose();

_Bitmap = new Bitmap(Bounds.Width, Bounds.Height);
_Bitmap.MakeTransparent(Color.Transparent);
_Graphics = Graphics.FromImage(_Bitmap);
_Graphics.FillRectangle(Brushes.Transparent, Bounds);

I thought that I might have to manually set the bitmap as transparent.

In my handlers OnPaint method it does this:

protected override void OnPaint(PaintEventArgs e)
{
    if (_pDevice != null)
    {
        try
        {
            _pDevice.update();
            _Graphics.ReleaseHdc();

            if (_bZoomWindow)
            {
                //_Graphics.DrawRectangle(_selectionPen, _rcRubberBand);
                using (GraphicsPath gp = new GraphicsPath())
                {
                    gp.AddRectangle(_rcRubberBand);
                    gp.Widen(_selectionPen);
                    _Graphics.FillPath(Brushes.WhiteSmoke, gp);
                }
            }

            OdRxDictionary Properties = _graphicsDevice.properties();
            //if (helperDevice.UnderlyingDevice.Properties.Contains("WindowHDC"))
            //    helperDevice.UnderlyingDevice.Properties.AtPut("WindowHDC", new RxVariant((Int32)graphics.GetHdc()));
            if (Properties.ContainsKey("WindowHDC"))
                Properties.putAt("WindowHDC", new OdRxVariantValue(_Graphics.GetHdc().ToInt32())); // hWnd necessary for DirectX device
        }
        catch (System.Exception ex)
        {
            _Graphics.DrawString(ex.ToString(), new Font("Arial", 16), new SolidBrush(Color.Black), new PointF(150.0F, 150.0F));
        }

        e.Graphics.DrawImageUnscaled(_Bitmap, 0, 0);
    }
}

The problem is that the rectangle is drawing with a black background. So it is obliterating the drawing underneath that is on the bitmap:

Zoom window

How do I draw just the rectangle? What am I missing? I am sorry if this is a dumb question!

1
I think there's a Graphics.DrawRectangle method that will draw just the lines... - jimbobmcgee
FillRectangle(Brushes.Transparent.. should work but unfortunately it doesn't make the pixels transparent. You need a routine that goes deeper.. One simple workaround is to turn off antialiasing, draw in a weird color you don't need and then call maketransparent.. - TaW
@TaW I am open to suggestions. 😀 What if I just draw 4 lines one by one? - Andrew Truckle
Can you post MCVE? - Reza Aghaei
Minimal, Complete, and Verifiable Example to reproduce the problem. The first benefit of such example is for you. Most times when you write such example you solve the problem yourself. It also lets other users to reproduce the problem, this way they can help more. - Reza Aghaei

1 Answers

2
votes

Painting with transparency is unfortunately only supported in one way: By applying the RGB channels in the strenght of the alpha value.

With alpha = 0 nothing happens.

Other modes are desirable but not supported in GDI+ drawing.

One simple workaround is to turn off antialiasing, draw in a weird color you don't need and then call MakeTransparent.

Bitmap bmp = new Bitmap(244, 244, PixelFormat.Format32bppArgb);
Color funnyColor = Color.FromArgb(255, 123, 45, 67);

using (Graphics g = Graphics.FromImage(bmp))
{
    g.Clear(Color.LawnGreen);
    using (SolidBrush br = new SolidBrush(funnyColor ))
    {
        // no anti-aliased pixels!
        g.SmoothingMode = SmoothingMode.None;
        // draw your stuff..
        g.FillEllipse( br , 14, 14, 88, 88);
    }       
    bmp.MakeTransparent(funnyColor );
    // do what you want..
    bmp.Save(someFileName, ImageFormat.Png);
}

Of course you can use all DrawXXX methods including FillPath or DrawRectangle.

The result is a green bitmap with a transparent hole in it. Here it is in Paint.Net:

Example

For other modes, that maybe would copy the alpha channel or mix it with the previous value you would have to write routines of your own or find a lib that has it, but I think this should be all you need atm.


Edit by Andrew Truckle

The proposed answer is really good. However, since I was using Teigha.Net as the basis of the application, in the end I went with this code:

protected override void OnMouseMove(MouseEventArgs e)
{
    if (_bZoomWindowing)
        UpdateRubberBandRectangle(e.Location);

    if (_bPanWindowMode)
        UpdateRubberBandLine(e.Location);

    base.OnMouseMove(e);
}

private void UpdateRubberBandRectangle(Point Location)
{
    // Do we need to erase the old one?
    if (!_rcLastRubberBand.IsEmpty)
    {
        using (Region r = new Region(Rectangle.Inflate(_rcLastRubberBand, 2, 2)))
        {
            r.Exclude(Rectangle.Inflate(_rcLastRubberBand, -2, -2));

            _pDevice.invalidate(new OdGsDCRect(_rcLastRubberBand.Left - 2, _rcLastRubberBand.Right + 2,
                                           _rcLastRubberBand.Top - 2, _rcLastRubberBand.Bottom + 2));
            Invalidate(r);
        }
    }

    // Draw the new one
    if (!_selectionStart.IsEmpty && !_selectionEnd.IsEmpty && _selectionEnd != Location)
    {
        _rcLastRubberBand = _rcRubberBand;

        _selectionEnd = Location;
        _rcRubberBand = GetSelectionRectangle(_selectionStart, _selectionEnd);

        using (Region r = new Region(Rectangle.Inflate(_rcRubberBand, 2, 2)))
        {
            r.Exclude(Rectangle.Inflate(_rcRubberBand, -2, -2));

            _pDevice.invalidate(new OdGsDCRect(_rcRubberBand.Left - 2, _rcRubberBand.Right + 2,
                                           _rcRubberBand.Top - 2, _rcRubberBand.Bottom + 2));
            Invalidate(r);
        }
    }
}

private void UpdateRubberBandLine(Point Location)
{
    // Do we need to erase the last rubber band line? (Rectangle already expanded by 2 pixels)
    if (!_rcLastRubberBandLine.IsEmpty)
    {
        using (Region r = new Region(_rcLastRubberBandLine))
        {
            _pDevice.invalidate(new OdGsDCRect(_rcLastRubberBandLine.Left, _rcLastRubberBandLine.Right,
                                               _rcLastRubberBandLine.Top, _rcLastRubberBandLine.Bottom));
            Invalidate(r);
        }

    }

    // Draw the new one now
    _RubberLineEnd = Location;
    _rcLastRubberBandLine = GetSelectionRectangle(_RubberLineStart, _RubberLineEnd);
    _rcLastRubberBandLine.Inflate(2, 2);
    using (Region r = new Region(_rcLastRubberBandLine))
    {
        _pDevice.invalidate(new OdGsDCRect(_rcLastRubberBandLine.Left, _rcLastRubberBandLine.Right,
                                           _rcLastRubberBandLine.Top, _rcLastRubberBandLine.Bottom));
        Invalidate(r);
    }
}

Notice that I am making use of Region objects. Also, the invalidating is being handled by OdGsDevice _pDevice which is a Teigha object. In my situation this worked fabulously.