// This file Copyright © Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #import #import #import #import #import #import // Source: https://stackoverflow.com/a/74756071 // Posted by Bri Bri // Retrieved 2025-11-22, License - CC BY-SA 4.0 namespace bribri { CGBitmapInfo CGBitmapInfoForQImage(QImage const& image) { CGBitmapInfo bitmapInfo = kCGImageAlphaNone; switch (image.format()) { case QImage::Format_ARGB32: bitmapInfo = kCGImageAlphaFirst | kCGBitmapByteOrder32Host; break; case QImage::Format_RGB32: bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; break; case QImage::Format_RGBA8888_Premultiplied: bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big; break; case QImage::Format_RGBA8888: bitmapInfo = kCGImageAlphaLast | kCGBitmapByteOrder32Big; break; case QImage::Format_RGBX8888: bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big; break; case QImage::Format_ARGB32_Premultiplied: bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; break; default: break; } return bitmapInfo; } QImage CGImageToQImage(CGImageRef cgImage) { size_t const width = CGImageGetWidth(cgImage); size_t const height = CGImageGetHeight(cgImage); QImage image(width, height, QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); CGContextRef context = CGBitmapContextCreate((void*)image.bits(), image.width(), image.height(), 8, image.bytesPerLine(), colorSpace, CGBitmapInfoForQImage(image)); // Scale the context so that painting happens in device-independent pixels qreal const devicePixelRatio = image.devicePixelRatio(); CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio); CGRect rect = CGRectMake(0, 0, width, height); CGContextDrawImage(context, rect, cgImage); CFRelease(colorSpace); CFRelease(context); return image; } } // namespace bribri QPixmap loadSFSymbol(QString const symbol_name, int const pixel_size) { if (NSImage* image = [NSImage imageWithSystemSymbolName:symbol_name.toNSString() accessibilityDescription:nil]) { auto* configuration = [NSImageSymbolConfiguration configurationWithPointSize:pixel_size weight:NSFontWeightRegular]; if (@available(macOS 12.0, *)) { // use whatever color QPalette::ButtonText is using // @available check needed for configurationWithHierarchicalColor QColor const qfg = qApp->palette().color(QPalette::ButtonText); NSColor* nsfg = [NSColor colorWithCalibratedRed:qfg.redF() green:qfg.greenF() blue:qfg.blueF() alpha:qfg.alphaF()]; auto* colorConfig = [NSImageSymbolConfiguration configurationWithHierarchicalColor:nsfg]; configuration = [configuration configurationByApplyingConfiguration:colorConfig]; } image = [image imageWithSymbolConfiguration:configuration]; // NSImage -> QPixmap NSRect image_rect = NSMakeRect(0, 0, pixel_size, pixel_size); CGImageRef cgimg = [image CGImageForProposedRect:&image_rect context:nil hints:nil]; return QPixmap::fromImage(bribri::CGImageToQImage(cgimg)); } return {}; }