/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins 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 "qpaintengine_mac_p.h" #if defined(QT_PRINTSUPPORT_LIB) #include "qprintengine_mac_p.h" #endif #include #include #include #include #include #if defined(QT_PRINTSUPPORT_LIB) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qcocoahelpers.h" #include QT_BEGIN_NAMESPACE /***************************************************************************** QCoreGraphicsPaintEngine utility functions *****************************************************************************/ void qt_mac_cgimage_data_free(void *, const void *memoryToFree, size_t) { free(const_cast(memoryToFree)); } CGImageRef qt_mac_create_imagemask(const QPixmap &pixmap, const QRectF &sr) { QImage image = pixmap.toImage(); if (image.format() != QImage::Format_ARGB32_Premultiplied) image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); const int sx = qRound(sr.x()), sy = qRound(sr.y()), sw = qRound(sr.width()), sh = qRound(sr.height()); const int sbpr = image.bytesPerLine(); const uint nbytes = sw * sh; // alpha is always 255 for bitmaps, ignore it in this case. const quint32 mask = pixmap.depth() == 1 ? 0x00ffffff : 0xffffffff; quint8 *dptr = static_cast(malloc(nbytes)); quint32 *sptr = reinterpret_cast(image.scanLine(0)), *srow; for (int y = sy, offset=0; y < sh; ++y) { srow = sptr + (y * (sbpr / 4)); for (int x = sx; x < sw; ++x) *(dptr+(offset++)) = (*(srow+x) & mask) ? 255 : 0; } QCFType provider = CGDataProviderCreateWithData(nullptr, dptr, nbytes, qt_mac_cgimage_data_free); return CGImageMaskCreate(sw, sh, 8, 8, nbytes / sh, provider, nullptr, false); } //conversion inline static float qt_mac_convert_color_to_cg(int c) { return ((float)c * 1000 / 255) / 1000; } CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &t) { return CGAffineTransformMake(t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy()); } inline static QCFType cgColorForQColor(const QColor &col) { CGFloat components[] = { qt_mac_convert_color_to_cg(col.red()), qt_mac_convert_color_to_cg(col.green()), qt_mac_convert_color_to_cg(col.blue()), qt_mac_convert_color_to_cg(col.alpha()) }; QCFType colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); return CGColorCreate(colorSpace, components); } // There's architectural problems with using native gradients // on the Mac at the moment, so disable them. // #define QT_MAC_USE_NATIVE_GRADIENTS #ifdef QT_MAC_USE_NATIVE_GRADIENTS static bool drawGradientNatively(const QGradient *gradient) { return gradient->spread() == QGradient::PadSpread; } // gradiant callback static void qt_mac_color_gradient_function(void *info, const CGFloat *in, CGFloat *out) { QBrush *brush = static_cast(info); Q_ASSERT(brush && brush->gradient()); const QGradientStops stops = brush->gradient()->stops(); const int n = stops.count(); Q_ASSERT(n >= 1); const QGradientStop *begin = stops.constBegin(); const QGradientStop *end = begin + n; qreal p = in[0]; const QGradientStop *i = begin; while (i != end && i->first < p) ++i; QRgb c; if (i == begin) { c = begin->second.rgba(); } else if (i == end) { c = (end - 1)->second.rgba(); } else { const QGradientStop &s1 = *(i - 1); const QGradientStop &s2 = *i; qreal p1 = s1.first; qreal p2 = s2.first; QRgb c1 = s1.second.rgba(); QRgb c2 = s2.second.rgba(); int idist = 256 * (p - p1) / (p2 - p1); int dist = 256 - idist; c = qRgba(INTERPOLATE_PIXEL_256(qRed(c1), dist, qRed(c2), idist), INTERPOLATE_PIXEL_256(qGreen(c1), dist, qGreen(c2), idist), INTERPOLATE_PIXEL_256(qBlue(c1), dist, qBlue(c2), idist), INTERPOLATE_PIXEL_256(qAlpha(c1), dist, qAlpha(c2), idist)); } out[0] = qt_mac_convert_color_to_cg(qRed(c)); out[1] = qt_mac_convert_color_to_cg(qGreen(c)); out[2] = qt_mac_convert_color_to_cg(qBlue(c)); out[3] = qt_mac_convert_color_to_cg(qAlpha(c)); } #endif //clipping handling void QCoreGraphicsPaintEnginePrivate::resetClip() { static bool inReset = false; if (inReset) return; inReset = true; CGAffineTransform old_xform = CGContextGetCTM(hd); //setup xforms CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform)); while (stackCount > 0) { restoreGraphicsState(); } saveGraphicsState(); inReset = false; //reset xforms CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); CGContextConcatCTM(hd, old_xform); } static CGRect qt_mac_compose_rect(const QRectF &r, float off=0) { return CGRectMake(r.x()+off, r.y()+off, r.width(), r.height()); } static CGMutablePathRef qt_mac_compose_path(const QPainterPath &p, float off=0) { CGMutablePathRef ret = CGPathCreateMutable(); QPointF startPt; for (int i=0; i 0 && p.elementAt(i - 1).x == startPt.x() && p.elementAt(i - 1).y == startPt.y()) CGPathCloseSubpath(ret); startPt = QPointF(elm.x, elm.y); CGPathMoveToPoint(ret, 0, elm.x+off, elm.y+off); break; case QPainterPath::LineToElement: CGPathAddLineToPoint(ret, 0, elm.x+off, elm.y+off); break; case QPainterPath::CurveToElement: Q_ASSERT(p.elementAt(i+1).type == QPainterPath::CurveToDataElement); Q_ASSERT(p.elementAt(i+2).type == QPainterPath::CurveToDataElement); CGPathAddCurveToPoint(ret, 0, elm.x+off, elm.y+off, p.elementAt(i+1).x+off, p.elementAt(i+1).y+off, p.elementAt(i+2).x+off, p.elementAt(i+2).y+off); i+=2; break; default: qFatal("QCoreGraphicsPaintEngine::drawPath(), unhandled type: %d", elm.type); break; } } if (!p.isEmpty() && p.elementAt(p.elementCount() - 1).x == startPt.x() && p.elementAt(p.elementCount() - 1).y == startPt.y()) CGPathCloseSubpath(ret); return ret; } //pattern handling (tiling) #if 1 # define QMACPATTERN_MASK_MULTIPLIER 32 #else # define QMACPATTERN_MASK_MULTIPLIER 1 #endif class QMacPattern { public: QMacPattern() : as_mask(false), pdev(0), image(0) { data.bytes = 0; } ~QMacPattern() { CGImageRelease(image); } int width() { if (image) return CGImageGetWidth(image); if (data.bytes) return 8*QMACPATTERN_MASK_MULTIPLIER; return data.pixmap.width(); } int height() { if (image) return CGImageGetHeight(image); if (data.bytes) return 8*QMACPATTERN_MASK_MULTIPLIER; return data.pixmap.height(); } //input QColor foreground; bool as_mask; struct { QPixmap pixmap; const uchar *bytes; } data; QPaintDevice *pdev; //output CGImageRef image; }; static void qt_mac_draw_pattern(void *info, CGContextRef c) { QMacPattern *pat = (QMacPattern*)info; int w = 0, h = 0; bool isBitmap = (pat->data.pixmap.depth() == 1); if (!pat->image) { //lazy cache if (pat->as_mask) { Q_ASSERT(pat->data.bytes); w = h = 8; #if (QMACPATTERN_MASK_MULTIPLIER == 1) CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, pat->data.bytes, w*h, nullptr); pat->image = CGImageMaskCreate(w, h, 1, 1, 1, provider, nullptr, false); CGDataProviderRelease(provider); #else const int numBytes = (w*h)/sizeof(uchar); uchar xor_bytes[numBytes]; for (int i = 0; i < numBytes; ++i) xor_bytes[i] = pat->data.bytes[i] ^ 0xFF; CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, xor_bytes, w*h, nullptr); CGImageRef swatch = CGImageMaskCreate(w, h, 1, 1, 1, provider, nullptr, false); CGDataProviderRelease(provider); const QColor c0(0, 0, 0, 0), c1(255, 255, 255, 255); QPixmap pm(w*QMACPATTERN_MASK_MULTIPLIER, h*QMACPATTERN_MASK_MULTIPLIER); pm.fill(c0); QMacCGContext pm_ctx(&pm); CGContextSetFillColorWithColor(c, cgColorForQColor(c1)); CGRect rect = CGRectMake(0, 0, w, h); for (int x = 0; x < QMACPATTERN_MASK_MULTIPLIER; ++x) { rect.origin.x = x * w; for (int y = 0; y < QMACPATTERN_MASK_MULTIPLIER; ++y) { rect.origin.y = y * h; qt_mac_drawCGImage(pm_ctx, &rect, swatch); } } pat->image = qt_mac_create_imagemask(pm, pm.rect()); CGImageRelease(swatch); w *= QMACPATTERN_MASK_MULTIPLIER; h *= QMACPATTERN_MASK_MULTIPLIER; #endif } else { w = pat->data.pixmap.width(); h = pat->data.pixmap.height(); if (isBitmap) pat->image = qt_mac_create_imagemask(pat->data.pixmap, pat->data.pixmap.rect()); else pat->image = qt_mac_toCGImage(pat->data.pixmap.toImage()); } } else { w = CGImageGetWidth(pat->image); h = CGImageGetHeight(pat->image); } //draw bool needRestore = false; if (CGImageIsMask(pat->image)) { CGContextSaveGState(c); CGContextSetFillColorWithColor(c, cgColorForQColor(pat->foreground)); } CGRect rect = CGRectMake(0, 0, w, h); qt_mac_drawCGImage(c, &rect, pat->image); if (needRestore) CGContextRestoreGState(c); } static void qt_mac_dispose_pattern(void *info) { QMacPattern *pat = (QMacPattern*)info; delete pat; } /***************************************************************************** QCoreGraphicsPaintEngine member functions *****************************************************************************/ inline static QPaintEngine::PaintEngineFeatures qt_mac_cg_features() { return QPaintEngine::PaintEngineFeatures(QPaintEngine::AllFeatures & ~QPaintEngine::PaintOutsidePaintEvent & ~QPaintEngine::PerspectiveTransform & ~QPaintEngine::ConicalGradientFill & ~QPaintEngine::LinearGradientFill & ~QPaintEngine::RadialGradientFill & ~QPaintEngine::BrushStroke); } QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine() : QPaintEngine(*(new QCoreGraphicsPaintEnginePrivate), qt_mac_cg_features()) { } QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr) : QPaintEngine(dptr, qt_mac_cg_features()) { } QCoreGraphicsPaintEngine::~QCoreGraphicsPaintEngine() { } bool QCoreGraphicsPaintEngine::begin(QPaintDevice *pdev) { Q_D(QCoreGraphicsPaintEngine); if (isActive()) { // already active painting qWarning("QCoreGraphicsPaintEngine::begin: Painter already active"); return false; } //initialization d->pdev = pdev; d->complexXForm = false; d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth; d->cosmeticPenSize = 1; d->current.clipEnabled = false; d->pixelSize = QPoint(1,1); QMacCGContext ctx(pdev); d->hd = CGContextRetain(ctx); if (d->hd) { d->saveGraphicsState(); d->orig_xform = CGContextGetCTM(d->hd); if (d->shading) { CGShadingRelease(d->shading); d->shading = nullptr; } d->setClip(nullptr); //clear the context's clipping } setActive(true); if (d->pdev->devType() == QInternal::Widget) { // device is a widget QWidget *w = (QWidget*)d->pdev; bool unclipped = w->testAttribute(Qt::WA_PaintUnclipped); if ((w->windowType() == Qt::Desktop)) { if (!unclipped) qWarning("QCoreGraphicsPaintEngine::begin: Does not support clipped desktop on OS X"); // ## need to do [qt_mac_window_for(w) makeKeyAndOrderFront]; (need to rename the file) } else if (unclipped) { qWarning("QCoreGraphicsPaintEngine::begin: Does not support unclipped painting"); } } else if (d->pdev->devType() == QInternal::Pixmap) { // device is a pixmap QPixmap *pm = (QPixmap*)d->pdev; if (pm->isNull()) { qWarning("QCoreGraphicsPaintEngine::begin: Cannot paint null pixmap"); end(); return false; } } setDirty(QPaintEngine::DirtyPen); setDirty(QPaintEngine::DirtyBrush); setDirty(QPaintEngine::DirtyBackground); setDirty(QPaintEngine::DirtyHints); return true; } bool QCoreGraphicsPaintEngine::end() { Q_D(QCoreGraphicsPaintEngine); setActive(false); if (d->pdev->devType() == QInternal::Widget && static_cast(d->pdev)->windowType() == Qt::Desktop) { // ### need to do [qt_mac_window_for(static_cast(d->pdev)) orderOut]; (need to rename) } if (d->shading) { CGShadingRelease(d->shading); d->shading = 0; } d->pdev = nullptr; if (d->hd) { d->restoreGraphicsState(); CGContextSynchronize(d->hd); CGContextRelease(d->hd); d->hd = nullptr; } return true; } void QCoreGraphicsPaintEngine::updateState(const QPaintEngineState &state) { Q_D(QCoreGraphicsPaintEngine); QPaintEngine::DirtyFlags flags = state.state(); if (flags & DirtyTransform) updateMatrix(state.transform()); if (flags & DirtyClipEnabled) { if (state.isClipEnabled()) updateClipPath(painter()->clipPath(), Qt::ReplaceClip); else updateClipPath(QPainterPath(), Qt::NoClip); } if (flags & DirtyClipPath) { updateClipPath(state.clipPath(), state.clipOperation()); } else if (flags & DirtyClipRegion) { updateClipRegion(state.clipRegion(), state.clipOperation()); } // If the clip has changed we need to update all other states // too, since they are included in the system context on OSX, // and changing the clip resets that context back to scratch. if (flags & (DirtyClipPath | DirtyClipRegion | DirtyClipEnabled)) flags |= AllDirty; if (flags & DirtyPen) updatePen(state.pen()); if (flags & (DirtyBrush|DirtyBrushOrigin)) updateBrush(state.brush(), state.brushOrigin()); if (flags & DirtyFont) updateFont(state.font()); if (flags & DirtyOpacity) updateOpacity(state.opacity()); if (flags & DirtyHints) updateRenderHints(state.renderHints()); if (flags & DirtyCompositionMode) updateCompositionMode(state.compositionMode()); if (flags & (DirtyPen | DirtyTransform | DirtyHints)) { if (!qt_pen_is_cosmetic(d->current.pen, state.renderHints())) { d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticNone; } else if (d->current.transform.m11() < d->current.transform.m22()-1.0 || d->current.transform.m11() > d->current.transform.m22()+1.0) { d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath; d->cosmeticPenSize = d->adjustPenWidth(d->current.pen.widthF()); if (!d->cosmeticPenSize) d->cosmeticPenSize = 1.0; } else { d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth; static const float sqrt2 = std::sqrt(2.0f); qreal width = d->current.pen.widthF(); if (!width) width = 1; d->cosmeticPenSize = std::sqrt(std::pow(d->pixelSize.y(), 2) + std::pow(d->pixelSize.x(), 2)) / sqrt2 * width; } } } void QCoreGraphicsPaintEngine::updatePen(const QPen &pen) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); d->current.pen = pen; d->setStrokePen(pen); } void QCoreGraphicsPaintEngine::updateBrush(const QBrush &brush, const QPointF &brushOrigin) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); d->current.brush = brush; #ifdef QT_MAC_USE_NATIVE_GRADIENTS // Quartz supports only pad spread if (const QGradient *gradient = brush.gradient()) { if (drawGradientNatively(gradient)) { gccaps |= QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill; } else { gccaps &= ~(QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill); } } #endif if (d->shading) { CGShadingRelease(d->shading); d->shading = nullptr; } d->setFillBrush(brushOrigin); } void QCoreGraphicsPaintEngine::updateOpacity(qreal opacity) { Q_D(QCoreGraphicsPaintEngine); CGContextSetAlpha(d->hd, opacity); } void QCoreGraphicsPaintEngine::updateFont(const QFont &) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); updatePen(d->current.pen); } void QCoreGraphicsPaintEngine::updateMatrix(const QTransform &transform) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (qt_is_nan(transform.m11()) || qt_is_nan(transform.m12()) || qt_is_nan(transform.m13()) || qt_is_nan(transform.m21()) || qt_is_nan(transform.m22()) || qt_is_nan(transform.m23()) || qt_is_nan(transform.m31()) || qt_is_nan(transform.m32()) || qt_is_nan(transform.m33())) return; d->current.transform = transform; d->setTransform(transform.isIdentity() ? 0 : &transform); d->complexXForm = (transform.m11() != 1 || transform.m22() != 1 || transform.m12() != 0 || transform.m21() != 0); d->pixelSize = d->devicePixelSize(d->hd); } void QCoreGraphicsPaintEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (op == Qt::NoClip) { if (d->current.clipEnabled) { d->current.clipEnabled = false; d->current.clip = QRegion(); d->setClip(nullptr); } } else { if (!d->current.clipEnabled) op = Qt::ReplaceClip; d->current.clipEnabled = true; QRegion clipRegion(p.toFillPolygon().toPolygon(), p.fillRule()); if (op == Qt::ReplaceClip) { d->current.clip = clipRegion; d->setClip(nullptr); if (p.isEmpty()) { CGRect rect = CGRectMake(0, 0, 0, 0); CGContextClipToRect(d->hd, rect); } else { CGMutablePathRef path = qt_mac_compose_path(p); CGContextBeginPath(d->hd); CGContextAddPath(d->hd, path); if (p.fillRule() == Qt::WindingFill) CGContextClip(d->hd); else CGContextEOClip(d->hd); CGPathRelease(path); } } else if (op == Qt::IntersectClip) { d->current.clip = d->current.clip.intersected(clipRegion); d->setClip(&d->current.clip); } } } void QCoreGraphicsPaintEngine::updateClipRegion(const QRegion &clipRegion, Qt::ClipOperation op) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (op == Qt::NoClip) { d->current.clipEnabled = false; d->current.clip = QRegion(); d->setClip(nullptr); } else { if (!d->current.clipEnabled) op = Qt::ReplaceClip; d->current.clipEnabled = true; if (op == Qt::IntersectClip) d->current.clip = d->current.clip.intersected(clipRegion); else if (op == Qt::ReplaceClip) d->current.clip = clipRegion; d->setClip(&d->current.clip); } } void QCoreGraphicsPaintEngine::drawPath(const QPainterPath &p) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; CGMutablePathRef path = qt_mac_compose_path(p); uchar ops = QCoreGraphicsPaintEnginePrivate::CGStroke; if (p.fillRule() == Qt::WindingFill) ops |= QCoreGraphicsPaintEnginePrivate::CGFill; else ops |= QCoreGraphicsPaintEnginePrivate::CGEOFill; CGContextBeginPath(d->hd); d->drawPath(ops, path); CGPathRelease(path); } void QCoreGraphicsPaintEngine::drawRects(const QRectF *rects, int rectCount) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; for (int i=0; idrawPath(QCoreGraphicsPaintEnginePrivate::CGFill|QCoreGraphicsPaintEnginePrivate::CGStroke, path); CGPathRelease(path); } } void QCoreGraphicsPaintEngine::drawPoints(const QPointF *points, int pointCount) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; if (d->current.pen.capStyle() == Qt::FlatCap) CGContextSetLineCap(d->hd, kCGLineCapSquare); CGMutablePathRef path = CGPathCreateMutable(); for (int i=0; i < pointCount; i++) { float x = points[i].x(), y = points[i].y(); CGPathMoveToPoint(path, nullptr, x, y); CGPathAddLineToPoint(path, nullptr, x+0.001, y); } bool doRestore = false; if (d->cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticNone && !(state->renderHints() & QPainter::Antialiasing)) { //we don't want adjusted pens for point rendering doRestore = true; d->saveGraphicsState(); CGContextSetLineWidth(d->hd, d->current.pen.widthF()); } d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); if (doRestore) d->restoreGraphicsState(); CGPathRelease(path); if (d->current.pen.capStyle() == Qt::FlatCap) CGContextSetLineCap(d->hd, kCGLineCapButt); } void QCoreGraphicsPaintEngine::drawEllipse(const QRectF &r) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; CGMutablePathRef path = CGPathCreateMutable(); CGAffineTransform transform = CGAffineTransformMakeScale(r.width() / r.height(), 1); CGPathAddArc(path, &transform,(r.x() + (r.width() / 2)) / (r.width() / r.height()), r.y() + (r.height() / 2), r.height() / 2, 0, (2 * M_PI), false); d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill | QCoreGraphicsPaintEnginePrivate::CGStroke, path); CGPathRelease(path); } void QCoreGraphicsPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, nullptr, points[0].x(), points[0].y()); for (int x = 1; x < pointCount; ++x) CGPathAddLineToPoint(path, nullptr, points[x].x(), points[x].y()); if (mode != PolylineMode && points[0] != points[pointCount-1]) CGPathAddLineToPoint(path, nullptr, points[0].x(), points[0].y()); uint op = QCoreGraphicsPaintEnginePrivate::CGStroke; if (mode != PolylineMode) op |= mode == OddEvenMode ? QCoreGraphicsPaintEnginePrivate::CGEOFill : QCoreGraphicsPaintEnginePrivate::CGFill; d->drawPath(op, path); CGPathRelease(path); } void QCoreGraphicsPaintEngine::drawLines(const QLineF *lines, int lineCount) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; CGMutablePathRef path = CGPathCreateMutable(); for (int i = 0; i < lineCount; i++) { const QPointF start = lines[i].p1(), end = lines[i].p2(); CGPathMoveToPoint(path, nullptr, start.x(), start.y()); CGPathAddLineToPoint(path, nullptr, end.x(), end.y()); } d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); CGPathRelease(path); } void QCoreGraphicsPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; if (pm.isNull()) return; bool differentSize = (QRectF(0, 0, pm.width(), pm.height()) != sr), doRestore = false; CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); QCFType image; bool isBitmap = (pm.depth() == 1); if (isBitmap) { doRestore = true; d->saveGraphicsState(); const QColor &col = d->current.pen.color(); CGContextSetFillColorWithColor(d->hd, cgColorForQColor(col)); image = qt_mac_create_imagemask(pm, sr); } else if (differentSize) { QCFType img = qt_mac_toCGImage(pm.toImage()); if (img) image = CGImageCreateWithImageInRect(img, CGRectMake(qRound(sr.x()), qRound(sr.y()), qRound(sr.width()), qRound(sr.height()))); } else { image = qt_mac_toCGImage(pm.toImage()); } qt_mac_drawCGImage(d->hd, &rect, image); if (doRestore) d->restoreGraphicsState(); } void QCoreGraphicsPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, Qt::ImageConversionFlags flags) { Q_D(QCoreGraphicsPaintEngine); Q_UNUSED(flags); Q_ASSERT(isActive()); if (img.isNull() || state->compositionMode() == QPainter::CompositionMode_Destination) return; QCFType cgimage = qt_mac_toCGImage(img); CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); if (QRectF(0, 0, img.width(), img.height()) != sr) cgimage = CGImageCreateWithImageInRect(cgimage, CGRectMake(sr.x(), sr.y(), sr.width(), sr.height())); qt_mac_drawCGImage(d->hd, &rect, cgimage); } void QCoreGraphicsPaintEngine::initialize() { } void QCoreGraphicsPaintEngine::cleanup() { } CGContextRef QCoreGraphicsPaintEngine::handle() const { return d_func()->hd; } void QCoreGraphicsPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p) { Q_D(QCoreGraphicsPaintEngine); Q_ASSERT(isActive()); if (state->compositionMode() == QPainter::CompositionMode_Destination) return; //save the old state d->saveGraphicsState(); //setup the pattern QMacPattern *qpattern = new QMacPattern; qpattern->data.pixmap = pixmap; qpattern->foreground = d->current.pen.color(); qpattern->pdev = d->pdev; CGPatternCallbacks callbks; callbks.version = 0; callbks.drawPattern = qt_mac_draw_pattern; callbks.releaseInfo = qt_mac_dispose_pattern; const int width = qpattern->width(), height = qpattern->height(); CGAffineTransform trans = CGContextGetCTM(d->hd); CGPatternRef pat = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), trans, width, height, kCGPatternTilingNoDistortion, true, &callbks); CGColorSpaceRef cs = CGColorSpaceCreatePattern(nullptr); CGContextSetFillColorSpace(d->hd, cs); CGFloat component = 1.0; //just one CGContextSetFillPattern(d->hd, pat, &component); CGSize phase = CGSizeApplyAffineTransform(CGSizeMake(-(p.x()-r.x()), -(p.y()-r.y())), trans); CGContextSetPatternPhase(d->hd, phase); //fill the rectangle CGRect mac_rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); CGContextFillRect(d->hd, mac_rect); //restore the state d->restoreGraphicsState(); //cleanup CGColorSpaceRelease(cs); CGPatternRelease(pat); } void QCoreGraphicsPaintEngine::drawTextItem(const QPointF &pos, const QTextItem &item) { Q_D(QCoreGraphicsPaintEngine); if (d->current.transform.type() == QTransform::TxProject #ifndef QMAC_NATIVE_GRADIENTS || painter()->pen().brush().gradient() //Just let the base engine "emulate" the gradient #endif ) { QPaintEngine::drawTextItem(pos, item); return; } if (state->compositionMode() == QPainter::CompositionMode_Destination) return; const QTextItemInt &ti = static_cast(item); QPen oldPen = painter()->pen(); QBrush oldBrush = painter()->brush(); QPointF oldBrushOrigin = painter()->brushOrigin(); updatePen(Qt::NoPen); updateBrush(oldPen.brush(), QPointF(0, 0)); Q_ASSERT(type() == QPaintEngine::CoreGraphics); QFontEngine *fe = ti.fontEngine; const bool textAA = ((state->renderHints() & QPainter::TextAntialiasing) && !(fe->fontDef.styleStrategy & QFont::NoAntialias)); const bool lineAA = state->renderHints() & QPainter::Antialiasing; if (textAA != lineAA) CGContextSetShouldAntialias(d->hd, textAA); const bool smoothing = textAA && !(fe->fontDef.styleStrategy & QFont::NoSubpixelAntialias); if (d->disabledSmoothFonts == smoothing) CGContextSetShouldSmoothFonts(d->hd, smoothing); if (ti.glyphs.numGlyphs) { switch (fe->type()) { case QFontEngine::Mac: static_cast(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height()); break; case QFontEngine::Box: d->drawBoxTextItem(pos, ti); break; default: break; } } if (textAA != lineAA) CGContextSetShouldAntialias(d->hd, !textAA); if (smoothing == d->disabledSmoothFonts) CGContextSetShouldSmoothFonts(d->hd, !d->disabledSmoothFonts); updatePen(oldPen); updateBrush(oldBrush, oldBrushOrigin); } QPainter::RenderHints QCoreGraphicsPaintEngine::supportedRenderHints() const { return QPainter::RenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); } enum CGCompositeMode { kCGCompositeModeClear = 0, kCGCompositeModeCopy = 1, kCGCompositeModeSourceOver = 2, kCGCompositeModeSourceIn = 3, kCGCompositeModeSourceOut = 4, kCGCompositeModeSourceAtop = 5, kCGCompositeModeDestinationOver = 6, kCGCompositeModeDestinationIn = 7, kCGCompositeModeDestinationOut = 8, kCGCompositeModeDestinationAtop = 9, kCGCompositeModeXOR = 10, kCGCompositeModePlusDarker = 11, // (max (0, (1-d) + (1-s))) kCGCompositeModePlusLighter = 12, // (min (1, s + d)) }; extern "C" { extern void CGContextSetCompositeOperation(CGContextRef, int); } // private function, but is in all versions of OS X. void QCoreGraphicsPaintEngine::updateCompositionMode(QPainter::CompositionMode mode) { int cg_mode = kCGBlendModeNormal; switch (mode) { case QPainter::CompositionMode_Multiply: cg_mode = kCGBlendModeMultiply; break; case QPainter::CompositionMode_Screen: cg_mode = kCGBlendModeScreen; break; case QPainter::CompositionMode_Overlay: cg_mode = kCGBlendModeOverlay; break; case QPainter::CompositionMode_Darken: cg_mode = kCGBlendModeDarken; break; case QPainter::CompositionMode_Lighten: cg_mode = kCGBlendModeLighten; break; case QPainter::CompositionMode_ColorDodge: cg_mode = kCGBlendModeColorDodge; break; case QPainter::CompositionMode_ColorBurn: cg_mode = kCGBlendModeColorBurn; break; case QPainter::CompositionMode_HardLight: cg_mode = kCGBlendModeHardLight; break; case QPainter::CompositionMode_SoftLight: cg_mode = kCGBlendModeSoftLight; break; case QPainter::CompositionMode_Difference: cg_mode = kCGBlendModeDifference; break; case QPainter::CompositionMode_Exclusion: cg_mode = kCGBlendModeExclusion; break; case QPainter::CompositionMode_Plus: cg_mode = kCGBlendModePlusLighter; break; case QPainter::CompositionMode_SourceOver: cg_mode = kCGBlendModeNormal; break; case QPainter::CompositionMode_DestinationOver: cg_mode = kCGBlendModeDestinationOver; break; case QPainter::CompositionMode_Clear: cg_mode = kCGBlendModeClear; break; case QPainter::CompositionMode_Source: cg_mode = kCGBlendModeCopy; break; case QPainter::CompositionMode_Destination: cg_mode = -1; break; case QPainter::CompositionMode_SourceIn: cg_mode = kCGBlendModeSourceIn; break; case QPainter::CompositionMode_DestinationIn: cg_mode = kCGCompositeModeDestinationIn; break; case QPainter::CompositionMode_SourceOut: cg_mode = kCGBlendModeSourceOut; break; case QPainter::CompositionMode_DestinationOut: cg_mode = kCGBlendModeDestinationOver; break; case QPainter::CompositionMode_SourceAtop: cg_mode = kCGBlendModeSourceAtop; break; case QPainter::CompositionMode_DestinationAtop: cg_mode = kCGBlendModeDestinationAtop; break; case QPainter::CompositionMode_Xor: cg_mode = kCGBlendModeXOR; break; default: break; } if (cg_mode > -1) { CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode)); } } void QCoreGraphicsPaintEngine::updateRenderHints(QPainter::RenderHints hints) { Q_D(QCoreGraphicsPaintEngine); CGContextSetShouldAntialias(d->hd, hints & QPainter::Antialiasing); CGContextSetInterpolationQuality(d->hd, (hints & QPainter::SmoothPixmapTransform) ? kCGInterpolationHigh : kCGInterpolationNone); bool textAntialiasing = (hints & QPainter::TextAntialiasing) == QPainter::TextAntialiasing; if (!textAntialiasing || d->disabledSmoothFonts) { d->disabledSmoothFonts = !textAntialiasing; CGContextSetShouldSmoothFonts(d->hd, textAntialiasing); } } /* Returns the size of one device pixel in user-space coordinates. */ QPointF QCoreGraphicsPaintEnginePrivate::devicePixelSize(CGContextRef) { QPointF p1 = current.transform.inverted().map(QPointF(0, 0)); QPointF p2 = current.transform.inverted().map(QPointF(1, 1)); return QPointF(qAbs(p2.x() - p1.x()), qAbs(p2.y() - p1.y())); } /* Adjusts the pen width so we get correct line widths in the non-transformed, aliased case. */ float QCoreGraphicsPaintEnginePrivate::adjustPenWidth(float penWidth) { Q_Q(QCoreGraphicsPaintEngine); float ret = penWidth; if (!complexXForm && !(q->state->renderHints() & QPainter::Antialiasing)) { if (penWidth < 2) ret = 1; else if (penWidth < 3) ret = 1.5; else ret = penWidth -1; } return ret; } void QCoreGraphicsPaintEnginePrivate::setStrokePen(const QPen &pen) { //pencap CGLineCap cglinecap = kCGLineCapButt; if (pen.capStyle() == Qt::SquareCap) cglinecap = kCGLineCapSquare; else if (pen.capStyle() == Qt::RoundCap) cglinecap = kCGLineCapRound; CGContextSetLineCap(hd, cglinecap); CGContextSetLineWidth(hd, adjustPenWidth(pen.widthF())); //join CGLineJoin cglinejoin = kCGLineJoinMiter; if (pen.joinStyle() == Qt::BevelJoin) cglinejoin = kCGLineJoinBevel; else if (pen.joinStyle() == Qt::RoundJoin) cglinejoin = kCGLineJoinRound; CGContextSetLineJoin(hd, cglinejoin); // CGContextSetMiterLimit(hd, pen.miterLimit()); //pen style QVector linedashes; if (pen.style() == Qt::CustomDashLine) { QVector customs = pen.dashPattern(); for (int i = 0; i < customs.size(); ++i) linedashes.append(customs.at(i)); } else if (pen.style() == Qt::DashLine) { linedashes.append(4); linedashes.append(2); } else if (pen.style() == Qt::DotLine) { linedashes.append(1); linedashes.append(2); } else if (pen.style() == Qt::DashDotLine) { linedashes.append(4); linedashes.append(2); linedashes.append(1); linedashes.append(2); } else if (pen.style() == Qt::DashDotDotLine) { linedashes.append(4); linedashes.append(2); linedashes.append(1); linedashes.append(2); linedashes.append(1); linedashes.append(2); } const CGFloat cglinewidth = pen.widthF() <= 0.0f ? 1.0f : float(pen.widthF()); for (int i = 0; i < linedashes.size(); ++i) { linedashes[i] *= cglinewidth; if (cglinewidth < 3 && (cglinecap == kCGLineCapSquare || cglinecap == kCGLineCapRound)) { if ((i%2)) linedashes[i] += cglinewidth/2; else linedashes[i] -= cglinewidth/2; } } CGContextSetLineDash(hd, pen.dashOffset() * cglinewidth, linedashes.data(), linedashes.size()); // color CGContextSetStrokeColorWithColor(hd, cgColorForQColor(pen.color())); } // Add our own patterns here to deal with the fact that the coordinate system // is flipped vertically with Quartz2D. static const uchar *qt_mac_patternForBrush(int brushStyle) { Q_ASSERT(brushStyle > Qt::SolidPattern && brushStyle < Qt::LinearGradientPattern); static const uchar dense1_pat[] = { 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00 }; static const uchar dense2_pat[] = { 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88 }; static const uchar dense3_pat[] = { 0x11, 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x44, 0xaa }; static const uchar dense4_pat[] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }; static const uchar dense5_pat[] = { 0xee, 0x55, 0xbb, 0x55, 0xee, 0x55, 0xbb, 0x55 }; static const uchar dense6_pat[] = { 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77 }; static const uchar dense7_pat[] = { 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, 0xff }; static const uchar hor_pat[] = { 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff }; static const uchar ver_pat[] = { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }; static const uchar cross_pat[] = { 0xef, 0xef, 0xef, 0xef, 0x00, 0xef, 0xef, 0xef }; static const uchar fdiag_pat[] = { 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe }; static const uchar bdiag_pat[] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f }; static const uchar dcross_pat[] = { 0x7e, 0xbd, 0xdb, 0xe7, 0xe7, 0xdb, 0xbd, 0x7e }; static const uchar *const pat_tbl[] = { dense1_pat, dense2_pat, dense3_pat, dense4_pat, dense5_pat, dense6_pat, dense7_pat, hor_pat, ver_pat, cross_pat, bdiag_pat, fdiag_pat, dcross_pat }; return pat_tbl[brushStyle - Qt::Dense1Pattern]; } void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) { // pattern Qt::BrushStyle bs = current.brush.style(); #ifdef QT_MAC_USE_NATIVE_GRADIENTS if (bs == Qt::LinearGradientPattern || bs == Qt::RadialGradientPattern) { const QGradient *grad = static_cast(current.brush.gradient()); if (drawGradientNatively(grad)) { Q_ASSERT(grad->spread() == QGradient::PadSpread); static const CGFloat domain[] = { 0.0f, +1.0f }; static const CGFunctionCallbacks callbacks = { 0, qt_mac_color_gradient_function, nullptr }; CGFunctionRef fill_func = CGFunctionCreate(reinterpret_cast(¤t.brush), 1, domain, 4, nullptr, &callbacks); CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB) if (bs == Qt::LinearGradientPattern) { const QLinearGradient *linearGrad = static_cast(grad); const QPointF start(linearGrad->start()); const QPointF stop(linearGrad->finalStop()); shading = CGShadingCreateAxial(colorspace, CGPointMake(start.x(), start.y()), CGPointMake(stop.x(), stop.y()), fill_func, true, true); } else { Q_ASSERT(bs == Qt::RadialGradientPattern); const QRadialGradient *radialGrad = static_cast(grad); QPointF center(radialGrad->center()); QPointF focal(radialGrad->focalPoint()); qreal radius = radialGrad->radius(); qreal focalRadius = radialGrad->focalRadius(); shading = CGShadingCreateRadial(colorspace, CGPointMake(focal.x(), focal.y()), focalRadius, CGPointMake(center.x(), center.y()), radius, fill_func, false, true); } CGFunctionRelease(fill_func); } } else #endif if (bs != Qt::SolidPattern && bs != Qt::NoBrush #ifndef QT_MAC_USE_NATIVE_GRADIENTS && (bs < Qt::LinearGradientPattern || bs > Qt::ConicalGradientPattern) #endif ) { QMacPattern *qpattern = new QMacPattern; qpattern->pdev = pdev; CGFloat components[4] = { 1.0, 1.0, 1.0, 1.0 }; CGColorSpaceRef base_colorspace = nullptr; if (bs == Qt::TexturePattern) { qpattern->data.pixmap = current.brush.texture(); if (qpattern->data.pixmap.isQBitmap()) { const QColor &col = current.brush.color(); components[0] = qt_mac_convert_color_to_cg(col.red()); components[1] = qt_mac_convert_color_to_cg(col.green()); components[2] = qt_mac_convert_color_to_cg(col.blue()); base_colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); } } else { qpattern->as_mask = true; qpattern->data.bytes = qt_mac_patternForBrush(bs); const QColor &col = current.brush.color(); components[0] = qt_mac_convert_color_to_cg(col.red()); components[1] = qt_mac_convert_color_to_cg(col.green()); components[2] = qt_mac_convert_color_to_cg(col.blue()); base_colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); } int width = qpattern->width(), height = qpattern->height(); qpattern->foreground = current.brush.color(); CGColorSpaceRef fill_colorspace = CGColorSpaceCreatePattern(base_colorspace); CGContextSetFillColorSpace(hd, fill_colorspace); CGAffineTransform xform = CGContextGetCTM(hd); xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(current.brush.transform()), xform); xform = CGAffineTransformTranslate(xform, offset.x(), offset.y()); CGPatternCallbacks callbks; callbks.version = 0; callbks.drawPattern = qt_mac_draw_pattern; callbks.releaseInfo = qt_mac_dispose_pattern; CGPatternRef fill_pattern = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), xform, width, height, kCGPatternTilingNoDistortion, !base_colorspace, &callbks); CGContextSetFillPattern(hd, fill_pattern, components); CGPatternRelease(fill_pattern); CGColorSpaceRelease(base_colorspace); CGColorSpaceRelease(fill_colorspace); } else if (bs != Qt::NoBrush) { CGContextSetFillColorWithColor(hd, cgColorForQColor(current.brush.color())); } } void QCoreGraphicsPaintEnginePrivate::setClip(const QRegion *rgn) { Q_Q(QCoreGraphicsPaintEngine); if (hd) { resetClip(); QRegion sysClip = q->systemClip(); if (!sysClip.isEmpty()) qt_mac_clip_cg(hd, sysClip, &orig_xform); if (rgn) qt_mac_clip_cg(hd, *rgn, nullptr); } } struct qt_mac_cg_transform_path { CGMutablePathRef path; CGAffineTransform transform; }; void qt_mac_cg_transform_path_apply(void *info, const CGPathElement *element) { Q_ASSERT(info && element); qt_mac_cg_transform_path *t = (qt_mac_cg_transform_path*)info; switch (element->type) { case kCGPathElementMoveToPoint: CGPathMoveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y); break; case kCGPathElementAddLineToPoint: CGPathAddLineToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y); break; case kCGPathElementAddQuadCurveToPoint: CGPathAddQuadCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y, element->points[1].x, element->points[1].y); break; case kCGPathElementAddCurveToPoint: CGPathAddCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y, element->points[1].x, element->points[1].y, element->points[2].x, element->points[2].y); break; case kCGPathElementCloseSubpath: CGPathCloseSubpath(t->path); break; default: qDebug() << "Unhandled path transform type: " << element->type; } } void QCoreGraphicsPaintEnginePrivate::drawPath(uchar ops, CGMutablePathRef path) { Q_Q(QCoreGraphicsPaintEngine); Q_ASSERT((ops & (CGFill | CGEOFill)) != (CGFill | CGEOFill)); //can't really happen if ((ops & (CGFill | CGEOFill))) { if (shading) { Q_ASSERT(path); CGContextBeginPath(hd); CGContextAddPath(hd, path); saveGraphicsState(); if (ops & CGFill) CGContextClip(hd); else if (ops & CGEOFill) CGContextEOClip(hd); if (current.brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode) { CGRect boundingBox = CGPathGetBoundingBox(path); CGContextConcatCTM(hd, CGAffineTransformMake(boundingBox.size.width, 0, 0, boundingBox.size.height, boundingBox.origin.x, boundingBox.origin.y)); } CGContextDrawShading(hd, shading); restoreGraphicsState(); ops &= ~CGFill; ops &= ~CGEOFill; } else if (current.brush.style() == Qt::NoBrush) { ops &= ~CGFill; ops &= ~CGEOFill; } } if ((ops & CGStroke) && current.pen.style() == Qt::NoPen) ops &= ~CGStroke; if (ops & (CGEOFill | CGFill)) { CGContextBeginPath(hd); CGContextAddPath(hd, path); if (ops & CGEOFill) { CGContextEOFillPath(hd); } else { CGContextFillPath(hd); } } // Avoid saving and restoring the context if we can. const bool needContextSave = (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone || !(q->state->renderHints() & QPainter::Antialiasing)); if (ops & CGStroke) { if (needContextSave) saveGraphicsState(); CGContextBeginPath(hd); // Translate a fraction of a pixel size in the y direction // to make sure that primitives painted at pixel borders // fills the right pixel. This is needed since the y xais // in the Quartz coordinate system is inverted compared to Qt. if (!(q->state->renderHints() & QPainter::Antialiasing)) { if (current.pen.style() == Qt::SolidLine || current.pen.width() >= 3) CGContextTranslateCTM(hd, double(pixelSize.x()) * 0.25, double(pixelSize.y()) * 0.25); else CGContextTranslateCTM(hd, 0, double(pixelSize.y()) * 0.1); } if (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone) { // If antialiazing is enabled, use the cosmetic pen size directly. if (q->state->renderHints() & QPainter::Antialiasing) CGContextSetLineWidth(hd, cosmeticPenSize); else if (current.pen.widthF() <= 1) CGContextSetLineWidth(hd, cosmeticPenSize * 0.9f); else CGContextSetLineWidth(hd, cosmeticPenSize); } if (cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath) { qt_mac_cg_transform_path t; t.transform = qt_mac_convert_transform_to_cg(current.transform); t.path = CGPathCreateMutable(); CGPathApply(path, &t, qt_mac_cg_transform_path_apply); //transform the path setTransform(nullptr); //unset the context transform CGContextSetLineWidth(hd, cosmeticPenSize); CGContextAddPath(hd, t.path); CGPathRelease(t.path); } else { CGContextAddPath(hd, path); } CGContextStrokePath(hd); if (needContextSave) restoreGraphicsState(); } } QT_END_NAMESPACE