• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: why is quartz still interpolating ??
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: why is quartz still interpolating ??


  • Subject: Re: why is quartz still interpolating ??
  • From: Dietmar Planitzer <email@hidden>
  • Date: Tue, 20 Apr 2004 10:52:04 +0200

On Apr 20, 2004, at 1:00 AM, M. Uli Kusterer wrote:

At 12:41 Uhr -0400 19.04.2004, Robert Clair wrote:
I draw a bunch of single color bitmaps. Since the color may change, I drop
down into core graphics and use image masks. My problem is that I can't
get Quartz to stop interpolating - no matter what I do I wind up with a
few pixels that are neither the color that I'm drawing in nor the
background color.

Very likely you're not seeing interpolation, you're seeing Quartz's attempt at producing a drawing "between the pixels" because you specified the coordinates wrong.

In Quartz (as opposed to Quickdraw) the lines of the coordinates go through the middle of the pixels (not along the upper left, as e.g. it was in Quickdraw). So, if you want to draw a full pixel at the upper left, you don't draw at { 0, 0 }, but rather you draw at { 0.5, 0.5 }.


Well, not quite. Things are actually more complicated because on one side Quartz implements the PostScript imaging model but on the other side it adds anti-aliasing to it.

Note: the following discussion ignores the CTM for simplification purposes.

In PostScript a pixel (i, j) is defined as a half-open region which contains the set of points (s, t) such that i <= s < i+1 and j <= t < j+1. Expressed in a human way this means that the coordinates of a pixel are basically derived from a user coordinate by applying the floor function: i = floorf(s) and j = floorf(t). Thus, the user coordinates 10.5, 10.9 and 10.0 all represent the same pixel, namely 10.

Obviously this definition doesn't work with anti-aliasing. In this case its important to preserve the fractional part of a user coordinate after it has been transformed into device space and use it for the computation of the pixel coverage. And this is exactly what Quartz does with anti-aliasing turned on.

Now is the right time where its necessary to point out a special feature of the PostScript imaging model: the stroke operator applies _half_ of the line width on the _left_ side of a line and half of the line width on the _right_ side of the line. This is in sharp contrast to de facto every other graphics library out there which apply the line width uniformly on one side and only one side of a line. Some people consider the PS model to be a misfeature, because it makes the act of stroking a general path with anti-aliasing turned on the correct way, well, an interesting adventure.

We can see now that there is a difference between stroking and filling a path from the perspective of how the coordinates must be specified:

1) Filling a path: coordinates must fall on the pixel grid. More exactly, on the lower-left corner of a pixel. This rule applies to images, too.

Examples:

1.a) The rectangle with origin (10, 10) and size (100, 100) will be filled as expected even if anti-aliasing is turned on.

1.b) The rectangle with origin (10.5, 10.5) and size (100, 100) will be _incorrectly_ filled if anti-aliasing is turned on, because its corners cover only 25% of the pixels in device space. Also, its edges go through the center of device pixels which implies that they cover only 50% of those pixels. The end result is a filled rectangle with "fuzzy" edges and corners.

2) Stroking a path: coordinates must fall between pixels because, as mentioned above, the stroke operator will add half the line width to either side of the coordinates.

Examples:

2.a) The rectangle with origin (10, 10) and size (100, 100) will be _incorrectly_ stroked, if the current line width is set to 1 and line cap is set to projecting square.

Lets look more closely at what actually happens in the case of the left edge which starts at (10, 10) and goes to (10, 110): The stroke operator takes the user coordinates (10, 10) and (10, 110) and computes a new path which represents the stroked path: (9.5, 9.5), (10.5, 9.5), (10.5, 110.5), (9.5, 110.5). We now have a problem because this new path falls between device pixels. Consequently, after the application of anti-aliasing, we end up with a fuzzy looking edge which has a rendered line width of two pixels rather then the expected one pixel.

2.b) The rectangle with origin (10.5, 10.5) and size (100, 100) will be drawn as expected because (again left edge only) the stroked path is: (9, 9), (11, 9), (11, 111), (9, 111). This path exactly covers its device pixels with the consequence that we get a nice sharp line with an actual line width of one pixel.


Intermezzo: Its often suggested that you should simply translate the coordinate system of a view by half a pixel up and to the right in order to "fix" the fuzzy line problem. However, this solution has at least two fundamental problems:

1) It helps the stroke case, but introduces a new problem in the fill and image drawing case. Now the coordinates of filled paths and images will be off by half a pixel and we get fuzzy edges.

2) It creates a discrepancy between input device coordinates and output device coordinates. This discrepancy is commonly not noticeable as long as scaling doesn't come into play, but not with scaling. Consider a graphics app which implements zooming with a maximum zoom level of 1600%. At 100% zoom level the mouse and the screen are off by only half a pixel. At 1600% there is a 8 pixel gap which a user will definitely notice.


A similar problem even exists in PostScript without anti-aliasing. Depending on where exactly a transformed user space coordinate falls in device space, a stroked line may end up looking thicker than expected. I.e. a line with an indented line with of 1 pixel may end up being two pixels wide. This is why a special feature called automatic stroke adjustment was added. The stroke adjustment machinery takes a path which should be stroked and transforms it into device space, adjusts the coordinates and then transforms it back into user space before it starts the actual stroke operation. Coordinates are adjusted in such a way that they end up in the center of device pixels.

Automatic stroke adjustment is especially handy in connection with arbitrary affine transformations, because the CTM is applied to both the path construction operators (CGContextMoveTo(), CGContextAddLineTo(), etc.) and the line width at the time you call CGContextStrokePath(). However, this makes predicting the correct coordinates easily a nontrivial exercise as soon as CTM changes like scaling, rotation or shearing come into play.

Maybe its time to ask Apple for the addition of stroke adjustment to Quartz.

Back in the DPS days stroke adjustment was turned on by default, so you could always use "simple", on the pixel grid, coordinates no matter if you where filling or stroking a path and the right thing would happened.


Regards,

Dietmar Planitzer
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.


References: 
 >why is quartz still interpolating ?? (From: Robert Clair <email@hidden>)
 >Re: why is quartz still interpolating ?? (From: "M. Uli Kusterer" <email@hidden>)

  • Prev by Date: Re: internal frameworks in app bundle
  • Next by Date: initWithBitmapDataPlanes + colorspace
  • Previous by thread: Re: why is quartz still interpolating ??
  • Next by thread: Re: why is quartz still interpolating ??
  • Index(es):
    • Date
    • Thread