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
--- 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;