Retina Headaches (or how does Sam still have hair?)

I’m trying to get the sort arrow using NSImageNamed constants.
I’ve adapted the code from this post and it works well on non-retina systems.

When I try to request the image at double size and shrink it down in the canvas, the final output still looks poor. But from what I’m trying to understand it seems this is how you’re supposed to do it. Did I miss something?

I know what you’re going to say,
“Yada yada yada, 2016r1.”
But this needs to be backwards compatible and I’m losing my fsck()ing mind.
I don’t have a retina monitor, so this is getting on my nerves really fast.
I can’t debug with 55 characters across of space (I have to use the half resolution method)

Request code:

  #if TargetMacOS then
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    Declare Function imageNamed Lib "Cocoa" selector "imageNamed:" _
    (class_id As Ptr, name As CFStringRef) As Ptr
    
    //get the NSImage
    Dim iPtr As Ptr = imageNamed(NSClassFromString("NSImage"), "NSAscendingSortIndicator") 
    
    //we have an NSImage
    if iPtr <> Nil Then
      
      declare function size lib "Cocoa" selector "size" (obj_id as Ptr) as NSSize
      //get the size of the NSImage
      dim s As NSSize //= size(iPtr)
      s.width = w
      s.height = h
      
      dim p as new picture(s.width,s.height)
      //get the graphics of the picture as CGContext
      dim cntx as Ptr=ptr(p.Graphics.Handle(5)) // Graphics.HandleTypeCGContextRef
      dim rect As NSRect
      rect.rsize=s
      
      declare function CGImageForProposedRect lib "Cocoa" _
      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=CGImageForProposedRect(iPtr,rect,nil,nil) 
      
      soft declare sub CGContextDrawImage lib "Cocoa" _
      (context as Ptr, rect as NSRect, image as Ptr)
      
      //Draw the CGImage to our Xojo picture
      CGContextDrawImage cntx, rect, cgPtr 
      
      Return p
    else
      // An error occurred
    end
  #endif

Draw Code:

g.DrawPicture(pIndicatorPicture, PositionX, PositionY, _
        iIndicatorW, iIndicatorH, 0, 0, iIndicatorW * ScalingFactor, iIndicatorH * ScalingFactor)

PositionX and PositionY are defined and work well.
iIndicatorW and iIndicatorH are both 12. The multiplied versions are what goes into the request function.
The image I get back from NSImage is REALLY pixelated when it comes out at 24x24, and is still pixelated when shrunk to 12 by 12.

Send help, and liquor.

Using macOSlib I use this routine, and to my eyes it works ok both on retina and not-retina screens.
//isDisabled is a color-property; isPlus= property; the canvas is 20x20

Sub Paint(g As Graphics, areas() As REALbasic.Rect)
dim p as picture
if isPlus then
p = SystemIcons.AddTemplate
else
p = SystemIcons.RemoveTemplate
end if
p = ModernizePicture§
if scalFactor = 2 then//isretina
dim ppp as new picture(p.Width/2,p.Height/2)
ppp.Graphics.DrawPicture p,0,0, p.Width/2, p.Height/2,0,0,p.width,p.height
p = ppp
end if
if me.Enabled then
g.DrawPicture p,(me.Width - p.Width) / 2, (me.Height - p.Height) / 2
else
g.DrawPicture p.IconTemplateSetColor( isDisabled ), (me.Width - p.Width) / 2, (me.Height - p.Height) / 2
end if

#Pragma Unused areas
End Sub

Tim, for testing on a non Retina screen, maybe try the Quartz Debug Tool. I use it on a non-retina iMac and it works fine.
I think I found it on https://developer.apple.com/downloads/ but I cannot find it anymore. Maybe it has moved.

When installed, you can select HiDPI in your System Preferences->Displays->Scaled
The resolution goes way down but I think it’s good enough for testing. Especially if you happen to have an extra screen hooked up.

[quote=262231:@Marco Hof]When installed, you can select HiDPI in your System Preferences->Displays->Scaled
The resolution goes way down but I think it’s good enough for testing. Especially if you happen to have an extra screen hooked up.[/quote]
That’s what I’m using. Have you seen the IDE on 960x540?
I counted. 55 cols of text, and when the error pane pops up, 4 rows. Like holy cow I can’t do this.

