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