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