Skip to content

Commit

Permalink
QCocoaIcon helpers using qcoregraphics
Browse files Browse the repository at this point in the history
  • Loading branch information
kleuter committed Aug 4, 2021
1 parent e05ec7f commit b6d5c34
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 45 deletions.
4 changes: 2 additions & 2 deletions qcocoabutton.mm
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
if (pimpl->isSpecialButton())
return;

NSImage *img = QCocoaIcon::iconToNSImage(image);
NSImage *img = QCocoaIcon::imageFromQIcon(image);

if (img)
pimpl->setImage(img);
Expand All @@ -123,7 +123,7 @@
{
QMacAutoReleasePool pool;

NSImage *img = QCocoaIcon::iconToNSImage(icon);
NSImage *img = QCocoaIcon::standardIcon(icon);

if (img)
pimpl->setImage(img);
Expand Down
4 changes: 2 additions & 2 deletions qcocoacontrols.pro
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ INCLUDEPATH += $${MY_COMMON}
HEADERS += qcocoawidget.h qcocoabutton.h qcocoasegmentedbutton.h qcocoaslider.h qcocoagradientbutton.h qcocoabox.h qcocoamessagebox.h qcocoaicon.h \
qcocoabutton_p.h qcocoabuttonactionmenu.h qcocoapreferencesdialog.h qcocoapopover.h qcocoamenubaritem.h

SOURCES += qcocoapreferencesdialog.cpp
SOURCES += qcocoapreferencesdialog.cpp qcoregraphics.h

OBJECTIVE_SOURCES += qcocoawidget.mm qcocoabutton.mm qcocoabutton_p.mm qcocoasegmentedbutton.mm qcocoaslider.mm qcocoagradientbutton.mm qcocoabox.mm \
qcocoamessagebox.mm qcocoapreferencesdialog_mac.mm qcocoaicon.mm qcocoabuttonactionmenu.mm qcocoapopover.mm qcocoamenubaritem.mm
qcocoamessagebox.mm qcocoapreferencesdialog_mac.mm qcocoaicon.mm qcocoabuttonactionmenu.mm qcocoapopover.mm qcocoamenubaritem.mm qcoregraphics.mm

SOURCES += bigsurtoolbar.mm bigsurtoolbar.h
5 changes: 3 additions & 2 deletions qcocoaicon.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ class QCocoaIcon
Info, // Shows or hides an information window or view.
};

static NSImage * iconToNSImage(StandardIcon type);
static NSImage * iconToNSImage(const QIcon &icon, const QWidget *widget = nullptr);
static NSImage *imageFromQImage(const QImage &img);
static NSImage * imageFromQIcon(const QIcon &icon);

static NSImage * standardIcon(StandardIcon type);
static QPixmap standardIcon(StandardIcon type, int nSize);
};

Expand Down
46 changes: 10 additions & 36 deletions qcocoaicon.mm
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#include "stdafx.h"
#include "qcocoaicon.h"
#include "qcoregraphics.h"

#include <QtMacExtras>

#import <AppKit/NSImage.h>

NSImage * QCocoaIcon::iconToNSImage(StandardIcon type)
NSImage * QCocoaIcon::standardIcon(StandardIcon type)
{
QString str;

Expand Down Expand Up @@ -41,40 +40,14 @@
return nsImage;
}

NSImage *QCocoaIcon::iconToNSImage(const QIcon& icon, const QWidget *widget)
NSImage *QCocoaIcon::imageFromQIcon(const QIcon& icon)
{
if (icon.isNull())
return 0;

NSImage *image = nil;
image = [[NSImage alloc] init];

int nScreenNumber = QApplication::desktop()->screenNumber(widget);

if (nScreenNumber != -1)
{
QScreen *screen = QApplication::screens().at(nScreenNumber);

if (screen)
{
qreal pixelRatio = screen->devicePixelRatio();

for (QSize sz : icon.availableSizes())
{
QPixmap pixmap = icon.pixmap(sz);

CGImageRef cgimage = QtMac::toCGImageRef(pixmap);
NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
// https://stackoverflow.com/questions/24945615/how-to-show-in-memory-nsimage-as-retina-2x
[bitmapRep setSize: NSMakeSize(pixmap.width() / pixelRatio, pixmap.height() / pixelRatio ) ];
[image addRepresentation:bitmapRep];
[bitmapRep release];
CFRelease(cgimage);
}
}
}
return [NSImage imageFromQIcon: icon];
}

