blob: 7fb075d19ed81c1ba580a707e1f67c994e67fceb [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qcoregraphics_p.h"
#include <private/qcore_mac_p.h>
#include <qpa/qplatformpixmap.h>
#include <QtGui/qicon.h>
#include <QtGui/private/qpaintengine_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qoperatingsystemversion.h>
QT_USE_NAMESPACE
QT_BEGIN_NAMESPACE
// ---------------------- Images ----------------------
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 qt_mac_toCGImage(const QImage &inImage)
{
CGImageRef cgImage = inImage.toCGImage();
if (cgImage)
return cgImage;
// Convert image data to a known-good format if the fast conversion fails.
return inImage.convertToFormat(QImage::Format_ARGB32_Premultiplied).toCGImage();
}
CGImageRef qt_mac_toCGImageMask(const QImage &image)
{
static const auto deleter = [](void *image, const void *, size_t) { delete static_cast<QImage *>(image); };
QCFType<CGDataProviderRef> dataProvider =
CGDataProviderCreateWithData(new QImage(image), image.bits(),
image.sizeInBytes(), deleter);
return CGImageMaskCreate(image.width(), image.height(), 8, image.depth(),
image.bytesPerLine(), dataProvider, NULL, false);
}
void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage)
{
CGContextSaveGState( inContext );
CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds));
CGContextScaleCTM(inContext, 1, -1);
CGContextDrawImage(inContext, *inBounds, inImage);
CGContextRestoreGState(inContext);
}
QImage qt_mac_toQImage(CGImageRef image)
{
const size_t w = CGImageGetWidth(image),
h = CGImageGetHeight(image);
QImage ret(w, h, QImage::Format_ARGB32_Premultiplied);
ret.fill(Qt::transparent);
CGRect rect = CGRectMake(0, 0, w, h);
QMacCGContext ctx(&ret);
qt_mac_drawCGImage(ctx, &rect, image);
return ret;
}
#ifdef Q_OS_MACOS
QT_END_NAMESPACE
@implementation NSImage (QtExtras)
+ (instancetype)imageFromQImage:(const QImage &)image
{
if (image.isNull())
return nil;
QCFType<CGImageRef> cgImage = image.toCGImage();
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 = (image.size() / image.devicePixelRatioF()).toCGSize();
[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 = image.toCGImage();
if (!cgImage)
continue;
auto *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
imageRep.size = (image.size() / image.devicePixelRatioF()).toCGSize();
[nsImage addRepresentation:[imageRep autorelease]];
}
if (!nsImage.representations.count)
return nil;
[nsImage setTemplate:icon.isMask()];
if (size)
nsImage.size = CGSizeMake(size, size);
return nsImage;
}
@end
QT_BEGIN_NAMESPACE
QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
{
const NSSize pixmapSize = NSMakeSize(size.width(), size.height());
QPixmap pixmap(pixmapSize.width, pixmapSize.height);
pixmap.fill(Qt::transparent);
[image setSize:pixmapSize];
const NSRect iconRect = NSMakeRect(0, 0, pixmapSize.width, pixmapSize.height);
QMacCGContext ctx(&pixmap);
if (!ctx)
return QPixmap();
NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
if (!gc)
return QPixmap();
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:gc];
[image drawInRect:iconRect fromRect:iconRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil];
[NSGraphicsContext restoreGraphicsState];
return pixmap;
}
#endif // Q_OS_MACOS
// ---------------------- Colors and Brushes ----------------------
QColor qt_mac_toQColor(CGColorRef color)
{
QColor qtColor;
CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
const CGFloat *components = CGColorGetComponents(color);
if (model == kCGColorSpaceModelRGB) {
qtColor.setRgbF(components[0], components[1], components[2], components[3]);
} else if (model == kCGColorSpaceModelCMYK) {
qtColor.setCmykF(components[0], components[1], components[2], components[3]);
} else if (model == kCGColorSpaceModelMonochrome) {
qtColor.setRgbF(components[0], components[0], components[0], components[1]);
} else {
// Colorspace we can't deal with.
qWarning("Qt: qt_mac_toQColor: cannot convert from colorspace model: %d", model);
Q_ASSERT(false);
}
return qtColor;
}
#ifdef Q_OS_MACOS
QColor qt_mac_toQColor(const NSColor *color)
{
QColor qtColor;
NSString *colorSpace = [color colorSpaceName];
if (colorSpace == NSDeviceCMYKColorSpace) {
CGFloat cyan, magenta, yellow, black, alpha;
[color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha];
qtColor.setCmykF(cyan, magenta, yellow, black, alpha);
} else {
NSColor *tmpColor;
tmpColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
CGFloat red, green, blue, alpha;
[tmpColor getRed:&red green:&green blue:&blue alpha:&alpha];
qtColor.setRgbF(red, green, blue, alpha);
}
return qtColor;
}
#endif
QBrush qt_mac_toQBrush(CGColorRef color)
{
QBrush qtBrush;
CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
if (model == kCGColorSpaceModelPattern) {
// Colorspace we can't deal with; the color is drawn directly using a callback.
qWarning("Qt: qt_mac_toQBrush: cannot convert from colorspace model: %d", model);
Q_ASSERT(false);
} else {
qtBrush.setStyle(Qt::SolidPattern);
qtBrush.setColor(qt_mac_toQColor(color));
}
return qtBrush;
}
#ifdef Q_OS_MACOS
static bool qt_mac_isSystemColorOrInstance(const NSColor *color, NSString *colorNameComponent, NSString *className)
{
// We specifically do not want isKindOfClass: here
if ([color.className isEqualToString:className]) // NSPatternColorSpace
return true;
if ([color.catalogNameComponent isEqualToString:@"System"] &&
[color.colorNameComponent isEqualToString:colorNameComponent] &&
[color.colorSpaceName isEqualToString:NSNamedColorSpace])
return true;
return false;
}
QBrush qt_mac_toQBrush(const NSColor *color, QPalette::ColorGroup colorGroup)
{
QBrush qtBrush;
// QTBUG-49773: This calls NSDrawMenuItemBackground to render a 1 by n gradient; could use HITheme
if ([color.className isEqualToString:@"NSMenuItemHighlightColor"]) {
qWarning("Qt: qt_mac_toQBrush: cannot convert from NSMenuItemHighlightColor");
return qtBrush;
}
// Not a catalog color or a manifestation of System.windowBackgroundColor;
// only retrieved from NSWindow.backgroundColor directly
if ([color.className isEqualToString:@"NSMetalPatternColor"]) {
// NSTexturedBackgroundWindowMask, could theoretically handle this without private API by
// creating a window with the appropriate properties and then calling NSWindow.backgroundColor.patternImage,
// which returns a texture sized 1 by (window height, including frame), backed by a CGPattern
// which follows the window key state... probably need to allow QBrush to store a function pointer
// like CGPattern does
qWarning("Qt: qt_mac_toQBrush: cannot convert from NSMetalPatternColor");
return qtBrush;
}
// No public API to get these colors/stops;
// both accurately obtained through runtime object inspection on OS X 10.11
// (the NSColor object has NSGradient i-vars for both color groups)
if (qt_mac_isSystemColorOrInstance(color, @"_sourceListBackgroundColor", @"NSSourceListBackgroundColor")) {
QLinearGradient gradient;
if (colorGroup == QPalette::Active) {
gradient.setColorAt(0, QColor(233, 237, 242));
gradient.setColorAt(0.5, QColor(225, 229, 235));
gradient.setColorAt(1, QColor(209, 216, 224));
} else {
gradient.setColorAt(0, QColor(248, 248, 248));
gradient.setColorAt(0.5, QColor(240, 240, 240));
gradient.setColorAt(1, QColor(235, 235, 235));
}
return QBrush(gradient);
}
// A couple colors are special... they are actually instances of NSGradientPatternColor, which
// override set/setFill/setStroke to instead initialize an internal color
// ([NSColor colorWithCalibratedWhite:0.909804 alpha:1.000000]) while still returning the
// ruled lines pattern image (from OS X 10.4) to the user from -[NSColor patternImage]
// (and providing no public API to get the underlying color without this insanity)
if (qt_mac_isSystemColorOrInstance(color, @"controlColor", @"NSGradientPatternColor") ||
qt_mac_isSystemColorOrInstance(color, @"windowBackgroundColor", @"NSGradientPatternColor")) {
qtBrush.setStyle(Qt::SolidPattern);
qtBrush.setColor(qt_mac_toQColor(color.CGColor));
return qtBrush;
}
if (NSColor *patternColor = [color colorUsingColorSpaceName:NSPatternColorSpace]) {
NSImage *patternImage = patternColor.patternImage;
const QSizeF sz(patternImage.size.width, patternImage.size.height);
// FIXME: QBrush is not resolution independent (QTBUG-49774)
qtBrush.setTexture(qt_mac_toQPixmap(patternImage, sz));
} else {
qtBrush.setStyle(Qt::SolidPattern);
qtBrush.setColor(qt_mac_toQColor(color));
}
return qtBrush;
}
#endif
// ---------------------- Geometry Helpers ----------------------
void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform)
{
CGAffineTransform old_xform = CGAffineTransformIdentity;
if (orig_xform) { //setup xforms
old_xform = CGContextGetCTM(hd);
CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform));
CGContextConcatCTM(hd, *orig_xform);
}
//do the clipping
CGContextBeginPath(hd);
if (rgn.isEmpty()) {
CGContextAddRect(hd, CGRectMake(0, 0, 0, 0));
} else {
for (const QRect &r : rgn) {
CGRect mac_r = CGRectMake(r.x(), r.y(), r.width(), r.height());
CGContextAddRect(hd, mac_r);
}
}
CGContextClip(hd);
if (orig_xform) {//reset xforms
CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd)));
CGContextConcatCTM(hd, old_xform);
}
}
// move to QRegion?
void qt_mac_scale_region(QRegion *region, qreal scaleFactor)
{
if (!region || !region->rectCount())
return;
QVector<QRect> scaledRects;
scaledRects.reserve(region->rectCount());
for (const QRect &rect : *region)
scaledRects.append(QRect(rect.topLeft() * scaleFactor, rect.size() * scaleFactor));
region->setRects(&scaledRects[0], scaledRects.count());
}
// ---------------------- QMacCGContext ----------------------
QMacCGContext::QMacCGContext(QPaintDevice *paintDevice)
{
initialize(paintDevice);
}
void QMacCGContext::initialize(QPaintDevice *paintDevice)
{
// Find the underlying QImage of the paint device
switch (int deviceType = paintDevice->devType()) {
case QInternal::Pixmap: {
auto *platformPixmap = static_cast<QPixmap*>(paintDevice)->handle();
if (platformPixmap && platformPixmap->classId() == QPlatformPixmap::RasterClass)
initialize(platformPixmap->buffer());
else
qWarning() << "QMacCGContext: Unsupported pixmap class" << platformPixmap->classId();
break;
}
case QInternal::Image:
initialize(static_cast<const QImage *>(paintDevice));
break;
case QInternal::Widget:
qWarning() << "QMacCGContext: not implemented: Widget class";
break;
default:
qWarning() << "QMacCGContext:: Unsupported paint device type" << deviceType;
}
}
QMacCGContext::QMacCGContext(QPainter *painter)
{
QPaintEngine *paintEngine = painter->paintEngine();
// Handle the case of QMacPrintEngine, which has an internal QCoreGraphicsPaintEngine
while (QPaintEngine *aggregateEngine = QPaintEnginePrivate::get(paintEngine)->aggregateEngine())
paintEngine = aggregateEngine;
paintEngine->syncState();
if (Qt::HANDLE handle = QPaintEnginePrivate::get(paintEngine)->nativeHandle()) {
context = static_cast<CGContextRef>(handle);
return;
}
if (paintEngine->type() != QPaintEngine::Raster) {
qWarning() << "QMacCGContext:: Unsupported paint engine type" << paintEngine->type();
return;
}
// The raster paint engine always operates on a QImage
Q_ASSERT(paintEngine->paintDevice()->devType() == QInternal::Image);
// On behalf of one of these supported painter devices
switch (int painterDeviceType = painter->device()->devType()) {
case QInternal::Pixmap:
case QInternal::Image:
case QInternal::Widget:
break;
default:
qWarning() << "QMacCGContext:: Unsupported paint device type" << painterDeviceType;
return;
}
// Applying the clip is so entangled with the rest of the context setup
// that for simplicity we just pass in the painter.
initialize(static_cast<const QImage *>(paintEngine->paintDevice()), painter);
}
void QMacCGContext::initialize(const QImage *image, QPainter *painter)
{
QCFType<CGColorSpaceRef> colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
context = CGBitmapContextCreate((void *)image->bits(), image->width(), image->height(), 8,
image->bytesPerLine(), colorSpace, qt_mac_bitmapInfoForImage(*image));
// Invert y axis
CGContextTranslateCTM(context, 0, image->height());
CGContextScaleCTM(context, 1, -1);
const qreal devicePixelRatio = image->devicePixelRatio();
if (painter && painter->device()->devType() == QInternal::Widget) {
// Set the clip rect which is an intersection of the system clip and the painter clip
QRegion clip = painter->paintEngine()->systemClip();
QTransform deviceTransform = painter->deviceTransform();
if (painter->hasClipping()) {
// To make matters more interesting the painter clip is in device-independent pixels,
// so we need to scale it to match the device-pixels of the system clip.
QRegion painterClip = painter->clipRegion();
qt_mac_scale_region(&painterClip, devicePixelRatio);
painterClip.translate(deviceTransform.dx(), deviceTransform.dy());
if (clip.isEmpty())
clip = painterClip;
else
clip &= painterClip;
}
qt_mac_clip_cg(context, clip, 0);
CGContextTranslateCTM(context, deviceTransform.dx(), deviceTransform.dy());
}
// Scale the context so that painting happens in device-independent pixels
CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio);
}
QT_END_NAMESPACE