How do graphic containers work?

混江龙づ霸主 提交于 2021-01-26 20:34:10

问题


I'm trying to figure out how exactly gdi+ graphics containers works with different graphic units. Take a look at the below code. It compiles, you can paste it into a fresh new form.

void Form2_Paint(object sender, PaintEventArgs e)
{
    var gfx = e.Graphics;

    System.Diagnostics.Debug.WriteLine("DpiX={0}, DpiY={1}", gfx.DpiX, gfx.DpiY);

    gfx.PageUnit = GraphicsUnit.Inch;

    var pen = new Pen(Color.Black, 0.01f);

    // Create outer container, 2 inches in size with X and Y set to 0.1 inches
    var outerContainer = gfx.BeginContainer(
        new RectangleF(0.1f, 0.1f, 2, 2),
        new RectangleF(0, 0, 2, 2),
        GraphicsUnit.Pixel);

    // Draw the outer rectangle
    gfx.DrawRectangle(pen, new Rectangle(0, 0, 2, 2));

    // Create inner container, 1 inch in size with X and Y set to 0.1 inches
    var innerContainer = gfx.BeginContainer(
        new RectangleF(0.1f, 0.1f, 1, 1),
        new RectangleF(0, 0, 1, 1),
        GraphicsUnit.Pixel);

    // Draw the inner rectangle
    gfx.DrawRectangle(pen, new Rectangle(0, 0, 1, 1));

    gfx.EndContainer(innerContainer);

    gfx.EndContainer(outerContainer);
}

Above code is a pretty simple example of nested graphic containers, I used no scaling transformation. This is how the form looks like when above paint handler is used:

graphics container illustration

It's pretty simple. Now, I will try to describe what the problem is.

This is a signature of the BeginContainer method:

public GraphicsContainer BeginContainer(
    RectangleF dstrect,
    RectangleF srcrect,
    GraphicsUnit unit
)

What I'm failing to understand is GraphicsUnit unit argument.

From MSDN:

Member of the GraphicsUnit enumeration that specifies the unit of measure for the container.

This appears not to be true!

As you can see in my code I'm using Inch units as such: gfx.PageUnit = GraphicsUnit.Inch.

But, when I'm creating containers this is what I'm passing as units argument to the BeginContainer method: GraphicsUnit.Pixel. What happens after the container is created? Inches are being used (what I actually want). But if I pass GraphicsUnit.Inch (or millimeters or anything else) to the argument, pixels are used. So, it seems that in order to accomplish what I want (use inches) I have to specify pixels?

This makes no sense to me. You can try changing units in BeginContainer methods in above code and observe strange results. I've read the MSDN and everything I could gather on this but I am still clueless.

I'm writing software which draws a lot of stuff using gdi+ and it's using millimeter units for printing purposes - when I started using containers I was quite surprised that I apparently need to specify pixels as units. I'm really suspicious of any printing code where pixels are mentioned. It must be that I have a big misunderstanding of this matter.

So, all above considered, my question is: what is the purpose of the unit argument in this method?


回答1:


Unfortunately, GDI+ is as far as I can tell one of the most poorly documented APIs in Windows. Some browsing on the web turns up very few people really using it much, and no insight into your question.

Even more unfortunately, the GDI+ API was basically copied straight over to the .NET Graphics object. Even the documentation was for the most part just copied verbatim. Note the similarities between the .NET Graphics.BeginContainer Method (RectangleF, RectangleF, GraphicsUnit) and the Windows Graphics.BeginContainer(const RectF, const RectF, Unit) method pages.

To further complicate matters, there is this blog entry which tantalizingly reads:

Another example is the Save and BeginContainer methods in Graphics class, which perform very differently, yet have the same MSDN documentation that fails to differentiate between the two calls

…but fails to go into any detail as to how these two methods are in fact different.

Now, all that said, with some experimentation, I think I've decoded the parameters and behavior:

  • The unit parameter appears to be used to specify the units used for the srcrect parameter
  • The dstrect parameters is specified in the units currently in use for the Graphics object
  • The new container winds up with its Graphics.PageUnit set back to the default value of GraphicsUnit.Display

I could not find any hint of the first two points above in the documentation. It was only through experimentation and careful observation that I learned that, and frankly without actual documentation to back my conclusion up, I'm still not 100% sure about it.

There is a hint of the third point in the documentation of the BeginContainer() method:

The graphics state established by the BeginContainer method includes the rendering qualities of the default graphics state; any rendering-quality state changes existing when the method is called are reset to the default values.

At first glance, this sentence seems to be saying that only the "rendering-quality state changes" are reset. But reading more carefully, one sees that part of the state is simply being called out in particular, as it is included in all of the state that is reset. Granted, a more careful reading does not gain oneself any additional understanding :(, but at least one can see the sentence shouldn't be taken as the last word on everything that's reset.


So the key to getting things to work correctly would be to specify the units you want to work in (e.g. GraphicsUnit.Inch) at every step of the way: in the initial settings before you create a container, in the call to BeginContainer() (but here, only to control the way srcrect is being interpreted), and then finally just after BeginContainer(), setting the Graphics.PageUnit property again.

When I did it that way, I was able to use whatever units I wanted to drawing. I could even mix and match, though of course that resulted in some non-intuitive values being passed for the container rectangles as compared to the rectangles I was drawing.

As an example, here's a snippet where I use inches for the initial Graphics state, and millimeters for the container:

gfx.PageUnit = GraphicsUnit.Inch;

using (Pen blackPen = new Pen(Color.Black, 0.01f))
using (Pen redPen = new Pen(Color.Red, 0.01f))
{
    gfx.DrawRectangle(blackPen, .25f, .25f, 2, 2);

    var outerContainer = gfx.BeginContainer(
        new RectangleF(.25f, .25f, 2, 2),
        new RectangleF(0, 0, 2 * 25.4f, 2 * 25.4f),
        GraphicsUnit.Millimeter);

    gfx.PageUnit = GraphicsUnit.Millimeter;
    gfx.DrawRectangle(redPen, .25f * 25.4f, .25f * 25.4f, 1.5f * 25.4f, 1.5f * 25.4f);

    gfx.EndContainer(outerContainer);
}

That produces this image:

nested rectangles

So I'm able to draw a 2x2 inch rectangle outside the container, and then inside the container I draw a 1.5x1.5 inch rectangle, but do so using millimeters (converting explicitly in the parameters, just to make it clearer to myself what I'm doing).




回答2:


Perhaps, you don't need to care about at all.

I failed to pass the right arguments to Graphics.BeginContainer as well. However, using the Transform methods (ResetTransform in the end) worked perfectly for the problem I had to solve.

So, first consider the Transform methods before trying to understand containers.



来源:https://stackoverflow.com/questions/29170453/how-do-graphic-containers-work

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!