Bug 1403989 - Automatically treat black menu icons as templates, so that they get drawn inverted in hovered menuitems. r=jrmuizel,spohl
authorMarkus Stange <mstange@themasta.com>
Mon, 16 Apr 2018 15:23:23 -0400
changeset 414163 079e73b36f8b15168cccce5e78ae2d1521119ef7
parent 414162 4183020e7017569bdf931c7630dd907ed01ef8ee
child 414164 2512c9c24244babd77b3413825b176c54ac60836
push id62809
push usermstange@themasta.com
push dateWed, 18 Apr 2018 03:41:44 +0000
treeherderautoland@079e73b36f8b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel, spohl
bugs1403989
milestone61.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 1403989 - Automatically treat black menu icons as templates, so that they get drawn inverted in hovered menuitems. r=jrmuizel,spohl MozReview-Commit-ID: Bn69Ij0BfRa
widget/cocoa/nsCocoaUtils.h
widget/cocoa/nsCocoaUtils.mm
widget/cocoa/nsMenuItemIconX.mm
--- a/widget/cocoa/nsCocoaUtils.h
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -266,20 +266,24 @@ public:
   // 3 utility functions to go from a frame of imgIContainer to CGImage and then to NSImage
   // Convert imgIContainer -> CGImageRef, caller owns result
   
   /** Creates a <code>CGImageRef</code> from a frame contained in an <code>imgIContainer</code>.
       Copies the pixel data from the indicated frame of the <code>imgIContainer</code> into a new <code>CGImageRef</code>.
       The caller owns the <code>CGImageRef</code>. 
       @param aFrame the frame to convert
       @param aResult the resulting CGImageRef
+      @param aIsEntirelyBlack an outparam that, if non-null, will be set to a
+                              bool that indicates whether the RGB values on all
+                              pixels are zero
       @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
    */
   static nsresult CreateCGImageFromSurface(SourceSurface* aSurface,
-                                           CGImageRef* aResult);
+                                           CGImageRef* aResult,
+                                           bool* aIsEntirelyBlack = nullptr);
   
   /** Creates a Cocoa <code>NSImage</code> from a <code>CGImageRef</code>.
       Copies the pixel data from the <code>CGImageRef</code> into a new <code>NSImage</code>.
       The caller owns the <code>NSImage</code>. 
       @param aInputImage the image to convert
       @param aResult the resulting NSImage
       @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
    */
--- a/widget/cocoa/nsCocoaUtils.mm
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -356,18 +356,38 @@ void data_ss_release_callback(void *aDat
                               size_t size)
 {
   if (aDataSourceSurface) {
     static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
     static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
   }
 }
 
+// This function assumes little endian byte order.
+static bool
+ComputeIsEntirelyBlack(const DataSourceSurface::MappedSurface& aMap,
+                       const IntSize& aSize)
+{
+  for (int32_t y = 0; y < aSize.height; y++) {
+    size_t rowStart = y * aMap.mStride;
+    for (int32_t x = 0; x < aSize.width; x++) {
+      size_t index = rowStart + x * 4;
+      if (aMap.mData[index + 0] != 0 ||
+          aMap.mData[index + 1] != 0 ||
+          aMap.mData[index + 2] != 0) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface,
-                                                CGImageRef* aResult)
+                                                CGImageRef* aResult,
+                                                bool* aIsEntirelyBlack)
 {
   RefPtr<DataSourceSurface> dataSurface;
 
   if (aSurface->GetFormat() ==  SurfaceFormat::B8G8R8A8) {
     dataSurface = aSurface->GetDataSurface();
   } else {
     // CGImageCreate only supports 16- and 32-bit bit-depth
     // Convert format to SurfaceFormat::B8G8R8A8
@@ -385,16 +405,20 @@ nsresult nsCocoaUtils::CreateCGImageFrom
   }
 
   DataSourceSurface::MappedSurface map;
   if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
     return NS_ERROR_FAILURE;
   }
   // The Unmap() call happens in data_ss_release_callback
 
+  if (aIsEntirelyBlack) {
+    *aIsEntirelyBlack = ComputeIsEntirelyBlack(map, dataSurface->GetSize());
+  }
+
   // Create a CGImageRef with the bits from the image, taking into account
   // the alpha ordering and endianness of the machine so we don't have to
   // touch the bits ourselves.
   CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().take(),
                                                                   map.mData,
                                                                   map.mStride * height,
                                                                   data_ss_release_callback);
   CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
--- a/widget/cocoa/nsMenuItemIconX.mm
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -350,17 +350,19 @@ nsMenuItemIconX::OnFrameComplete(imgIReq
     imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
                              imgIContainer::FLAG_SYNC_DECODE);
   if (!surface) {
     [mNativeMenuItem setImage:nil];
     return NS_ERROR_FAILURE;
   }
 
   CGImageRef origImage = NULL;
-  nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage);
+  bool isEntirelyBlack = false;
+  nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage,
+                                                       &isEntirelyBlack);
   if (NS_FAILED(rv) || !origImage) {
     [mNativeMenuItem setImage:nil];
     return NS_ERROR_FAILURE;
   }
 
   bool createSubImage = !(mImageRegionRect.x == 0 && mImageRegionRect.y == 0 &&
                             mImageRegionRect.width == origWidth && mImageRegionRect.height == origHeight);
 
@@ -383,16 +385,24 @@ nsMenuItemIconX::OnFrameComplete(imgIReq
   NSImage *newImage = nil;
   rv = nsCocoaUtils::CreateNSImageFromCGImage(finalImage, &newImage);
   if (NS_FAILED(rv) || !newImage) {
     [mNativeMenuItem setImage:nil];
     ::CGImageRelease(finalImage);
     return NS_ERROR_FAILURE;
   }
 
+  // If all the color channels in the image are black, treat the image as a
+  // template. This will cause macOS to use the image's alpha channel as a mask
+  // and it will fill it with a color that looks good in the context that it's
+  // used in. For example, for regular menu items, the image will be black, but
+  // when the menu item is hovered (and its background is blue), it will be
+  // filled with white.
+  [newImage setTemplate:isEntirelyBlack];
+
   [newImage setSize:NSMakeSize(kIconWidth, kIconHeight)];
   [mNativeMenuItem setImage:newImage];
 
   [newImage release];
   ::CGImageRelease(finalImage);
 
   mLoadedIcon = true;
   mSetIcon = true;