0

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!

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • I think there's a Graphics.DrawRectangle method that will draw just the lines... – jimbobmcgee Jun 05 '16 at 21:23
  • 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 Jun 05 '16 at 21:42
  • @TaW I am open to suggestions. What if I just draw 4 lines one by one? – Andrew Truckle Jun 05 '16 at 21:44
  • Can you post [MCVE](http://stackoverflow.com/help/mcve)? – Reza Aghaei Jun 05 '16 at 21:46
  • @RezaAghaei MCVE? What is that? – Andrew Truckle Jun 05 '16 at 21:48
  • 1
    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 Jun 05 '16 at 21:52
  • Four lines? DrawRectangle should be good enough. I wonder what the whole code is supposed to achieve other than drawing the rubberband rectangle for which you really need just one line? Can yo give us more background about the direcx device you mention? The black filling does not come from the FillPath I'm pretty sure. For making the path transparent simply add MakeTransparent(Color.WhiteSmoke) – TaW Jun 05 '16 at 22:22
  • @TaW That is just it. All I want is the rectangle. We should still be able to see the map through the rectangle. Perhaps I was misleading with my use of the word transparent. When I used DrawRectangle it still ended up with black inside – Andrew Truckle Jun 05 '16 at 22:22
  • OK, DrawRectangle draws without filling. I wonder how all that code has accumulated..?-) A simple `e.Graphics.DrawRectangle( somePen,_rcRubberBand); ` should do. Note that we are in the OnPaint event, so this draws onto the surface of a control, not into a Bitmap.. I'm not sure that re-inventing double-buffering is such a good idea. Simply using a PictureBox would be a lot easier.. – TaW Jun 05 '16 at 22:25
  • @TaW See the calls that connect the graphics object hdc with the pDevice? That is what I am drawing in. That graphics is from bitmap. Then at end we send to control graphics. It is very late here. Will try again tomorrow. Night. – Andrew Truckle Jun 05 '16 at 22:36
  • @TaW I had to call the internal custom view object invalidate method before the usercontrols. All the problems have gone away. so I am not sure to tick the answer. What do you think? What you have written is accurate but not in the context of the question. I have given it a vote though. Thought? – Andrew Truckle Jun 06 '16 at 09:54
  • Then leave it as it is! You may either add the solution to your question or write a short answer yourself, so the question won't remain open. I'll leave my answer as it may be useful for folks who come looking for actual transparency ;-) – TaW Jun 06 '16 at 09:58

1 Answers1

3

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.

TaW
  • 53,122
  • 8
  • 69
  • 111