Why are RECTs endpoint-exclusive?


Endpoint-exclusive RECTs and lines are much easier to work with.

For example, the width of a rectangle is right - left, and its height is bottom - top. If rectangles were endpoint-inclusive, then there would be annoying +1's everywhere.

End-point exclusive rectangles also scale properly.

For example, suppose you have two rectangles (0,0)-(100,100) and (100,100)-(200,200). These two rectangles barely touch at the corner. Now suppose you magnify these rectangles by 2, so they are now (0,0)-(200,200) and (200,200)-(400,400). Notice that they still barely touch at the corner. Also the length of each edge doubled from 100 to 200.

Now suppose endpoints were inclusive, so the two rectangles would be (0,0)-(99,99) and (100,100)-(199,199). Now when you double them, you get (0,0)-(198,198) and (200,200)-(398,398). Notice that they no longer touch any more because (199,199) is missing. Note also that the length of the side of the square is now 199 pixels instead of 200.

Similar problems occur if you need to do subpixel computations.

"But that's silly -- who ever does magnification or subpixel computations?"

Well, magnification is used more than you think. In addition to the obvious things like zooming in/out, it's also used in printing (since printers are 300dpi but the screen is usually much lower resolution) and in GDI mapping (ScaleWindowExtEx, StretchBlt). And subpixel computations are used in anti-aliasing.

With apologies to Alvy Ray, I think the best way to interpret this is to view pixels as living between coordinates, not at them. For example, here's a picture of the pixel that lives between (10,10) and (11,11). (In other words, this pixel is the rectangle (10,10)-(11,11).)

  10 11
10      
11      
       

With this interpretation, the exclusion of the endpoint becomes much more natural. For example, here's the rectangle (10,10)-(13,12):

  10 11 12 13
10          
11          
12          
           

Observe that this rectangle starts at (10,10) and ends at (13,12), just like its coordinates say.

