Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit deb9431b authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implemented clipping mask feature of SVG

Still not implemented:
1) Blending using linear RGB
2) Doesn't work if a shape has a filter stack
3) Shapes are leaking... but it should be fixed on the later
   stages of implementing SVG support. Right now shape ownership
   rules are not quite clear to me.
4) Clip mask will not be found if placed into <def> category. *But*
   at the moment the <def> feature is implemented incorrectly. The
   inheritance of the 'def' elements should come from 'def', but not
   from the place of instantiation. So this part should be refactored
   anyway.
parent 26b91a47
......@@ -116,6 +116,8 @@ set(kritaflake_SRCS
KoTosContainer.cpp
KoTosContainerModel.cpp
KoClipPath.cpp
KoClipMask.cpp
KoClipMaskPainter.cpp
KoCurveFit.cpp
commands/KoShapeGroupCommand.cpp
commands/KoShapeAlignCommand.cpp
......
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@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.
*/
#include "KoClipMask.h"
#include <QRectF>
#include <QTransform>
#include <QPainter>
#include <KoShape.h>
#include "kis_algebra_2d.h"
#include <KoViewConverter.h>
#include <KoShapePainter.h>
struct KoClipMask::Private {
CoordinateSystem coordinates = ObjectBoundingBox;
CoordinateSystem contentCoordinates = UserSpaceOnUse;
QRectF maskRect = QRectF(-0.1, -0.1, 1.2, 1.2);
QList<KoShape*> shapes;
QTransform extraShapeTransform;
};
KoClipMask::KoClipMask()
: m_d(new Private)
{
}
KoClipMask::~KoClipMask()
{
// TODO: yes, yes, shapes are leaked!
}
KoClipMask::KoClipMask(const KoClipMask &rhs)
: m_d(new Private(*rhs.m_d))
{
// TODO: yes, we leak shapes at the moment!
}
KoClipMask *KoClipMask::clone() const
{
return new KoClipMask(*this);
}
KoClipMask::CoordinateSystem KoClipMask::coordinates() const
{
return m_d->coordinates;
}
void KoClipMask::setCoordinates(KoClipMask::CoordinateSystem value)
{
m_d->coordinates = value;
}
KoClipMask::CoordinateSystem KoClipMask::contentCoordinates() const
{
return m_d->contentCoordinates;
}
void KoClipMask::setContentCoordinates(KoClipMask::CoordinateSystem value)
{
m_d->contentCoordinates = value;
}
QRectF KoClipMask::maskRect() const
{
return m_d->maskRect;
}
void KoClipMask::setMaskRect(const QRectF &value)
{
m_d->maskRect = value;
}
QList<KoShape *> KoClipMask::shapes() const
{
return m_d->shapes;
}
void KoClipMask::setShapes(const QList<KoShape *> &value)
{
m_d->shapes = value;
}
bool KoClipMask::isEmpty() const
{
return m_d->shapes.isEmpty();
}
QTransform KoClipMask::extraShapeTransform() const
{
return m_d->extraShapeTransform;
}
void KoClipMask::setExtraShapeTransform(const QTransform &value)
{
m_d->extraShapeTransform = value;
}
void KoClipMask::drawMask(QPainter *painter, KoShape *shape)
{
painter->save();
QPainterPath clipPathInShapeSpace;
if (m_d->coordinates == ObjectBoundingBox) {
QTransform relativeToShape = KisAlgebra2D::mapToRect(shape->outlineRect());
clipPathInShapeSpace.addPolygon(relativeToShape.map(m_d->maskRect));
} else {
clipPathInShapeSpace.addRect(m_d->maskRect);
clipPathInShapeSpace = m_d->extraShapeTransform.map(clipPathInShapeSpace);
}
painter->setClipPath(clipPathInShapeSpace, Qt::IntersectClip);
if (m_d->contentCoordinates == ObjectBoundingBox) {
QTransform relativeToShape = KisAlgebra2D::mapToRect(shape->outlineRect());
painter->setTransform(relativeToShape, true);
} else {
painter->setTransform(m_d->extraShapeTransform, true);
}
KoViewConverter converter;
KoShapePainter p;
p.setShapes(m_d->shapes);
p.paint(*painter, converter);
painter->restore();
}
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@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 KOCLIPMASK_H
#define KOCLIPMASK_H
#include "kritaflake_export.h"
#include <QScopedPointer>
#include <QList>
class KoShape;
class QRectF;
class QTransform;
class QPainter;
class KRITAFLAKE_EXPORT KoClipMask
{
public:
enum CoordinateSystem {
UserSpaceOnUse,
ObjectBoundingBox
};
public:
KoClipMask();
~KoClipMask();
KoClipMask *clone() const;
CoordinateSystem coordinates() const;
void setCoordinates(CoordinateSystem value);
CoordinateSystem contentCoordinates() const;
void setContentCoordinates(CoordinateSystem value);
QRectF maskRect() const;
void setMaskRect(const QRectF &value);
QList<KoShape *> shapes() const;
void setShapes(const QList<KoShape *> &value);
bool isEmpty() const;
QTransform extraShapeTransform() const;
void setExtraShapeTransform(const QTransform &value);
void drawMask(QPainter *painter, KoShape *shape);
private:
KoClipMask(const KoClipMask &rhs);
struct Private;
const QScopedPointer<Private> m_d;
};
#endif // KOCLIPMASK_H
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@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.
*/
#include "KoClipMaskPainter.h"
#include <QPainter>
#include <QRectF>
#include "kis_global.h"
struct KoClipMaskPainter::Private
{
QPainter *globalPainter;
QImage shapeImage;
QImage maskImage;
QPainter shapePainter;
QPainter maskPainter;
QRect alignedGlobalClipRect;
};
KoClipMaskPainter::KoClipMaskPainter(QPainter *painter, const QRectF &globalClipRect)
: m_d(new Private)
{
m_d->globalPainter = painter;
m_d->alignedGlobalClipRect = globalClipRect.toAlignedRect();
m_d->shapeImage = QImage(m_d->alignedGlobalClipRect.size(), QImage::Format_ARGB32);
m_d->maskImage = QImage(m_d->alignedGlobalClipRect.size(), QImage::Format_ARGB32);
m_d->shapeImage.fill(0);
m_d->maskImage.fill(0);
QTransform moveToBufferTransform =
QTransform::fromTranslate(-m_d->alignedGlobalClipRect.x(),
-m_d->alignedGlobalClipRect.y());
m_d->shapePainter.begin(&m_d->shapeImage);
m_d->shapePainter.setTransform(moveToBufferTransform);
m_d->shapePainter.setTransform(painter->transform(), true);
if (painter->hasClipping()) {
m_d->shapePainter.setClipPath(painter->clipPath());
}
m_d->shapePainter.setOpacity(painter->opacity());
m_d->shapePainter.setBrush(painter->brush());
m_d->shapePainter.setPen(painter->pen());
m_d->maskPainter.begin(&m_d->maskImage);
m_d->maskPainter.setTransform(moveToBufferTransform);
m_d->maskPainter.setTransform(painter->transform(), true);
if (painter->hasClipping()) {
m_d->maskPainter.setClipPath(painter->clipPath());
}
m_d->maskPainter.setOpacity(painter->opacity());
m_d->maskPainter.setBrush(painter->brush());
m_d->maskPainter.setPen(painter->pen());
}
KoClipMaskPainter::~KoClipMaskPainter()
{
}
QPainter *KoClipMaskPainter::shapePainter()
{
return &m_d->shapePainter;
}
QPainter *KoClipMaskPainter::maskPainter()
{
return &m_d->maskPainter;
}
void KoClipMaskPainter::renderOnGlobalPainter()
{
KIS_ASSERT_RECOVER_RETURN(m_d->maskImage.size() == m_d->shapeImage.size());
for (int y = 0; y < m_d->maskImage.height(); y++) {
QRgb *shapeData = reinterpret_cast<QRgb*>(m_d->shapeImage.scanLine(y));
QRgb *maskData = reinterpret_cast<QRgb*>(m_d->maskImage.scanLine(y));
for (int x = 0; x < m_d->maskImage.width(); x++) {
const qreal normCoeff = 1.0 / 255.0 * 255.0;
qreal maskValue = qreal(qAlpha(*maskData)) *
(0.2125 * qRed(*maskData) +
0.7154 * qGreen(*maskData) +
0.0721 * qBlue(*maskData));
int alpha = qRound(maskValue * qAlpha(*shapeData) * normCoeff);
*shapeData = (alpha << 24) | (*shapeData & 0x00ffffff);
shapeData++;
maskData++;
}
}
KIS_ASSERT_RECOVER_RETURN(m_d->shapeImage.size() == m_d->alignedGlobalClipRect.size());
QPainterPath globalClipPath;
if (m_d->globalPainter->hasClipping()) {
globalClipPath = m_d->globalPainter->transform().map(m_d->globalPainter->clipPath());
}
m_d->globalPainter->save();
m_d->globalPainter->setTransform(QTransform());
if (!globalClipPath.isEmpty()) {
m_d->globalPainter->setClipPath(globalClipPath);
}
m_d->globalPainter->drawImage(m_d->alignedGlobalClipRect.topLeft(), m_d->shapeImage);
m_d->globalPainter->restore();
}
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@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 KOCLIPMASKPAINTER_H
#define KOCLIPMASKPAINTER_H
#include "kritaflake_export.h"
#include <QScopedPointer>
class QPainter;
class QRectF;
class KRITAFLAKE_EXPORT KoClipMaskPainter
{
public:
KoClipMaskPainter(QPainter *painter, const QRectF &globalClipRect);
~KoClipMaskPainter();
QPainter* shapePainter();
QPainter* maskPainter();
void renderOnGlobalPainter();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif // KOCLIPMASKPAINTER_H
......@@ -1223,6 +1223,18 @@ KoClipPath * KoShape::clipPath() const
return d->clipPath;
}
void KoShape::setClipMask(KoClipMask *clipMask)
{
Q_D(KoShape);
d->clipMask.reset(clipMask);
}
KoClipMask* KoShape::clipMask() const
{
Q_D(const KoShape);
return d->clipMask.data();
}
QTransform KoShape::transform() const
{
Q_D(const KoShape);
......
......@@ -52,6 +52,7 @@ class KoShapePrivate;
class KoFilterEffectStack;
class KoSnapData;
class KoClipPath;
class KoClipMask;
class KoShapePaintingContext;
class KoShapeAnchor;
class KoBorder;
......@@ -763,6 +764,12 @@ public:
/// Returns the currently set clip path or 0 if there is no clip path set
KoClipPath * clipPath() const;
/// Sets a new clip mask, removing the old one. The mask is owned by the shape.
void setClipMask(KoClipMask *clipMask);
/// Returns the currently set clip mask or 0 if there is no clip mask set
KoClipMask* clipMask() const;
/**
* Setting the shape to keep its aspect-ratio has the effect that user-scaling will
* keep the width/hight ratio intact so as not to distort shapes that rely on that
......
......@@ -41,6 +41,7 @@
#include "KoShapeBackground.h"
#include <KoRTree.h>
#include "KoClipPath.h"
#include "KoClipMaskPainter.h"
#include "KoShapePaintingContext.h"
#include "KoViewConverter.h"
......@@ -296,7 +297,7 @@ void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter,
d->selection->paint(painter, converter, paintContext);
}
}
#include "kis_debug.h"
void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
qreal transparency = shape->transparency(true);
......@@ -310,15 +311,33 @@ void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewC
painter.restore();
}
if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) {
painter.save();
shape->paint(painter, converter, paintContext);
painter.restore();
QScopedPointer<KoClipMaskPainter> clipMaskPainter;
QPainter *shapePainter = &painter;
KoClipMask *clipMask = shape->clipMask();
if (clipMask) {
clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect()));
shapePainter = clipMaskPainter->shapePainter();
}
shapePainter->save();
shape->paint(*shapePainter, converter, paintContext);
shapePainter->restore();
if (shape->stroke()) {
painter.save();
shape->stroke()->paint(shape, painter, converter);
painter.restore();
shapePainter->save();
shape->stroke()->paint(shape, *shapePainter, converter);
shapePainter->restore();
}
if (clipMask) {
shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
clipMaskPainter->renderOnGlobalPainter();
}
} else {
// TODO: clipping mask is not implemented for this case!
// There are filter effects, then we need to prerender the shape on an image, to filter it
QRectF shapeBound(QPointF(), shape->size());
// First step, compute the rectangle used for the image
......
......@@ -25,6 +25,9 @@
#include <QPoint>
#include <QPaintDevice>
#include <QTransform>
#include <QScopedPointer>
#include <KoClipMask.h>
class KoBorder;
class KoShapeManager;
......@@ -81,6 +84,7 @@ public:
KoShapeShadow * shadow; ///< the current shape shadow
KoBorder *border; ///< the current shape border
KoClipPath * clipPath; ///< the current clip path
QScopedPointer<KoClipMask> clipMask; ///< the current clip mask
QMap<QString, QString> additionalAttributes;
QMap<QByteArray, QString> additionalStyleAttributes;
KoFilterEffectStack *filterEffectStack; ///< stack of filter effects applied to the shape
......
......@@ -49,6 +49,7 @@ public:
QString filterId; ///< the current filter id
QString clipPathId; ///< the current clip path id
QString clipMaskId; ///< the current clip mask id
Qt::FillRule clipRule; ///< the current clip rule
qreal opacity; ///< the shapes opacity
......
......@@ -99,6 +99,7 @@ SvgGraphicsContext *SvgLoadingContext::pushGraphicsContext(const KoXmlElement &e
gc->filterId.clear(); // filters are not inherited
gc->clipPathId.clear(); // clip paths are not inherited
gc->clipMaskId.clear(); // clip masks are not inherited
gc->display = true; // display is not inherited
gc->opacity = 1.0; // opacity is not inherited
gc->baselineShift.clear(); // baseline-shift is not inherited
......
......@@ -50,6 +50,7 @@
#include "KoFilterEffectStack.h"
#include "KoFilterEffectLoadingContext.h"
#include <KoClipPath.h>
#include <KoClipMask.h>
#include <KoXmlNS.h>
#include "SvgUtil.h"
......@@ -520,6 +521,59 @@ bool SvgParser::parseClipPath(const KoXmlElement &e)
return true;
}
bool SvgParser::parseClipMask(const KoXmlElement &e)
{
QSharedPointer<KoClipMask> clipMask(new KoClipMask);
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipMask->setCoordinates(
e.attribute("maskUnits") == "userSpaceOnUse" ?
KoClipMask::UserSpaceOnUse :
KoClipMask::ObjectBoundingBox);
clipMask->setContentCoordinates(
e.attribute("maskContentUnits") == "objectBoundingBox" ?
KoClipMask::ObjectBoundingBox :
KoClipMask::UserSpaceOnUse);
QRectF maskRect;
if (clipMask->coordinates() == KoClipMask::ObjectBoundingBox) {
maskRect.setRect(
SvgUtil::fromPercentage(e.attribute("x", "-10%")),
SvgUtil::fromPercentage(e.attribute("y", "-10%")),
SvgUtil::fromPercentage(e.attribute("width", "120%")),
SvgUtil::fromPercentage(e.attribute("height", "120%")));
} else {
maskRect.setRect(
parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
parseUnitX(e.attribute("width", "120%")),
parseUnitY(e.attribute("height", "120%")));
}
clipMask->setMaskRect(maskRect);
// ensure that the clip mask is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
QList<KoShape*> shapes = parseGroup(e);
KIS_ASSERT_RECOVER_NOOP(shapes.size() <= 1);
m_context.popGraphicsContext();
if (shapes.isEmpty()) return false;
clipMask->setShapes(shapes);
m_clipMasks.insert(id, clipMask);
return true;
}
void SvgParser::uploadStyleToContext(const KoXmlElement &e)
{
SvgStyles styles = m_context.styleParser().collectStyles(e);
......@@ -541,6 +595,7 @@ void SvgParser::applyCurrentStyle(KoShape *shape)
applyFilter(shape);
applyClipping(shape);
applyMaskClipping(shape);
if (!gc->display || !gc->visible) {
/**
......@@ -575,6 +630,7 @@ void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles)
}
applyFilter(obj);
applyClipping(obj);
applyMaskClipping(obj);
if (!gc->display || !gc->visible) {
obj->setVisible(false);
......@@ -857,8 +913,6 @@ void SvgParser::applyFilter(KoShape *shape)
}
}
void SvgParser::applyClipping(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
......@@ -896,6 +950,35 @@ void SvgParser::applyClipping(KoShape *shape)
shape->setClipPath(clipPathObject);
}
void SvgParser::applyMaskClipping(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (gc->clipMaskId.isEmpty())
return;
QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
if (!originalClipMask || originalClipMask->isEmpty()) return;
KoClipMask *clipMask = originalClipMask->clone();
QTransform extraShapeTransform;
KIS_ASSERT_RECOVER_NOOP(m_coordinateSystemOnLoading.contains(shape));
if (m_coordinateSystemOnLoading.contains(shape)) {
extraShapeTransform =
shape->absoluteTransformation(0).inverted() *
m_coordinateSystemOnLoading[shape];
}
clipMask->setExtraShapeTransform(extraShapeTransform);
shape->setClipMask(clipMask);
}
QList<KoShape*> SvgParser::parseUse(const KoXmlElement &e)
{
QList<KoShape*> shapes;
......@@ -1108,6 +1191,8 @@ QList<KoShape*> SvgParser::parseContainer(const KoXmlElement &e)
parseFilter(b);
} else if (b.tagName() == "clipPath") {
parseClipPath(b);
} else if (b.tagName() == "mask") {
parseClipMask(b);
} else if (b.tagName() == "style") {
m_context.addStyleSheet(b);
} else if (b.tagName() == "rect" ||
......
......@@ -27,6 +27,7 @@
#include <QMap>