Commit 7d06c391 authored by Sharaf Zaman's avatar Sharaf Zaman

Render meshgradient on QImage and then on QPainter

Primary reason for this is, that rendering meshpatches
every single time a translation is done, is very
expensive. So, we render it the first time and reuse it.

The way we do it is:

1) First we render the patch in scaled-up version of
   QImage, this helps us with loss of quality which is
   a consequence of `QPainter&` passed in
   `KoMeshGradientBackground::paint` as a scaled version.

2) We render the whole patch on QImage, not just the patch
   inside clipped region.

3) Then we render the QImage on QPainter in user coordinates
   i.e independent from the shape.

NOTE: One thing which has to be taken care of is, when we resize
the shape, the patch is resized as well. Which is one of the
expected behavior. But there is one more behavior, that is
the patches should not be scaled, which can help us with
having only selected portion of the patches in view.

Maniphest: T13101
parent c652035c
......@@ -7,18 +7,25 @@
#include <QPainterPath>
#include <QDebug>
#include "KoMeshPatchesRenderer.h"
class KoMeshGradientBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
, gradient(0)
, renderer(new KoMeshPatchesRenderer)
{}
~Private() { delete gradient; }
~Private() {
delete gradient;
delete renderer;
}
SvgMeshGradient *gradient;
QTransform matrix;
KoMeshPatchesRenderer *renderer;
};
KoMeshGradientBackground::KoMeshGradientBackground(SvgMeshGradient *gradient, const QTransform &matrix)
......@@ -36,72 +43,33 @@ KoMeshGradientBackground::~KoMeshGradientBackground()
void KoMeshGradientBackground::paint(QPainter &painter,
KoShapePaintingContext &,
const QPainterPath &) const
const QPainterPath &fillPath) const
{
if (!d->gradient || !d->gradient->isValid()) return;
painter.save();
for (int row = 0; row < d->gradient->getMeshArray()->numRows(); ++row) {
for (int col = 0; col < d->gradient->getMeshArray()->numColumns(); ++col) {
SvgMeshPatch *patch = d->gradient->getMeshArray()->getPatch(row, col);
fillPatch(painter, patch);
}
}
}
void KoMeshGradientBackground::fillPatch(QPainter &painter, const SvgMeshPatch *patch) const
{
QRegion clipRegion = painter.clipRegion();
KoPathShape *patchPath = patch->getPath();
// if patch is outside the bounding box of the clipped region, don't render
if (!clipRegion.contains(patchPath->boundingRect().toRect()))
return;
QColor color0 = patch->getStop(SvgMeshPatch::Top).color;
QColor color1 = patch->getStop(SvgMeshPatch::Right).color;
QColor color2 = patch->getStop(SvgMeshPatch::Bottom).color;
QColor color3 = patch->getStop(SvgMeshPatch::Left).color;
QRectF meshBoundingRect = d->gradient->boundingRect();
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
if (d->renderer->patchImage()->isNull()) {
quint8 c[4][4];
cs->fromQColor(color0, c[0]);
cs->fromQColor(color1, c[1]);
cs->fromQColor(color2, c[2]);
cs->fromQColor(color3, c[3]);
d->renderer->configure(meshBoundingRect, painter.transform());
const quint8 threshold = 0;
// check if color variation is acceptable and patch size is less than ~pixel width/heigh
if ((cs->difference(c[0], c[1]) > threshold || cs->difference(c[1], c[2]) > threshold ||
cs->difference(c[2], c[3]) > threshold || cs->difference(c[3], c[0]) > threshold) &&
patch->size().width() > 1 && patch->size().height() > 1) {
QVector<SvgMeshPatch*> patches;
patch->subdivide(patches);
for (const auto& p: patches) {
fillPatch(painter, p);
}
for (auto& p: patches) {
delete p;
for (int row = 0; row < d->gradient->getMeshArray()->numRows(); ++row) {
for (int col = 0; col < d->gradient->getMeshArray()->numColumns(); ++col) {
SvgMeshPatch *patch = d->gradient->getMeshArray()->getPatch(row, col);
d->renderer->fillPatch(patch);
}
}
} else {
quint8 mixed[4];
cs->mixColorsOp()->mixColors(c[0], 4, mixed);
QColor average;
cs->toQColor(mixed, &average);
// uncomment to debug
// d->renderer->patchImage()->save("mesh-patch.png");
}
QPen pen(average);
painter.setPen(pen);
painter.setClipRect(fillPath.boundingRect());
painter.drawPath(patchPath->outline());
// patch is to be drawn wrt. to "user" coordinates
painter.drawImage(meshBoundingRect, *d->renderer->patchImage());
QBrush brush(average);
painter.fillPath(patchPath->outline(), brush);
}
painter.restore();
}
bool KoMeshGradientBackground::compareTo(const KoShapeBackground *other) const
......
......@@ -13,8 +13,6 @@ public:
void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
void fillPatch(QPainter &painter, const SvgMeshPatch *patch) const;
bool compareTo(const KoShapeBackground *other) const override;
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
......
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2020 Sharaf Zaman <sharafzaz121@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KOMESHPATCHESRENDERER_H
#define KOMESHPATCHESRENDERER_H
#include <QImage>
#include <QPainter>
#include <QPainterPath>
#include <KoColorSpaceRegistry.h>
#include <KoMixColorsOp.h>
#include <SvgMeshPatch.h>
struct KoMeshPatchesRenderer {
public:
KoMeshPatchesRenderer()
{}
void configure(QRectF gradientgRect, const QTransform& painterTransform) {
// NOTE: This is a necessary step to prevent loss of quality, because painterTransform is scaled.
// we wish to scale the patch, but not translate, because patch should stay inside
// the boundingRect
QTransform painterTransformShifted = painterTransform *
QTransform::fromTranslate(painterTransform.dx(), painterTransform.dy()).inverted();
// boundingRect of the scaled version
QRectF scaledGradientRect = painterTransformShifted.mapRect(gradientgRect);
m_patch = QImage(scaledGradientRect.size().toSize(), QImage::Format_ARGB32);
m_patch.fill(Qt::transparent);
m_patchPainter.begin(&m_patch);
m_patchPainter.setRenderHint(QPainter::Antialiasing);
// this ensures that the patch renders inside the boundingRect
m_patchPainter.translate(-scaledGradientRect.topLeft());
// upscale the patch to the same scaling factor as the painterTransform
m_patchPainter.setTransform(painterTransformShifted, true);
}
void fillPatch(const SvgMeshPatch *patch) {
KoPathShape *patchPath = patch->getPath();
QColor color0 = patch->getStop(SvgMeshPatch::Top).color;
QColor color1 = patch->getStop(SvgMeshPatch::Right).color;
QColor color2 = patch->getStop(SvgMeshPatch::Bottom).color;
QColor color3 = patch->getStop(SvgMeshPatch::Left).color;
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
quint8 c[4][4];
cs->fromQColor(color0, c[0]);
cs->fromQColor(color1, c[1]);
cs->fromQColor(color2, c[2]);
cs->fromQColor(color3, c[3]);
const quint8 threshold = 0;
// check if color variation is acceptable and patch size is less than ~pixel width/heigh
if ((cs->difference(c[0], c[1]) > threshold || cs->difference(c[1], c[2]) > threshold ||
cs->difference(c[2], c[3]) > threshold || cs->difference(c[3], c[0]) > threshold) &&
patch->size().width() > 1 && patch->size().height() > 1) {
QVector<SvgMeshPatch*> patches;
patch->subdivide(patches);
for (const auto& p: patches) {
fillPatch(p);
}
for (auto& p: patches) {
delete p;
}
} else {
quint8 mixed[4];
cs->mixColorsOp()->mixColors(c[0], 4, mixed);
QColor average;
cs->toQColor(mixed, &average);
QPen pen(average);
m_patchPainter.setPen(pen);
m_patchPainter.drawPath(patchPath->outline());
QBrush brush(average);
m_patchPainter.fillPath(patchPath->outline(), brush);
m_patchPainter.drawPath(patchPath->outline());
}
}
QImage* patchImage() {
return &m_patch;
}
private:
QImage m_patch;
QPainter m_patchPainter;
};
#endif // KOMESHPATCHESRENDERER_H
......@@ -179,6 +179,26 @@ void SvgMeshArray::setTransform(const QTransform& matrix)
}
}
}
QRectF SvgMeshArray::boundingRect() const
{
KIS_ASSERT(numRows() > 0 && numColumns() > 0);
// because meshpatches are adjacent and share sides. If combined, it *should* form a rectangle
qreal width = 0, height = 0;
QPointF start = m_array[0][0]->boundingRect().topLeft();
for (int row = 0; row < numRows(); ++row) {
height += m_array[row][0]->boundingRect().height();
}
for (int col = 0; col < numColumns(); ++col) {
width += m_array[0][col]->boundingRect().width();
}
return QRectF(start, QSizeF(width, height));
}
QColor SvgMeshArray::getColor(SvgMeshPatch::Type edge, int row, int col) const
{
return getStop(edge, row, col).color;
......
......@@ -48,6 +48,8 @@ public:
void setTransform(const QTransform& matrix);
QRectF boundingRect() const;
// get color of a stop
QColor getColor(SvgMeshPatch::Type edge, int row, int col) const;
......
......@@ -48,6 +48,11 @@ bool SvgMeshGradient::isValid() const
return m_mesharray->numRows() != 0;
}
QRectF SvgMeshGradient::boundingRect() const
{
return m_mesharray->boundingRect();
}
QScopedPointer<SvgMeshArray>& SvgMeshGradient::getMeshArray()
{
return m_mesharray;
......
......@@ -40,6 +40,9 @@ public:
void setTransform(const QTransform& matrix);
bool isValid() const;
// returns boundingRect of the meshpatches in "user" coordinates (QPainter's)
QRectF boundingRect() const;
QScopedPointer<SvgMeshArray>& getMeshArray();
private:
......
......@@ -283,6 +283,12 @@ int SvgMeshPatch::countPoints() const
return m_nodes.size();
}
QRectF SvgMeshPatch::boundingRect() const
{
return m_path->absoluteOutlineRect();
}
QPointF SvgMeshPatch::parseMeshPath(const QString& s, bool pathIncomplete, const QPointF lastPoint)
{
// bits and pieces from KoPathShapeLoader, see the copyright above
......
......@@ -74,6 +74,8 @@ public:
int countPoints() const;
QRectF boundingRect() const;
/* Parses raw pathstr and adds path to the shape, if the path isn't
* complete, it will have to be computed and given with pathIncomplete = true
* (Ideal case for std::optional)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment