Search code examples
c#winformsbitmaponpaintgraphicspath

How do I draw a transparent rectangle?


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!


Solution

  • 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.