Looking into macoslib’s methods right now. I can’t include the whole of macoslib for one tiny function that’s crazy.
Thank you both for your suggestions! Would love a more definitive answer too!

Update:

[quote=262230:@Carlo Rubini][code]Sub Paint(g As Graphics, areas() As REALbasic.Rect)
dim p as picture
if isPlus then
p = SystemIcons.AddTemplate
else
p = SystemIcons.RemoveTemplate
end if
p = ModernizePicture§
if scalFactor = 2 then//isretina
dim ppp as new picture(p.Width/2,p.Height/2)
ppp.Graphics.DrawPicture p,0,0, p.Width/2, p.Height/2,0,0,p.width,p.height
p = ppp
end if
if me.Enabled then
g.DrawPicture p,(me.Width - p.Width) / 2, (me.Height - p.Height) / 2
else
g.DrawPicture p.IconTemplateSetColor( isDisabled ), (me.Width - p.Width) / 2, (me.Height - p.Height) / 2
end if

#Pragma Unused areas
End Sub
[/code]
[/quote]
So ModernizePicture is awfully hard to find, all it does apparently is add transparency.
Other than that, this is taking a picture twice the size and drawing it down.
I’m confused as to why this is not working for me.

I think NSAscendingSortIndicator is just tiny to begin with. I’m sure they look good on a listbox header or something but I guess they’re not good to blow up.

If Unicode doesn’t have anything useful, maybe this has something you can use: https://dl.dropboxusercontent.com/u/609785/Xojo/Forum/Expand.zip

As for the DrawPicture statement, not sure what iIndicatorW * ScalingFactor, iIndicatorH * ScalingFactor does. But just in case, the last two are the original size. So if you want to draw a 32x32, use a 64x64 pic and then g.DrawPicture(pic, X, Y, pic.width / 2, pic.height / 2, 0, 0, pic.width pic.height)

@Tim: Sorry for the apparent confusion. I mentioned macOSlib just to make you aware that the whole function depended on it to get systemIcons.
As for modernizePicture, it’s only a habit of mine.
So, once you have got your icon/picture, the relevant part is only:

p = //your picture
if scalFactor = 2 then//isretina
dim ppp as new picture(p.Width/2,p.Height/2)
ppp.Graphics.DrawPicture p,0,0, p.Width/2, p.Height/2,0,0,p.width,p.height
p = ppp
end if
g.DrawPicture p,(me.Width - p.Width) / 2, (me.Height - p.Height) / 2//draw the icon in the centre of the canvas

I would add: to me this works because macOSlib returns the system.icon at a fixed size. If the icon you get is of a different size, then the above snippet may not help you.

I’ve tried out your code and think I figured it out by am confused by a few things.

[quote=262223:@Tim Parnell].
dim s As NSSize //= size(iPtr)
s.width = w
s.height = h

  dim p as new picture(s.width,s.height)[/quote]

What are w and h and why is size commented out? I put getting the actual size back in and got 9x9, not 12x12 or 24x24.

Anyways, doing this works for me

dim s As NSSize = size(iPtr) s.width = s.width * 2 s.height = s.height * 2
The original size is 9x9. So for non-retina you’d need a 9x9 pixel Picture, but for retina you need an 18x18 pixel Picture. The next line does this

dim p as new picture(s.width,s.height)

and the rest falls in place to draw the full 18x18 pixels.

Your draw code I wasn’t sure about. The Picture itself will be 18x18 pixels but it needs to be drawn into a 9x9 space to be retina.

g.DrawPicture( pic, 0, 0, pic.Width/2, pic.Height/2, 0, 0, pic.Width, pic.Height )

Notice it’s not being scaled down to a 9x9 region until drawing to screen. It looks like before you were creating a 9x9 pixel image then drawing it at 18x18, or 12x12 to 24x24, or something. There’s alot going on here and I can’t be sure about things, especially because I don’t have retina to test. Just that when I draw the 18x18 image it looks clean on my non-retina screen (while the 9x9 scaled up to 18x18 looks bad).