return image;
NSImage *QCocoaIcon::imageFromQImage(const QImage &img)
{
return [NSImage imageFromQImage: img];
}

QPixmap QCocoaIcon::standardIcon(StandardIcon type, int nSize)
Expand All @@ -83,7 +56,7 @@

QPixmap pix;

NSImage *nsImage = QCocoaIcon::iconToNSImage(type);
NSImage *nsImage = QCocoaIcon::standardIcon(type);

if (nsImage)
{
Expand All @@ -100,3 +73,4 @@

return pix;
}

2 changes: 1 addition & 1 deletion qcocoamessagebox.mm
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ - (void) alertDidEnd:(NSAlert *) alert returnCode:(NSInteger) returnCode context
if (icon() == QMessageBox::Critical)
alert.alertStyle = NSAlertStyleCritical;
else if (!iconPixmap().isNull()) //NSAlertStyleWarning почему-то не меняет иконки поэтому буду использовать этот метод
alert.icon = QCocoaIcon::iconToNSImage(iconPixmap());
alert.icon = QCocoaIcon::imageFromQIcon(iconPixmap());

QCheckBox *checkBtn = checkBox();

Expand Down
4 changes: 2 additions & 2 deletions qcocoasegmentedbutton.mm
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ - (SEL)action
if (!pimpl)
return;

NSImage *nsImage = QCocoaIcon::iconToNSImage(icon, this);
NSImage *nsImage = QCocoaIcon::imageFromQIcon(icon);

