Bug 1403989 - Automatically treat black menu icons as templates, so that they get drawn inverted in hovered menuitems. r=spohl, r=jrmuizel draft
authorMarkus Stange <mstange@themasta.com>
Mon, 16 Apr 2018 15:23:23 -0400
changeset 784097 c583912802bf6f74c0b890d8836ff879c986985b
parent 782895 6276ec7ebbf33e3484997b189f20fc1511534187
push id106853
push userbmo:mstange@themasta.com
push dateWed, 18 Apr 2018 03:39:45 +0000
reviewersspohl, jrmuizel
bugs1403989
milestone61.0a1
Bug 1403989 - Automatically treat black menu icons as templates, so that they get drawn inverted in hovered menuitems. r=spohl, r=jrmuizel 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;