Re: why is quartz still interpolating ??
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.