Also, there should be some way to put the 18x18 Picture into one of the new Indexed Pictures at 2x position and then you don’t have to worry about drawing scaled.

Don’t forget, if you do want to use the new framework retina stuff, you can wrap the new code with:

#if XojoVersion > 2016 then ...new code here... #else ... Old code here... #endif

You need to use setSize for the NSImage for retina (x2)/nonretina and then draw it to your context. This should get you the respected results as @Will also stated.
Using size to determine the actually size should not work as it will give you the lowest resolution. (9x9)

Will, I have changed the size to 9x9 just in case - but this has still had no effect.
The 18x18 comes back crisp when in non-retina mode, but when I switch to retina mode and shrink, it is pixelated beyond reasonable, as if the original was pixelated - but it’s not! (testing retina on a non-retina display is REALLY not fun)

Screenshot without drawing down:
Top is non-retina bottom is retina.
Non-retina is definitely more crisp.

[quote=262304:@Rob Egal]You need to use setSize for the NSImage for retina (x2)/nonretina and then draw it to your context. This should get you the respected results as @Will also stated.
Using size to determine the actually size should not work as it will give you the lowest resolution. (9x9)[/quote]
I don’t follow. I’m telling it how big the image should be, why would it be anything other than what I tell it?

You may want to use a font instead for that symbol. They scale perfectly and are as crisp as can be in Retina mode.

[quote=262388:@Tim Parnell]Will, I have changed the size to 9x9 just in case - but this has still had no effect.
The 18x18 comes back crisp when in non-retina mode, but when I switch to retina mode and shrink, it is pixelated beyond reasonable, as if the original was pixelated - but it’s not! (testing retina on a non-retina display is REALLY not fun)

Screenshot without drawing down:
Top is non-retina bottom is retina.
Non-retina is definitely more crisp.

I don’t follow. I’m telling it how big the image should be, why would it be anything other than what I tell it?[/quote]

NSImage can have multiple represetations for retina an none. This means in your code:
get the NSimage
determine if retina
->yes: setSize of NSImage to x2
no: do nothing or setSize to 9x9
get the CGImage

[quote=262231:@Marco Hof]For testing on a non Retina screen, maybe try the Quartz Debug Tool. I use it on a non-retina iMac and it works fine. …
When installed, you can select HiDPI in your System Preferences->Displays->Scaled
The resolution goes way down but I think it’s good enough for testing. Especially if you happen to have an extra screen hooked up.[/quote]
I also looked for this recently, and found it here: Open XCode, -> “Open Developer Tool” in the XCode application Menu item, -> “More Developer Tools…” points you to this one:
https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Testing/Testing.html#//apple_ref/doc/uid/TP40012302-CH6-SW2

[quote=262395:@Rob Egal]NSImage can have multiple represetations for retina an none. This means in your code:
get the NSimage
determine if retina
->yes: setSize of NSImage to x2
no: do nothing or setSize to 9x9
get the CGImage[/quote]
Okay.

I tried this and it’s had no effect though. What is still wrong?

      dim s As NSSize
      s.width = w * ScalingFactor
      s.height = h * ScalingFactor
      
      declare sub setSize lib "Cocoa" selector "setSize:" (obj_id as Ptr, value as NSSize)
      setSize(iPtr, s)

ScalingFactor is a computed property. So non-retina would return 1, retina returns 2.
w and h are sent in as 9 and 9
This block just gets swapped into the original code from above.

I have since worked on another section of this canvas in regards to drawing retina and successfully got it to work. So it’s quite clear now the error lies in getting the NSImage somehow, and not my drawing/shrinking code.

[quote=262388:@Tim Parnell]Screenshot without drawing down:
Top is non-retina bottom is retina.[/quote]
Yeah, something weird is going on. I ran Quartz Debug and see that too, but only in HiDPI (Xojo and System Settings).

It seems to happen when drawing from the NSImage to the Xojo Picture. Maybe the Picture has a scale setting that’s affecting the draw.

A workaround is to not draw to a Xojo Picture, instead just get and store the NSImage Ptr and draw with that.

Dim iPtr As Ptr = imageNamed(NSClassFromString("NSImage"), "NSAscendingSortIndicator") storedPtr = iPtr

