| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtGui module 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 <qtprintsupportglobal.h> |
| |
| #ifndef QT_NO_PRINTER |
| #include <qdebug.h> |
| #include "private/qpaintengine_alpha_p.h" |
| |
| #include "private/qpainter_p.h" |
| #include "private/qpicture_p.h" |
| #include "private/qfont_p.h" |
| #include "QtGui/qpicture.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| QAlphaPaintEngine::QAlphaPaintEngine(QAlphaPaintEnginePrivate &data, PaintEngineFeatures devcaps) |
| : QPaintEngine(data, devcaps) |
| { |
| |
| } |
| |
| QAlphaPaintEngine::~QAlphaPaintEngine() |
| { |
| |
| } |
| |
| bool QAlphaPaintEngine::begin(QPaintDevice *pdev) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| d->m_continueCall = true; |
| if (d->m_pass != 0) { |
| return true; |
| } |
| |
| d->m_savedcaps = gccaps; |
| d->m_pdev = pdev; |
| |
| d->m_alphaPen = false; |
| d->m_alphaBrush = false; |
| d->m_alphaOpacity = false; |
| d->m_hasalpha = false; |
| d->m_advancedPen = false; |
| d->m_advancedBrush = false; |
| d->m_complexTransform = false; |
| d->m_emulateProjectiveTransforms = false; |
| |
| // clear alpha region |
| d->m_alphargn = QRegion(); |
| d->m_cliprgn = QRegion(); |
| d->m_pen = QPen(); |
| d->m_transform = QTransform(); |
| |
| flushAndInit(); |
| |
| return true; |
| } |
| |
| bool QAlphaPaintEngine::end() |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| d->m_continueCall = true; |
| if (d->m_pass != 0) { |
| return true; |
| } |
| |
| flushAndInit(false); |
| return true; |
| } |
| |
| void QAlphaPaintEngine::updateState(const QPaintEngineState &state) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| DirtyFlags flags = state.state(); |
| if (flags & QPaintEngine::DirtyTransform) { |
| d->m_transform = state.transform(); |
| d->m_complexTransform = (d->m_transform.type() > QTransform::TxScale); |
| d->m_emulateProjectiveTransforms = !(d->m_savedcaps & QPaintEngine::PerspectiveTransform) |
| && !(d->m_savedcaps & QPaintEngine::AlphaBlend) |
| && (d->m_transform.type() >= QTransform::TxProject); |
| } |
| if (flags & QPaintEngine::DirtyPen) { |
| d->m_pen = state.pen(); |
| if (d->m_pen.style() == Qt::NoPen) { |
| d->m_advancedPen = false; |
| d->m_alphaPen = false; |
| } else { |
| d->m_advancedPen = (d->m_pen.brush().style() != Qt::SolidPattern); |
| d->m_alphaPen = !d->m_pen.brush().isOpaque(); |
| } |
| } |
| |
| if (d->m_pass != 0) { |
| d->m_continueCall = true; |
| return; |
| } |
| d->m_continueCall = false; |
| |
| if (flags & QPaintEngine::DirtyOpacity) { |
| d->m_alphaOpacity = (state.opacity() != 1.0f); |
| } |
| |
| if (flags & QPaintEngine::DirtyBrush) { |
| if (state.brush().style() == Qt::NoBrush) { |
| d->m_advancedBrush = false; |
| d->m_alphaBrush = false; |
| } else { |
| d->m_advancedBrush = (state.brush().style() != Qt::SolidPattern); |
| d->m_alphaBrush = !state.brush().isOpaque(); |
| } |
| } |
| |
| |
| d->m_hasalpha = d->m_alphaOpacity || d->m_alphaBrush || d->m_alphaPen; |
| |
| if (d->m_picengine) { |
| const QPainter *p = painter(); |
| d->m_picpainter->setPen(p->pen()); |
| d->m_picpainter->setBrush(p->brush()); |
| d->m_picpainter->setBrushOrigin(p->brushOrigin()); |
| d->m_picpainter->setFont(p->font()); |
| d->m_picpainter->setOpacity(p->opacity()); |
| d->m_picpainter->setTransform(p->combinedTransform()); |
| d->m_picengine->updateState(state); |
| } |
| } |
| |
| void QAlphaPaintEngine::drawPath(const QPainterPath &path) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| QRectF tr = d->addPenWidth(path); |
| |
| if (d->m_pass == 0) { |
| d->m_continueCall = false; |
| if (d->canSeeTroughBackground(d->m_hasalpha, tr) || d->m_advancedPen || d->m_advancedBrush |
| || d->m_emulateProjectiveTransforms) |
| { |
| d->addAlphaRect(tr); |
| } |
| |
| d->addDirtyRect(tr); |
| |
| if (d->m_picengine) |
| d->m_picengine->drawPath(path); |
| } else { |
| d->m_continueCall = !d->fullyContained(tr); |
| } |
| } |
| |
| void QAlphaPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| QPolygonF poly; |
| poly.reserve(pointCount); |
| for (int i = 0; i < pointCount; ++i) |
| poly.append(points[i]); |
| |
| QPainterPath path; |
| path.addPolygon(poly); |
| QRectF tr = d->addPenWidth(path); |
| |
| if (d->m_pass == 0) { |
| d->m_continueCall = false; |
| if (d->canSeeTroughBackground(d->m_hasalpha, tr) || d->m_advancedPen || d->m_advancedBrush |
| || d->m_emulateProjectiveTransforms) |
| { |
| d->addAlphaRect(tr); |
| } |
| |
| d->addDirtyRect(tr); |
| |
| if (d->m_picengine) |
| d->m_picengine->drawPolygon(points, pointCount, mode); |
| } else { |
| d->m_continueCall = !d->fullyContained(tr); |
| } |
| } |
| |
| void QAlphaPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| QRectF tr = d->m_transform.mapRect(r); |
| if (d->m_pass == 0) { |
| d->m_continueCall = false; |
| if (d->canSeeTroughBackground(pm.hasAlpha() || d->m_alphaOpacity, tr) || d->m_complexTransform || pm.isQBitmap()) { |
| d->addAlphaRect(tr); |
| } |
| |
| d->addDirtyRect(tr); |
| |
| if (d->m_picengine) |
| d->m_picengine->drawPixmap(r, pm, sr); |
| |
| } else { |
| d->m_continueCall = !d->fullyContained(tr); |
| } |
| } |
| |
| void QAlphaPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| QRectF tr(p.x(), p.y() - textItem.ascent(), textItem.width() + 5, textItem.ascent() + textItem.descent() + 5); |
| tr = d->m_transform.mapRect(tr); |
| |
| if (d->m_pass == 0) { |
| d->m_continueCall = false; |
| if (d->canSeeTroughBackground(d->m_alphaPen || d->m_alphaOpacity, tr) || d->m_advancedPen) { |
| d->addAlphaRect(tr); |
| } |
| |
| d->addDirtyRect(tr); |
| |
| if (d->m_picengine) { |
| d->m_picengine->drawTextItem(p, textItem); |
| } |
| } else { |
| d->m_continueCall = !d->fullyContained(tr); |
| } |
| } |
| |
| void QAlphaPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| QRectF brect = d->m_transform.mapRect(r); |
| |
| if (d->m_pass == 0) { |
| d->m_continueCall = false; |
| if (d->canSeeTroughBackground(pixmap.hasAlpha() || d->m_alphaOpacity, brect) || d->m_complexTransform || pixmap.isQBitmap()) { |
| d->addAlphaRect(brect); |
| } |
| |
| d->addDirtyRect(brect); |
| |
| if (d->m_picengine) |
| d->m_picengine->drawTiledPixmap(r, pixmap, s); |
| } else { |
| d->m_continueCall = !d->fullyContained(brect); |
| } |
| } |
| |
| QRegion QAlphaPaintEngine::alphaClipping() const |
| { |
| Q_D(const QAlphaPaintEngine); |
| return d->m_cliprgn; |
| } |
| |
| bool QAlphaPaintEngine::continueCall() const |
| { |
| Q_D(const QAlphaPaintEngine); |
| return d->m_continueCall; |
| } |
| |
| void QAlphaPaintEngine::flushAndInit(bool init) |
| { |
| Q_D(QAlphaPaintEngine); |
| Q_ASSERT(d->m_pass == 0); |
| |
| if (d->m_pic) { |
| d->m_picpainter->end(); |
| |
| // set clip region |
| d->m_alphargn = d->m_alphargn.intersected(QRect(0, 0, d->m_pdev->width(), d->m_pdev->height())); |
| |
| // just use the bounding rect if it's a complex region.. |
| if (d->m_alphargn.rectCount() > 10) { |
| QRect br = d->m_alphargn.boundingRect(); |
| d->m_alphargn = QRegion(br); |
| } |
| |
| const auto oldAlphaRegion = d->m_cliprgn = d->m_alphargn; |
| |
| // now replay the QPicture |
| ++d->m_pass; // we are now doing pass #2 |
| |
| // reset states |
| gccaps = d->m_savedcaps; |
| |
| painter()->save(); |
| d->resetState(painter()); |
| |
| // make sure the output from QPicture is unscaled |
| QTransform mtx; |
| mtx.scale(1.0f / (qreal(d->m_pdev->logicalDpiX()) / qreal(qt_defaultDpiX())), |
| 1.0f / (qreal(d->m_pdev->logicalDpiY()) / qreal(qt_defaultDpiY()))); |
| painter()->setTransform(mtx); |
| painter()->drawPicture(0, 0, *d->m_pic); |
| |
| d->m_cliprgn = QRegion(); |
| d->resetState(painter()); |
| |
| // fill in the alpha images |
| for (const auto &rect : oldAlphaRegion) |
| d->drawAlphaImage(rect); |
| |
| d->m_alphargn = QRegion(); |
| |
| painter()->restore(); |
| |
| --d->m_pass; // pass #2 finished |
| |
| cleanUp(); |
| } |
| |
| if (init) { |
| gccaps = PaintEngineFeatures(AllFeatures & ~QPaintEngine::ObjectBoundingModeGradients); |
| |
| d->m_pic = new QPicture(); |
| d->m_pic->d_ptr->in_memory_only = true; |
| d->m_picpainter = new QPainter(d->m_pic); |
| d->m_picengine = d->m_picpainter->paintEngine(); |
| |
| // When newPage() is called and the m_picpainter is recreated |
| // we have to copy the current state of the original printer |
| // painter back to the m_picpainter |
| d->m_picpainter->setPen(painter()->pen()); |
| d->m_picpainter->setBrush(painter()->brush()); |
| d->m_picpainter->setBrushOrigin(painter()->brushOrigin()); |
| d->m_picpainter->setFont(painter()->font()); |
| d->m_picpainter->setOpacity(painter()->opacity()); |
| d->m_picpainter->setTransform(painter()->combinedTransform()); |
| d->m_picengine->syncState(); |
| QPainterState &state = *d->m_picpainter->d_func()->state; |
| QPainter *oldPainter = state.painter; |
| state = *painter()->d_func()->state; |
| state.painter = oldPainter; |
| } |
| } |
| |
| void QAlphaPaintEngine::cleanUp() |
| { |
| Q_D(QAlphaPaintEngine); |
| |
| delete d->m_picpainter; |
| delete d->m_pic; |
| |
| d->m_picpainter = nullptr; |
| d->m_pic = nullptr; |
| d->m_picengine = nullptr; |
| } |
| |
| QAlphaPaintEnginePrivate::QAlphaPaintEnginePrivate() |
| : m_pass(0), |
| m_pic(nullptr), |
| m_picengine(nullptr), |
| m_picpainter(nullptr), |
| m_numberOfCachedRects(0), |
| m_hasalpha(false), |
| m_alphaPen(false), |
| m_alphaBrush(false), |
| m_alphaOpacity(false), |
| m_advancedPen(false), |
| m_advancedBrush(false), |
| m_complexTransform(false) |
| { |
| |
| } |
| |
| QAlphaPaintEnginePrivate::~QAlphaPaintEnginePrivate() |
| { |
| delete m_picpainter; |
| delete m_pic; |
| } |
| |
| QRectF QAlphaPaintEnginePrivate::addPenWidth(const QPainterPath &path) |
| { |
| Q_Q(QAlphaPaintEngine); |
| |
| QPainterPath tmp = path; |
| |
| if (m_pen.style() == Qt::NoPen) |
| return (path.controlPointRect() * m_transform).boundingRect(); |
| bool cosmetic = qt_pen_is_cosmetic(m_pen, q->state->renderHints()); |
| if (cosmetic) |
| tmp = path * m_transform; |
| |
| QPainterPathStroker stroker; |
| if (m_pen.widthF() == 0.0f) |
| stroker.setWidth(1.0); |
| else |
| stroker.setWidth(m_pen.widthF()); |
| stroker.setJoinStyle(m_pen.joinStyle()); |
| stroker.setCapStyle(m_pen.capStyle()); |
| tmp = stroker.createStroke(tmp); |
| if (cosmetic) |
| return tmp.controlPointRect(); |
| |
| return (tmp.controlPointRect() * m_transform).boundingRect(); |
| } |
| |
| void QAlphaPaintEnginePrivate::addAlphaRect(const QRectF &rect) |
| { |
| m_alphargn |= rect.toAlignedRect(); |
| } |
| |
| bool QAlphaPaintEnginePrivate::canSeeTroughBackground(bool somethingInRectHasAlpha, const QRectF &rect) const |
| { |
| if (somethingInRectHasAlpha) { |
| if (m_dirtyRects.count() != m_numberOfCachedRects) { |
| m_cachedDirtyRgn.setRects(m_dirtyRects.constData(), m_dirtyRects.count()); |
| m_numberOfCachedRects = m_dirtyRects.count(); |
| } |
| return m_cachedDirtyRgn.intersects(rect.toAlignedRect()); |
| } |
| return false; |
| } |
| |
| void QAlphaPaintEnginePrivate::drawAlphaImage(const QRectF &rect) |
| { |
| Q_Q(QAlphaPaintEngine); |
| |
| qreal dpiX = qMax(m_pdev->logicalDpiX(), 300); |
| qreal dpiY = qMax(m_pdev->logicalDpiY(), 300); |
| qreal xscale = (dpiX / m_pdev->logicalDpiX()); |
| qreal yscale = (dpiY / m_pdev->logicalDpiY()); |
| |
| QTransform picscale; |
| picscale.scale(xscale, yscale); |
| |
| const int tileSize = 2048; |
| QSize size((int(rect.width() * xscale)), int(rect.height() * yscale)); |
| int divw = (size.width() / tileSize); |
| int divh = (size.height() / tileSize); |
| divw += 1; |
| divh += 1; |
| |
| int incx = int(rect.width() / divw); |
| int incy = int(rect.height() / divh); |
| |
| for (int y=0; y<divh; ++y) { |
| int ypos = int((incy * y) + rect.y()); |
| int height = int((y == (divh - 1)) ? (rect.height() - (incy * y)) : incy) + 1; |
| |
| for (int x=0; x<divw; ++x) { |
| int xpos = int((incx * x) + rect.x()); |
| int width = int((x == (divw - 1)) ? (rect.width() - (incx * x)) : incx) + 1; |
| |
| QSize imgsize((int)(width * xscale), (int)(height * yscale)); |
| QImage img(imgsize, QImage::Format_RGB32); |
| img.fill(0xffffffff); |
| |
| QPainter imgpainter(&img); |
| imgpainter.setTransform(picscale); |
| QPointF picpos(qreal(-xpos), qreal(-ypos)); |
| imgpainter.drawPicture(picpos, *m_pic); |
| imgpainter.end(); |
| |
| q->painter()->setTransform(QTransform()); |
| QRect r(xpos, ypos, width, height); |
| q->painter()->drawImage(r, img); |
| } |
| } |
| } |
| |
| bool QAlphaPaintEnginePrivate::fullyContained(const QRectF &rect) const |
| { |
| QRegion r(rect.toAlignedRect()); |
| return (m_cliprgn.intersected(r) == r); |
| } |
| |
| void QAlphaPaintEnginePrivate::resetState(QPainter *p) |
| { |
| p->setPen(QPen()); |
| p->setBrush(QBrush()); |
| p->setBrushOrigin(0,0); |
| p->setBackground(QBrush()); |
| p->setFont(QFont()); |
| p->setTransform(QTransform()); |
| // The view transform is already recorded and included in the |
| // picture we're about to replay. If we don't turn if off, |
| // the view matrix will be applied twice. |
| p->setViewTransformEnabled(false); |
| p->setClipRegion(QRegion(), Qt::NoClip); |
| p->setClipPath(QPainterPath(), Qt::NoClip); |
| p->setClipping(false); |
| p->setOpacity(1.0f); |
| } |
| |
| |
| QT_END_NAMESPACE |
| |
| #endif // QT_NO_PRINTER |