Bug 888689 - Render SVG cursors correctly on retina displays. r=mstange

This commit is contained in:
Evan Wallace 2013-12-04 17:46:19 -05:00
parent 0e710fb645
commit fc7d7c444e
5 changed files with 84 additions and 19 deletions

View File

@ -910,7 +910,7 @@ NS_IMETHODIMP nsChildView::SetCursor(imgIContainer* aCursor,
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
nsBaseWidget::SetCursor(aCursor, aHotspotX, aHotspotY);
return [[nsCursorManager sharedInstance] setCursorWithImage:aCursor hotSpotX:aHotspotX hotSpotY:aHotspotY];
return [[nsCursorManager sharedInstance] setCursorWithImage:aCursor hotSpotX:aHotspotX hotSpotY:aHotspotY scaleFactor:BackingScaleFactor()];
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

View File

@ -242,9 +242,10 @@ class nsCocoaUtils
@param aImage the image to extract a frame from
@param aWhichFrame the frame to extract (see imgIContainer FRAME_*)
@param aResult the resulting NSImage
@param scaleFactor the desired scale factor of the NSImage (2 for a retina display)
@return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
*/
static nsresult CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult);
static nsresult CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor);
/**
* Returns nsAString for aSrc.

View File

@ -282,7 +282,7 @@ nsresult nsCocoaUtils::CreateCGImageFromSurface(gfxImageSurface *aFrame, CGImage
32,
stride,
colorSpace,
kCGBitmapByteOrder32Host | kCGImageAlphaFirst,
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
dataProvider,
NULL,
0,
@ -296,35 +296,91 @@ nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
// Be very careful when creating the NSImage that the backing NSImageRep is
// exactly 1:1 with the input image. On a retina display, both [NSImage
// lockFocus] and [NSImage initWithCGImage:size:] will create an image with a
// 2x backing NSImageRep. This prevents NSCursor from recognizing a retina
// cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the
// size of the NSImage.
//
// For example, if a 32x32 SVG cursor is rendered on a retina display, then
// aInputImage will be 64x64. The resulting NSImage will be scaled back down
// to 32x32 so it stays the correct size on the screen by changing its size
// (resizing a NSImage only scales the image and doesn't resample the data).
// If aInputImage is converted using [NSImage initWithCGImage:size:] then the
// bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since
// it will expect a 64x64 bitmap.
int32_t width = ::CGImageGetWidth(aInputImage);
int32_t height = ::CGImageGetHeight(aInputImage);
NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height);
// Create a new image to receive the Quartz image data.
*aResult = [[NSImage alloc] initWithSize:imageRect.size];
NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:0
bitsPerPixel:0];
[*aResult lockFocus];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:context];
// Get the Quartz context and draw.
CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage);
[*aResult unlockFocus];
[NSGraphicsContext restoreGraphicsState];
*aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
[*aResult addRepresentation:offscreenRep];
[offscreenRep release];
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult)
nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor)
{
nsRefPtr<gfxASurface> surface;
aImage->GetFrame(aWhichFrame,
imgIContainer::FLAG_SYNC_DECODE,
getter_AddRefs(surface));
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
nsRefPtr<gfxImageSurface> frame;
int32_t width = 0, height = 0;
aImage->GetWidth(&width);
aImage->GetHeight(&height);
nsRefPtr<gfxImageSurface> frame(surface->GetAsReadableARGB32ImageSurface());
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
// Render a vector image at the correct resolution on a retina display
if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) {
int scaledWidth = (int)ceilf(width * scaleFactor);
int scaledHeight = (int)ceilf(height * scaleFactor);
frame = new gfxImageSurface(gfxIntSize(scaledWidth, scaledHeight), gfxImageFormatARGB32);
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
nsRefPtr<gfxContext> context = new gfxContext(frame);
NS_ENSURE_TRUE(context, NS_ERROR_FAILURE);
aImage->Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(),
gfxRect(0.0f, 0.0f, scaledWidth, scaledHeight),
nsIntRect(0, 0, width, height),
nsIntSize(scaledWidth, scaledHeight),
nullptr, aWhichFrame, imgIContainer::FLAG_SYNC_DECODE);
}
else {
nsRefPtr<gfxASurface> surface;
aImage->GetFrame(aWhichFrame,
imgIContainer::FLAG_SYNC_DECODE,
getter_AddRefs(surface));
NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
frame = surface->GetAsReadableARGB32ImageSurface();
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
}
CGImageRef imageRef = NULL;
nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(frame, &imageRef);
@ -337,6 +393,11 @@ nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, ui
return NS_ERROR_FAILURE;
}
::CGImageRelease(imageRef);
// Ensure the image will be rendered the correct size on a retina display
NSSize size = NSMakeSize(width, height);
[*aResult setSize:size];
[[[*aResult representations] objectAtIndex:0] setSize:size];
return NS_OK;
}

View File

@ -37,8 +37,9 @@
@param aCursorImage the cursor image to use
@param aHotSpotX the x coordinate of the cursor's hotspot
@param aHotSpotY the y coordinate of the cursor's hotspot
@param scaleFactor the scale factor of the target display (2 for a retina display)
*/
- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY;
- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor;
/*! @method sharedInstance

View File

@ -9,6 +9,7 @@
#include <math.h>
static nsCursorManager *gInstance;
static CGFloat sCursorScaleFactor = 0.0f;
static imgIContainer *sCursorImgContainer = nullptr;
static const nsCursor sCustomCursor = eCursorCount;
@ -240,11 +241,11 @@ static const nsCursor sCustomCursor = eCursorCount;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY
- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
// As the user moves the mouse, this gets called repeatedly with the same aCursorImage
if (sCursorImgContainer == aCursorImage && mCurrentMacCursor) {
if (sCursorImgContainer == aCursorImage && sCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
[self setMacCursor:mCurrentMacCursor];
return NS_OK;
}
@ -259,7 +260,7 @@ static const nsCursor sCustomCursor = eCursorCount;
}
NSImage *cursorImage;
nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(aCursorImage, imgIContainer::FRAME_FIRST, &cursorImage);
nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(aCursorImage, imgIContainer::FRAME_FIRST, &cursorImage, scaleFactor);
if (NS_FAILED(rv) || !cursorImage) {
return NS_ERROR_FAILURE;
}
@ -274,6 +275,7 @@ static const nsCursor sCustomCursor = eCursorCount;
NS_IF_RELEASE(sCursorImgContainer);
sCursorImgContainer = aCursorImage;
sCursorScaleFactor = scaleFactor;
NS_ADDREF(sCursorImgContainer);
return NS_OK;