| /**************************************************************************** |
| ** |
| ** 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 <qbitmap.h> |
| #include <qpaintdevice.h> |
| #include <qpainterpath.h> |
| #include <qpixmapcache.h> |
| #include <private/qpaintengine_raster_p.h> |
| #if defined(QT_PRINTSUPPORT_LIB) |
| #include <qprinter.h> |
| #endif |
| #include <qstack.h> |
| #include <qwidget.h> |
| #include <qvarlengtharray.h> |
| #include <qdebug.h> |
| #include <qcoreapplication.h> |
| #include <qmath.h> |
| |
| #include <qpa/qplatformpixmap.h> |
| |
| #include <private/qfont_p.h> |
| #include <private/qfontengine_p.h> |
| #include <private/qfontengine_coretext_p.h> |
| #include <private/qnumeric_p.h> |
| #include <private/qpainter_p.h> |
| #include <private/qpainterpath_p.h> |
| #include <private/qtextengine_p.h> |
| #include <private/qcoregraphics_p.h> |
| |
| #include "qcocoahelpers.h" |
| |
| #include <string.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /***************************************************************************** |
| QCoreGraphicsPaintEngine utility functions |
| *****************************************************************************/ |
| |
| void qt_mac_cgimage_data_free(void *, const void *memoryToFree, size_t) |
| { |
| free(const_cast<void *>(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<quint8 *>(malloc(nbytes)); |
| quint32 *sptr = reinterpret_cast<quint32 *>(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<CGDataProviderRef> 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<CGColorRef> 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<CGColorSpaceRef> 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<QBrush *>(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<p.elementCount(); ++i) { |
| const QPainterPath::Element &elm = p.elementAt(i); |
| switch (elm.type) { |
| case QPainterPath::MoveToElement: |
| if (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<QWidget*>(d->pdev)->windowType() == Qt::Desktop) { |
| // ### need to do [qt_mac_window_for(static_cast<QWidget *>(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; i<rectCount; ++i) { |
| QRectF r = rects[i]; |
| |
| CGMutablePathRef path = CGPathCreateMutable(); |
| CGPathAddRect(path, nullptr, qt_mac_compose_rect(r)); |
| d->drawPath(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<CGImageRef> 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<CGImageRef> 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<CGImageRef> 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<const QTextItemInt &>(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<QCoreTextFontEngine *>(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<CGFloat> linedashes; |
| if (pen.style() == Qt::CustomDashLine) { |
| QVector<qreal> 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<const QGradient*>(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<void *>(¤t.brush), |
| 1, domain, 4, nullptr, &callbacks); |
| |
| CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB) |
| if (bs == Qt::LinearGradientPattern) { |
| const QLinearGradient *linearGrad = static_cast<const QLinearGradient *>(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<const QRadialGradient *>(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 |