How to Use NSImage for Retina Image Handling

I am a novice Xojo user and as a novice I don’t expect or want to use declares often or at all. I can see having to use declares for advanced functionality, but not for something like determining when to display a retina image, or to get access to system images. Isn’t that what I purchased Xojo for?

I don’t want to have to copy the whole MacOSLib into my project (there’s a lot of code I don’t understand and it also significantly adds to compile time), and I don’t want purchase a third party solution for functionality that Xojo should already come with.

The Xojo user guide states to use BackingScaleFactor, but I have read on this forum that it is no longer recommended to use this to determine whether or not to display a retina image. Several posts on this forum recommended to use NSImage which will allow the OS to handle which image is loaded.

Is there a lightweight, simple way to work with NSImage? Can someone show a clear example on how exactly to do this?

Thank you

no need for macoslib and others.

Just use paint event to draw picture smaller.
So 144dpi picture in memory and draw with drawpictue.

g.drawpicture pic, 0, 0, g.width, g.height, 0, 0, pic.width, pic.height.

I need this for custom toolbar button images.

Maybe you should check http://ohanaware.com/retinakit/

At this time Xojo does not natively handle retina for Mac desktop. As Michel suggested, RetinaKit is best for a novice, as Sam has done all the hard work to make retina easy (and display images on Windows)

[quote=155246:@Mark Scardingo]I need this for custom toolbar button images.
[/quote]

You can set the icon of the toolbutton to an image with the right number of pixels (32 or 64) and Xojo will scale them down. Works fine for retina @2x to set a 64x64 picture object.

I wrote a class that you can add to a window to be notified of resolution changes (ie the user drags to/from a retina screen). From there you can create pictures that are the proper size and repopulate the toolbar icons. RetinaNotifier.zip

Using NSImage is the technically correct way of getting Retina on OS X, there’s plenty of quick tricks you can do to fake it, but you’ll spend time cleaning up after those tricks, especially when Apple change the API and remove functions that they tell people to not use, then again they remove functions for the fun of it too!

The main advantage of NSImage (via MBS, MacOSLib or the Retina Kit) is that for most controls on OS X (toolbars, pushbuttons, popupmenus, menus, dock, status item, segmented controls, image wells), you’re letting the system handle Retina, which means all you need then do is make sure you have the correct artwork, the system will then dynamically load and display the correct artwork when it’s needed, it manages the memory and caching for you. The other advantage of using the correct methods is that when Apple shift to @3x for OS X (which they’re starting to do for iOS), all you’ll need to do is to add the artwork.

Another neat trick of using NSImage, is access to system provided icons. If you use these icons, they’ll be consistent with the different versions of Aqua (although they should have renamed it Crayola in Yosemite).
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSImage_Class/#//apple_ref/doc/constant_group/Image_Template_Constants

If it’s one thing I’ve learned, replicating system functions nearly always results in more work and hassle over the long run.

Here’s some feedback reports that you should sign on to also.
<https://xojo.com/issue/26385>
<https://xojo.com/issue/25108>
<https://xojo.com/issue/37376>
<https://xojo.com/issue/36562>
<https://xojo.com/issue/34924>
<https://xojo.com/issue/34926>

[quote=155280:@jim mckay]You can set the icon of the toolbutton to an image with the right number of pixels (32 or 64) and Xojo will scale them down. Works fine for retina @2x to set a 64x64 picture object.

I wrote a class that you can add to a window to be notified of resolution changes (ie the user drags to/from a retina screen). From there you can create pictures that are the proper size and repopulate the toolbar icons. RetinaNotifier.zip[/quote]

Jim, I’m a little confused on this part. If I always use a 64x64 picture for my toolbar icon and Xojo scales it down automatically for me for non-retina displays, why do I need to check if the user is using retina?

Thank you

Apple suggests that non-retina versions of icons are not simply scaled down versions of the retina ones. They should also have less “detail” so as to be less of a blurry mess at the smaller size.

So to create a custom toolbar icon , using any kind of paint or icon editor, I need to:

  1. Set the icon workspace to 64 pixels x 64 pixels and create my icon.
  2. Then do the same for 32 pixels x 32 pixels.
  3. Suffix the 64 pixel file name with “2x”.

Where does DPI fit into this? When saving a file in the icon editor it asks for a DPI and defaults to 72. Do I need to change this to 144?

Thanks

I’ve been doing some reading on the Mac Developer site. Assuming I do what’s suggested here and “Adopt the @2x naming convention” and “Create a set of icons that includes high-resolution versions”. Then I package them up using tiffutil into one single TIFF file. Can I use NSString like this? Will it then handle on its own when to use the high resolution version?

The GetPictureFromRef code was taken from Jim Mckay shown in this thread. I renamed it and made some other small changes. Many thanks to him.

