NSImages rendered at runtime and resolution independent UI...

  • I am attempting to get my code ready for resolution independence and I
    am trying understand the best way to deal with icon images that I
    create at runtime. The below is a cut down example of icon image cache
    generation code that I have (the simplest example I have and not one
    that would visually benefit the most but...).

    Ideally when the user interface scale factor is other then 1.0 I would
    want to create an NSImage that is still 12 point by 12 point but has
    its pixel content scaled (DPI other then 72) at the time it is created
    by the user interface scale factor. I know the framework will scale it
    as needed when I go to draw the image but I want to create the image
    content at the resolution needed for display to avoid unneeded
    interpolation and rep caching.

    It isn't clear to me if the following drawing code will automatically
    create an image 12 by 12 point image with pixel DPI already adjusted
    as needed for screen display (avoiding unneeded interpolation) or
    should I modify the code in some fashion to allow the creating of
    image content at a higher DPI then 72?

    If I need to do the later any recommendations on the best way to go
    about doing this?

    static NSImage* coloredPenIcons[12] = {nil};
    if (coloredPenIcons[0] == nil) {
        NSSize imageSize = NSMakeSize(12.0, 12.0); // size in points

        NSBezierPath* penBody = [NSBezierPath bezierPath];
        [penBody moveToPoint:NSMakePoint(1.0, 3.0)];
        ...

        NSBezierPath* penShadow = [NSBezierPath bezierPath];
        [penShadow moveToPoint:NSMakePoint(1.0, 2.0)];
        ...

        NSAffineTransform* transform = [NSAffineTransform transform];

        // Translate the origin to the center of the point grid, the
    coordinate (0,0)
        // is considered the lower left edge of a point in user coordinate space so
        // translating by 0.5 will place our drawing centered in the point grid.
        [transform translateXBy:0.5 yBy:0.5];

        NSColor* shadowColor = [[NSColor colorWithCalibratedRed:COLOR256(150)
                                                          green:COLOR256(150)
                                                          blue:COLOR256(150)
                                                          alpha:1.0] set];
        for (int i = 0; i < 12; i++) {
            NSImage* image = [[NSImage alloc] initWithSize:imageSize];

            [image lockFocus];
            [[NSGraphicsContext currentContext] setShouldAntialias:NO];
            [transform concat];

            [[NSColor colorWithCalibratedRed:COLOR256(150)
                                      green:COLOR256(150)
                                        blue:COLOR256(150)
                                      alpha:1.0] set];
            [penShadow stroke];

            ...color for pen body varies based on index...
            [penBody fill];
            [penBody stroke];
            [image unlockFocus];

            coloredPenIcons[i] = image;
        }
    }

    -Shawn
  • On Oct 27, 2006, at 1:57 PM, Shawn Erickson wrote:

    > I am attempting to get my code ready for resolution independence and I
    > am trying understand the best way to deal with icon images that I
    > create at runtime. The below is a cut down example of icon image cache
    > generation code that I have (the simplest example I have and not one
    > that would visually benefit the most but...).
    >
    > Ideally when the user interface scale factor is other then 1.0 I would
    > want to create an NSImage that is still 12 point by 12 point but has
    > its pixel content scaled (DPI other then 72) at the time it is created
    > by the user interface scale factor. I know the framework will scale it
    > as needed when I go to draw the image but I want to create the image
    > content at the resolution needed for display to avoid unneeded
    > interpolation and rep caching.
    >
    > It isn't clear to me if the following drawing code will automatically
    > create an image 12 by 12 point image with pixel DPI already adjusted
    > as needed for screen display (avoiding unneeded interpolation) or
    > should I modify the code in some fashion to allow the creating of
    > image content at a higher DPI then 72?

    In some older code I had in my res-savvy framework, I used to create
    cached NSImage objects.  I believe what I saw was that it indeed auto-
    scaled things for you.  i.e. the size you feed it is the point size,
    but one based upon 72 dpi.

    However, I had many very strange drawing artifacts in those NSImage
    instances.  When I would draw bezier paths or text into the image,
    they were always scaled correctly.  For example, If I specified a
    size of 128x128 and my scale factor is set at 2.0, I would end up
    with a bitmap image rep of 256x256 pixels with a very crisp path/text
    in it.  When drawing other images into the cached NSImage, I would
    get random failures.  Sometimes the image was scaled correctly; other
    times it was not scaled at all, or even scaled in the wrong direction
    (i.e. 0.5x instead of 2.0x).

    I filed a bug on this a while back.  This was, BTW, all done when on
    Tiger.

    In my particular case, I really didn't need to get fancy and cache
    images like that.  So, my widgets ended up just drawing the images,
    bezier paths and text directly in their drawRect,
    drawInteriorWithFrame, etc. methods.

    Since what you have below is only drawing bezier paths, you should be
    OK.  If you need to draw images and run into bad scaling, please file
    a bug as that will show someone else being able to reproduce it.

    Finally, just to throw these helpful tips out there...

    * For static images that you load, it's best to move to vector-based
    formats (e.g. PDF).  I believe even NSCursor can benefit from images
    of that type to provide you truly res-independent large cursors

    * When you cannot move to vector-based, I've found that it's best to
    have your artwork in two flavors: 72dpi and 288dpi.  As I didn't have
    tools to create multi-resolution image files at the time, I just
    provided the flavors as separate files.  At runtime, I query the
    scale factor and if it's <= 1.0, I use the 72dpi image; otherwise the
    288dpi.  I found that I got poor downscaling of the 288dpi flavor.
    Note that this is the same strategy to employ for app icons; you want
    to provide a large one and one or more 'hints' at the smaller sizes.

    Note that I have done no testing with multi-resolution image files,
    so don't know if the Cocoa frameworks will do the right thing (i.e.
    which flavor is used depending upon the scale factor).

    >
    > If I need to do the later any recommendations on the best way to go
    > about doing this?
    >
    > static NSImage* coloredPenIcons[12] = {nil};
    > if (coloredPenIcons[0] == nil) {
    > NSSize imageSize = NSMakeSize(12.0, 12.0); // size in points
    >
    > NSBezierPath* penBody = [NSBezierPath bezierPath];
    > [penBody moveToPoint:NSMakePoint(1.0, 3.0)];
    > ...
    >
    > NSBezierPath* penShadow = [NSBezierPath bezierPath];
    > [penShadow moveToPoint:NSMakePoint(1.0, 2.0)];
    > ...
    >
    > NSAffineTransform* transform = [NSAffineTransform transform];
    >
    > // Translate the origin to the center of the point grid, the
    > coordinate (0,0)
    > // is considered the lower left edge of a point in user
    > coordinate space so
    > // translating by 0.5 will place our drawing centered in the
    > point grid.
    > [transform translateXBy:0.5 yBy:0.5];
    >
    > NSColor* shadowColor = [[NSColor colorWithCalibratedRed:COLOR256
    > (150)
    > green:COLOR256
    > (150)
    > blue:COLOR256
    > (150)
    > alpha:1.0] set];
    > for (int i = 0; i < 12; i++) {
    > NSImage* image = [[NSImage alloc] initWithSize:imageSize];
    >
    > [image lockFocus];
    > [[NSGraphicsContext currentContext] setShouldAntialias:NO];
    > [transform concat];
    >
    > [[NSColor colorWithCalibratedRed:COLOR256(150)
    > green:COLOR256(150)
    > blue:COLOR256(150)
    > alpha:1.0] set];
    > [penShadow stroke];
    >
    > ...color for pen body varies based on index...
    > [penBody fill];
    > [penBody stroke];
    > [image unlockFocus];
    >
    > coloredPenIcons[i] = image;
    > }
    > }
    >
    > -Shawn
    > _______________________________________________
    > Do not post admin requests to the list. They will be ignored.
    > Cocoa-dev mailing list      (<Cocoa-dev...>)
    > Help/Unsubscribe/Update your Subscription:
    > http://lists.apple.com/mailman/options/cocoa-dev/<rsharp...>
    >
    > This email sent to <rsharp...>

    ___________________________________________________________
    Ricky A. Sharp        mailto:<rsharp...>
    Instant Interactive(tm)  http://www.instantinteractive.com
  • Hello,

    On 27.10.2006, at 21:00, Shawn Erickson wrote:

    > I am attempting to get my code ready for resolution independence and I
    > am trying understand the best way to deal with icon images that I
    > create at runtime.

    For creating images (NSBitmapImageRep objects) with arbitrary resolution
    I use a Tiger-only method which is not bound to screen or printer
    properties while rendering and does not create a cached rep
    (NSCachedImageRep).

    I testet the following code to create an NSBitmapImageRep object
    with a given size (bSize) and resolution (dpi). I tried it
    for rendering Bezier curves in different resolutions.
    (For me it worked).

    // size is given in dots (= 1/72 inch)
    - (void) bezierExampleWithSize:(NSSize)bSize resolution:(float)dpi
    {
        NSSize imgSize = bSize;  // the result image shall have this
    size in dots
        float scale = dpi/72.0;  // nothing to scale for 72 dpi (some
    use 96 dpi !)
        // a dot has the dimension of a length and is measured in 1/72 inch

        // number of pixels corresponding to size and resolution
        int pixelsWide = roundf( scale*imgSize.width );
        int pixelsHigh = roundf( scale*imgSize.height );

        NSBitmapImageRep *newRep =
            [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
                  pixelsWide:pixelsWide
                  pixelsHigh:pixelsHigh
                  bitsPerSample:8
                  samplesPerPixel:4
                  hasAlpha:YES  // must be even you don't use alpha
                  isPlanar:NO  // planar imageReps are not supported
                  colorSpaceName:NSCalibratedRGBColorSpace
                  bytesPerRow:0 // let the system use a good value
                  bitsPerPixel:0 ];

        // the new rep still has a resolution of 72 dpi
        [NSGraphicsContext saveGraphicsState];  // push the state
        // the following is Tiger - only
        NSGraphicsContext *context = [NSGraphicsContext
    graphicsContextWithBitmapImageRep:newRep];
        [NSGraphicsContext setCurrentContext:context];

        // set the scaling
        NSAffineTransform *transform = [NSAffineTransform
    transform];    // identity matrix
        [transform scaleBy:scale];
        [transform concat];

        // ----    start example
        // create a Bezier Curve (whatever you want, this is a small
    example)
        NSBezierPath *bezierPath = [NSBezierPath bezierPath];
        // the following values arre given in dots, whatever the
    resolution may be
        [bezierPath appendBezierPathWithRect:
            NSMakeRect( 10, 10, (imgSize.width-20.0),
    (imgSize.height-20))];

        [[NSColor greenColor] set];
        [bezierPath setLineWidth:5.0];
        [bezierPath stroke];  // this draws the Bezier curve into newRep

        // a bit more example
        [[NSColor redColor]set];  // add a small filled red rect
        [NSBezierPath fillRect:NSMakeRect( 15, 15, 20.0, 20)];
        // ----    end example

        // done, restore the graphic state
        [NSGraphicsContext restoreGraphicsState];  // pop the state

        // finally we set the new size (it's not allowed to do it earlier!)
        [newRep setSize:bSize];

        // for test purposes only: write it out as a test image
        // I use png because it compresses much better than tiff with
    LZW compression
        // **NO** gif please, gif images know nothing about resolution
        // **NO** bmp, bmp images have a resolution field, but it is not
    used in Cocoa
        // **NO** jpeg, jpeg cannot handle images with high frquencies,
    i.e. sharp lines
        [[newRep representationUsingType:NSPNGFileType properties:nil]
            writeToFile:[NSString stringWithFormat:@"/tmp/bezierImage_%
    d.png", (int)dpi]
              atomically:YES];
    }

    But really resolution independent is only an NSEpsImageRep or an
    NSPDFImageRep, which
    needs some (much) more programming. EPS and PDF render their contents
    lazy, i.e. not before
    it is needed and after they know about resolution. Will there once be
    an NSSVGImageRep ?

    Heinrich Giesen

    --
    Heinrich Giesen
    <giesenH...>
  • > It isn't clear to me if the following drawing code will automatically
    > create an image 12 by 12 point image with pixel DPI already adjusted
    > as needed for screen display.

    Yep, that's what will happen.  Right now, if you lock focus and draw
    in an image, you're drawing into a view placed in an offscreen window
    (cf. -[NSCachedImageRep window], -[NSCachedImageRep rect]).  The
    window has the same default scaling between window space and the top
    level view as do most windows.

    In the future the drawing may not always go into a real window, but
    the scaling behavior will be preserved.  If you think you need finer
    control over your bits, you can draw to an NSBitmapImageRep via
    -[NSGraphicsContext graphicsContextWithBitmapImageRep:] or to a
    CGBitmapContext.

    Ricky adds:
    > I had many very strange drawing artifacts in those NSImage instances.

    There are fixes in this code for Leopard.  Please file bugs (like you
    said you did), and hopefully we'll get them fixed!

    and..
    > Note that I have done no testing with multi-resolution image files,
    > so don't know if the Cocoa frameworks will do the right thing (i.e.
    > which flavor is used depending upon the scale factor).

    Again, Cocoa should do the right thing, but there have been a few
    fixes.  Please file bugs if you see the wrong rep selected, especially
    if you happen to be on Leopard.

    Ken Ferry
    Cocoa frameworks

    On 10/27/06, Shawn Erickson <shawnce...> wrote:
    > I am attempting to get my code ready for resolution independence and I
    > am trying understand the best way to deal with icon images that I
    > create at runtime. The below is a cut down example of icon image cache
    > generation code that I have (the simplest example I have and not one
    > that would visually benefit the most but...).
    >
    > Ideally when the user interface scale factor is other then 1.0 I would
    > want to create an NSImage that is still 12 point by 12 point but has
    > its pixel content scaled (DPI other then 72) at the time it is created
    > by the user interface scale factor. I know the framework will scale it
    > as needed when I go to draw the image but I want to create the image
    > content at the resolution needed for display to avoid unneeded
    > interpolation and rep caching.
    >
    > It isn't clear to me if the following drawing code will automatically
    > create an image 12 by 12 point image with pixel DPI already adjusted
    > as needed for screen display (avoiding unneeded interpolation) or
    > should I modify the code in some fashion to allow the creating of
    > image content at a higher DPI then 72?
    >
    > If I need to do the later any recommendations on the best way to go
    > about doing this?
    >
    > static NSImage* coloredPenIcons[12] = {nil};
    > if (coloredPenIcons[0] == nil) {
    > NSSize imageSize = NSMakeSize(12.0, 12.0); // size in points
    >
    > NSBezierPath* penBody = [NSBezierPath bezierPath];
    > [penBody moveToPoint:NSMakePoint(1.0, 3.0)];
    > ...
    >
    > NSBezierPath* penShadow = [NSBezierPath bezierPath];
    > [penShadow moveToPoint:NSMakePoint(1.0, 2.0)];
    > ...
    >
    > NSAffineTransform* transform = [NSAffineTransform transform];
    >
    > // Translate the origin to the center of the point grid, the
    > coordinate (0,0)
    > // is considered the lower left edge of a point in user coordinate space so
    > // translating by 0.5 will place our drawing centered in the point grid.
    > [transform translateXBy:0.5 yBy:0.5];
    >
    > NSColor* shadowColor = [[NSColor colorWithCalibratedRed:COLOR256(150)
    > green:COLOR256(150)
    > blue:COLOR256(150)
    > alpha:1.0] set];
    > for (int i = 0; i < 12; i++) {
    > NSImage* image = [[NSImage alloc] initWithSize:imageSize];
    >
    > [image lockFocus];
    > [[NSGraphicsContext currentContext] setShouldAntialias:NO];
    > [transform concat];
    >
    > [[NSColor colorWithCalibratedRed:COLOR256(150)
    > green:COLOR256(150)
    > blue:COLOR256(150)
    > alpha:1.0] set];
    > [penShadow stroke];
    >
    > ...color for pen body varies based on index...
    > [penBody fill];
    > [penBody stroke];
    > [image unlockFocus];
    >
    > coloredPenIcons[i] = image;
    > }
    > }
    >
    > -Shawn
    > _______________________________________________
    > Do not post admin requests to the list. They will be ignored.
    > Cocoa-dev mailing list      (<Cocoa-dev...>)
    > Help/Unsubscribe/Update your Subscription:
    > http://lists.apple.com/mailman/options/cocoa-dev/<kenferry...>
    >
    > This email sent to <kenferry...>
    >
  • On Oct 31, 2006, at 2:01 AM, Ken Ferry wrote:

    >> It isn't clear to me if the following drawing code will automatically
    >> create an image 12 by 12 point image with pixel DPI already adjusted
    >> as needed for screen display.
    >
    > Yep, that's what will happen.  Right now, if you lock focus and draw
    > in an image, you're drawing into a view placed in an offscreen window
    > (cf. -[NSCachedImageRep window], -[NSCachedImageRep rect]).  The
    > window has the same default scaling between window space and the top
    > level view as do most windows.
    >
    > In the future the drawing may not always go into a real window, but
    > the scaling behavior will be preserved.  If you think you need finer
    > control over your bits, you can draw to an NSBitmapImageRep via
    > -[NSGraphicsContext graphicsContextWithBitmapImageRep:] or to a
    > CGBitmapContext.

    Thanks for the verification Ken. I thought I saw it happening...
    glad to know it is expected and will be maintained.

    -Shawn
previous month october 2006 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