Comments (38)
  1. Anonymous says:

    Hi Raymond, I ran into issues related to this just yesterday! (great BLOG btw).

    1) What is the width of a RECT?

    width = rect.right – rect.left +1?

    2) Are RECTs ever interpreted differently? consider FillRect vs. InvalidateRect or GetClientRect

    Basically, I have a VB picturebox, that is 640×480 pixels. I call GetClientRect and get (0,0,640,480), then I call MyGetRectWidth(rect) and get 639!

    am I misunderstanding something here? :)

    thx!

    – Alex

  2. Anonymous says:

    The width of a rectangle is "rect.right-rect.left" and you should assume that’s how all Windows functions treat rectangles. Any exceptions are (or should be) explicitly called out in the function documentation. I don’t know of any exceptions offhand.

  3. Anonymous says:

    It’s 20 years old, but the Quickdraw section of Inside Macintosh Volume I still has the clearest explanation of why coordinates-between-the-pixels is a Good Thing.

  4. Anonymous says:

    I think the preferred convention is that the center of pixel N,M is at (N+0.5,M+0.5), and if you must use a box filter, then the footprint of the filter is N <= x < N+1 and M <= y < M+1.

    The reason we do this is the same reason arrays start at zero rather than one in C. It seems awkward at first, but it makes the math much cleaner.

  5. Anonymous says:

    You might want to tell the Windows CE guys: FillRect draws one pixel more to the right and bottom on CE than it does on the desktop.

    Of course, there’s the backwards-compatibility issue: if it gets changed now, it breaks every program already written for CE. Not nearly as many as on the desktop, true, but a significant enough number.

    I wonder if this wart made it into .NET CF?

  6. Anonymous says:

    And then there are MetaFiles. Raymond can you tell us a story

    what’s happening with rectangles when we use them with

    (Enhanced)MetaFiles?

    Please! Please!

  7. Anonymous says:

    Java uses the same scheme for substrings: myString.substring(start, end) where end is excluded. The length of the substring is simply (end – start). It’s simple. I love it.

    The problem is, Java substrings are so convenient that nearly every time I do string manipulation in C#, I curse the .NET-folks for using that old-fashioned (start, length) notation.

  8. Anonymous says:

    Dan Maas:

    X+0.5, Y+0.5 being the center of the pixel is only valid if you’re using floating point graphics libraries such as GDI+, DirectX or OpenGL.

    GDI and regular Win32 are integer, and so that definition doesn’t really hold.

    (Of course, GDI is fixed-point internally, but that representation doesn’t generally make it out of the API).

  9. Anonymous says:

    Sorry, CW, I don’t know anything about metafiles. And sorry, Mike, I don’t have any contacts in Windows CE (so I can’t even verify whether the problem indeed exists).

  10. Anonymous says:

    Raymond, I have a question which you might be able to answer.

    All of us good boys and girls who did our homework have known for years that NT’s POSIX subsystem (and by association Interix/SFU) support fork() [for those that don’t know: create a duplicate of a process, usually done in such a way that no/very few pages are actually copied until they’re written to – called Copy-on-Write, or COW].

    The Win32 API (as far as I can tell by RTFMing, Googling, newsgrouping, and generally given up trying some time ago) doesn’t contain any equivalent – not even a closely guarded one. If I’m wrong, stop me now :)

    Assuming I’m on-base up until now, the question is this: POSIX/Interix/SFU all need to get some kind of help from the kernel at some point along the way in order to actually perform the fork. After all, they’re subsystems (or a subsystem, depending on how you look at it), not kernel-mode drivers, so they don’t have much more access to the MM than I do in my Win32 process. By what mechanism do they get this "help" — and how POSIX-specific is the help that they get? Specifically, would it be at all possible to utilize this support code – assuming it exists – from within the Win32 subsystem in order to achieve the same thing?

    [Unsuprisingly enough, the fact that POSIX apps run in a different subsystem means they end up being really quite dull — no "best of both worlds" for developers there]

    Anyway, if you know the answer (or can point to some MSDN material that explains), I’d be very much appreciative. And apologies for a great big comment which bears practically no relation to rectangles :)

  11. Anonymous says:

    Sorry, Mo, I have no idea. Gosh, people seem to be asking a lot of questions about things I know nothing about…

  12. Anonymous says:

    Another off-topic question but maybe you can answer this… Is there an official policy on whether C++ exceptions should be able to propagate through Win32 API callbacks? i.e. if I throw an exception inside of a WndProc is it supposed to come out of the message loop or are the results "undefined"? I know this doesn’t work with GCC on Windows but I am wondering what VC++ does. I assume you have to catch the exception before it propagates back out into Win32 code?

  13. Anonymous says:

    Great article! I found this site through the aformentioned kuro5hin comment. I really dig the inside look at the Windows platform. Keep ’em comming!

  14. Anonymous says:

    Thanks anyway, Raymond. It was worth a try :)

    I guess it’s back to the drawing board with that one..

    (I promise I won’t use any undocumented APIs, though…)

  15. Anonymous says:

    Dan: I can’t think of an official policy off the top of my head against throwing C++ exceptions across Win32 callbacks, but my gut tells me nothing good can come of it…

  16. Anonymous says:

    Another disadvantage of inclusive rects is that you can’t represent empty rects with them.

    Simon Cooke:

    Even with integer coordinates you still need pixel center rules for rasterization of certain primitives, such as solid ellipses. It is also possible to set scaling modes to gain direct access to 28.4 coordinates under NT GDI.

  17. Anonymous says:

    Re: fork() and SFU

    http://weblogs.asp.net/jdzions would be a better place to ask this.

  18. Anonymous says:

    I agree endpoint-exclusive is a much better arrangement than endpoint-inclusive, but, I always wondered why the RECT structure have right/bottom instead of width/height, because rectangle’s size and location are really two separate properties. And if you have width/height, you only need to touch left/top to move the RECT around.

    And remotely related to this – I don’t know if you know this Raymond – in multiple monitor support, why is it designed such that the sides of monitor RECTs only need to touch each other ? This makes things like centering a window on screen a major hassle, sometimes not even possible, it’s theoretically possible to have 4 video cards positioned along the sides of the virtual screen, but leave a hole in the center.

    Why wasn’t it designed such that all video cards must form one big rectangle and cover all of it, and have the same bit depth ? Sure that reduces the flexibility, but it’s a lot easier to program.

  19. Anonymous says:

    right/bottom vs. width/height – it’s a matter of style I guess. I happen to like right/bottom but then again I’m biased.

    Multiple monitors: One could turn the question around. Why arbitrarily restrict all monitors to be the same resolution and be at the same color depth? In the early days of multiple monitors, the two video cards had very different capabilities. You had the onboard video that came with your motherboard (usually pretty lame) and a video card that you bought separately (pretty powerful). Forcing all video cards to be at the same resolution and color depth would mean that you would be running your expensive video card at some awful resolution like 800×600 because that’s all the motherboard video could do. And then you’d be complaining, "I paid $200 for this video card. Why are you forcing me to run this pathetic resolution! Microsoft must be in a conspiracy with the video card manufacturers!"

    And you shouldn’t be trying to center on the virtual screen anyway. Pick a monitor and center on that monitor. If you try to center on the virtual screen, you will often end up with a window that is exactly cut in half by the two monitors, which is pretty useless.

  20. Anonymous says:

    Look here:

    http://www.cygwin.com/ml/cygwin/2002-02/msg00068.html

    I own that book (Windows NT/2000 Native API Reference) and indeed it contains an example code that does a fork with copy-on-write

  21. Anonymous says:

    Nothing to do with this article, Ramon, just an oddity I came across recently. In the MFC’s AFXRES.H there are command ids for things like copy, paste, etc. and in WINUSER.H there are ids for messages which perform those types of actions (WM_COPY, WM_PASTE, etc.). Why are they not in the same order?

  22. Anonymous says:

    On C++ exceptions in callbacks: if compiled with MSVC, a C++ exception is simply a Win32 SEH exception with code 0xE06D7363 (or 0xE0 ‘m”s”c’). IIRC, a pointer to the thrown object gets passed around in one of the SEH exception’s parameters.

    So the answer to Dan’s question is whether an SEH exception can be successfully propogated through the Win32 API at this point. I think it probably can. MFC sets up a try/catch block in AfxCallWndProc (wincore.cpp) to stop exceptions propogating out of your message handlers, although this may be for debugging purposes. ATL doesn’t bother.

  23. Anonymous says:

    Ramon? Sorry, Raymond, I guess my typing fingers are slacking. I shall punich them appropriately.

  24. Anonymous says:

    There is a difference between FillRectangle() and DrawRectangle(). FillRectangle() is endpoint-exclusive but DrawRectangle() is endpoint-inclusive.

    Some sample-code:

    private void button1_Click(object sender, System.EventArgs e)

    {

    Graphics g=System.Drawing.Graphics.FromHwnd(this.Handle);

    g.FillRectangle(Brushes.Beige,30,30, 100,100);

    g.DrawRectangle(Pens.Black,30,30,100,60);

    Rectangle r1=new Rectangle(30,150,100,100);

    Rectangle r2=new Rectangle(30,150,100,60);

    g.FillRectangle(Brushes.Beige,r1);

    g.DrawRectangle(Pens.Black,r2);

    }

    Both rectangles have the same width but the drawn rectangle is one pixel wider than the filled one… Why is this?

  25. Anonymous says:

    If you use FillEllipse() and DrawEllipse() there seems to be the same inconsistency. Maybe this applies to all Drawing-Functions.

    Its especially apparent when using small values for width/height.

  26. Anonymous says:

    Mike: Now I remember – throwing across a callback will often mess up the activation context stack. These sorts of bugs are incredibly hard to track down. You end up dying with STATUS_SXS_EARLY_DEACTIVATION or STATUS_SXS_INVALID_DEACTIVATION for no apparent reason.

    Moi: You’ll have to ask an MFC person.

    hans: Um, that’s some .NET thing isn’t it. You should ask a .NET person.

  27. Anonymous says:

    Raymond, your mention of multiple monitors reminds me of something (and I think you can answer this one ;) )

    A while ago you gave a great explantion of WM_NCCALCSIZE. Could you do the same for the MINMAXINFO struct? My specific problem is figuring out what to put in ptMaxSize. The docs only say "this refers to the primary monitor" which isn’t much help.

  28. Anonymous says:

    And Raymond, since we sort-of got onto the topic of exception handling, can we expect your dissertation on it soon? That’s one I’m looking forward to!

  29. Anonymous says:

    Raymond: Seems like the graphic code above (Hans’ comment) is GDI+, so it’s not only a .NET thing :)

  30. Anonymous says:

    Okay, well GDI+ is less than 5 years old so I’m going to declare it "not old enough". :-)

  31. Anonymous says:

    Seth, if you want an excellent article on how exception handling works, try this one:

    http://weblogs.asp.net/cbrumme/archive/2003/10/01/51524.aspx

  32. Anonymous says:

    declare it "not old enough". :-)

    Yes,

    It says "The Old New Thing" and "not actually a .NET blog".

    I don’t know how to ask, but what I am trying here is something

    like, in a way and sort of, heh, Raymond did you look at .NET stuff?

    Or better, do you participate on it? Do you like it? Is there on

    this blog or wherever some insight from you about .NET and

    related technologies? Looks to me as if you are the only guy from

    MS discussing good old Win32 stuff, while everybody else is

    doing .NET.

    PS. That’s why I don’t read any other programming blogs but yours.

    If you go .NET, then I don’t know what I’ll do. I’ll be lost!

  33. Anonymous says:

    Hey stop introducing more .NET content into this "not .NET" blog! I use it but I don’t write about it.

  34. Anonymous says:

    Why wasn’t it designed such that all video cards must form one big rectangle and cover all of it, and have the same bit depth ?

    If your monitor screens aren’t at quite the same height, you won’t want the mouse cursor to jump up or down as it crosses between them. Well, I don’t anyway.

  35. Anonymous says:

    Preparing to combine multiple context menu extensions into one.

  36. Anonymous says:

    Using the TTN_GETDISPINFO notification.

Comments are closed.