This chapter describes how to start adapting your application to take advantage of resolution independence.
Cocoa applications require little work to support resolution independence because the Cocoa frameworks handle the scaling for you. However, depending on how you manipulate windows and the views within them, you may need to make some changes. Of course, in addition to code changes, you may need to provide higher-resolution versions of any custom artwork.
Because of scaling, the coordinates of the window frame and its top-level view (the frame view) are not always the same. For example, say you have a window frame view with dimensions 80 x 80 points. If the scale factor is 1.0, there is a 1 : 1 correspondence between the units of the frame view and its owning window; that is, the window is displayed as being 80 x 80 pixels. However, if the scale factor is 1.25, the window size is displayed 25% larger, resulting in a 100 x 100 pixel window. Any calls that return the size of the window return 100 x 100.
Note: In the resolution-independent world, all Cocoa views are scaled if the scale factor is not 1.0; however, if the scaling for a view is due only to the scale factor, the NSView
method isRotatedOrScaledFromBase
returns NO
. This result minimizes possible overhead from scrolling and similar operations.
An application must not assume that the window frame and the view frames within the window use the same coordinate system. For example, an application that positions a view based on the window frame does not always get correct results.
Historically, compositing was done in the base coordinate system of the image being rendered, regardless of the coordinate system of the owning view. To allow compositing to support resolution independence, you can assume that all base coordinates are transformed by the current scale factor.
Here are some common cases.
Compositing a 72 dpi 100 x 100 source image in a 1.25 scale factor window.
A 72 dpi 100 x 100 image (stored in an NSImageRep
object) contains 100 x 100 pixels. When composited into a view in a scaled window, the image is scaled to fill 125 x 125 pixels using the proper interpolation algorithm. Any coordinate transforms on the destination view (aside from the window scaling) are ignored.
Compositing a 90 dpi 100 x 100 source image in a 1.25 scale factor window.
A 90 dpi 100 x 100 image contains 125 x 125 pixels. When composited into a view in a scaled window, this image (rendered from an NSCachedImageRep
object) contains 125 x 125 pixels, so no interpolation is needed. Any coordinate transforms on the destination view (aside from the window scaling) are ignored.
Creating an NSCachedImageRep
object from a 72 dpi 100 x 100 source image to display in a 1.25 scale factor window.
A 72 dpi 100 x 100 source image contains 100 x 100 pixels. The NSCachedImageRep
object is created with a size of 100 x 100, but holds 125 x 125 pixels because of the scale factor. The source image is scaled to fit the required pixel size using the proper interpolation algorithm. When the cached image is drawn, the pixels are copied 1 to 1 from the cached image to the destination window.
Each NSImageRep
object that contains bitmapped data indicates its resolution (dots-per-inch) because the image size is defined in points as well as in pixel width and height. A 72 dpi NSImageRep
object has a 1 : 1 correspondence between points and pixels, while a 144 dpi NSImageRep
object has a 1 : 2 correspondence between points and pixels. NSCachedImageRep
objects are stored already scaled to the destination window; for example, a 100 x 100 NSCachedImageRep
for a window with a scale factor of 1.25 would report a size of 100 x 100 points, but pixel dimensions of 125 x 125.
Cocoa supports several methods that your application can use to obtain scale factor information. It should be noted that most Cocoa applications do not need to use these methods.
To obtain the global scale factor (as set in Quartz Debug), use the userSpaceScaleFactor
method in the NSScreen
class:
@interface NSScreen : NSObject |
... |
- (CGFloat)userSpaceScaleFactor; |
... |
@end |
To obtain the scale factor for a particular window, use the userSpaceScaleFactor
method in the NSWindow
class:
@interface NSWindow : NSResponder |
... |
- (CGFloat)userSpaceScaleFactor; |
... |
@end |
If you want to create a window that should not be scaled (for example, a custom window), you can specify the NSUnscaledWindowMask
mask at window creation time. For unscaled windows, the userSpaceScaleFactor
method returns 1.0.
To support resolution independence, you may need to convert rectangles or points from the coordinate system of one NSView
instance to another (typically the superview or subview), or from one NSView
instance to the containing window. The NSView
class defines six methods that convert rectangles, points, and sizes in either direction:
Convert to the receiver from the specified view | Convert from the receiver to the specified view |
---|---|
|
|
|
|
|
|
The convert...:fromView:
methods convert the values to the receiver's coordinate system, from the coordinate system of the view passed as the second parameter. If nil
is passed as the view, the values are assumed to be in the window's base coordinate system and are converted to the receiver's coordinate system. The convert..:toView:
methods do the inverse, converting values in the receiver's coordinate system to the coordinate system of the view passed as a parameter. If the view parameter is nil
, the values are converted to the base coordinate system of the receiver's window.
For converting to and from the screen coordinate system, NSWindow
defines the convertBaseToScreen:
and convertScreenToBase:
methods.
For more information about coordinate conversion in views, see the chapter Working with the View Hierarchy in View Programming Guide. The chapter Coordinate Systems and Transforms in Cocoa Drawing Guide may also be helpful.
In Mac OS X v10.5, NSView
provides a new set of methods that should be used when performing pixel alignment of view content. These methods provide the means to transform geometry to and from a base coordinate space that is pixel-aligned with the backing store into which the view is being drawn.
Convert to the base coordinate system | Convert from the base coordinate system |
---|---|
|
|
|
|
|
|
These new coordinate transform methods provide a way to abstract view content drawing code from the details of particular backing store configurations, and always achieve correct pixel alignment without having to special-case for layer-backed vs. conventional view rendering mode.
For more information, see Application Kit Release Notes (Snow Leopard).
Both the NSWindow
and NSScreen
classes define a deviceDescription
method. This method returns a dictionary containing a NSDeviceResolution
key. The NSDeviceResolution
key has historically contained an NSSize
value of (72.0, 72.0). In Mac OS X v10.4 and later, NSDeviceResolution
contains an NSSize
value of (72.0 * scale factor, 72.0 * scale factor).
Carbon applications have two scaling options: framework-scaled mode and magnified mode. You set these modes on a window-by-window basis by setting the appropriate attribute at window creation time. If you do not select a scale mode, the system assumes magnified mode by default. You can specify the scale mode of a window only at window creation time.
As described previously, a window can use the framework-scaled mode if it uses HIView-based controls (that is, it uses compositing mode) and draws exclusively with Quartz. At drawing time, a scaling transform is applied to the Quartz context used by the views. Also, in order to support older functions, any window coordinate information (window bounds, mouse position, and so on) is automatically translated to reflect the proper window- or view-centric origin before being passed to the application.
A major benefit of framework-scaled mode is that windows loaded from nib files automatically work at all scale factors with no need to reposition their contents.
You specify framework-scaled mode by setting the kWindowFrameworkScaledAttribute
attribute at window creation time or by choosing Framework Scaled for the Scaling popup button in the window inspector in Interface Builder version 2.5 and later.
While specifying framework-scaled mode means most of the scaling work is handled for you, you still need to supply higher-resolution versions of any custom artwork, such as icons, background images, and so on.
Magnified mode is the default rendering mode. If the scale factor is not 1.0, all windows that are not tagged as being in framework-scaled mode are scaled in magnified mode. As described previously, the window is simply scaled to match the scale factor.
Important: Because magnified windows do not look as crisp as properly scaled windows, you should adopt framework scaling as soon as possible.
In magnified mode, all onscreen coordinates are mapped to their user space equivalents when passed to the application. For example, if the scale factor is 2.0, a mouse click onscreen at a particular pixel is mapped to its window or view-centric equivalent location when passed in a mouse-down event.
To determine the scale factor for your application, you can use the HIGetScaleFactor
function:
CGFloat HIGetScaleFactor (void); |
If you need to determine the scale mode for a particular window (and also the application scale factor), you can call the HIWindowGetScaleMode
function:
OSStatus HIWindowGetScaleMode ( |
HIWindowRef inWindow, |
HIWindowScaleMode *outMode, |
CGFloat *outScaleFactor); |
On output, outMode
returns one of the following values:
kHIWindowScaleModeUnscaled
The window is not scaled at all because the scale factor is 1.0.
kHIWindowScaleModeMagnified
The window‘s backing store is being magnified because the scale factor is not equal to 1.0 and because the window was not created with the framework-scaled attribute.
kHIWindowScaleModeFrameworkScaled
The window‘s contents are scaled to match the scale factor because the scale factor is not equal to 1.0 and because the window was created with the framework-scaled attribute.
Note: A fourth scale mode named kHIWindowScaleModeApplicationScaled
was available in Mac OS X v10.4 but was never fully implemented and is not supported at all in Mac OS X v10.5 and later.
Because the scale mode in Carbon applications can be on a window-by-window basis, you may often need to convert between the various coordinate systems involved. The HIGeometry programming interface (see HIGeometry Reference) provides three functions that simplify conversion:
HIPointConvert
to translate an HIPoint
structure.
HIRectConvert
to translate an HIRect
structure. Note that the HIRect
structure has an organization different from that of the older QuickDraw Rect
structure.
HISizeConvert
to translate an HISize
structure.
These conversion functions require you to specify the source and destination coordinate spaces as well as any associated objects, if required. For example, if you wanted to translate a point into view coordinates, you must specify the HIView to which the coordinates refer. You specify the coordinate spaces by passing the following constants:
kHICoordSpace72DPIGlobal
, which specifies the old global coordinate system defined by QuickDraw. When the scale factor is 1.0, this space is equivalent to kHICoordSpaceScreenPixel
.
kHICoordSpaceScreenPixel
, which is the coordinate space defined by the actual screen pixels.
kHICoordSpaceWindow
, which specifies a window-centric coordinate system, with the origin (0,0) being the top-left corner of the window's structure region.
kHICoordSpaceView
, which specifies an HIView-centric coordinate system. The origin (0,0) is the top-left corner of the view.
The conversion functions take floating-point coordinates, which means that rounding may be necessary in certain cases. Which way to round depends on whether the system is more forgiving of overstating or understating the value. For example:
When the coordinate is used to define some sort of maximal area, you should outset the value. That is, round the value so that it defines a larger area rather a smaller one. For example, you should outset coordinate values that define a view's structure shape, because that area defines the maximum bounds into which the view can draw.
When defining a minimal area, you should inset the value. For example, you should inset the coordinate values for a view's opaque region, because that area defines the largest area that can be assumed to be opaque.
You can use the Quartz 2D functions CGRectInset
and CGRectIntegral
to simplify inset and outset operations. The BSD Library functions ceil
and floor
(available in math.h
) may also be useful.
Keep in mind that HIShapeRef
values take only integer coordinates. If you attempt to create a shape from floating-point coordinates (for example, by calling HIShapeCreateWithRect
on an HIRect
object), the call automatically rounds any non-integer coordinates to outset the shape. To avoid unexpected results, you should round any coordinates appropriately (inset or outset) before creating an HIShape based upon it.
If your application uses custom controls or menus, you may need to make some changes to make them compatible with resolution independence.
If you are still using QuickDraw to draw, you should adopt Quartz. If you are using the Appearance Manager to draw control elements, use HITheme (which is Quartz-savvy) instead.
In framework-scaled mode, the Quartz context passed to your custom view in the kEventControlDraw
event has already been transformed to match the scale factor, so you probably won't need to update your drawing code.
If your application still uses custom MDEFs, the Menu Manager creates windows to hold them and scales them appropriately, so they are effectively in magnified mode. However, you should consider updating your MDEFs to custom HIView-based menus.
When using view-based menus, the Menu Manager can automatically scale them in framework-scaled mode. Currently a workaround exists that allows kEventMenuDrawItem
and kEventMenuDrawItemContent
handlers to use QuickDraw calls even in framework-scaled mode. The standard menu view creates a temporary GWorld
object and sets it to the current QuickDraw port before sending any menu drawing events. After the draw event, the Menu Manager copies the contents of the graphics world into the view. However, this workaround should be considered a temporary fix and you should plan to update your menu drawing handlers to draw into the supplied CGContext
event parameter.
The following Carbon technologies will not be updated to support resolution independence:
TextEdit. Applications should use MLTE, the editable Unicode text control, or HITextView instead.
The edit text control. Applications should use the editable Unicode text control or HITextView instead.
The list box control. Applications should use the data browser control instead.
Java SE (Standard Edition) 6 in Mac OS X v10.5 supports resolution independence at runtime.
All drawing is done in framework-scaled mode. Text, vector drawing, and most system controls are drawn correctly scaled with no additional work on your part. Any bitmap images are magnified to fit the designated space.
All Java drawing methods (and their associated parameters) interpret coordinates as points, not pixels. Currently, no resolution independence–specific methods exist.
Even if you rely on framework scaling, there may be cases where you want to know in advance how to scale your content. To do so, you can use the Quartz 2D function CGContextGetUserSpaceToDeviceSpaceTransform
.
CGAffineTransform CGContextGetUserSpaceToDeviceSpaceTransform ( |
CGContextRef theContext); |
This call returns the transform matrix used to resize your window, converting from user space (that is, where you draw into the context) to the coordinate space of the display device. For example, you may want to transform your window to device space to determine the new coordinates of its elements. You can adjust these coordinates to make sure that window elements line up correctly, then do a reverse transform to obtain the user space coordinates needed for the best presentation at that scale factor.
Note: The transform you receive describes the sum of all the transformations applied to the graphics context, not just the scaling. For example, the transform includes any rotation or translation applied to the context.
For simple conversions between user space and device space, you can also use one of the Quartz conversion functions described in CGContext Reference. These functions convert only global coordinates, so you need to perform additional calculations to translate the results to view-centric coordinates.
If your application interacts with other applications, you need to make sure that all the applications agree on the coordinate system; otherwise, strange behavior may result.
Apple’s accessibility interfaces support resolution independence, so you don’t need to worry about translating between coordinate systems when supporting accessibility. The accessibility interfaces always return coordinates in screen pixels.
For the accessibility Carbon events that have event parameters containing coordinates, an event handler can ask for the parameter value in either screen pixel or 72DPI global coordinates, depending on which parameter type is used. For example, typeHIPoint
and typeHIPoint72DPIGlobal
return 72DPI global coordinates, while typeHIPointScreenPixel
returns screen pixel coordinates. Similar parameter type constants are available for HIRect
, HISize
, and CGFloat
.
Most OpenGL problems with resolution independence are caused by a mismatch between the screen pixels and the points of the drawing environment. The Cocoa class NSOpenGLView
has been updated to handle common problems. If you are drawing directly to the screen (that is, on a pixel-by-pixel basis), you need to obtain the current scale factor and scale all your images manually.
Last updated: 2007-05-04