Commit 8c1b215d authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fix preview of Shape Layers in Transform Tool and Move Tool

This patch defines new "type" of layers, KisCroppedOriginalLayerInterface,
which tells that this layer may have some data outside layer bounds that
is not rendered normally. So the tools that want to use this data
should first call:

interface->forceUpdateHiddenAreaOnOriginal()

and wait until the layer generates this data.

BUG:392717
parent b6d75fa1
......@@ -137,6 +137,7 @@ set(kritaimage_LIB_SRCS
lazybrush/kis_colorize_mask.cpp
lazybrush/kis_colorize_stroke_strategy.cpp
KisDelayedUpdateNodeInterface.cpp
KisCroppedOriginalLayerInterface.cpp
kis_adjustment_layer.cc
kis_selection_based_layer.cpp
kis_node_filter_interface.cpp
......
/*
* Copyright (c) 2019 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 "KisCroppedOriginalLayerInterface.h"
KisCroppedOriginalLayerInterface::~KisCroppedOriginalLayerInterface()
{
}
/*
* Copyright (c) 2019 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 KISCROPPEDORIGINALLAYERINTERFACE_H
#define KISCROPPEDORIGINALLAYERINTERFACE_H
#include "kritaimage_export.h"
class KRITAIMAGE_EXPORT KisCroppedOriginalLayerInterface
{
public:
virtual ~KisCroppedOriginalLayerInterface();
/**
* Force regeneration of the hidden part of original() device
* (the one outside image bounds). After regeneration is completed,
* the layer will emit dirty signals itself, so no manual forced
* update is needed.
*/
virtual void forceUpdateHiddenAreaOnOriginal() = 0;
};
#endif // KISCROPPEDORIGINALLAYERINTERFACE_H
......@@ -52,6 +52,7 @@
#include "commands/kis_node_property_list_command.h"
#include "commands/kis_node_compositeop_command.h"
#include <KisDelayedUpdateNodeInterface.h>
#include <KisCroppedOriginalLayerInterface.h>
#include "krita_utils.h"
#include "kis_image_signal_router.h"
......@@ -1548,6 +1549,18 @@ namespace KisLayerUtils {
});
}
void forceAllHiddenOriginalsUpdate(KisNodeSP root)
{
KisLayerUtils::recursiveApplyNodes(root,
[] (KisNodeSP node) {
KisCroppedOriginalLayerInterface *croppedUpdate =
dynamic_cast<KisCroppedOriginalLayerInterface*>(node.data());
if (croppedUpdate) {
croppedUpdate->forceUpdateHiddenAreaOnOriginal();
}
});
}
KisImageSP findImageByHierarchy(KisNodeSP node)
{
while (node) {
......
......@@ -57,6 +57,8 @@ namespace KisLayerUtils
KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root);
KRITAIMAGE_EXPORT bool hasDelayedNodeWithUpdates(KisNodeSP root);
KRITAIMAGE_EXPORT void forceAllHiddenOriginalsUpdate(KisNodeSP root);
KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy);
......
......@@ -484,6 +484,11 @@ bool KisShapeLayer::hasPendingTimedUpdates() const
return m_d->canvas->hasPendingUpdates();
}
void KisShapeLayer::forceUpdateHiddenAreaOnOriginal()
{
m_d->canvas->forceRepaintWithHiddenAreas();
}
bool KisShapeLayer::saveShapesToStore(KoStore *store, QList<KoShape *> shapes, const QSizeF &sizeInPt)
{
if (!store->open("content.svg")) {
......
......@@ -26,6 +26,7 @@
#include <kis_external_layer_iface.h>
#include <kritaui_export.h>
#include <KisDelayedUpdateNodeInterface.h>
#include <KisCroppedOriginalLayerInterface.h>
class QRect;
class QIcon;
......@@ -50,7 +51,11 @@ const QString KIS_SHAPE_LAYER_ID = "KisShapeLayer";
XXX: what about removing shapes?
*/
class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer, public KisDelayedUpdateNodeInterface
class KRITAUI_EXPORT KisShapeLayer
: public KisExternalLayer,
public KoShapeLayer,
public KisDelayedUpdateNodeInterface,
public KisCroppedOriginalLayerInterface
{
Q_OBJECT
......@@ -151,6 +156,8 @@ public:
*/
bool hasPendingTimedUpdates() const override;
void forceUpdateHiddenAreaOnOriginal() override;
protected:
using KoShape::isVisible;
......
......@@ -295,20 +295,29 @@ void KisShapeLayerCanvas::slotImageSizeChanged()
void KisShapeLayerCanvas::repaint()
{
QRect repaintRect;
bool forceUpdateHiddenAreasOnly = false;
{
QMutexLocker locker(&m_dirtyRegionMutex);
repaintRect = m_dirtyRegion.boundingRect();
forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly;
m_dirtyRegion = QRegion();
m_forceUpdateHiddenAreasOnly = false;
}
if (repaintRect.isEmpty()) {
return;
}
if (!forceUpdateHiddenAreasOnly) {
if (repaintRect.isEmpty()) {
return;
}
// Crop the update rect by the image bounds. We keep the cache consistent
// by tracking the size of the image in slotImageSizeChanged()
repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds());
// Crop the update rect by the image bounds. We keep the cache consistent
// by tracking the size of the image in slotImageSizeChanged()
repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds());
} else {
const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes());
repaintRect = kisGrowRect(m_viewConverter->documentToView(shapesBounds).toAlignedRect(), 2);
}
QImage image(repaintRect.width(), repaintRect.height(), QImage::Format_ARGB32);
image.fill(0);
......@@ -329,8 +338,14 @@ void KisShapeLayerCanvas::repaint()
KisPaintDeviceSP dev = new KisPaintDevice(m_projection->colorSpace());
dev->convertFromQImage(image, 0);
if (forceUpdateHiddenAreasOnly) {
m_projection->clear();
}
KisPainter::copyAreaOptimized(repaintRect.topLeft(), dev, m_projection, QRect(QPoint(), repaintRect.size()));
m_projection->purgeDefaultPixels();
m_parentLayer->setDirty(repaintRect);
m_hasChangedWhileBeingInvisible |= !m_parentLayer->visible(true);
......@@ -358,6 +373,21 @@ bool KisShapeLayerCanvas::hasPendingUpdates() const
return m_hasUpdateInCompressor || m_hasDirectSyncRepaintInitiated;
}
void KisShapeLayerCanvas::forceRepaintWithHiddenAreas()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->image());
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_isDestroying);
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_updatesBlocked);
{
QMutexLocker locker(&m_dirtyRegionMutex);
m_forceUpdateHiddenAreasOnly = true;
}
m_asyncUpdateSignalCompressor.stop();
slotStartAsyncRepaint();
}
void KisShapeLayerCanvas::resetCache()
{
m_projection->clear();
......
......@@ -48,6 +48,8 @@ public:
virtual void forceRepaint() = 0;
virtual bool hasPendingUpdates() const = 0;
virtual void forceRepaintWithHiddenAreas() { forceRepaint(); }
bool hasChangedWhileBeingInvisible();
virtual void rerenderAfterBeingInvisible() = 0;
virtual void resetCache() = 0;
......@@ -104,6 +106,8 @@ public:
void forceRepaint() override;
bool hasPendingUpdates() const override;
void forceRepaintWithHiddenAreas() override;
void resetCache() override;
void rerenderAfterBeingInvisible() override;
......@@ -129,6 +133,7 @@ private:
volatile bool m_hasUpdateInCompressor = false;
volatile bool m_hasDirectSyncRepaintInitiated = false;
bool m_forceUpdateHiddenAreasOnly = false;
QRegion m_dirtyRegion;
QMutex m_dirtyRegionMutex;
......
......@@ -26,6 +26,10 @@
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "KisRunnableStrokeJobData.h"
#include "KisRunnableStrokeJobUtils.h"
#include "KisRunnableStrokeJobsInterface.h"
MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes,
KisUpdatesFacade *updatesFacade,
......@@ -89,17 +93,35 @@ void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node)
void MoveStrokeStrategy::initStrokeCallback()
{
QRect handlesRect;
QVector<KisRunnableStrokeJobData*> jobs;
Q_FOREACH(KisNodeSP node, m_nodes) {
saveInitialNodeOffsets(node);
handlesRect |= KisLayerUtils::recursiveNodeExactBounds(node);
}
KritaUtils::addJobBarrier(jobs, [this]() {
Q_FOREACH(KisNodeSP node, m_nodes) {
KisLayerUtils::forceAllHiddenOriginalsUpdate(node);
}
});
KritaUtils::addJobBarrier(jobs, [this]() {
Q_FOREACH(KisNodeSP node, m_nodes) {
KisLayerUtils::forceAllDelayedNodesUpdate(node);
}
});
KritaUtils::addJobBarrier(jobs, [this]() {
QRect handlesRect;
Q_FOREACH(KisNodeSP node, m_nodes) {
saveInitialNodeOffsets(node);
handlesRect |= KisLayerUtils::recursiveNodeExactBounds(node);
}
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
emit this->sigHandlesRectCalculated(handlesRect);
m_updateTimer.start();
});
emit sigHandlesRectCalculated(handlesRect);
m_updateTimer.start();
runnableJobsInterface()->addRunnableJobs(jobs);
}
void MoveStrokeStrategy::finishStrokeCallback()
......
......@@ -564,6 +564,13 @@ void TransformStrokeStrategy::initStrokeCallback()
extraInitJobs << lastCommandUndoJobs;
KritaUtils::addJobSequential(extraInitJobs, [this]() {
/**
* We must request shape layers to rerender areas outside image bounds
*/
KisLayerUtils::forceAllHiddenOriginalsUpdate(m_rootNode);
});
KritaUtils::addJobBarrier(extraInitJobs, [this]() {
/**
* We must ensure that the currently selected subtree
* has finished all its updates.
......
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