Skip navigation
C.Oldendorf
Currently Being Moderated

PS Plug-in preview image fails on Apple Retina displays - e. g. that of the Dissolve example

Nov 25, 2013 3:16 AM

We have a problem with our PS plug-ins for Mac OS X when used on Apple's Retina Display MacBook Pro models.

The issue is that the preview image appears multifold.The problem as such also exists with the unchanged Dissolve example from the current PS CC SDK. So I’ll use that to illustrate the issue.

 

Even the Dissolve plug-in displays a 4-up preview window on a Retina display as shown below:  

 

 

Interestingly, with the dialog still up, if one connects an external display (we used Apple 27” Cinema LED display) and moves the dialog back and forth between Retina display and external display, the preview actually switches between the 4 up and the regular preview as shown below as the dialog crosses over from one display to the other.

 

Spontaneously we know of nothing we could do within the plug-in that would correct this.  It might be something messed up in the display driver, well beyond the reach of a Photoshop plugin, particularly as the preview display is handled by a single special call that is part of the Adobe SDK.

 

So my question is has anyone else solved this problem? Is the Adobe team aware of the Problem and does a solution seem feasible or is this really on Apple’s end to fix?

 
Replies
  • Currently Being Moderated
    Jan 24, 2014 1:45 AM   in reply to C.Oldendorf

    Hi

     

    Any solution to this?  Similar behaviour occurs in our own plugin, but only when running PS CC on a Retina display.  Everything is OK when running the plugin under CS6 with Retina display.

     

    I thought I'd have a look and see if the dissolve example works, but I'm experiencing the same as C.Oldendorf.  I haven't yet tested dissolve on CS6 with Retina.

     

    Any suggestions/advice/fixes would be helpful.

     

    Thanks

     

    Jamie

     
    |
    Mark as:
  • Currently Being Moderated
    Jan 25, 2014 2:12 AM   in reply to C.Oldendorf

    Thanks for clarifying that, I wasn't aware CS6 (pre update) isn't Retina compatible.

     

    I found the following in the CC SDK docs:

     

    propHDPIAware - This property reflects the host applications is currently in High DPI and any dialog created by a plug in should also be High DPI aware.

     

    At this point I'm not sure what being High DPI aware involves, but I'm guessing the proxy preview needs to be scaled differently (x2).

     

    This is all new to me    Would be nice to see a working example or some pointers.

     
    |
    Mark as:
  • Currently Being Moderated
    Jan 29, 2014 2:06 AM   in reply to C.Oldendorf

    Thanks, I'm primarily a Windows developer, so the Mac is still very much a learn as I go platform.

     

    Completely ignoring the multiple screen issue, I thought I'd try to get it work on just one screen.  I tried increasing my proxy buffer to match the size of the 'backing store', in this case 128x128 becomes 256x256.  Initially it seemed to work, but after multiple tests it still crashes when doing a gf->displayPixels().  I'm currently at a loss what to do next.

     

    What puzzles me is other plugins manage to work, such as AlienSkin EyeCandy etc.  So there must be some workable solution.  Are they using native Mac routines to draw the image?

     

    I'm really keen to hear how others have gotten around this.

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 6, 2014 10:28 AM   in reply to js.acclaim

    Attached (well I can't attach, so text of files coming in two more posts) is an update to DissolveProxyView.m and DissolveProxyView.h that works on Retina displays. You should be able to drop them in and recompile the Dissolve example.

     

    We added two routines:

     

    - (CGFloat)getCurrentScaleFactor

     

    This is the important one as we call it before we draw anything to the screen. It asks the NSView what it has for a backing store and uses that value to calculate how many pixels are involved. You have to multiply to get pixel coordinates and divide again to get logical coordinates.

     

    Here is the Apple developer documentation:

     

    https://developer.apple.com/library/mac/documentation/cocoa/reference/ applicationkit/classes/NSView_Class/Reference/NSView.html

     

    search for convertSizeToBacking on that page. This gets the scale for our current view and divides or multiplies as necessary. We cleaned up the variable names to help out as well.

     

    - (void)viewDidChangeBackingProperties

     

    This routine gets called during initialization and then at any time your dialog passes from one screen to another. Our example doesn't do anything but you might want to know when things change. Note, this is after it happens so you don't know what you came from so you need to keep track yourself. Also note that you do not have to do anything when your dialog is on two screens with different backing. The OS will handle that for you.

     

    Carefully read the documentation to know if you are getting logical view coordinates, such as [self bounds], I actually read it and am still confused. The debugger will give you the "real" documentation. On a 1x monitor the two coordinate spaces will be identical. On a 2x monitor the logical coordinates will be 1/2 the physical coordinates. Since the view coordinates are always logical, a NSView will be the same logical size on any monitor. The pixel backing store will vary. It is up to the developer to ensure the physical pixel data matches the views backing store. Using getCurrentScaleFactor, they can normalize their coordinates between the two spaces.

     

    If you are passing in a CGContext to Photoshop, keep all coordinates in pixel coordinates by ensuring the CGContext has a scale matrix of 1.0. An example of this would be drawing into an off-screen CGContext.

     

    Here is another web page to read:

     

    https://developer.apple.com/library/mac/documentation/GraphicsAnimatio n/Conceptual/HighResolutionOSX/APIs/APIs.html#//apple_ref/doc/uid/TP40 012302-CH5-SW2

     

    P.S. I would like to thank John Hanson for fixing up the example and explaining to me what is going here! Many thanks John.

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 6, 2014 10:29 AM   in reply to Tom Ruark

    // ADOBE SYSTEMS INCORPORATED

    // Copyright  2009 Adobe Systems Incorporated

    // All Rights Reserved

    //

    // NOTICE:  Adobe permits you to use, modify, and distribute this

    // file in accordance with the terms of the Adobe license agreement

    // accompanying it.  If you have received this file from a source

    // other than Adobe, then your use, modification, or distribution

    // of it requires the prior written permission of Adobe.

    //-------------------------------------------------------------------- -----------

     

     

    #import "DissolveProxyView.h"

    #import "PIGeneral.h"

    #import "Dissolve.h"

    #import "FilterBigDocument.h"

    #import "DissolveController.h"

     

     

    extern void UpdateProxyBuffer(void);

    extern void ResetProxyBuffer(void);

     

     

    extern DissolveController *gDissolveController;

     

     

    /* Make sure this is unique to you and everyone you might encounter, search for

    "Preventing Name Conflicts" or use this link

    http://developer.apple.com/mac/library/documentation/UserExperience/Co nceptual/PreferencePanes/Tasks/Conflicts.html

    */

    @implementation DissolveProxyView

     

     

    - (id)initWithFrame:(NSRect)frame

    {

        self = [super initWithFrame:frame];

        if (self)

              {

                        currentColor = [NSColor clearColor];

                        currentZoomState = ZoomCommandUninited;

                        NSLog(@"SetColor to clearColor");

        }

        return self;

    }

     

     

    - (void)layoutSubviews

    {

              NSLog(@"in layout");

    }

     

     

    - (BOOL)isFlipped

    {

              return YES;

    }

     

     

    - (void)setDispositionColor:(int16)newColor

    {

              NSLog(@"start setColor to %d", newColor);

              switch (newColor)

              {

                        case 0:

                                  currentColor = [NSColor clearColor];

                                  NSLog(@"end setColor to clearColor");

                                  break;

                        default:

                        case 1:

                                  currentColor = [NSColor blueColor];

                                  NSLog(@"end setColor to blueColor");

                                  break;

                        case 2:

                                  currentColor = [NSColor redColor];

                                  NSLog(@"end setColor to redColor");

                                  break;

                        case 3:

                                  currentColor = [NSColor greenColor];

                                  NSLog(@"end setColor to greenColor");

                                  break;

              }

    }

     

     

    - (CGFloat)getCurrentScaleFactor

     

     

    {

              /*

                        Get the backing scale factor for the view. Don't bother worrying about width versus height

                        either will work

              */

              NSSize backingSize = NSMakeSize(1,1);

              if ([self respondsToSelector:@selector(convertSizeToBacking:)])

                  backingSize = [self convertSizeToBacking: backingSize];

     

              // Make sure we are not 0. It shouldn't happen but we don't want to divide by 0.

              if (backingSize.width < 1.0)

                        backingSize.width = 1.0;

     

              return backingSize.width;

    }

     

     

     

     

    - (void)viewDidChangeBackingProperties

              {

              /*

                        You get this message from the OS when your window crosses over monitors. This is the point

                        at which, if you have zoom capability, you will want to zoom your view so it looks to be

                        realtively the same size on both monitors - it is the same thing Photoshop does.

     

                        This message is kind of tricky because you will get this when your view is first created.

                        In this case, you do not want to automiatically zoom the view. Additionally you can only

                        determine what you are now, not what you were, thus you need to keep track yourself.

              */

              ZoomCommandState oldState = currentZoomState;

     

              CGFloat scaleFactor = [self getCurrentScaleFactor];

     

              // Compare to 1.0 allowing for epsilon

     

              currentZoomState = scaleFactor > 1.0001 ? ZoomCommandHigh : ZoomCommandLow;

     

     

              if (oldState != currentZoomState && oldState != ZoomCommandUninited)

                        {

                        // This is where you automatically zoom your view so that it remains relatively the same

                        // size when crossing monitors. You will want to zoom out when going from 2x to 1x and zoom

                        // in when going from 1x to 2x (double it or half it!)

                        }

              }

     

     

    - (void)drawRect:(NSRect)rect

    {

     

     

              // I appear to get called on another thread

              // during first display

              if ( ! [NSThread isMainThread] )

              {

                        // this seems to work

                        [self setNeedsDisplay:YES];

                        return;

     

                        // return; can't do that because I get a blank preview

     

     

                        /* this will crash but would seem like the right thing to do

                        [gDissolveController performSelectorOnMainThread:@selector(updateProxy:)

                                                                             withObject:NULL

                                                                          waitUntilDone:NO];

                         */

     

     

                        /* might work if i could figure out rect to pass in

                        [self performSelectorOnMainThread:@selector(drawRect:)

                                               withObject:rect

                                                                                              waitUntilDone:YES];

                         */

              }

     

     

              NSLog(@"in drawRect, %f %f %f %f", rect.size.width, rect.size.height, rect.origin.x, rect.origin.y);

     

     

              CGFloat scaleFactor = [self getCurrentScaleFactor];

     

              /*

                        We need to deal in both logical units (NSView space) and pixelData units (the image space unit)

              */

     

     

              // Create a temporary rectangle of the drawing area which is in pixelData space

              NSRect drawRectPixelData = NSMakeRect (rect.origin.x * scaleFactor,

                                                                                                        rect.origin.y * scaleFactor,

                                                                                                        rect.size.width * scaleFactor,

                                                                                                        rect.size.height * scaleFactor);

     

              short pixelDataHeight = static_cast <short> (drawRectPixelData.size.height);

              short pixelDataWidth = static_cast <short> (drawRectPixelData.size.width);

     

              // Setup proxyRect in pixelData coordinates

              gData->proxyRect.top = 0;

              gData->proxyRect.left = 0;

              gData->proxyRect.bottom = pixelDataHeight;

              gData->proxyRect.right = pixelDataWidth;

     

              // A temporary pixelData rect for working

              Rect pixelDataRect;

              pixelDataRect.top = gData->proxyRect.top;

              pixelDataRect.left = gData->proxyRect.left;

              pixelDataRect.bottom = gData->proxyRect.bottom;

              pixelDataRect.right = gData->proxyRect.right;

     

              SetupFilterRecordForProxy();

              CreateProxyBuffer();

              CreateDissolveBuffer(gData->proxyWidth, gData->proxyHeight);

              ResetProxyBuffer();

              UpdateProxyBuffer();

     

              // Note: buffer width and height are in pixelData space

              short bufferHeight = gData->proxyHeight;

              short bufferWidth  = gData->proxyWidth;

     

              /* Set up the output map. */

     

     

              // For this example, GetInRect returns pixelData space units

              VRect inRectPixelData = GetInRect();

     

     

              // Setup the outMap in pixelData units

              PSPixelMap outMap;

              outMap.version       = 1;

              outMap.bounds.top    = inRectPixelData.top;

              outMap.bounds.left   = inRectPixelData.left;

              outMap.bounds.bottom = inRectPixelData.bottom;

              outMap.bounds.right  = inRectPixelData.right;

              outMap.imageMode     = DisplayPixelsMode(gFilterRecord->imageMode);

              outMap.rowBytes      = gData->proxyWidth;

              outMap.colBytes                     = 1;

              outMap.planeBytes           = gData->proxyPlaneSize;

              outMap.baseAddr                     = gData->proxyBuffer;

     

              outMap.mat                              = NULL;

              outMap.masks                    = NULL;

              outMap.maskPhaseRow = 0;

              outMap.maskPhaseCol = 0;

     

              /*

                        Compute where we are going to display it. Lets do this from pixelData units and convert it

                        to logical space later.

              */

     

              short pixelDataDstRow = static_cast <short> (drawRectPixelData.origin.x);

              short pixelDataDstCol = static_cast <short> (drawRectPixelData.origin.y);

     

              if (pixelDataHeight > bufferHeight)

              {

     

                        Rect eraseArea;

                        eraseArea.top = gData->proxyRect.top;

                        eraseArea.left = gData->proxyRect.left;

                        eraseArea.bottom = gData->proxyRect.bottom;

                        eraseArea.right = gData->proxyRect.right;

     

     

                        pixelDataDstRow += (pixelDataHeight - bufferHeight) / 2;

     

                        eraseArea.bottom = pixelDataDstRow;

     

     

                        pixelDataRect.top = pixelDataDstRow;

     

                        eraseArea = pixelDataRect;

                        eraseArea.top = pixelDataDstRow + bufferHeight;

     

                        pixelDataRect.bottom = pixelDataDstRow + bufferHeight;

     

                        pixelDataHeight = bufferHeight;

     

              }

     

              if (pixelDataWidth > bufferWidth)

              {

     

                        Rect eraseArea = pixelDataRect;

     

     

                        pixelDataDstCol += (pixelDataWidth - bufferWidth) / 2;

     

                        eraseArea.right = pixelDataDstCol;

     

                        pixelDataRect.left = pixelDataDstCol;

     

                        eraseArea = pixelDataRect;

                        eraseArea.left = pixelDataDstCol + bufferWidth;

     

                        pixelDataRect.right = pixelDataDstCol + bufferWidth;

     

                        pixelDataWidth = bufferWidth;

     

              }

     

     

              /* Compute the source. */

     

              // Note: for this example: GetOutRect returns pixelData space

              VRect outRectPixelData = GetOutRect();

     

     

              VRect pixelSrcRect;

     

              pixelSrcRect.top    = outRectPixelData.top;

              pixelSrcRect.left   = outRectPixelData.left;

     

              if (bufferHeight > pixelDataHeight)

                        pixelSrcRect.top += (bufferHeight - pixelDataHeight) / 2;

     

              if (bufferWidth > pixelDataWidth)

                        pixelSrcRect.left += (bufferWidth - pixelDataWidth) / 2;

     

              pixelSrcRect.bottom = pixelSrcRect.top  + pixelDataHeight;

              pixelSrcRect.right  = pixelSrcRect.left + pixelDataWidth;

     

              // Create our destination rectangle for displayPixels in logical space

              VRect logicalSrcRect;

              logicalSrcRect.left = pixelSrcRect.left / scaleFactor;

              logicalSrcRect.top = pixelSrcRect.top / scaleFactor;

              logicalSrcRect.right = pixelSrcRect.right / scaleFactor;

              logicalSrcRect.bottom = pixelSrcRect.bottom / scaleFactor;

     

              /* Display the data. */

     

              // Setup global target rectangle for this blit in logical units

              PSPlatformContext windowContext;

              CGContextRef cgContext = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];

              windowContext.fCGContextRef = (void *) cgContext;

              windowContext.fScreenRect.top = logicalSrcRect.top;

              windowContext.fScreenRect.left = logicalSrcRect.left;

              windowContext.fScreenRect.bottom = logicalSrcRect.bottom;

              windowContext.fScreenRect.right = logicalSrcRect.right;

     

     

              // Setup our rows and col in logical units

              short logicalDstRow = pixelDataDstRow / scaleFactor;

              short logicalDstCol = pixelDataDstCol / scaleFactor;

     

              if (gFilterRecord->displayPixels != NULL)

                        (*(gFilterRecord->displayPixels)) (&outMap, &logicalSrcRect, logicalDstRow, logicalDstCol, &windowContext);

     

     

              [gDissolveController updateCursor];

    }

     

     

    @end

     

     

    // end DissolveProxyView.m

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 6, 2014 10:30 AM   in reply to Tom Ruark

    // ADOBE SYSTEMS INCORPORATED

    // Copyright  2009 Adobe Systems Incorporated

    // All Rights Reserved

    //

    // NOTICE:  Adobe permits you to use, modify, and distribute this

    // file in accordance with the terms of the Adobe license agreement

    // accompanying it.  If you have received this file from a source

    // other than Adobe, then your use, modification, or distribution

    // of it requires the prior written permission of Adobe.

    //-------------------------------------------------------------------- -----------

     

     

    #import <Cocoa/Cocoa.h>

     

     

    #include "PSIntTypes.h"

     

     

     

     

    typedef enum

     

     

              {

              ZoomCommandUninited,

              ZoomCommandHigh,

              ZoomCommandLow

     

              } ZoomCommandState;

     

     

    /* Make sure this is unique to you and everyone you might encounter, search for

    "Preventing Name Conflicts" or use this link

    http://developer.apple.com/mac/library/documentation/UserExperience/Co nceptual/PreferencePanes/Tasks/Conflicts.html

    */

    @interface DissolveProxyView : NSView {

              NSColor * currentColor;

              ZoomCommandState currentZoomState;

    }

     

     

    - (CGFloat)getCurrentScaleFactor;

    - (void)setDispositionColor:(int16)newColor;

    - (BOOL)isFlipped;

     

     

     

     

    @end

     

     

    // end DissolveProxyView.h

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 6, 2014 10:39 AM   in reply to Tom Ruark

    Hi Tom

     

    Will that be included in SDK or just on user forum?

     

    Regards

    Bartek

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 6, 2014 10:42 AM   in reply to Bartek Kropaczewski

    I'll post a new SDK with this in it as well. Not sure of ETA of new SDK however.

     
    |
    Mark as:

More Like This

  • Retrieving data ...

Bookmarked By (0)

Answers + Points = Status

  • 10 points awarded for Correct Answers
  • 5 points awarded for Helpful Answers
  • 10,000+ points
  • 1,001-10,000 points
  • 501-1,000 points
  • 5-500 points