[code]Function GetPictureFromFile(f as FolderItem) As Picture
declare function URLWithString lib CocoaLib selector “URLWithString:” (id as Ptr, URLString as CFStringRef) as Ptr
declare function initWithContentsOfURL lib CocoaLib selector “initWithContentsOfURL:” (id as Ptr, url as Ptr) as Ptr
declare function alloc lib CocoaLib selector “alloc” (classRef as ptr) as ptr

// Get NSURL of file
dim url As Ptr
dim NSURLclass As Ptr = NSClassFromString(“NSURL”)
url = URLWithString( NSURLclass, f.URLPath )

// Open as NSImage
dim imageRef As Ptr
dim NSImageclass As Ptr = NSClassFromString(“NSImage”)
imageRef = initWithContentsOfURL( alloc( NSImageclass ), url )

return GetPictureFromRef( imageRef )
End Function
[/code]

[code]Function GetPictureFromRef(imageRef as Ptr) As Picture
If imageRef <> Nil Then

// Get the size of the NSImage
declare function size lib CocoaLib selector "size" (obj_id as Ptr) as NSSize
dim s As NSSize = size( imageRef )

// Get the graphics of the picture as CGContext
dim p as new picture(s.width,s.height)
dim cntx as Ptr = ptr(p.Graphics.Handle(Graphics.HandleTypeCGContextRef))

dim rect As NSRect
rect.rsize = s

declare function CGImageForProposedRect lib CocoaLib selector "CGImageForProposedRect:context:hints:" (obj_id as Ptr, byref proposedDestRect as NSRect, referenceContext as Ptr, hints as Ptr) as Ptr

// Convert the NSImage to CGImage
dim cgPtr As ptr
cgPtr = CGImageForProposedRect( imageRef, rect, nil, nil)

// Draw the CGImage to our Xojo picture
soft declare sub CGContextDrawImage lib CocoaLib (context as Ptr, rect as NSRect, image as Ptr)
CGContextDrawImage cntx, rect, cgPtr

Return p

End If
End Function
[/code]

[quote=155362:@Mark Scardingo]Jim, I’m a little confused on this part. If I always use a 64x64 picture for my toolbar icon and Xojo scales it down automatically for me for non-retina displays, why do I need to check if the user is using retina?

Thank you[/quote]
Like Greg said, you only want to use a 64x64 picture if you’re on retina @2x. Otherwise your icons will be “fuzzy” on a standard res display. The way I handle it is to always do my drawing based on vectors within the available area. That way, if you have a 32x32 space or a 64x64 space it’s irrelevant to the drawing routine.

for example, instead of doing something like
g.drawoval(10,10,16,16)
you would do something like
g.drawoval(g.width*.1,g.height*.1,g.width*.7,g.height*.7)

I also like to use rect’s to represent areas in the drawing for clarity, like
dim area1 as new realbasic.rect(g.width*.1,g.height*.1,g.width*.4,g.height*.4)
g.drawrect(area1.left,area1.top,area1.width,area1.height)

It’s good to be able to scale the drawing on demand. Also, by doing this you’ll be prepared for the eventuality of 3x or higher. And this kind of drawing comes in handy if you need to print something using a different resolution graphics object.

To clarify this, Xojo doesn’t technically “Scale” the image on retina. When you draw to a 32x32 space in retina, it is actually a 64x64 pixel space (2pixels per point=32x32 points). Xojo is using system drawing routines, so the retina capability is there, you just have to be aware of it.

So, drawing a 64x64 picture to a 32x32 graphics object to a non-retina display scales the image (and interpolates the pixels). Drawing a 64x64 picture to a 32x32 graphics object in retina, you are actually drawing 64x64 pixels to 64x64 pixels, displayed in a 32x32 point space. This only applies to graphics objects received from paint events. The toolbuttons draw the icons in their own internal “paint” event.

I would expect this to change as more of the new framework moves to desktop, and we eventually see a new graphics object with a scale property… (Greg? Norman?).

There is a much quicker and easier way to get to the same solution.

Dim p as picture = picture.open( folderitem )

I understand drawing in Paint requires drwaing at double the number of pixels. But what about using the Canvas backdrop, an Image Well, or a bevel button for instance ?

In iOS, all I have to do is to have two picture files in the bundle :

  • icon.png (32x32)
  • icon@2x.png (64x64)
    Then I set icon.png as picture for a control, and if the device is Retina 144dpi, it will use automatically the 64x64 picture.

Is it not the same for Mac OS X ?

Currently not, but if it’s how it works on iOS then I would expect it to when the new framework is in full swing.

Actually drawing in the paint event doesn’t require scaling, scaling is only required if you’re drawing to a 72 dpi picture intending to then draw that image at a different DPI. If it’s done correctly, you’ll never need to know what the actual pixel dimensions are, or what scale factor is required.

Again I expect Retina to be a lot easier once the framework is converted.