Commit f984eab0 authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Fix artifacts when saving vector layers into PNG files

This patch has two parts:

1) KisShapeLayer should block the updates of the shape-canvas.
   When shapes are added to the layer, they initiate shape
   manager updates. That is not what we want, because all the
   rendered pixel data has already been copied in initShapeLayer()
   call.

2) Add a sanity check in KisDocument::initiateSavingInBackground().
   Theoretically, there should be no pending updates after cloning
   operation. But if they still appear somehow (which is a bug),
   just force them wait until they complete their execution.

BUG:404976
CCBUG:404742
parent 3cb09556
......@@ -37,6 +37,13 @@ public:
* be asynchronous, so you should call image->waitForDone() after that.
*/
virtual void forceUpdateTimedNode() = 0;
/**
* @return true if forceUpdateTimedNode() is going to
* produce any real updates, that is the node has any
* updates still pending
*/
virtual bool hasPendingTimedUpdates() const = 0;
};
#endif // KISDELAYEDUPDATENODEINTERFACE_H
......@@ -1514,6 +1514,17 @@ namespace KisLayerUtils {
});
}
bool hasDelayedNodeWithUpdates(KisNodeSP root)
{
return recursiveFindNode(root,
[] (KisNodeSP node) {
KisDelayedUpdateNodeInterface *delayedUpdate =
dynamic_cast<KisDelayedUpdateNodeInterface*>(node.data());
return delayedUpdate ? delayedUpdate->hasPendingTimedUpdates() : false;
});
}
KisImageSP findImageByHierarchy(KisNodeSP node)
{
while (node) {
......
......@@ -51,6 +51,7 @@ namespace KisLayerUtils
*/
KRITAIMAGE_EXPORT bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes);
KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root);
KRITAIMAGE_EXPORT bool hasDelayedNodeWithUpdates(KisNodeSP root);
KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false);
......
......@@ -425,7 +425,7 @@ void KisTransformMask::setY(qint32 y)
void KisTransformMask::forceUpdateTimedNode()
{
if (m_d->updateSignalCompressor.isActive()) {
if (hasPendingTimedUpdates()) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->staticCacheValid);
m_d->updateSignalCompressor.stop();
......@@ -433,6 +433,11 @@ void KisTransformMask::forceUpdateTimedNode()
}
}
bool KisTransformMask::hasPendingTimedUpdates() const
{
return m_d->updateSignalCompressor.isActive();
}
void KisTransformMask::threadSafeForceStaticImageUpdate()
{
emit sigInternalForceStaticImageUpdate();
......
......@@ -75,6 +75,7 @@ public:
void setY(qint32 y) override;
void forceUpdateTimedNode() override;
bool hasPendingTimedUpdates() const override;
void threadSafeForceStaticImageUpdate();
......
......@@ -803,6 +803,23 @@ bool KisDocument::initiateSavingInBackground(const QString actionName,
return false;
}
auto waitForImage = [] (KisImageSP image) {
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
window->viewManager()->blockUntilOperationsFinishedForced(image);
}
}
};
{
KisNodeSP newRoot = clonedDocument->image()->root();
KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) {
KisLayerUtils::forceAllDelayedNodesUpdate(newRoot);
waitForImage(clonedDocument->image());
}
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
......
......@@ -115,6 +115,11 @@ public:
m_layer->signalUpdate(m_layer->boundingImageRect());
}
bool hasPendingUpdates() const override
{
return false;
}
void rerenderAfterBeingInvisible() override {}
void resetCache() override {}
void setImage(KisImageWSP /*image*/) override {}
......
......@@ -175,12 +175,16 @@ KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* c
*/
const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted();
m_d->canvas->setUpdatesBlocked(true);
Q_FOREACH (KoShape *shape, _rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
addShape(clonedShape);
}
m_d->canvas->setUpdatesBlocked(true);
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes)
......@@ -475,6 +479,11 @@ void KisShapeLayer::forceUpdateTimedNode()
m_d->canvas->forceRepaint();
}
bool KisShapeLayer::hasPendingTimedUpdates() const
{
return m_d->canvas->hasPendingUpdates();
}
bool KisShapeLayer::saveShapesToStore(KoStore *store, QList<KoShape *> shapes, const QSizeF &sizeInPt)
{
if (!store->open("content.svg")) {
......
......@@ -146,6 +146,11 @@ public:
*/
void forceUpdateTimedNode() override;
/**
* \return true if there are any pending updates in the delayed queue
*/
bool hasPendingTimedUpdates() const override;
protected:
using KoShape::isVisible;
......
......@@ -111,6 +111,16 @@ KoUnit KisShapeLayerCanvasBase::unit() const
return KoUnit(KoUnit::Point);
}
void KisShapeLayerCanvasBase::setUpdatesBlocked(bool value)
{
m_updatesBlocked = value;
}
bool KisShapeLayerCanvasBase::updatesBlocked() const
{
return m_updatesBlocked;
}
void KisShapeLayerCanvasBase::prepareForDestroying()
{
m_isDestroying = true;
......@@ -138,7 +148,7 @@ KisShapeLayerCanvas::KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP imag
connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint()));
connect(this, SIGNAL(forwardRepaint()), &m_canvasUpdateCompressor, SLOT(start()));
connect(&m_canvasUpdateCompressor, SIGNAL(timeout()), this, SLOT(repaint()));
connect(&m_canvasUpdateCompressor, SIGNAL(timeout()), this, SLOT(slotStartDirectSyncRepaint()));
setImage(image);
}
......@@ -207,7 +217,7 @@ private:
void KisShapeLayerCanvas::updateCanvas(const QVector<QRectF> &region)
{
if (!m_parentLayer->image() || m_isDestroying) {
if (!m_parentLayer->image() || m_isDestroying || m_updatesBlocked) {
return;
}
......@@ -240,6 +250,7 @@ void KisShapeLayerCanvas::updateCanvas(const QVector<QRectF> &region)
if (qApp->thread() == QThread::currentThread()) {
emit forwardRepaint();
m_hasDirectSyncRepaintInitiated = true;
} else {
m_asyncUpdateSignalCompressor.start();
m_hasUpdateInCompressor = true;
......@@ -258,6 +269,12 @@ void KisShapeLayerCanvas::slotStartAsyncRepaint()
m_image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer, this));
}
void KisShapeLayerCanvas::slotStartDirectSyncRepaint()
{
m_hasDirectSyncRepaintInitiated = false;
repaint();
}
void KisShapeLayerCanvas::slotImageSizeChanged()
{
QRegion dirtyCacheRegion;
......@@ -330,12 +347,17 @@ void KisShapeLayerCanvas::forceRepaint()
* The only real solution to this is to port vector tools to strokes framework.
*/
if (m_hasUpdateInCompressor) {
if (hasPendingUpdates()) {
m_asyncUpdateSignalCompressor.stop();
slotStartAsyncRepaint();
}
}
bool KisShapeLayerCanvas::hasPendingUpdates() const
{
return m_hasUpdateInCompressor || m_hasDirectSyncRepaintInitiated;
}
void KisShapeLayerCanvas::resetCache()
{
m_projection->clear();
......
......@@ -47,6 +47,7 @@ public:
virtual void setImage(KisImageWSP image) = 0;
void prepareForDestroying();
virtual void forceRepaint() = 0;
virtual bool hasPendingUpdates() const = 0;
bool hasChangedWhileBeingInvisible();
virtual void rerenderAfterBeingInvisible() = 0;
......@@ -66,12 +67,16 @@ public:
void updateInputMethodInfo() override {}
void setCursor(const QCursor &) override {}
void setUpdatesBlocked(bool value);
bool updatesBlocked() const;
protected:
QScopedPointer<KisImageViewConverter> m_viewConverter;
QScopedPointer<KoShapeManager> m_shapeManager;
QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
bool m_hasChangedWhileBeingInvisible {false};
bool m_isDestroying {false};
bool m_updatesBlocked {false};
};
/**
......@@ -98,6 +103,7 @@ public:
void updateCanvas(const QRectF& rc) override;
void updateCanvas(const QVector<QRectF> &region);
void forceRepaint() override;
bool hasPendingUpdates() const override;
void resetCache() override;
void rerenderAfterBeingInvisible() override;
......@@ -106,6 +112,7 @@ private Q_SLOTS:
friend class KisRepaintShapeLayerLayerJob;
void repaint();
void slotStartAsyncRepaint();
void slotStartDirectSyncRepaint();
void slotImageSizeChanged();
Q_SIGNALS:
......@@ -121,6 +128,7 @@ private:
KisThreadSafeSignalCompressor m_asyncUpdateSignalCompressor;
volatile bool m_hasUpdateInCompressor = false;
volatile bool m_hasDirectSyncRepaintInitiated = false;
QRegion m_dirtyRegion;
QMutex m_dirtyRegionMutex;
......
Supports Markdown
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