if (nsImage)
{
Expand All @@ -192,7 +192,7 @@ - (SEL)action

void QCocoaSegmentedButton::setSegmentIcon(int iSegment, QCocoaIcon::StandardIcon icon)
{
NSImage *nsImage = QCocoaIcon::iconToNSImage(icon);
NSImage *nsImage = QCocoaIcon::standardIcon(icon);

if (nsImage)
{
Expand Down
13 changes: 13 additions & 0 deletions qcoregraphics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef coregraphics_h
#define coregraphics_h

#import <AppKit/NSImage.h>

// taken from qcoregraphics.mm (Qt 5.15.4) and ported for older Qt 5 versions
@interface NSImage (QtExtras)
+ (instancetype)imageFromQImage:(const QT_PREPEND_NAMESPACE(QImage) &)image;
+ (instancetype)imageFromQIcon:(const QT_PREPEND_NAMESPACE(QIcon) &)icon;
+ (instancetype)imageFromQIcon:(const QT_PREPEND_NAMESPACE(QIcon) &)icon withSize:(int)size;
@end

#endif // coregraphics_h
201 changes: 201 additions & 0 deletions qcoregraphics.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#include "stdafx.h"
#include "qcoregraphics.h"

#if (QT_VERSION < QT_VERSION_CHECK(5, 7, 0))

// this adds const to non-const objects (like std::as_const)
template <typename T>
Q_DECL_CONSTEXPR typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; }
// prevent rvalue arguments:
template <typename T>
void qAsConst(const T &&) = delete;

// like std::exchange
template <typename T, typename U = T>
Q_DECL_RELAXED_CONSTEXPR T qExchange(T &t, U &&newValue)
{
T old = std::move(t);
t = std::forward<U>(newValue);
return old;
}
#endif

template <typename T, typename U, U (*RetainFunction)(U), void (*ReleaseFunction)(U)>
class QAppleRefCounted
{
public:
QAppleRefCounted() : value() {}
QAppleRefCounted(const T &t) : value(t) {}
QAppleRefCounted(T &&t) noexcept(std::is_nothrow_move_constructible<T>::value)
: value(std::move(t)) {}
QAppleRefCounted(QAppleRefCounted &&other)
noexcept(std::is_nothrow_move_assignable<T>::value &&
std::is_nothrow_move_constructible<T>::value)
: value(qExchange(other.value, T())) {}
QAppleRefCounted(const QAppleRefCounted &other) : value(other.value) { if (value) RetainFunction(value); }
~QAppleRefCounted() { if (value) ReleaseFunction(value); }
operator T() const { return value; }
void swap(QAppleRefCounted &other) noexcept(noexcept(qSwap(value, other.value)))
{ qSwap(value, other.value); }
QAppleRefCounted &operator=(const QAppleRefCounted &other)
{ QAppleRefCounted copy(other); swap(copy); return *this; }
QAppleRefCounted &operator=(QAppleRefCounted &&other)
noexcept(std::is_nothrow_move_assignable<T>::value &&
std::is_nothrow_move_constructible<T>::value)
{ QAppleRefCounted moved(std::move(other)); swap(moved); return *this; }
T *operator&() { return &value; }
protected:
T value;
};

/*
Helper class that automates refernce counting for CFtypes.
After constructing the QCFType object, it can be copied like a
value-based type.
Note that you must own the object you are wrapping.
This is typically the case if you get the object from a Core
Foundation function with the word "Create" or "Copy" in it. If
you got the object from a "Get" function, either retain it or use
constructFromGet(). One exception to this rule is the
HIThemeGet*Shape functions, which in reality are "Copy" functions.
*/
template <typename T>
class QCFType : public QAppleRefCounted<T, CFTypeRef, CFRetain, CFRelease>
{
using Base = QAppleRefCounted<T, CFTypeRef, CFRetain, CFRelease>;
public:
using Base::Base;
explicit QCFType(CFTypeRef r) : Base(static_cast<T>(r)) {}
template <typename X> X as() const { return reinterpret_cast<X>(this->value); }
static QCFType constructFromGet(const T &t)
{
if (t)
CFRetain(t);
return QCFType<T>(t);
}
};

#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
#define QImage2CGImageRef(img) img.toCGImage()
#define QSize2CGSize(sz) sz.toCGSize()
#else
#define QImage2CGImageRef(img) toCGImage(img)
#define QSize2CGSize(sz) CGSizeMake(sz.width(), sz.height())

CGBitmapInfo qt_mac_bitmapInfoForImage(const QImage &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;
}

CGImageRef toCGImage(const QImage &img)
{
if (img.isNull())
return nil;
CGBitmapInfo bitmapInfo = qt_mac_bitmapInfoForImage(img);
// Format not supported: return nil CGImageRef
if (bitmapInfo == kCGImageAlphaNone)
return nil;
// Create a data provider that owns a copy of the QImage and references the image data.
auto deleter = [](void *image, const void *, size_t)
{ delete static_cast<QImage *>(image); };
QCFType<CGDataProviderRef> dataProvider =
CGDataProviderCreateWithData(new QImage(img), img.bits(), img.byteCount(), deleter);
QCFType<CGColorSpaceRef> colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
const size_t bitsPerComponent = 8;
const size_t bitsPerPixel = 32;
const CGFloat *decode = nullptr;
const bool shouldInterpolate = false;
return CGImageCreate(img.width(), img.height(), bitsPerComponent, bitsPerPixel,
img.bytesPerLine(), colorSpace, bitmapInfo, dataProvider,
decode, shouldInterpolate, kCGRenderingIntentDefault);
}
#endif

@implementation NSImage (QtExtras)
+ (instancetype)imageFromQImage:(const QImage &)image
{
if (image.isNull())
return nil;

QCFType<CGImageRef> cgImage = QImage2CGImageRef(image);
if (!cgImage)
return nil;

// We set up the NSImage using an explicit NSBitmapImageRep, instead of
// [NSImage initWithCGImage:size:], as the former allows us to correctly
// set the size of the representation to account for the device pixel
// ratio of the original image, which in turn will be reflected by the
// NSImage.
auto nsImage = [[NSImage alloc] initWithSize:NSZeroSize];
auto *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
imageRep.size = QSize2CGSize((image.size() / image.devicePixelRatioF()));
[nsImage addRepresentation:[imageRep autorelease]];
Q_ASSERT(CGSizeEqualToSize(nsImage.size, imageRep.size));

return [nsImage autorelease];
}

+ (instancetype)imageFromQIcon:(const QIcon &)icon
{
return [NSImage imageFromQIcon:icon withSize:0];
}

+ (instancetype)imageFromQIcon:(const QIcon &)icon withSize:(int)size
{
if (icon.isNull())
return nil;

auto availableSizes = icon.availableSizes();
if (availableSizes.isEmpty() && size > 0)
availableSizes << QSize(size, size);

auto nsImage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];

for (QSize size : qAsConst(availableSizes)) {
QImage image = icon.pixmap(size).toImage();
if (image.isNull())
continue;

QCFType<CGImageRef> cgImage = QImage2CGImageRef(image);
if (!cgImage)
continue;

auto *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
imageRep.size = QSize2CGSize((image.size() / image.devicePixelRatioF()));
[nsImage addRepresentation:[imageRep autorelease]];
}

if (!nsImage.representations.count)
return nil;

[nsImage setTemplate:icon.isMask()];

if (size)
nsImage.size = CGSizeMake(size, size);

return nsImage;
}
@end

0 comments on commit b6d5c34

Please sign in to comment.