Strange issue in stroking a bezier path

  • Xcode 4.3.2, Lion 10.7.3,
    Apple 30" Cinema Display, Samsung SyncMaster 950p

    Consider the following simple drawRect to demonstrate the issue

    - (void)drawRect:(NSRect)dirtyRect
    {
        // Drawing code here.
        NSRect bounds = [self bounds];
        NSBezierPath *path = [NSBezierPath bezierPathWithRect:bounds];
        [path stroke];
    }

    In the specific case of the Cinema Display having a resolution setting of 1280 x 800
    the rectangle is not fully drawn every time. The top and right edges are missing.
    But sometimes they do draw. It seems quite arbitrary.

    I have tested this with all other resolutions on the Cinema Display under both standard
    and HiDPI and also on the Samsung CRT. They all work just fine. Even the Samsung at 1280 x 800.

    (I have tried things such as NSIntegralRect but nothing works except to change the resolution)

    Why would this occur and only at that resolution and only on the Cinema Display?

    Any ideas for tracking it down, or working around it, gratefully received.

    TIA and respect….

    Peter
  • I am having a similar problem but not with an Apple display and at a resolution of 1920 x 1080 and not with stroking a path. My problem occurs with textfield rendering when the fields abut one another. Sometimes the edges do not line up even when they are configured to do so in IB.

    (I'm staying in Snow Leopard using Xcode 3; I hate Lion and it's move away from the mouse. I suffer from familial tremors and have a tough enough time with the mouse and find the gesture pad impossible. I will stay on Snow Leopard until I am forced to a new machine. Needless to say I program as a hobby - just for my own enjoyment)

    At any rate I believe the problem is with the graphics chip and its ability to consistently activate pixels in close proximity when not drawn with a single stroke. I know of no solution except to adjust the placement of your drawn objects to minimize the effect.

    Changing monitors or resolution, I think, will not address the problem. A different, perhaps "better" graphics card might.

    On May 4, 2012, at 9:19 PM, Peter Teeson wrote:

    > Xcode 4.3.2, Lion 10.7.3,
    > Apple 30" Cinema Display, Samsung SyncMaster 950p
    >
    > Consider the following simple drawRect to demonstrate the issue
    >
    > - (void)drawRect:(NSRect)dirtyRect
    > {
    > // Drawing code here.
    > NSRect bounds = [self bounds];
    > NSBezierPath *path = [NSBezierPath bezierPathWithRect:bounds];
    > [path stroke];
    > }
    >
    > In the specific case of the Cinema Display having a resolution setting of 1280 x 800
    > the rectangle is not fully drawn every time. The top and right edges are missing.
    > But sometimes they do draw. It seems quite arbitrary.
    >
    > I have tested this with all other resolutions on the Cinema Display under both standard
    > and HiDPI and also on the Samsung CRT. They all work just fine. Even the Samsung at 1280 x 800.
    >
    > (I have tried things such as NSIntegralRect but nothing works except to change the resolution)
    >
    > Why would this occur and only at that resolution and only on the Cinema Display?
    >
    > Any ideas for tracking it down, or working around it, gratefully received.
    >
    > TIA and respect….
    >
    > Peter

    Charlie Dickman
    <3tothe4th...>
  • On 2012-05-04, at 10:22 PM, Charlie Dickman wrote:
    > (I'm staying in Snow Leopard using Xcode 3; I hate Lion and it's move away from the mouse.
    I don't understand. I only use a mouse on my Mac Pro. No Tablet. (I don't do iDevice apps)
    So I don't see any of the bouncy castle scroll things that some people abhor.

    > Changing monitors or resolution, I think, will not address the problem. A different, perhaps "better" graphics card might.
    It's an NVIDIA GeForce GT 120 card provided by Apple when I bought the MacPro and Display.

    Thanks for your thoughts but, with respect, I think we are dealing with different issues.

    > On May 4, 2012, at 9:19 PM, Peter Teeson wrote:
    >> Xcode 4.3.2, Lion 10.7.3,
    >> Apple 30" Cinema Display, Samsung SyncMaster 950p
    >>
    >> Consider the following simple drawRect to demonstrate the issue
    >>
    >> - (void)drawRect:(NSRect)dirtyRect
    >> {
    >> // Drawing code here.
    >> NSRect bounds = [self bounds];
    >> NSBezierPath *path = [NSBezierPath bezierPathWithRect:bounds];
    >> [path stroke];
    >> }
    >>
    >> In the specific case of the Cinema Display having a resolution setting of 1280 x 800
    >> the rectangle is not fully drawn every time. The top and right edges are missing.
    >> But sometimes they do draw. It seems quite arbitrary.
    >>
    >> I have tested this with all other resolutions on the Cinema Display under both standard
    >> and HiDPI and also on the Samsung CRT. They all work just fine. Even the Samsung at 1280 x 800.
    >>
    >> (I have tried things such as NSIntegralRect but nothing works except to change the resolution)
    >>
    >> Why would this occur and only at that resolution and only on the Cinema Display?
    >>
    >> Any ideas for tracking it down, or working around it, gratefully received.
    >>
    >> TIA and respect….
    >>
    >> Peter
    >
    > Charlie Dickman
    > <3tothe4th...>
    >
    >
    >
  • On May 4, 2012, at 8:19 PM, Peter Teeson wrote:

    > - (void)drawRect:(NSRect)dirtyRect
    > {
    > // Drawing code here.
    > NSRect bounds = [self bounds];
    > NSBezierPath *path = [NSBezierPath bezierPathWithRect:bounds];
    > [path stroke];
    > }
    >
    > In the specific case of the Cinema Display having a resolution setting of 1280 x 800
    > the rectangle is not fully drawn every time. The top and right edges are missing.

    Well, I don't think you're drawing what you think you are.  The bounds rect is outside of the view.  It's the exterior boundary of the view.  Now, when you stroke it, assuming you haven't changed the default line width of NSBezierPath, the stroke will be 1 point wide, half a point inside the view and half outside.  This may involve anti-aliasing or, under HiDPI, perhaps a one-pixel wide line since that's half a point.

    It's probably up to the graphics driver to decide precisely how to render that half-point-wide line.

    Generally, if you actually want to draw just within the view's bounds, you would inset the rectangle by half the line width.

    Regards,
    Ken
  • On 2012-05-04, at 10:58 PM, Ken Thomases wrote:
    > On May 4, 2012, at 8:19 PM, Peter Teeson wrote:
    >
    >> - (void)drawRect:(NSRect)dirtyRect
    >> {
    >> // Drawing code here.
    >> NSRect bounds = [self bounds];
    >> NSBezierPath *path = [NSBezierPath bezierPathWithRect:bounds];
    >> [path stroke];
    >> }
    >>
    >> In the specific case of the Cinema Display having a resolution setting of 1280 x 800
    >> the rectangle is not fully drawn every time. The top and right edges are missing.
    >
    > Well, I don't think you're drawing what you think you are.  The bounds rect is outside of the view.  It's the exterior boundary of the view.

    > Now, when you stroke it, assuming you haven't changed the default line width of NSBezierPath,

    > the stroke will be 1 point wide, half a point inside the view and half outside.
    > This may involve anti-aliasing or, under HiDPI, perhaps a one-pixel wide line since that's half a point.
    > It's probably up to the graphics driver to decide precisely how to render that half-point-wide line.
    >
    > Generally, if you actually want to draw just within the view's bounds, you would inset the rectangle by half the line width.
    >
    > Regards,
    > Ken
    Thanks so much Ken for that help. I am using NSInsetRect later on in my actual code for something else
    But I did not realize that the bounds stroke was straddling the view content edges.
    How did you learn that?

    Also I find it strange that it just happened to work at all other resolutions under both HiDPI and standard.
    So this may be an edge case (pun intended) in the driver in mapping to the actual hardware.
    I followed your suggestion and inset bounds by 0.5 and it works fine.

    Your contribution to my education made my day. Thanks a 1E6.

    Peter
  • > On 2012-05-04, at 10:58 PM, Ken Thomases wrote:
    >> Generally, if you actually want to draw just within the view's bounds, you would inset the rectangle by half the line width.

    Alternatively, if you don't need the NSBezierPath for something else and you don't need alpha, you could use NSFrameRect(rect). From the docs:

    <https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Appl
    icationKit/Miscellaneous/AppKit_Functions/Reference/reference.html#//apple_
    ref/doc/uid/20000695-SW33
    >
    > Draws a frame around the inside of aRect in the current color and using the NSCompositeCopy compositing operation. The width is equal to 1.0 in the current coordinate system. Since the frame is drawn inside the rectangle, it will be visible even if drawing is clipped to the rectangle.
    >
    > Because this function does not draw directly on the line, but rather inside it, it uses the current fill color (not stroke color) when drawing.

    I did not know until just now that it uses the fill color rather than the stroke color.

    If you *do* need alpha, you could do it with

        NSFrameRectWithWidthUsingOperation(rect, 1.0, NSCompositeSourceOver);

    On May 5, 2012, at 10:41 AM, Peter Teeson wrote:
    > Thanks so much Ken for that help. I am using NSInsetRect later on in my actual code for something else
    > But I did not realize that the bounds stroke was straddling the view content edges.
    > How did you learn that?

    The docs mention it, though it's understandable that you didn't think to look for it since it *looked* like a hardware issue.

    <https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Appl
    icationKit/Classes/nsbezierpath_Class/Reference/Reference.html#//apple_ref/
    doc/uid/20000339-CHDECDGE
    >
    > The drawn line is centered on the path with its sides parallel to the path segment. This method uses the current drawing attributes associated with the receiver.

    --Andy
  • On May 5, 2012, at 7:41 AM, Peter Teeson wrote:

    > But I did not realize that the bounds stroke was straddling the view content edges.
    > How did you learn that?

    If you think about it, drawing a stroke along a path _has_ to draw on both sides of the path (i.e. centered on the path) because there isn’t necessarily any inside or outside; the path might not be closed, or it might be a figure 8 or something.

    Another side effect of this is that, if the path width is an odd number of pixels, you should offset your coordinates by 0.5 to make sure you completely fill pixels, otherwise you’ll have fuzzy borders on your horizontal and vertical strokes.

    —Jens
  • On 2012-05-05, at 11:29 AM, Andy Lee wrote:
    > On May 5, 2012, at 10:41 AM, Peter Teeson wrote:
    >> Thanks so much Ken for that help. I am using NSInsetRect later on in my actual code for something else
    >> But I did not realize that the bounds stroke was straddling the view content edges.
    >> How did you learn that?
    >
    > The docs mention it, though it's understandable that you didn't think to look for it since it *looked* like a hardware issue.
    >
    > <https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Appl
    icationKit/Classes/nsbezierpath_Class/Reference/Reference.html#//apple_ref/
    doc/uid/20000339-CHDECDGE
    >
    >> The drawn line is centered on the path with its sides parallel to the path segment. This method uses the current drawing attributes associated with the receiver.
    >
    > --Andy
    Thanks Andy. I should have looked up NSBezierPath using AppKiDo and looked at -stroke.
    But I thought I knew what a Bezier was so just found a suitable method for my test.
    Never gave a thought to how -stroke works.

    Thanks so much for helping me.

    respect….

    Peter

    P.S. You can learn something new every day unless you are very careful. ;}
  • First of all thanks Jens for taking the time to reply and educate me.

    On 2012-05-05, at 12:40 PM, Jens Alfke wrote:
    > On May 5, 2012, at 7:41 AM, Peter Teeson wrote:
    >> But I did not realize that the bounds stroke was straddling the view content edges.
    >> How did you learn that?
    >
    > If you think about it, drawing a stroke along a path _has_ to draw on both sides of the path
    > (i.e. centered on the path) because there isn’t necessarily any inside or outside;
    > the path might not be closed, or it might be a figure 8 or something.
    Good points Jens. I never gave a thought to look up -stroke in the NSBezierPath reference.

    > Another side effect of this is that, if the path width is an odd number of pixels,
    > you should offset your coordinates by 0.5 to make sure you completely fill pixels,
    > otherwise you’ll have fuzzy borders on your horizontal and vertical strokes.
    >
    > —Jens

    As to odd number of pixels I was under the impression that the parameters are in points,
    which are a unit of length* whereas pixels are related to hardware specs. e.g. Line width is in points.
    So it is not the responsibility of the device driver to map points to pixels in a device dependent way?

    I wish to write resolution independent code which to me implies I should use points.
    Are you saying that I have to compute the path width in pixels? And then adjust for valence? etc?
    If so I have been harbouring an illusion that I'm responsible for points and the drivers are responsible
    for mapping to the hardware. If I am wrong then doesn't this place an undue burden on the programmers.

    Have I got it wrong? But anyway thanks for taking the time.

    respect….

    Peter

    *[Point is a physical unit of length, used in typography.
    It's equal to 1/12 Pica, and 1 Pica = 1/6 inch. So 1 pt = 1/72 inch]
  • On May 5, 2012, at 1:12 PM, Peter Teeson wrote:

    > As to odd number of pixels I was under the impression that the parameters are in points,
    > which are a unit of length* whereas pixels are related to hardware specs. e.g. Line width is in points.

    That’s right. But everything you draw is going to be mapped onto pixels by the time it’s onscreen.

    > So it is not the responsibility of the device driver to map points to pixels in a device dependent way?

    It does that, but the results may not always be exactly what you want. :) The default behavior is to antialias, so if you stroke a one-point-wide black rectangle on integer point boundaries (assuming the usual 1pt==1px scaling) you will in fact get a two-pixel wide gray rectangle. Which is probably not what you wanted. This is because the edges are on pixel boundaries so the strokes are going halfway through the pixels on either side. The solution as I said is to add 0.5 to the coordinates so that the stroke edges fall on pixel boundaries.

    (IIRC there are some AppKit geometry utility functions that will adjust coordinate values to fit pixel boundaries in a resolution-independent way. I don’t remember their names offhand.)

    Someday every device will have retina-quality graphics and these details won’t matter, but currently they definitely do, especially for UI elements with thin horizontal and vertical lines. (And actually they may matter even on retina displays. These details of pixel rounding are definitely important at 300dpi on printers, which is why PostScript fonts are hinted — hinting consists almost entirely of moving edges of curves onto pixel boundaries. I remember from the old days that unhinted fonts on a 300dpi LaserWriter looked like shit. It turns out that even if your eye can’t distinguish individual pixels 1/300” across, it can easily tell the difference between a 1/300” and a 1/150” thick line.)

    —Jens
  • On May 5, 2012, at 4:46 PM, Jens Alfke <jens...> wrote:

    >
    > It does that, but the results may not always be exactly what you want. :) The default behavior is to antialias, so if you stroke a one-point-wide black rectangle on integer point boundaries (assuming the usual 1pt==1px scaling) you will in fact get a two-pixel wide gray rectangle. Which is probably not what you wanted. This is because the edges are on pixel boundaries so the strokes are going halfway through the pixels on either side. The solution as I said is to add 0.5 to the coordinates so that the stroke edges fall on pixel boundaries.
    >
    > (IIRC there are some AppKit geometry utility functions that will adjust coordinate values to fit pixel boundaries in a resolution-independent way. I don’t remember their names offhand.)

    -centerScanRect:, -convertRectToBacking: and friends. Which if I understand correctly you should now *always* use instead of adding half pixels because of HiDPI.

    >
    > Someday every device will have retina-quality graphics and these details won’t matter

    It still matters because coordinates in Quartz always refer to the infinitely thin space between device pixels.

    --Kyle Sluder
  • On 2012-05-05, at 7:55 PM, Kyle Sluder wrote:
    > On May 5, 2012, at 4:46 PM, Jens Alfke <jens...> wrote:
    >>
    >> (IIRC there are some AppKit geometry utility functions that will adjust coordinate values to fit pixel boundaries in a resolution-independent way. I don’t remember their names offhand.)
    > -centerScanRect:, -convertRectToBacking: and friends. Which if I understand correctly you should now *always* use instead of adding half pixels because of HiDPI.
    >> Someday every device will have retina-quality graphics and these details won’t matter
    > It still matters because coordinates in Quartz always refer to the infinitely thin space between device pixels.
    >
    > --Kyle Sluder
    Thanks very much Jens and Kyle and everyone who contributed. Most valuable. From now on I'll be using the
    the NSView -convertPointFrom/To and -convertRectTo/From methods in their various forms.
    As for NSView's -centerScanRect I will try to understand where that should be used by examining the code examples.
    Also NSScreen -convertRectToBacking and-convertRectFromBacking methods as appropriate.

    This is my first time digging deeply into graphics. My previous work was primarily DB, network and IOKit related.
    THanks again and

    respect….

    Peter
previous month may 2012 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today