Sub Paint(g As Graphics, areas() As REALbasic.Rect) declare sub draw lib "Cocoa" selector "drawInRect:" (id As Ptr, rect As NSRect) dim r As NSRect r.pos.x = 50 r.pos.y = g.Height - 50 //inverted y r.rsize.width = 9 r.rsize.height = 9 draw(storedPtr, r) End Sub

Or wait, I think it has something to do with Xojo trying to be smart about scaling for HiDPI. If you set Vertical/HorizontalResolution of pIndicatorPicture to 144 and just draw it straight, g.DrawPicture(pIndicatorPicture, 50, 50), then it looks good (on my non-retina in HiDPI mode).

Pictures do have ScaleX and ScaleY properties as well as resolutions settings and that definitely could affect things

They never used to because 1 point = 1 pixel but that is no longer true

I modified your original code slightly and it works here without a problem…

//get the size of the NSImage dim s As NSSize = size(iPtr) //size is going to return a point value here- we'll convert to pixels. s.width = s.width*ScaleFactor s.height = s.height*ScaleFactor
and to paint it,

g.DrawPicture(pIndicatorPicture, PositionX, PositionY, _
        pIndicatorPicture.width/ScaleFactor,pIndicatorPicture.height/ScaleFactor, 0, 0, pIndicatorPicture.width ,pIndicatorPicture.height )

If you’re on a non-retina system everything is drawn unscaled. On a retina system, the pIndicatorPicture is at 2x and must be drawn down at .5x

It’s the Graphics object that has ScaleX and ScaleY. Changing Graphics.ScaleX/Y modifies Graphics.Width/Height, but Picture.Width/Height still returns the pixel size, even after modifying Picture.Vertical/HorizontalResolution.

All of these settings: Graphics.ScaleX/Y, Picture.Vertical/HorizontalResolution, the Paints g.ScaleX/Y and any CG declared transform, affect what is drawn. The trick (I’ve learned) is that coordinates are points and change with scale. So when Tim draws the 18x18 pixel image it looks bad because it’s actually drawing at 18x18 points which is 36x36 pixels. Changing Vertical/HorizontalResolution makes it draw in 9x9 points which looks good with 18x18 pixels. Possibly there’s a way to do the same using just ScaleX/Ys.

After reading the HiDPISupport.pdf when it first came out and some experimentation I thought I understood all this but now I’m confused again. I get it for the most part but there’s edge cases like this I didn’t appreciate. Maybe it’s because I didn’t run Quartz Debug before, I guess another round of experiments and cataloging is needed. I’d like to see the HiDPISupport.pdf expanded on, or maybe I just need to reread it :slight_smile:

I just had my hair cut really short for the summer… It’s not even 10:00 am and it’s hot (29?c/84?f inside).

After a quick overview (not in depth mind you), the thing I’d recommend is not rendering it to a Xojo picture. You’re getting an image from the system, it may have one representation or it may have 12 (yes some images do).

Let the OS do the donkey work, and simply draw the NSImage where you need it drawn or set the NSImage as image/icon on controls.

I would also suggest checking out the Retina Kit demo as it covers most scenarios, let the OS do the hard work. http://www.ohanaware.com/retinakit/

Oh and find a real Retina machine. The first application we shipped that was Retina ready was tested on a 2007 24" iMac (with HIDPI enabled) and it flew. When I purchased an actually retina machine, the performance was horrible (even though the hardware was much faster than the iMac). I then spent a lot of time optimizing the application to gain back performance. I would really recommend a 2015 MacBook (not a 2016 version) for the simple fact that it’s a slow Retina compatible machine. You make your application smooth on that machine and it’ll fly on everything else. Just get an APP for it.

[quote=262449:@Will Shank]It’s the Graphics object that has ScaleX and ScaleY. Changing Graphics.ScaleX/Y modifies Graphics.Width/Height, but Picture.Width/Height still returns the pixel size, even after modifying Picture.Vertical/HorizontalResolution.
[/quote]
Yeah wrote that moved on to something else then rereading it with your reply following I went “DOH !”
Palm has been applied to forehead