none
GdiPlus : Region from GraphicPath without filling it

    Question

  • Hi everybody.

    I need to get a Gdi+ Region from a GraphicPath without filling it, and I don't know how to do it.

    A draw is better than words, so here is what I want to do :


    1. I have an ellipse, than I want to be displayed unfilled (whith a known line width)
    2. I have a full rectangle, and I want it to substracte itself from the ellipse.
    3. Then, I want to get the ellipse, unfilled, with a substracted part

    So, here is my code :

    -----------------------------------------------------
    // lGraphics and lBrush defined upper...
    
    // Graphic paths
    GraphicsPath lGraphicPathEllipse;
    GraphicsPath lGraphicPathRectangle;
    
    // Rectangles
    Rect lRectEllipse(0, 0, 100, 100);
    Rect lRectRectangle(10, -10, 100, 80);
    
    // Add the shapes to the graphic pathes
    lGraphicPathEllipse.AddEllipse(lRectEllipse);
    lGraphicPathRectangle.AddRectangle(lRectRectangle);
    
    // Create the regions
    Region lRegionEllipse(&lGraphicPathEllipse);
    Region lRegionRectangle(&lGraphicPathRectangle);
    
    // Exclude the rectangle from the ellipse
    lRegionEllipse.Exclude(&lRegionRectangle);
    
    // Draw the ellipse region
    lGraphics->FillRegion(&lBrush, &lRegionEllipse);

    -----------------------------------------------------

    The problem is that what I get is actually this :


    It seemes like creating a Region from a GraphicPath automatically fill the region.

    Is it possible to grate a Region from a GraphicPath with only the outline? By giving a Pen or a line width for instance?

    I've read thing about the GraphicPath Widen function, but it doesn't really do what I want.

    It's strange to see that a simple thing like this can't be done.

    Or maybe I have miss something!

    Thanks for any help, and have a good day.

    Alex.
    Wednesday, December 27, 2017 4:00 PM

Answers

  • Ok, so I reply to my own answer.

    Using Widen() with a pen width which is less than 1px will always be 1px wide. This is a bug as it's not documented by Microsoft.

    The workaround I found is to first apply a scale to the GraphicsPath (i.e x1000), then call the Widen() with a scaled Pen (i.e. a x1000 width), and then scale back the GraphicsPath (i.e /1000).

    With our example it does :

    case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hdc = BeginPaint(hWnd, &ps);
    		Graphics graphics(hdc);
    		Matrix lMatrixIn;
    		Matrix lMatrixOut;
    		GraphicsPath pathWiden;
    		
    		const float penWidth = 0.7f;
    		const float scaleValue = 1000.0f;
    
    		Pen redPen(Color::Red, penWidth * scaleValue);
    		PointF point1(3.0f, 1.0f);
    		PointF point2(6.0f, 1.0f);
    
    		// Add the line
    		pathWiden.AddLine(point1, point2);
    
    		// Scale in
    		lMatrixIn.Scale(scaleValue, scaleValue);
    		pathWiden.Transform(&lMatrixIn);
    
    		// Widen with "big" pen width
    		pathWiden.Widen(&redPen);
    
    		// Scale out
    		lMatrixOut.Scale(1 / scaleValue, 1 / scaleValue);
    		pathWiden.Transform(&lMatrixOut);
    
    		// Display
    		graphics.ScaleTransform(100, 100);
    		graphics.DrawPath(&blackPen, &pathWiden);
    
    		EndPaint(hWnd, &ps);
    	}
    	break;

    There is the same kind of problem with the IsVisible() function of the Region object...

    So, to sum up I scale everything x1000 and when I want to display it or get results (IsVisible, GetBounds...), I apply the invert scale and then it works well!

    Thanks Castorix31for your time.

    • Marked as answer by afouquet Wednesday, January 03, 2018 4:44 PM
    Wednesday, January 03, 2018 4:44 PM

