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 108731 66bc6ceca2f34b2054edf6d18a2a104ad39deee5
parent 108730 b16e828c9443fd7558ab311513f06976bc440f1e
child 108732 8d715c5958385ef03ab775a068635a09f39686a9
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerssmichaud
bugs674373
milestone18.0a1
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)