Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcoregraphics.mm
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <private/qcore_mac_p.h>
7#include <qpa/qplatformpixmap.h>
8#include <QtGui/qicon.h>
9#include <QtGui/private/qpaintengine_p.h>
10#include <QtCore/qdebug.h>
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qoperatingsystemversion.h>
13#include <QtGui/qcolorspace.h>
14#include <QtGui/private/qicon_p.h>
15
16#if defined(Q_OS_MACOS)
17# include <AppKit/AppKit.h>
18#elif defined(QT_PLATFORM_UIKIT)
19# include <UIKit/UIKit.h>
20#endif
21
22#include <Accelerate/Accelerate.h>
23
24QT_USE_NAMESPACE
25
26QT_BEGIN_NAMESPACE
27
28// ---------------------- Images ----------------------
29
30std::optional<vImage_CGImageFormat> qt_mac_cgImageFormatForImage(const QImage &image)
31{
32 const QPixelFormat format = image.pixelFormat();
33
34 // FIXME: Support other color models, such as Grayscale and Alpha,
35 // which would require the calling code to use a non-RGB color space.
36 if (format.colorModel() != QPixelFormat::RGB)
37 return {};
38
39 const int alphaBits = format.alphaSize();
40
41 CGBitmapInfo bitmapInfo = [&]{
42 if (!alphaBits)
43 return kCGImageAlphaNone;
44
45 if (format.channelCount() == 1)
46 return kCGImageAlphaOnly;
47
48 return CGImageAlphaInfo(
49 (format.alphaUsage() == QPixelFormat::IgnoresAlpha ?
50 kCGImageAlphaNoneSkipLast
51 : (format.premultiplied() == QPixelFormat::Premultiplied ?
52 kCGImageAlphaPremultipliedLast : kCGImageAlphaLast)
53 ) // 'First' variants have a value one more than their 'Last'
54 + (format.alphaPosition() == QPixelFormat::AtBeginning ? 1 : 0)
55 );
56 }();
57
58 const std::tuple rgbBits{format.redSize(), format.greenSize(), format.blueSize() };
59
60 const CGImageByteOrderInfo byteOrder16Bit =
61 format.byteOrder() == QPixelFormat::LittleEndian ?
62 kCGImageByteOrder16Little : kCGImageByteOrder16Big;
63
64 const CGImageByteOrderInfo byteOrder32Bit =
65 format.byteOrder() == QPixelFormat::LittleEndian ?
66 kCGImageByteOrder32Little : kCGImageByteOrder32Big;
67
68 static const auto isPacked = [](const QPixelFormat f) {
69 return f.redSize() == f.greenSize()
70 && f.greenSize() == f.blueSize()
71 && (!f.alphaSize() || f.alphaSize() == f.blueSize());
72 };
73
74 switch (format.typeInterpretation()) {
75 case QPixelFormat::UnsignedByte:
76 // Qt always uses UnsignedByte for BigEndian formats, instead of
77 // representing e.g. Format_RGBX8888 as UnsignedInteger+BigEndian,
78 // so we need to look at the bits per pixel as well.
79 if (format.bitsPerPixel() == 32)
80 bitmapInfo |= kCGImageByteOrder32Big;
81 else if (format.bitsPerPixel() == 16)
82 bitmapInfo |= kCGImageByteOrder16Big;
83 else
84 bitmapInfo |= kCGImageByteOrderDefault;
85 break;
86 case QPixelFormat::UnsignedShort:
87 bitmapInfo |= byteOrder16Bit;
88 if (isPacked(format))
89 bitmapInfo |= kCGImagePixelFormatPacked;
90 else if (rgbBits == std::tuple{5,5,5} && alphaBits == 1)
91 bitmapInfo |= kCGImagePixelFormatRGB555;
92 else if (rgbBits == std::tuple{5,6,5} && !alphaBits)
93 bitmapInfo |= kCGImagePixelFormatRGB565;
94 else
95 return {};
96 break;
97 case QPixelFormat::UnsignedInteger:
98 bitmapInfo |= byteOrder32Bit;
99 if (isPacked(format))
100 bitmapInfo |= kCGImagePixelFormatPacked;
101 else if (rgbBits == std::tuple{10,10,10} && alphaBits == 2)
102 bitmapInfo |= kCGImagePixelFormatRGB101010;
103 else
104 return {};
105 break;
106 case QPixelFormat::FloatingPoint:
107 bitmapInfo |= kCGBitmapFloatComponents;
108 if (!isPacked(format))
109 return {};
110 if (format.bitsPerPixel() == 128)
111 bitmapInfo |= byteOrder32Bit; // Full float
112 else if (format.bitsPerPixel() == 64)
113 bitmapInfo |= byteOrder16Bit; // Half float
114 else
115 return {};
116 }
117
118 // By trial and error the logic for the bits per component
119 // seems to be the smallest of the color channels. This is
120 // also somewhat corroborated by the vImage documentation.
121 const uint32_t bitsPerComponent = std::min({
122 format.redSize(), format.greenSize(), format.blueSize()
123 });
124
125 QCFType<CGColorSpaceRef> colorSpace = [&]{
126 if (const auto colorSpace = image.colorSpace(); colorSpace.isValid()) {
127 QCFType<CFDataRef> iccData = colorSpace.iccProfile().toCFData();
128 return CGColorSpaceCreateWithICCData(iccData);
129 } else {
130 return CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
131 }
132 }();
133
134 return vImage_CGImageFormat{
135 bitsPerComponent, format.bitsPerPixel(),
136 colorSpace, bitmapInfo, 0, nullptr,
137 kCGRenderingIntentDefault
138 };
139}
140
141CGImageRef qt_mac_toCGImage(const QImage &inImage)
142{
143 CGImageRef cgImage = inImage.toCGImage();
144 if (cgImage)
145 return cgImage;
146
147 // Convert image data to a known-good format if the fast conversion fails.
148 return inImage.convertToFormat(QImage::Format_ARGB32_Premultiplied).toCGImage();
149}
150
151void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage)
152{
153 CGContextSaveGState( inContext );
154 CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds));
155 CGContextScaleCTM(inContext, 1, -1);
156
157 CGContextDrawImage(inContext, *inBounds, inImage);
158
159 CGContextRestoreGState(inContext);
160}
161
163{
164 if (!image)
165 return QImage::Format_Invalid;
166
167 const CGColorSpaceRef colorSpace = CGImageGetColorSpace(image);
168 if (CGColorSpaceGetModel(colorSpace) != kCGColorSpaceModelRGB)
169 return QImage::Format_Invalid;
170
171 const CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
172 const auto byteOrder = CGImageByteOrderInfo(bitmapInfo & kCGBitmapByteOrderMask);
173
174 auto qtByteOrder = [&]() -> std::optional<QPixelFormat::ByteOrder> {
175 switch (byteOrder) {
176 case kCGImageByteOrder16Big:
177 case kCGImageByteOrder32Big:
178 case kCGImageByteOrderDefault:
179 return QPixelFormat::BigEndian;
180 case kCGImageByteOrder16Little:
181 case kCGImageByteOrder32Little:
182 return QPixelFormat::LittleEndian;
183 default:
184 return {};
185 }
186 }();
187 if (!qtByteOrder)
188 return QImage::Format_Invalid;
189
190 auto typeInterpretation = [&]() -> std::optional<QPixelFormat::TypeInterpretation> {
191 if (bitmapInfo & kCGBitmapFloatComponents)
192 return QPixelFormat::FloatingPoint;
193 else if (qtByteOrder == QPixelFormat::BigEndian)
194 // Qt always uses UnsignedByte for BigEndian formats, instead of
195 // representing e.g. Format_RGBX8888 as UnsignedInteger+BigEndian.
196 return QPixelFormat::UnsignedByte;
197 else if (byteOrder == kCGImageByteOrder16Little)
198 return QPixelFormat::UnsignedShort;
199 else if (byteOrder == kCGImageByteOrder32Little)
200 return QPixelFormat::UnsignedInteger;
201 else
202 return {};
203 }();
204 if (!typeInterpretation)
205 return QImage::Format_Invalid;
206
207 const auto alphaInfo = CGImageAlphaInfo(bitmapInfo & kCGBitmapAlphaInfoMask);
208
209 QPixelFormat::AlphaPosition alphaPosition = [&]{
210 switch (alphaInfo) {
211 case kCGImageAlphaNone:
212 case kCGImageAlphaFirst:
213 case kCGImageAlphaNoneSkipFirst:
214 case kCGImageAlphaPremultipliedFirst:
215 return QPixelFormat::AtBeginning;
216 default:
217 return QPixelFormat::AtEnd;
218 }
219 }();
220
221 QPixelFormat::AlphaUsage alphaUsage = [&]{
222 switch (alphaInfo) {
223 case kCGImageAlphaNone:
224 case kCGImageAlphaNoneSkipLast:
225 case kCGImageAlphaNoneSkipFirst:
226 return QPixelFormat::IgnoresAlpha;
227 default:
228 return QPixelFormat::UsesAlpha;
229 }
230 }();
231
232 QPixelFormat::AlphaPremultiplied alphaPremultiplied = [&]{
233 switch (alphaInfo) {
234 case kCGImageAlphaPremultipliedFirst:
235 case kCGImageAlphaPremultipliedLast:
236 return QPixelFormat::Premultiplied;
237 default:
238 return QPixelFormat::NotPremultiplied;
239 }
240 }();
241
242 auto [redSize, greenSize, blueSize, alphaSize] = [&]() -> std::tuple<uchar,uchar,uchar,uchar> {
243 const auto pixelFormat = CGImagePixelFormatInfo(bitmapInfo & kCGImagePixelFormatMask);
244 const size_t bpc = CGImageGetBitsPerComponent(image);
245 if (pixelFormat == kCGImagePixelFormatPacked)
246 return {bpc, bpc, bpc, alphaInfo != kCGImageAlphaNone ? bpc : 0};
247 else if (pixelFormat == kCGImagePixelFormatRGB555)
248 return {5, 5, 5, 1};
249 else if (pixelFormat == kCGImagePixelFormatRGB565)
250 return {5, 6, 5, 0};
251 else if (pixelFormat == kCGImagePixelFormatRGB101010)
252 return {10, 10, 10, 2};
253 else
254 return {0, 0, 0, 0};
255 }();
256
257 QPixelFormat pixelFormat(QPixelFormat::RGB, redSize, greenSize, blueSize, 0, 0,
258 alphaSize, alphaUsage, alphaPosition, alphaPremultiplied,
259 *typeInterpretation, *qtByteOrder);
260
261 return QImage::toImageFormat(pixelFormat);
262}
263
264QImage qt_mac_toQImage(CGImageRef cgImage)
265{
266 const size_t width = CGImageGetWidth(cgImage);
267 const size_t height = CGImageGetHeight(cgImage);
268
269 QImage image = [&]() -> QImage {
270 QImage::Format imageFormat = qt_mac_imageFormatForCGImage(cgImage);
271 if (imageFormat == QImage::Format_Invalid)
272 return {};
273
274 CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
275 if (!dataProvider)
276 return {};
277
278 // Despite its name, this should not copy the actual image data
279 CFDataRef data = CGDataProviderCopyData(dataProvider);
280 if (!data)
281 return {};
282
283 // Adopt data for the lifetime of the QImage
284 return QImage(CFDataGetBytePtr(data), width, height,
285 CGImageGetBytesPerRow(cgImage), imageFormat,
286 QImageCleanupFunction(CFRelease), (void*)data);
287 }();
288
289 if (image.isNull()) {
290 // Fall back to drawing to a know good format
291 image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
292 image.fill(Qt::transparent);
293 QMacCGContext context(&image);
294 CGRect rect = CGRectMake(0, 0, width, height);
295 qt_mac_drawCGImage(context, &rect, cgImage);
296 }
297
298 if (!image.isNull()) {
299 CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgImage);
300 QCFType<CFDataRef> iccData = CGColorSpaceCopyICCData(colorSpace);
301 image.setColorSpace(QColorSpace::fromIccProfile(QByteArray::fromRawCFData(iccData)));
302 }
303
304 return image;
305}
306
307QImage qt_mac_padToSquareImage(const QImage &image)
308{
309 if (image.width() == image.height())
310 return image;
311
312 const int size = std::max(image.width(), image.height());
313 QImage squareImage(size, size, image.format());
314 squareImage.setDevicePixelRatio(image.devicePixelRatio());
315 squareImage.fill(Qt::transparent);
316
317 QPoint pos((size - image.width()) / (2.0 * image.devicePixelRatio()),
318 (size - image.height()) / (2.0 * image.devicePixelRatio()));
319
320 QPainter painter(&squareImage);
321 painter.drawImage(pos, image);
322 painter.end();
323
324 return squareImage;
325}
326
327#ifdef Q_OS_MACOS
328
329QT_END_NAMESPACE
330
331@implementation NSImage (QtExtras)
332+ (instancetype)imageFromQImage:(const QImage &)image
333{
334 if (image.isNull())
335 return nil;
336
337 QCFType<CGImageRef> cgImage = image.toCGImage();
338 if (!cgImage)
339 return nil;
340
341 // We set up the NSImage using an explicit NSBitmapImageRep, instead of
342 // [NSImage initWithCGImage:size:], as the former allows us to correctly
343 // set the size of the representation to account for the device pixel
344 // ratio of the original image, which in turn will be reflected by the
345 // NSImage.
346 auto nsImage = [[NSImage alloc] initWithSize:NSZeroSize];
347 auto *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
348 imageRep.size = image.deviceIndependentSize().toCGSize();
349 [nsImage addRepresentation:[imageRep autorelease]];
350 Q_ASSERT(CGSizeEqualToSize(nsImage.size, imageRep.size));
351
352 return [nsImage autorelease];
353}
354
355+ (instancetype)imageFromQIcon:(const QIcon &)icon
356{
357 return [NSImage imageFromQIcon:icon withSize:QSize()];
358}
359
360+ (instancetype)imageFromQIcon:(const QIcon &)icon withSize:(const QSize &)size
361{
362 return [NSImage imageFromQIcon:icon withSize:size withMode:QIcon::Normal withState:QIcon::Off];
363}
364
365+ (instancetype)imageFromQIcon:(const QIcon &)icon withSize:(const QSize &)size
366 withMode:(QIcon::Mode)mode withState:(QIcon::State)state
367
368{
369 if (icon.isNull())
370 return nil;
371
372 auto availableSizes = icon.availableSizes();
373 if (availableSizes.isEmpty() && !size.isNull())
374 availableSizes << size;
375
376 auto nsImage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
377
378 for (QSize size : std::as_const(availableSizes)) {
379 const QImage image = icon.pixmap(size, mode, state).toImage();
380 if (image.isNull())
381 continue;
382
383 QCFType<CGImageRef> cgImage = image.toCGImage();
384 if (!cgImage)
385 continue;
386
387 auto *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
388 imageRep.size = image.deviceIndependentSize().toCGSize();
389 [nsImage addRepresentation:[imageRep autorelease]];
390 // Match behavior of loading icns files, where the NSImage size
391 // reflects the largest representation.
392 nsImage.size = imageRep.size;
393 }
394
395 if (!nsImage.representations.count)
396 return nil;
397
398 [nsImage setTemplate:icon.isMask()];
399
400 if (!size.isNull()) {
401 auto imageSize = QSizeF::fromCGSize(nsImage.size);
402 nsImage.size = imageSize.scaled(size, Qt::KeepAspectRatio).toCGSize();
403 }
404
405 return nsImage;
406}
407
408+ (instancetype)internalImageFromQIcon:(const QT_PREPEND_NAMESPACE(QIcon) &)icon
409{
410 if (icon.isNull())
411 return nil;
412
413 // Check if the icon is backed by an NSImage. If so, we can use that directly.
414 auto *iconPrivate = QIconPrivate::get(&icon);
415 NSImage *iconImage = nullptr;
416 iconPrivate->engine->virtual_hook(QIconPrivate::PlatformIconHook, &iconImage);
417 return iconImage;
418}
419
420@end
421
422QT_BEGIN_NAMESPACE
423
424QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
425{
426 // ### TODO: add parameter so that we can decide whether to maintain the aspect
427 // ratio of the image (positioning the image inside the pixmap of size \a size),
428 // or whether we want to fill the resulting pixmap by stretching the image.
429 const NSSize pixmapSize = NSMakeSize(size.width(), size.height());
430 QPixmap pixmap(pixmapSize.width, pixmapSize.height);
431 pixmap.fill(Qt::transparent);
432 [image setSize:pixmapSize];
433 const NSRect iconRect = NSMakeRect(0, 0, pixmapSize.width, pixmapSize.height);
434 QMacCGContext ctx(&pixmap);
435 if (!ctx)
436 return QPixmap();
437 NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
438 if (!gc)
439 return QPixmap();
440 [NSGraphicsContext saveGraphicsState];
441 [NSGraphicsContext setCurrentContext:gc];
442 [image drawInRect:iconRect fromRect:iconRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil];
443 [NSGraphicsContext restoreGraphicsState];
444 return pixmap;
445}
446
447#endif // Q_OS_MACOS
448
449#ifdef QT_PLATFORM_UIKIT
450
451QImage qt_mac_toQImage(const UIImage *image, QSizeF size)
452{
453 // ### TODO: same as above
454 QImage ret(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
455 ret.fill(Qt::transparent);
456 QMacCGContext ctx(&ret);
457 if (!ctx)
458 return QImage();
459 UIGraphicsPushContext(ctx);
460 const CGRect rect = CGRectMake(0, 0, size.width(), size.height());
461 [image drawInRect:rect];
462 UIGraphicsPopContext();
463 return ret;
464}
465
466#endif // QT_PLATFORM_UIKIT
467
468// ---------------------- Colors and Brushes ----------------------
469
470QColor qt_mac_toQColor(CGColorRef color)
471{
472 QColor qtColor;
473 CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
474 const CGFloat *components = CGColorGetComponents(color);
475 if (model == kCGColorSpaceModelRGB) {
476 qtColor.setRgbF(components[0], components[1], components[2], components[3]);
477 } else if (model == kCGColorSpaceModelCMYK) {
478 qtColor.setCmykF(components[0], components[1], components[2], components[3]);
479 } else if (model == kCGColorSpaceModelMonochrome) {
480 qtColor.setRgbF(components[0], components[0], components[0], components[1]);
481 } else {
482 // Colorspace we can't deal with.
483 qWarning("Qt: qt_mac_toQColor: cannot convert from colorspace model: %d", model);
484 Q_ASSERT(false);
485 }
486 return qtColor;
487}
488
489#ifdef Q_OS_MACOS
490QColor qt_mac_toQColor(const NSColor *color)
491{
492 if (!color)
493 return QColor();
494
495 QColor qtColor;
496 switch (color.type) {
497 case NSColorTypeComponentBased: {
498 const NSColorSpace *colorSpace = [color colorSpace];
499 if (colorSpace == NSColorSpace.genericRGBColorSpace
500 && color.numberOfComponents == 4) { // rbga
501 CGFloat components[4];
502 [color getComponents:components];
503 qtColor.setRgbF(components[0], components[1], components[2], components[3]);
504 break;
505 } else if (colorSpace == NSColorSpace.genericCMYKColorSpace
506 && color.numberOfComponents == 5) { // cmyk + alpha
507 CGFloat components[5];
508 [color getComponents:components];
509 qtColor.setCmykF(components[0], components[1], components[2], components[3], components[4]);
510 break;
511 }
512 }
513 Q_FALLTHROUGH();
514 default: {
515 const NSColor *tmpColor = [color colorUsingColorSpace:NSColorSpace.genericRGBColorSpace];
516 CGFloat red = 0, green = 0, blue = 0, alpha = 0;
517 [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha];
518 qtColor.setRgbF(red, green, blue, alpha);
519 break;
520 }
521 }
522
523 return qtColor;
524}
525#endif
526
527QBrush qt_mac_toQBrush(CGColorRef color)
528{
529 QBrush qtBrush;
530 CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
531 if (model == kCGColorSpaceModelPattern) {
532 // Colorspace we can't deal with; the color is drawn directly using a callback.
533 qWarning("Qt: qt_mac_toQBrush: cannot convert from colorspace model: %d", model);
534 Q_ASSERT(false);
535 } else {
536 qtBrush.setStyle(Qt::SolidPattern);
537 qtBrush.setColor(qt_mac_toQColor(color));
538 }
539 return qtBrush;
540}
541
542#ifdef Q_OS_MACOS
543static bool qt_mac_isSystemColorOrInstance(const NSColor *color, NSString *colorNameComponent, NSString *className)
544{
545 // We specifically do not want isKindOfClass: here
546 if ([color.className isEqualToString:className]) // NSPatternColorSpace
547 return true;
548 if (color.type == NSColorTypeCatalog &&
549 [color.catalogNameComponent isEqualToString:@"System"] &&
550 [color.colorNameComponent isEqualToString:colorNameComponent])
551 return true;
552 return false;
553}
554
555QBrush qt_mac_toQBrush(const NSColor *color, QPalette::ColorGroup colorGroup)
556{
557 QBrush qtBrush;
558
559 // QTBUG-49773: This calls NSDrawMenuItemBackground to render a 1 by n gradient; could use HITheme
560 if ([color.className isEqualToString:@"NSMenuItemHighlightColor"]) {
561 qWarning("Qt: qt_mac_toQBrush: cannot convert from NSMenuItemHighlightColor");
562 return qtBrush;
563 }
564
565 // Not a catalog color or a manifestation of System.windowBackgroundColor;
566 // only retrieved from NSWindow.backgroundColor directly
567 if ([color.className isEqualToString:@"NSMetalPatternColor"]) {
568 // NSTexturedBackgroundWindowMask, could theoretically handle this without private API by
569 // creating a window with the appropriate properties and then calling NSWindow.backgroundColor.patternImage,
570 // which returns a texture sized 1 by (window height, including frame), backed by a CGPattern
571 // which follows the window key state... probably need to allow QBrush to store a function pointer
572 // like CGPattern does
573 qWarning("Qt: qt_mac_toQBrush: cannot convert from NSMetalPatternColor");
574 return qtBrush;
575 }
576
577 // No public API to get these colors/stops;
578 // both accurately obtained through runtime object inspection on OS X 10.11
579 // (the NSColor object has NSGradient i-vars for both color groups)
580 if (qt_mac_isSystemColorOrInstance(color, @"_sourceListBackgroundColor", @"NSSourceListBackgroundColor")) {
581 QLinearGradient gradient;
582 if (colorGroup == QPalette::Active) {
583 gradient.setColorAt(0, QColor(233, 237, 242));
584 gradient.setColorAt(0.5, QColor(225, 229, 235));
585 gradient.setColorAt(1, QColor(209, 216, 224));
586 } else {
587 gradient.setColorAt(0, QColor(248, 248, 248));
588 gradient.setColorAt(0.5, QColor(240, 240, 240));
589 gradient.setColorAt(1, QColor(235, 235, 235));
590 }
591 return QBrush(gradient);
592 }
593
594 // A couple colors are special... they are actually instances of NSGradientPatternColor, which
595 // override set/setFill/setStroke to instead initialize an internal color
596 // ([NSColor colorWithCalibratedWhite:0.909804 alpha:1.000000]) while still returning the
597 // ruled lines pattern image (from OS X 10.4) to the user from -[NSColor patternImage]
598 // (and providing no public API to get the underlying color without this insanity)
599 if (qt_mac_isSystemColorOrInstance(color, @"controlColor", @"NSGradientPatternColor") ||
600 qt_mac_isSystemColorOrInstance(color, @"windowBackgroundColor", @"NSGradientPatternColor")) {
601 qtBrush.setStyle(Qt::SolidPattern);
602 qtBrush.setColor(qt_mac_toQColor(color.CGColor));
603 return qtBrush;
604 }
605
606 if (color.type == NSColorTypePattern) {
607 NSImage *patternImage = color.patternImage;
608 const QSizeF sz(patternImage.size.width, patternImage.size.height);
609 // FIXME: QBrush is not resolution independent (QTBUG-49774)
610 qtBrush.setTexture(qt_mac_toQPixmap(patternImage, sz));
611 } else {
612 qtBrush.setStyle(Qt::SolidPattern);
613 qtBrush.setColor(qt_mac_toQColor(color));
614 }
615 return qtBrush;
616}
617#endif
618
619// ---------------------- Geometry Helpers ----------------------
620
621void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform)
622{
623 CGAffineTransform old_xform = CGAffineTransformIdentity;
624 if (orig_xform) { //setup xforms
625 old_xform = CGContextGetCTM(hd);
626 CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform));
627 CGContextConcatCTM(hd, *orig_xform);
628 }
629
630 //do the clipping
631 CGContextBeginPath(hd);
632 if (rgn.isEmpty()) {
633 CGContextAddRect(hd, CGRectMake(0, 0, 0, 0));
634 } else {
635 for (const QRect &r : rgn) {
636 CGRect mac_r = CGRectMake(r.x(), r.y(), r.width(), r.height());
637 CGContextAddRect(hd, mac_r);
638 }
639 }
640 CGContextClip(hd);
641
642 if (orig_xform) {//reset xforms
643 CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd)));
644 CGContextConcatCTM(hd, old_xform);
645 }
646}
647
648// move to QRegion?
649void qt_mac_scale_region(QRegion *region, qreal scaleFactor)
650{
651 if (!region || !region->rectCount())
652 return;
653
654 QVector<QRect> scaledRects;
655 scaledRects.reserve(region->rectCount());
656
657 for (const QRect &rect : *region)
658 scaledRects.append(QRect(rect.topLeft() * scaleFactor, rect.size() * scaleFactor));
659
660 region->setRects(&scaledRects[0], scaledRects.count());
661}
662
663// ---------------------- QMacCGContext ----------------------
664
665QMacCGContext::QMacCGContext(QPaintDevice *paintDevice)
666{
667 initialize(paintDevice);
668}
669
670void QMacCGContext::initialize(QPaintDevice *paintDevice)
671{
672 // Find the underlying QImage of the paint device
673 switch (int deviceType = paintDevice->devType()) {
674 case QInternal::Pixmap: {
675 if (auto *platformPixmap = static_cast<QPixmap*>(paintDevice)->handle()) {
676 if (platformPixmap->classId() == QPlatformPixmap::RasterClass)
677 initialize(platformPixmap->buffer());
678 else
679 qWarning() << "QMacCGContext: Unsupported pixmap class" << platformPixmap->classId();
680 } else {
681 qWarning() << "QMacCGContext: Empty platformPixmap";
682 }
683 break;
684 }
685 case QInternal::Image:
686 initialize(static_cast<const QImage *>(paintDevice));
687 break;
688 case QInternal::Widget:
689 qWarning() << "QMacCGContext: not implemented: Widget class";
690 break;
691 default:
692 qWarning() << "QMacCGContext:: Unsupported paint device type" << deviceType;
693 }
694}
695
696QMacCGContext::QMacCGContext(QPainter *painter)
697{
698 QPaintEngine *paintEngine = painter->paintEngine();
699
700 // Handle the case of QMacPrintEngine, which has an internal QCoreGraphicsPaintEngine
701 while (QPaintEngine *aggregateEngine = QPaintEnginePrivate::get(paintEngine)->aggregateEngine())
702 paintEngine = aggregateEngine;
703
704 paintEngine->syncState();
705
706 if (Qt::HANDLE handle = QPaintEnginePrivate::get(paintEngine)->nativeHandle()) {
707 context = static_cast<CGContextRef>(handle);
708 return;
709 }
710
711 if (paintEngine->type() != QPaintEngine::Raster) {
712 qWarning() << "QMacCGContext:: Unsupported paint engine type" << paintEngine->type();
713 return;
714 }
715
716 // The raster paint engine always operates on a QImage
717 Q_ASSERT(paintEngine->paintDevice()->devType() == QInternal::Image);
718
719 // On behalf of one of these supported painter devices
720 switch (int painterDeviceType = painter->device()->devType()) {
721 case QInternal::Pixmap:
722 case QInternal::Image:
723 case QInternal::Widget:
724 break;
725 default:
726 qWarning() << "QMacCGContext:: Unsupported paint device type" << painterDeviceType;
727 return;
728 }
729
730 // Applying the clip is so entangled with the rest of the context setup
731 // that for simplicity we just pass in the painter.
732 initialize(static_cast<const QImage *>(paintEngine->paintDevice()), painter);
733}
734
735void QMacCGContext::initialize(const QImage *image, QPainter *painter)
736{
737 auto cgImageFormat = qt_mac_cgImageFormatForImage(*image);
738 if (!cgImageFormat) {
739 qWarning() << "QMacCGContext:: Could not get bitmap info for" << image;
740 return;
741 }
742
743 context = CGBitmapContextCreate((void *)image->bits(), image->width(), image->height(),
744 cgImageFormat->bitsPerComponent, image->bytesPerLine(), cgImageFormat->colorSpace,
745 cgImageFormat->bitmapInfo);
746
747 // Invert y axis
748 CGContextTranslateCTM(context, 0, image->height());
749 CGContextScaleCTM(context, 1, -1);
750
751 const qreal devicePixelRatio = image->devicePixelRatio();
752
753 if (painter && painter->device()->devType() == QInternal::Widget) {
754 // Set the clip rect which is an intersection of the system clip and the painter clip
755 QRegion clip = painter->paintEngine()->systemClip();
756 QTransform deviceTransform = painter->deviceTransform();
757
758 if (painter->hasClipping()) {
759 // To make matters more interesting the painter clip is in device-independent pixels,
760 // so we need to scale it to match the device-pixels of the system clip.
761 QRegion painterClip = painter->clipRegion();
762 qt_mac_scale_region(&painterClip, devicePixelRatio);
763
764 painterClip.translate(deviceTransform.dx(), deviceTransform.dy());
765
766 if (clip.isEmpty())
767 clip = painterClip;
768 else
769 clip &= painterClip;
770 }
771
772 qt_mac_clip_cg(context, clip, nullptr);
773
774 CGContextTranslateCTM(context, deviceTransform.dx(), deviceTransform.dy());
775 }
776
777 // Scale the context so that painting happens in device-independent pixels
778 CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio);
779}
780
781QT_END_NAMESPACE
\inmodule QtGui
Definition qbrush.h:29
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:30
\inmodule QtGui
Definition qimage.h:37
void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage)
QBrush qt_mac_toQBrush(CGColorRef color)
QImage::Format qt_mac_imageFormatForCGImage(CGImageRef image)
void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform)
QColor qt_mac_toQColor(CGColorRef color)
void qt_mac_scale_region(QRegion *region, qreal scaleFactor)
QImage qt_mac_toQImage(CGImageRef cgImage)
CGImageRef qt_mac_toCGImage(const QImage &inImage)
QImage qt_mac_padToSquareImage(const QImage &image)