All replies

  • If you need this for drawing, have you considered the clipping using IGraphics.ExcludeClip etc.?

    Wednesday, December 27, 2017 5:51 PM
  • I get the right result if I do for example (in a basic Win32 app) =>

        case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hdc = BeginPaint(hWnd, &ps);
    		Graphics graphics(hdc);
    
    		Rect rect(140, 140, 200, 100);
    		GraphicsPath path;
    		path.AddRectangle(rect);
    
    		Pen pen(Color(0, 0, 0, 0));
    		graphics.DrawPath(&pen, &path);
    
    		Region region(&path);
    		graphics.SetClip(&region, CombineModeExclude);
    
    		Rect rectEllipse(20, 20, 200, 200);
    		Pen penGreen(Color(255, 0, 255, 0));
    		penGreen.SetWidth(5);
    		graphics.DrawEllipse(&penGreen, rectEllipse);
    		
    		EndPaint(hWnd, &ps);
    	}
    	break;


    • Edited by Castorix31 Wednesday, December 27, 2017 7:37 PM
    Wednesday, December 27, 2017 7:36 PM
  • Castorix31, thanks for your answer.

    It solves my problem for the case I exposed as example, but it doesn't solve the opposite case, which is a possible case in my appalication.

    Another example to explain :

    1. I have a rectangle, that I want to be displayed filled
    2. I have an outline-only ellipse, and I want it to substracte itself from the rectangle.
    3. Then, I want to get the filled rectangle, with a substracted part (the ellipse outlines).

    I can't use your method in this case, because the SetClips has to be done on a region which I would build from the ellipse (from a GraphicsPath). And so, I will point on the same initial problem, which is the fact that creating a Region from a GraphicPath automatically fill the region.

    Thursday, December 28, 2017 9:59 AM
  • Or ih the 2 cases, you can just draw the figures over

    For the last case :

    case WM_PAINT:
    {
    	PAINTSTRUCT ps;
    	HDC hdc = BeginPaint(hWnd, &ps);
    	Graphics graphics(hdc);
    
    	Rect rect(40, 40, 200, 200);
    	Pen penGreen(Color(255, 0, 255, 0));
    	SolidBrush brushGreen(Color(255, 0, 255, 0));
    	graphics.FillRectangle(&brushGreen, rect);
    
    	Rect rectEllipse(140, 140, 200, 200);
    	Pen pen(Color(255, 255, 255, 255));
    	pen.SetWidth(5);
    	graphics.DrawEllipse(&pen, rectEllipse);
    
    	EndPaint(hWnd, &ps);
    }
    break;

    Thursday, December 28, 2017 10:34 AM
  • If I understand what you suggest, you want me to display the outline ellipse in white over the rectangle.

    I can't consider this solution, because things can be drawn behind (a background color or other shapes for example). So it might be "transparent" so we are able to see what's behind.

    You first code snippet was doing the things in the good way but I just have this problem with an outline as a Clip region.

    Thursday, December 28, 2017 10:55 AM
  • A test with Widen for the second case :

    case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hdc = BeginPaint(hWnd, &ps);
    		Graphics graphics(hdc);
    
    		Rect rect(40, 40, 200, 200);
    		Pen greenPen(Color(255, 0, 255, 0), 5);
    		SolidBrush greenBrush(Color(255, 0, 255, 0));
    
    		Rect rectEllipse(140, 140, 200, 200);
    		GraphicsPath path;
    
    		path.AddEllipse(rectEllipse);
    		path.Widen(&greenPen);
    		//path.Outline(NULL, FlatnessDefault);
    
    		Region region(&path);
    		graphics.SetClip(&region, CombineModeExclude);
    		graphics.FillRectangle(&greenBrush, rect);
    
    		EndPaint(hWnd, &ps);
    	}
    	break;


    • Edited by Castorix31 Thursday, December 28, 2017 1:37 PM
    Thursday, December 28, 2017 1:22 PM
  • Well, it seems like Widen is the guy that I need. I tried to use it at the beginning to "convert" a GraphicPath+Width to a Region. But I didn't get what I expected. I got a region which was wider than expected.

    Looking deeper, it seems like it works well if the Pen width is larger than 1px.

    But if it's not, I get a GraphicPath (or Region) with always a 1px wide (I use a ScaleTransform to "see" that).

    For example with this code :

    case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hdc = BeginPaint(hWnd, &ps);
    		Graphics graphics(hdc);
    
    		const float penWidth = 0.7f; // To change to see the phenomenon
    
    		Pen redPen(Color::Red, penWidth);
    		Pen blackPen(Color::Black, 0.01f);
    		PointF point1(3.0f, 1.0f);
    		PointF point2(6.0f, 1.0f);
    
    		GraphicsPath path;
    		GraphicsPath pathWiden;
    
    		path.AddLine(point1, point2);
    		pathWiden.AddLine(point1, point2);
    		pathWiden.Widen(&redPen);
    
    		graphics.ScaleTransform(100, 100);
    		graphics.DrawPath(&blackPen, &pathWiden);
    		graphics.DrawPath(&redPen, &path);
    
    		EndPaint(hWnd, &ps);
    	}
    	break;

    I get these results when I set penWidth successively to 1.1px, 0.7px, 0.4px and 0.1px :

    As I said before, the result is good with a penWidth upper than 1px only...

    Is this a known bug or is there a workaround?







    • Edited by afouquet Friday, December 29, 2017 8:29 AM
    Friday, December 29, 2017 8:26 AM
  • Ok, so I reply to my own answer.

    Using Widen() with a pen width which is less than 1px will always be 1px wide. This is a bug as it's not documented by Microsoft.

    The workaround I found is to first apply a scale to the GraphicsPath (i.e x1000), then call the Widen() with a scaled Pen (i.e. a x1000 width), and then scale back the GraphicsPath (i.e /1000).

    With our example it does :

    case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hdc = BeginPaint(hWnd, &ps);
    		Graphics graphics(hdc);
    		Matrix lMatrixIn;
    		Matrix lMatrixOut;
    		GraphicsPath pathWiden;
    		
    		const float penWidth = 0.7f;
    		const float scaleValue = 1000.0f;
    
    		Pen redPen(Color::Red, penWidth * scaleValue);
    		PointF point1(3.0f, 1.0f);
    		PointF point2(6.0f, 1.0f);
    
    		// Add the line
    		pathWiden.AddLine(point1, point2);
    
    		// Scale in
    		lMatrixIn.Scale(scaleValue, scaleValue);
    		pathWiden.Transform(&lMatrixIn);
    
    		// Widen with "big" pen width
    		pathWiden.Widen(&redPen);
    
    		// Scale out
    		lMatrixOut.Scale(1 / scaleValue, 1 / scaleValue);
    		pathWiden.Transform(&lMatrixOut);
    
    		// Display
    		graphics.ScaleTransform(100, 100);
    		graphics.DrawPath(&blackPen, &pathWiden);
    
    		EndPaint(hWnd, &ps);
    	}
    	break;

    There is the same kind of problem with the IsVisible() function of the Region object...

    So, to sum up I scale everything x1000 and when I want to display it or get results (IsVisible, GetBounds...), I apply the invert scale and then it works well!

    Thanks Castorix31for your time.

    • Marked as answer by afouquet Wednesday, January 03, 2018 4:44 PM
    Wednesday, January 03, 2018 4:44 PM