bug 674373 pt 1 - Mac OS X native theme rendering support for HiDPI display. r=smichaud
authorMarkus Stange <mstange@themasta.com>
Mon, 13 Aug 2012 17:45:57 +0100
changeset 114920 66bc6ceca2f34b2054edf6d18a2a104ad39deee5
parent 114919 b16e828c9443fd7558ab311513f06976bc440f1e
child 114921 8d715c5958385ef03ab775a068635a09f39686a9
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmichaud
bugs674373
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 674373 pt 1 - Mac OS X native theme rendering support for HiDPI display. r=smichaud
widget/cocoa/nsNativeThemeCocoa.mm
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -455,16 +455,26 @@ nsNativeThemeCocoa::~nsNativeThemeCocoa(
 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
 // CGContextDrawImage(), and also to avoid very poor drawing performance in
 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
 // different amounts of RAM.
 #define BITMAP_MAX_AREA 500000
 
+static int
+GetBackingScaleFactorForRendering(CGContextRef cgContext)
+{
+  CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
+  CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
+  float maxScale = NS_MAX(fabs(transformedUserSpacePixel.size.width),
+                          fabs(transformedUserSpacePixel.size.height));
+  return maxScale > 1.0 ? 2 : 1;  
+}
+
 /*
  * Draw the given NSCell into the given cgContext.
  *
  * destRect - the size and position of the resulting control rectangle
  * controlSize - the NSControlSize which will be given to the NSCell before
  *  asking it to render
  * naturalSize - The natural dimensions of this control.
  *  If the control rect size is not equal to either of these, a scale
@@ -537,35 +547,38 @@ static void DrawCellWithScaling(NSCell *
 
     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
     InflateControlRect(&tmpRect, controlSize, marginSet);
 
     // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
     w += MAX_FOCUS_RING_WIDTH * 2.0;
     h += MAX_FOCUS_RING_WIDTH * 2.0;
 
+    int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
     CGContextRef ctx = CGBitmapContextCreate(NULL,
-                                             (int) w, (int) h,
-                                             8, (int) w * 4,
+                                             (int) w * backingScaleFactor, (int) h * backingScaleFactor,
+                                             8, (int) w * backingScaleFactor * 4,
                                              rgb, kCGImageAlphaPremultipliedFirst);
     CGColorSpaceRelease(rgb);
 
     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
     // This is the first flip transform, applied to cgContext.
     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
     if (mirrorHorizontal) {
       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
     }
 
     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
 
+    CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
+
     // This is the second flip transform, applied to ctx.
     CGContextScaleCTM(ctx, 1.0f, -1.0f);
     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
 
     [cell drawWithFrame:tmpRect inView:view];
 
     [NSGraphicsContext setCurrentContext:savedContext];
 
@@ -962,22 +975,28 @@ RenderTransformedHIThemeControl(CGContex
   // is too large.
   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
     aFunc(aCGContext, drawRect, aData);
   } else {
     // Inflate the buffer to capture focus rings.
     int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
     int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
 
+    int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
-    CGContextRef bitmapctx = CGBitmapContextCreate(NULL, w, h, 8, w * 4,
+    CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
+                                                   w * backingScaleFactor,
+                                                   h * backingScaleFactor,
+                                                   8,
+                                                   w * backingScaleFactor * 4,
                                                    colorSpace,
                                                    kCGImageAlphaPremultipliedFirst);
     CGColorSpaceRelease(colorSpace);
 
+    CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
     CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
 
     // HITheme always wants to draw into a flipped context, or things
     // get confused.
     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
 
     aFunc(bitmapctx, drawRect, aData);
@@ -1769,20 +1788,22 @@ nsNativeThemeCocoa::GetParentScrollbarFr
 static bool
 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
 {
   if (![aWindow isKindOfClass:[ToolbarWindow class]] ||
       [(ToolbarWindow*)aWindow drawsContentsIntoWindowFrame])
     return false;
 
   float unifiedToolbarHeight = [(ToolbarWindow*)aWindow unifiedToolbarHeight];
+  CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
+  CGRect deviceRect = CGRectApplyAffineTransform(inBoxRect, ctm);
   return inBoxRect.origin.x == 0 &&
-         inBoxRect.size.width >= [aWindow frame].size.width &&
+         deviceRect.size.width >= [aWindow frame].size.width &&
          inBoxRect.origin.y <= 0.0 &&
-         inBoxRect.origin.y + inBoxRect.size.height <= unifiedToolbarHeight;
+         floor(inBoxRect.origin.y + inBoxRect.size.height) <= unifiedToolbarHeight;
 }
 
 void
 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
                                        NSWindow* aWindow)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
@@ -1871,16 +1892,24 @@ nsNativeThemeCocoa::DrawResizer(CGContex
   drawInfo.size = kHIThemeGrowBoxSizeNormal;
 
   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
                                   IsFrameRTL(aFrame));
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
+static bool
+IsContextZoomedToHighResolution(nsDeviceContext* aContext)
+{
+  const float devPixelsPerCSSPixel = 1.0f *
+    nsPresContext::AppUnitsPerCSSPixel() / aContext->AppUnitsPerDevPixel();
+  return devPixelsPerCSSPixel > 1.5f;
+}
+
 NS_IMETHODIMP
 nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
                                          nsIFrame* aFrame,
                                          uint8_t aWidgetType,
                                          const nsRect& aRect,
                                          const nsRect& aDirtyRect)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
@@ -1896,16 +1925,25 @@ nsNativeThemeCocoa::DrawWidgetBackground
   nativeWidgetRect.Round();
   if (nativeWidgetRect.IsEmpty())
     return NS_OK; // Don't attempt to draw invisible widgets.
 
   gfxContext* thebesCtx = aContext->ThebesContext();
   if (!thebesCtx)
     return NS_ERROR_FAILURE;
 
+  gfxContextMatrixAutoSaveRestore save(thebesCtx);
+
+  if (IsContextZoomedToHighResolution(aContext->DeviceContext())) {
+    // Use high-resolution drawing.
+    nativeWidgetRect.ScaleInverse(2.0f);
+    nativeDirtyRect.ScaleInverse(2.0f);
+    thebesCtx->Scale(2.0f, 2.0f);
+  }
+
   gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect);
 
   CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
   if (cgContext == nullptr) {
     // The Quartz surface handles 0x0 surfaces by internally
     // making all operations no-ops; there's no cgcontext created for them.
     // Unfortunately, this means that callers that want to render
     // directly to the CGContext need to be aware of this quirk.
@@ -2293,18 +2331,18 @@ nsNativeThemeCocoa::DrawWidgetBackground
 
     case NS_THEME_LISTBOX: {
       // We have to draw this by hand because kHIThemeFrameListBox drawing
       // is buggy on 10.5, see bug 579259.
       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
       CGContextFillRect(cgContext, macRect);
 
       // #8E8E8E for the top border, #BEBEBE for the rest.
-      int x = macRect.origin.x, y = macRect.origin.y;
-      int w = macRect.size.width, h = macRect.size.height;
+      float x = macRect.origin.x, y = macRect.origin.y;
+      float w = macRect.size.width, h = macRect.size.height;
       CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
       CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
       CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
       CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
       CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
       CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
     }
       break;
@@ -2444,16 +2482,20 @@ nsNativeThemeCocoa::GetWidgetBorder(nsDe
       break;
     }
 
     case NS_THEME_STATUSBAR:
       aResult->SizeTo(0, 1, 0, 0);
       break;
   }
 
+  if (IsContextZoomedToHighResolution(aContext)) {
+    *aResult = *aResult + *aResult; // doubled
+  }
+
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 // Return false here to indicate that CSS padding values should be used. There is
 // no reason to make a distinction between padding and border values, just specify
 // whatever values you want in GetWidgetBorder and only use this to return true
@@ -2713,16 +2755,20 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
       HIPoint pnt = { 0, 0 };
       HIRect bounds;
       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
       aResult->SizeTo(bounds.size.width, bounds.size.height);
       *aIsOverridable = false;
     }
   }
 
+  if (IsContextZoomedToHighResolution(aContext->DeviceContext())) {
+    *aResult = *aResult * 2;
+  }
+
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP
 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
                                      nsIAtom* aAttribute, bool* aShouldRepaint)