diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp
index 6663aade57..014aef52ab 100644
--- a/libs/image/kis_image_config.cpp
+++ b/libs/image/kis_image_config.cpp
@@ -1,521 +1,588 @@
/*
* Copyright (c) 2010 Dmitry Kazakov
*
* 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 "kis_image_config.h"
#include
#include
#include
#include
#include
#include "kis_debug.h"
#include
#include
#include
#include
#include "kis_global.h"
#include
#ifdef Q_OS_OSX
#include
#endif
KisImageConfig::KisImageConfig(bool readOnly)
: m_config( KSharedConfig::openConfig()->group(QString())),
m_readOnly(readOnly)
{
#ifdef Q_OS_OSX
// clear /var/folders/ swap path set by old broken Krita swap implementation in order to use new default swap dir.
QString swap = m_config.readEntry("swaplocation", "");
if (swap.startsWith("/var/folders/")) {
m_config.deleteEntry("swaplocation");
}
#endif
}
KisImageConfig::~KisImageConfig()
{
if (m_readOnly) return;
if (qApp->thread() != QThread::currentThread()) {
dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace();
return;
}
m_config.sync();
}
bool KisImageConfig::enableProgressReporting(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("enableProgressReporting", true) : true;
}
void KisImageConfig::setEnableProgressReporting(bool value)
{
m_config.writeEntry("enableProgressReporting", value);
}
bool KisImageConfig::enablePerfLog(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("enablePerfLog", false) :false;
}
void KisImageConfig::setEnablePerfLog(bool value)
{
m_config.writeEntry("enablePerfLog", value);
}
qreal KisImageConfig::transformMaskOffBoundsReadArea() const
{
return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5);
}
int KisImageConfig::updatePatchHeight() const
{
return m_config.readEntry("updatePatchHeight", 512);
}
void KisImageConfig::setUpdatePatchHeight(int value)
{
m_config.writeEntry("updatePatchHeight", value);
}
int KisImageConfig::updatePatchWidth() const
{
return m_config.readEntry("updatePatchWidth", 512);
}
void KisImageConfig::setUpdatePatchWidth(int value)
{
m_config.writeEntry("updatePatchWidth", value);
}
qreal KisImageConfig::maxCollectAlpha() const
{
return m_config.readEntry("maxCollectAlpha", 2.5);
}
qreal KisImageConfig::maxMergeAlpha() const
{
return m_config.readEntry("maxMergeAlpha", 1.);
}
qreal KisImageConfig::maxMergeCollectAlpha() const
{
return m_config.readEntry("maxMergeCollectAlpha", 1.5);
}
qreal KisImageConfig::schedulerBalancingRatio() const
{
/**
* updates-queue-size / strokes-queue-size
*/
return m_config.readEntry("schedulerBalancingRatio", 100.);
}
void KisImageConfig::setSchedulerBalancingRatio(qreal value)
{
m_config.writeEntry("schedulerBalancingRatio", value);
}
int KisImageConfig::maxSwapSize(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB
}
void KisImageConfig::setMaxSwapSize(int value)
{
m_config.writeEntry("maxSwapSize", value);
}
int KisImageConfig::swapSlabSize() const
{
return m_config.readEntry("swapSlabSize", 64); // in MiB
}
void KisImageConfig::setSwapSlabSize(int value)
{
m_config.writeEntry("swapSlabSize", value);
}
int KisImageConfig::swapWindowSize() const
{
return m_config.readEntry("swapWindowSize", 16); // in MiB
}
void KisImageConfig::setSwapWindowSize(int value)
{
m_config.writeEntry("swapWindowSize", value);
}
int KisImageConfig::tilesHardLimit() const
{
qreal hp = qreal(memoryHardLimitPercent()) / 100.0;
qreal pp = qreal(memoryPoolLimitPercent()) / 100.0;
return totalRAM() * hp * (1 - pp);
}
int KisImageConfig::tilesSoftLimit() const
{
qreal sp = qreal(memorySoftLimitPercent()) / 100.0;
return tilesHardLimit() * sp;
}
int KisImageConfig::poolLimit() const
{
qreal hp = qreal(memoryHardLimitPercent()) / 100.0;
qreal pp = qreal(memoryPoolLimitPercent()) / 100.0;
return totalRAM() * hp * pp;
}
qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("memoryHardLimitPercent", 50.) : 50.;
}
void KisImageConfig::setMemoryHardLimitPercent(qreal value)
{
m_config.writeEntry("memoryHardLimitPercent", value);
}
qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("memorySoftLimitPercent", 2.) : 2.;
}
void KisImageConfig::setMemorySoftLimitPercent(qreal value)
{
m_config.writeEntry("memorySoftLimitPercent", value);
}
qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0;
}
void KisImageConfig::setMemoryPoolLimitPercent(qreal value)
{
m_config.writeEntry("memoryPoolLimitPercent", value);
}
-QString KisImageConfig::swapDir(bool requestDefault)
+QString KisImageConfig::safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const
{
#ifdef Q_OS_OSX
// On OSX, QDir::tempPath() gives us a folder we cannot reply upon (usually
// something like /var/folders/.../...) and that will have vanished when we
// try to create the tmp file in KisMemoryWindow::KisMemoryWindow using
// swapFileTemplate. thus, we just pick the home folder if swapDir does not
// tell us otherwise.
// the other option here would be to use a "garbled name" temp file (i.e. no name
// KRITA_SWAP_FILE_XXXXXX) in an obscure /var/folders place, which is not
// nice to the user. having a clearly named swap file in the home folder is
// much nicer to Krita's users.
// furthermore, this is just a default and swapDir can always be configured
// to another location.
- QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/swap";
+ QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QDir::separator() + suffix;
#else
+ Q_UNUSED(suffix);
QString swap = QDir::tempPath();
#endif
if (requestDefault) {
return swap;
}
- QString configuredSwap = m_config.readEntry("swaplocation", swap);
+ const QString configuredSwap = m_config.readEntry(configKey, swap);
if (!configuredSwap.isEmpty()) {
swap = configuredSwap;
}
return swap;
}
+
+QString KisImageConfig::swapDir(bool requestDefault)
+{
+ return safelyGetWritableTempLocation("swap", "swaplocation", requestDefault);
+}
+
void KisImageConfig::setSwapDir(const QString &swapDir)
{
m_config.writeEntry("swaplocation", swapDir);
}
int KisImageConfig::numberOfOnionSkins() const
{
return m_config.readEntry("numberOfOnionSkins", 10);
}
void KisImageConfig::setNumberOfOnionSkins(int value)
{
m_config.writeEntry("numberOfOnionSkins", value);
}
int KisImageConfig::onionSkinTintFactor() const
{
return m_config.readEntry("onionSkinTintFactor", 192);
}
void KisImageConfig::setOnionSkinTintFactor(int value)
{
m_config.writeEntry("onionSkinTintFactor", value);
}
int KisImageConfig::onionSkinOpacity(int offset) const
{
int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1);
if (value < 0) {
const int num = numberOfOnionSkins();
const qreal dx = qreal(qAbs(offset)) / num;
value = 0.7 * exp(-pow2(dx) / 0.5) * 255;
}
return value;
}
void KisImageConfig::setOnionSkinOpacity(int offset, int value)
{
m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value);
}
bool KisImageConfig::onionSkinState(int offset) const
{
bool enableByDefault = (qAbs(offset) <= 2);
return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault);
}
void KisImageConfig::setOnionSkinState(int offset, bool value)
{
m_config.writeEntry("onionSkinState_" + QString::number(offset), value);
}
QColor KisImageConfig::onionSkinTintColorBackward() const
{
return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red));
}
void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value)
{
m_config.writeEntry("onionSkinTintColorBackward", value);
}
QColor KisImageConfig::onionSkinTintColorForward() const
{
return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green));
}
void KisImageConfig::setOnionSkinTintColorForward(const QColor &value)
{
m_config.writeEntry("oninSkinTintColorForward", value);
}
bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("lazyFrameCreationEnabled", true) : true;
}
void KisImageConfig::setLazyFrameCreationEnabled(bool value)
{
m_config.writeEntry("lazyFrameCreationEnabled", value);
}
#if defined Q_OS_LINUX
#include
#elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD
#include
#elif defined Q_OS_WIN
#include
#elif defined Q_OS_OSX
#include
#include
#endif
#include
int KisImageConfig::totalRAM()
{
// let's think that default memory size is 1000MiB
int totalMemory = 1000; // MiB
int error = 1;
#if defined Q_OS_LINUX
struct sysinfo info;
error = sysinfo(&info);
if(!error) {
totalMemory = info.totalram * info.mem_unit / (1UL << 20);
}
#elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD
u_long physmem;
# if defined HW_PHYSMEM64 // NetBSD only
int mib[] = {CTL_HW, HW_PHYSMEM64};
# else
int mib[] = {CTL_HW, HW_PHYSMEM};
# endif
size_t len = sizeof(physmem);
error = sysctl(mib, 2, &physmem, &len, 0, 0);
if(!error) {
totalMemory = physmem >> 20;
}
#elif defined Q_OS_WIN
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
error = !GlobalMemoryStatusEx(&status);
if (!error) {
totalMemory = status.ullTotalPhys >> 20;
}
// For 32 bit windows, the total memory available is at max the 2GB per process memory limit.
# if defined ENV32BIT
totalMemory = qMin(totalMemory, 2000);
# endif
#elif defined Q_OS_OSX
int mib[2] = { CTL_HW, HW_MEMSIZE };
u_int namelen = sizeof(mib) / sizeof(mib[0]);
uint64_t size;
size_t len = sizeof(size);
errno = 0;
if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) {
totalMemory = size >> 20;
error = 0;
}
else {
dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno);
}
#endif
if (error) {
warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default.";
}
return totalMemory;
}
bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true;
}
void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value)
{
m_config.writeEntry("showAdditionalOnionSkinsSettings", value);
}
int KisImageConfig::defaultFrameColorLabel() const
{
return m_config.readEntry("defaultFrameColorLabel", 0);
}
void KisImageConfig::setDefaultFrameColorLabel(int label)
{
m_config.writeEntry("defaultFrameColorLabel", label);
}
KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration()
{
KisProofingConfiguration *proofingConfig= new KisProofingConfiguration();
proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof");
proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA");
proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8");
proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3);
if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) {
proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation;
} else {
proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation;
}
QColor def(Qt::green);
m_config.readEntry("defaultProofingGamutwarning", def);
KoColor col(KoColorSpaceRegistry::instance()->rgb8());
col.fromQColor(def);
col.setOpacity(1.0);
proofingConfig->warningColor = col;
proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0);
return toQShared(proofingConfig);
}
void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState)
{
m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name());
m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id());
m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id());
m_config.writeEntry("defaultProofingProfileIntent", proofingIntent);
m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation);
QColor c;
c = warningColor.toQColor();
m_config.writeEntry("defaultProofingGamutwarning", c);
m_config.writeEntry("defaultProofingAdaptationState",adaptationState);
}
bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("useLodForColorizeMask", false) : false;
}
void KisImageConfig::setUseLodForColorizeMask(bool value)
{
m_config.writeEntry("useLodForColorizeMask", value);
}
int KisImageConfig::maxNumberOfThreads(bool defaultValue) const
{
return (defaultValue ? QThread::idealThreadCount() : m_config.readEntry("maxNumberOfThreads", QThread::idealThreadCount()));
}
void KisImageConfig::setMaxNumberOfThreads(int value)
{
if (value == QThread::idealThreadCount()) {
m_config.deleteEntry("maxNumberOfThreads");
} else {
m_config.writeEntry("maxNumberOfThreads", value);
}
}
int KisImageConfig::frameRenderingClones(bool defaultValue) const
{
const int defaultClonesCount = qMax(1, maxNumberOfThreads(defaultValue) / 2);
return defaultValue ? defaultClonesCount : m_config.readEntry("frameRenderingClones", defaultClonesCount);
}
void KisImageConfig::setFrameRenderingClones(int value)
{
m_config.writeEntry("frameRenderingClones", value);
}
int KisImageConfig::fpsLimit(bool defaultValue) const
{
return defaultValue ? 100 : m_config.readEntry("fpsLimit", 100);
}
void KisImageConfig::setFpsLimit(int value)
{
m_config.writeEntry("fpsLimit", value);
}
+
+bool KisImageConfig::useOnDiskAnimationCacheSwapping(bool defaultValue) const
+{
+ return defaultValue ? true : m_config.readEntry("useOnDiskAnimationCacheSwapping", true);
+}
+
+void KisImageConfig::setUseOnDiskAnimationCacheSwapping(bool value)
+{
+ m_config.writeEntry("useOnDiskAnimationCacheSwapping", value);
+}
+
+QString KisImageConfig::animationCacheDir(bool defaultValue) const
+{
+ return safelyGetWritableTempLocation("animation_cache", "animationCacheDir", defaultValue);
+}
+
+void KisImageConfig::setAnimationCacheDir(const QString &value)
+{
+ m_config.writeEntry("animationCacheDir", value);
+}
+
+bool KisImageConfig::useAnimationCacheFrameSizeLimit(bool defaultValue) const
+{
+ return defaultValue ? true : m_config.readEntry("useAnimationCacheFrameSizeLimit", true);
+}
+
+void KisImageConfig::setUseAnimationCacheFrameSizeLimit(bool value)
+{
+ m_config.writeEntry("useAnimationCacheFrameSizeLimit", value);
+}
+
+int KisImageConfig::animationCacheFrameSizeLimit(bool defaultValue) const
+{
+ return defaultValue ? 2500 : m_config.readEntry("animationCacheFrameSizeLimit", 2500);
+}
+
+void KisImageConfig::setAnimationCacheFrameSizeLimit(int value)
+{
+ m_config.writeEntry("animationCacheFrameSizeLimit", value);
+}
+
+bool KisImageConfig::useAnimationCacheRegionOfInterest(bool defaultValue) const
+{
+ return defaultValue ? true : m_config.readEntry("useAnimationCacheRegionOfInterest", true);
+}
+
+void KisImageConfig::setUseAnimationCacheRegionOfInterest(bool value)
+{
+ m_config.writeEntry("useAnimationCacheRegionOfInterest", value);
+}
+
+qreal KisImageConfig::animationCacheRegionOfInterestMargin(bool defaultValue) const
+{
+ return defaultValue ? 0.25 : m_config.readEntry("animationCacheRegionOfInterestMargin", 0.25);
+}
+
+void KisImageConfig::setAnimationCacheRegionOfInterestMargin(qreal value)
+{
+ m_config.writeEntry("animationCacheRegionOfInterestMargin", value);
+}
diff --git a/libs/image/kis_image_config.h b/libs/image/kis_image_config.h
index b701c1d2ab..78ba9f02b5 100644
--- a/libs/image/kis_image_config.h
+++ b/libs/image/kis_image_config.h
@@ -1,133 +1,153 @@
/*
* Copyright (c) 2010 Dmitry Kazakov
*
* 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 KIS_IMAGE_CONFIG_H_
#define KIS_IMAGE_CONFIG_H_
#include
#include "kritaimage_export.h"
#include "KisProofingConfiguration.h"
#include "kis_types.h"
class KRITAIMAGE_EXPORT KisImageConfig
{
public:
KisImageConfig(bool readOnly = false);
~KisImageConfig();
bool enableProgressReporting(bool requestDefault = false) const;
void setEnableProgressReporting(bool value);
bool enablePerfLog(bool requestDefault = false) const;
void setEnablePerfLog(bool value);
qreal transformMaskOffBoundsReadArea() const;
int updatePatchHeight() const;
void setUpdatePatchHeight(int value);
int updatePatchWidth() const;
void setUpdatePatchWidth(int value);
qreal maxCollectAlpha() const;
qreal maxMergeAlpha() const;
qreal maxMergeCollectAlpha() const;
qreal schedulerBalancingRatio() const;
void setSchedulerBalancingRatio(qreal value);
int maxSwapSize(bool requestDefault = false) const;
void setMaxSwapSize(int value);
int swapSlabSize() const;
void setSwapSlabSize(int value);
int swapWindowSize() const;
void setSwapWindowSize(int value);
int tilesHardLimit() const; // MiB
int tilesSoftLimit() const; // MiB
int poolLimit() const; // MiB
qreal memoryHardLimitPercent(bool requestDefault = false) const; // % of total RAM
qreal memorySoftLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() * (1 - 0.01 * memoryPoolLimitPercent())
qreal memoryPoolLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent()
void setMemoryHardLimitPercent(qreal value);
void setMemorySoftLimitPercent(qreal value);
void setMemoryPoolLimitPercent(qreal value);
static int totalRAM(); // MiB
/**
* @return a specific directory for the swapfile, if set. If not set, return an
* empty QString and use the default KDE directory.
*/
QString swapDir(bool requestDefault = false);
void setSwapDir(const QString &swapDir);
int numberOfOnionSkins() const;
void setNumberOfOnionSkins(int value);
int onionSkinTintFactor() const;
void setOnionSkinTintFactor(int value);
int onionSkinOpacity(int offset) const;
void setOnionSkinOpacity(int offset, int value);
bool onionSkinState(int offset) const;
void setOnionSkinState(int offset, bool value);
QColor onionSkinTintColorBackward() const;
void setOnionSkinTintColorBackward(const QColor &value);
QColor onionSkinTintColorForward() const;
void setOnionSkinTintColorForward(const QColor &value);
bool lazyFrameCreationEnabled(bool requestDefault = false) const;
void setLazyFrameCreationEnabled(bool value);
bool showAdditionalOnionSkinsSettings(bool requestDefault = false) const;
void setShowAdditionalOnionSkinsSettings(bool value);
int defaultFrameColorLabel() const;
void setDefaultFrameColorLabel(int label);
KisProofingConfigurationSP defaultProofingconfiguration();
void setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState);
bool useLodForColorizeMask(bool requestDefault = false) const;
void setUseLodForColorizeMask(bool value);
int maxNumberOfThreads(bool defaultValue = false) const;
void setMaxNumberOfThreads(int value);
int frameRenderingClones(bool defaultValue = false) const;
void setFrameRenderingClones(int value);
int fpsLimit(bool defaultValue = false) const;
void setFpsLimit(int value);
+ bool useOnDiskAnimationCacheSwapping(bool defaultValue = false) const;
+ void setUseOnDiskAnimationCacheSwapping(bool value);
+
+ QString animationCacheDir(bool defaultValue = false) const;
+ void setAnimationCacheDir(const QString &value);
+
+ bool useAnimationCacheFrameSizeLimit(bool defaultValue = false) const;
+ void setUseAnimationCacheFrameSizeLimit(bool value);
+
+ int animationCacheFrameSizeLimit(bool defaultValue = false) const;
+ void setAnimationCacheFrameSizeLimit(int value);
+
+ bool useAnimationCacheRegionOfInterest(bool defaultValue = false) const;
+ void setUseAnimationCacheRegionOfInterest(bool value);
+
+ qreal animationCacheRegionOfInterestMargin(bool defaultValue = false) const;
+ void setAnimationCacheRegionOfInterestMargin(qreal value);
+
private:
Q_DISABLE_COPY(KisImageConfig)
+ QString safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const;
+
private:
KConfigGroup m_config;
bool m_readOnly;
};
#endif /* KIS_IMAGE_CONFIG_H_ */
diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc
index 83f0f363e3..a331359180 100644
--- a/libs/image/kis_paint_device.cc
+++ b/libs/image/kis_paint_device.cc
@@ -1,2172 +1,2208 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2004 Boudewijn Rempt
*
* 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 "kis_paint_device.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_image.h"
#include "kis_random_sub_accessor.h"
#include "kis_selection.h"
#include "kis_node.h"
#include "kis_datamanager.h"
#include "kis_paint_device_writer.h"
#include "kis_selection_component.h"
#include "kis_pixel_selection.h"
#include "kis_repeat_iterators_pixel.h"
#include "kis_fixed_paint_device.h"
#include "tiles3/kis_hline_iterator.h"
#include "tiles3/kis_vline_iterator.h"
#include "tiles3/kis_random_accessor.h"
#include "kis_default_bounds.h"
#include "kis_lod_transform.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_paint_device_cache.h"
#include "kis_paint_device_data.h"
#include "kis_paint_device_frames_interface.h"
#include "kis_transform_worker.h"
#include "kis_filter_strategy.h"
#include "krita_utils.h"
struct KisPaintDeviceSPStaticRegistrar {
KisPaintDeviceSPStaticRegistrar() {
qRegisterMetaType("KisPaintDeviceSP");
}
};
static KisPaintDeviceSPStaticRegistrar __registrar;
struct KisPaintDevice::Private
{
/**
* Used when the paint device is loading to ensure no lod/animation
* interferes the process.
*/
static const KisDefaultBoundsSP transitionalDefaultBounds;
public:
class KisPaintDeviceStrategy;
class KisPaintDeviceWrappedStrategy;
Private(KisPaintDevice *paintDevice);
~Private();
KisPaintDevice *q;
KisNodeWSP parent;
QScopedPointer contentChannel;
KisDefaultBoundsBaseSP defaultBounds;
QScopedPointer basicStrategy;
QScopedPointer wrappedStrategy;
QMutex m_wrappedStrategyMutex;
QScopedPointer framesInterface;
bool isProjectionDevice;
KisPaintDeviceStrategy* currentStrategy();
void init(const KoColorSpace *cs, const quint8 *defaultPixel);
KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags);
bool assignProfile(const KoColorProfile * profile);
inline const KoColorSpace* colorSpace() const
{
return currentData()->colorSpace();
}
inline KisDataManagerSP dataManager() const
{
return currentData()->dataManager();
}
inline qint32 x() const
{
return currentData()->x();
}
inline qint32 y() const
{
return currentData()->y();
}
inline void setX(qint32 x)
{
currentData()->setX(x);
}
inline void setY(qint32 y)
{
currentData()->setY(y);
}
inline KisPaintDeviceCache* cache()
{
return currentData()->cache();
}
inline KisIteratorCompleteListener* cacheInvalidator() {
return currentData()->cacheInvalidator();
}
void cloneAllDataObjects(Private *rhs, bool copyFrames)
{
m_lodData.reset();
m_externalFrameData.reset();
if (!m_frames.isEmpty()) {
m_frames.clear();
}
if (!copyFrames) {
if (m_data) {
m_data->prepareClone(rhs->currentNonLodData(), true);
} else {
m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true));
}
} else {
if (m_data && !rhs->m_data) {
m_data.clear();
} else if (!m_data && rhs->m_data) {
m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true));
} else if (m_data && rhs->m_data) {
m_data->prepareClone(rhs->m_data.data(), true);
}
if (!rhs->m_frames.isEmpty()) {
FramesHash::const_iterator it = rhs->m_frames.constBegin();
FramesHash::const_iterator end = rhs->m_frames.constEnd();
for (; it != end; ++it) {
DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true));
m_frames.insert(it.key(), data);
}
}
m_nextFreeFrameId = rhs->m_nextFreeFrameId;
}
if (rhs->m_lodData) {
m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true));
}
}
void prepareClone(KisPaintDeviceSP src)
{
prepareCloneImpl(src, src->m_d->currentData());
Q_ASSERT(fastBitBltPossible(src));
}
bool fastBitBltPossible(KisPaintDeviceSP src)
{
return fastBitBltPossibleImpl(src->m_d->currentData());
}
int currentFrameId() const
{
KIS_ASSERT_RECOVER(contentChannel) {
return -1;
}
return !defaultBounds->currentLevelOfDetail() ?
contentChannel->frameIdAt(defaultBounds->currentTime()) :
-1;
}
KisDataManagerSP frameDataManager(int frameId) const
{
DataSP data = m_frames[frameId];
return data->dataManager();
}
void invalidateFrameCache(int frameId)
{
DataSP data = m_frames[frameId];
return data->cache()->invalidate();
}
private:
typedef KisPaintDeviceData Data;
typedef QSharedPointer DataSP;
typedef QHash FramesHash;
class FrameInsertionCommand : public KUndo2Command
{
public:
FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand)
: KUndo2Command(parentCommand),
m_hash(hash),
m_data(data),
m_frameId(frameId),
m_insert(insert)
{
}
void redo() override
{
doSwap(m_insert);
}
void undo() override
{
doSwap(!m_insert);
}
private:
void doSwap(bool insert)
{
if (insert) {
m_hash->insert(m_frameId, m_data);
} else {
DataSP deletedData = m_hash->take(m_frameId);
}
}
private:
FramesHash *m_hash;
DataSP m_data;
int m_frameId;
bool m_insert;
};
public:
int getNextFrameId() {
int frameId = 0;
while (m_frames.contains(frameId = m_nextFreeFrameId++));
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId));
return frameId;
}
int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand)
{
KIS_ASSERT_RECOVER(parentCommand) {
return -1;
}
DataSP data;
bool initialFrame = false;
if (m_frames.isEmpty()) {
/**
* Here we move the contents of the paint device to the
* new frame and clear m_data to make the "background" for
* the areas where there is no frame at all.
*/
data = toQShared(new Data(m_data.data(), true));
m_data->dataManager()->clear();
m_data->cache()->invalidate();
initialFrame = true;
} else if (copy) {
DataSP srcData = m_frames[copySrc];
data = toQShared(new Data(srcData.data(), true));
} else {
DataSP srcData = m_frames.begin().value();
data = toQShared(new Data(srcData.data(), false));
}
if (!initialFrame && !copy) {
data->setX(offset.x());
data->setY(offset.y());
}
int frameId = getNextFrameId();
KUndo2Command *cmd =
new FrameInsertionCommand(&m_frames,
data,
frameId, true,
parentCommand);
cmd->redo();
return frameId;
}
void deleteFrame(int frame, KUndo2Command *parentCommand)
{
KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame));
KIS_ASSERT_RECOVER_RETURN(parentCommand);
DataSP deletedData = m_frames[frame];
KUndo2Command *cmd =
new FrameInsertionCommand(&m_frames,
deletedData,
frame, false,
parentCommand);
cmd->redo();
}
QRect frameBounds(int frameId)
{
DataSP data = m_frames[frameId];
QRect extent = data->dataManager()->extent();
extent.translate(data->x(), data->y());
return extent;
}
QPoint frameOffset(int frameId) const
{
DataSP data = m_frames[frameId];
return QPoint(data->x(), data->y());
}
void setFrameOffset(int frameId, const QPoint &offset)
{
DataSP data = m_frames[frameId];
data->setX(offset.x());
data->setY(offset.y());
}
const QList frameIds() const
{
return m_frames.keys();
}
bool readFrame(QIODevice *stream, int frameId)
{
bool retval = false;
DataSP data = m_frames[frameId];
retval = data->dataManager()->read(stream);
data->cache()->invalidate();
return retval;
}
bool writeFrame(KisPaintDeviceWriter &store, int frameId)
{
DataSP data = m_frames[frameId];
return data->dataManager()->write(store);
}
void setFrameDefaultPixel(const KoColor &defPixel, int frameId)
{
DataSP data = m_frames[frameId];
KoColor color(defPixel);
color.convertTo(data->colorSpace());
data->dataManager()->setDefaultPixel(color.data());
}
KoColor frameDefaultPixel(int frameId) const
{
DataSP data = m_frames[frameId];
return KoColor(data->dataManager()->defaultPixel(),
data->colorSpace());
}
void fetchFrame(int frameId, KisPaintDeviceSP targetDevice);
void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice);
void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice);
void uploadFrameData(DataSP srcData, DataSP dstData);
struct LodDataStructImpl;
LodDataStruct* createLodDataStruct(int lod);
void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect);
void uploadLodDataStruct(LodDataStruct *dst);
QRegion regionForLodSyncing() const;
+ void updateLodDataManager(KisDataManager *srcDataManager,
+ KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset,
+ const QRect &originalRect, int lod);
+
+ void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod);
+
void tesingFetchLodDevice(KisPaintDeviceSP targetDevice);
private:
qint64 estimateDataSize(Data *data) const {
const QRect &rc = data->dataManager()->extent();
return rc.width() * rc.height() * data->colorSpace()->pixelSize();
}
public:
void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const {
imageData = 0;
temporaryData = 0;
lodData = 0;
if (m_data) {
imageData += estimateDataSize(m_data.data());
}
if (m_lodData) {
lodData += estimateDataSize(m_lodData.data());
}
if (m_externalFrameData) {
temporaryData += estimateDataSize(m_externalFrameData.data());
}
Q_FOREACH (DataSP value, m_frames.values()) {
imageData += estimateDataSize(value.data());
}
}
private:
QRegion syncWholeDevice(Data *srcData);
inline DataSP currentFrameData() const
{
DataSP data;
const int numberOfFrames = contentChannel->keyframeCount();
if (numberOfFrames > 1) {
int frameId = contentChannel->frameIdAt(defaultBounds->currentTime());
if (frameId == -1) {
data = m_data;
} else {
KIS_ASSERT_RECOVER(m_frames.contains(frameId)) {
return m_frames.begin().value();
}
data = m_frames[frameId];
}
} else if (numberOfFrames == 1) {
data = m_frames.begin().value();
} else {
data = m_data;
}
return data;
}
inline Data* currentNonLodData() const
{
Data *data = m_data.data();
if (contentChannel) {
data = currentFrameData().data();
} else if (isProjectionDevice && defaultBounds->externalFrameActive()) {
if (!m_externalFrameData) {
QMutexLocker l(&m_dataSwitchLock);
if (!m_externalFrameData) {
m_externalFrameData.reset(new Data(m_data.data(), false));
}
}
data = m_externalFrameData.data();
}
return data;
}
inline void ensureLodDataPresent() const
{
if (!m_lodData) {
Data *srcData = currentNonLodData();
QMutexLocker l(&m_dataSwitchLock);
if (!m_lodData) {
m_lodData.reset(new Data(srcData, false));
}
}
}
inline Data* currentData() const
{
Data *data;
if (defaultBounds->currentLevelOfDetail()) {
ensureLodDataPresent();
data = m_lodData.data();
} else {
data = currentNonLodData();
}
return data;
}
void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData)
{
currentData()->prepareClone(srcData);
q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace()));
q->setDefaultBounds(src->defaultBounds());
}
bool fastBitBltPossibleImpl(Data *srcData)
{
return x() == srcData->x() && y() == srcData->y() &&
*colorSpace() == *srcData->colorSpace();
}
QList allDataObjects() const
{
QList dataObjects;
if (m_frames.isEmpty()) {
dataObjects << m_data.data();
}
dataObjects << m_lodData.data();
dataObjects << m_externalFrameData.data();
Q_FOREACH (DataSP value, m_frames.values()) {
dataObjects << value.data();
}
return dataObjects;
}
void transferFromData(Data *data, KisPaintDeviceSP targetDevice);
struct Q_DECL_HIDDEN StrategyPolicy;
typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator;
typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator;
private:
friend class KisPaintDeviceFramesInterface;
private:
DataSP m_data;
mutable QScopedPointer m_lodData;
mutable QScopedPointer m_externalFrameData;
mutable QMutex m_dataSwitchLock;
FramesHash m_frames;
int m_nextFreeFrameId;
};
const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds();
#include "kis_paint_device_strategies.h"
KisPaintDevice::Private::Private(KisPaintDevice *paintDevice)
: q(paintDevice),
basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)),
isProjectionDevice(false),
m_data(new Data(paintDevice)),
m_nextFreeFrameId(0)
{
}
KisPaintDevice::Private::~Private()
{
m_frames.clear();
}
KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy()
{
if (!defaultBounds->wrapAroundMode()) {
return basicStrategy.data();
}
const QRect wrapRect = defaultBounds->bounds();
if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) {
QMutexLocker locker(&m_wrappedStrategyMutex);
if (!wrappedStrategy) {
wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this));
} else if (wrappedStrategy->wrapRect() != wrapRect) {
wrappedStrategy->setWrapRect(wrapRect);
}
}
return wrappedStrategy.data();
}
struct KisPaintDevice::Private::StrategyPolicy {
StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy,
KisDataManager *dataManager, qint32 offsetX, qint32 offsetY)
: m_strategy(strategy),
m_dataManager(dataManager),
m_offsetX(offsetX),
m_offsetY(offsetY)
{
}
KisHLineConstIteratorSP createConstIterator(const QRect &rect)
{
return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY);
}
KisHLineIteratorSP createIterator(const QRect &rect)
{
return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY);
}
int pixelSize() const
{
return m_dataManager->pixelSize();
}
KisPaintDeviceStrategy *m_strategy;
KisDataManager *m_dataManager;
int m_offsetX;
int m_offsetY;
};
struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct {
LodDataStructImpl(Data *_lodData) : lodData(_lodData) {}
QScopedPointer lodData;
};
QRegion KisPaintDevice::Private::regionForLodSyncing() const
{
Data *srcData = currentNonLodData();
return srcData->dataManager()->region().translated(srcData->x(), srcData->y());
}
KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0);
Data *srcData = currentNonLodData();
Data *lodData = new Data(srcData, false);
LodDataStruct *lodStruct = new LodDataStructImpl(lodData);
int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod);
int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod);
/**
* We compare color spaces as pure pointers, because they must be
* exactly the same, since they come from the common source.
*/
if (lodData->levelOfDetail() != newLod ||
lodData->colorSpace() != srcData->colorSpace() ||
lodData->x() != expectedX ||
lodData->y() != expectedY) {
lodData->prepareClone(srcData);
lodData->setLevelOfDetail(newLod);
lodData->setX(expectedX);
lodData->setY(expectedY);
// FIXME: different kind of synchronization
}
//QRegion dirtyRegion = syncWholeDevice(srcData);
lodData->cache()->invalidate();
return lodStruct;
}
-void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect)
+void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager,
+ KisDataManager *dstDataManager,
+ const QPoint &srcOffset,
+ const QPoint &dstOffset,
+ const QRect &originalRect,
+ int lod)
{
- LodDataStructImpl *dst = dynamic_cast(_dst);
- KIS_SAFE_ASSERT_RECOVER_RETURN(dst);
-
- Data *lodData = dst->lodData.data();
- Data *srcData = currentNonLodData();
-
- const int lod = lodData->levelOfDetail();
const int srcStepSize = 1 << lod;
KIS_ASSERT_RECOVER_RETURN(lod > 0);
const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod);
const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod);
if (!srcRect.isValid() || !dstRect.isValid()) return;
KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width());
- const int pixelSize = srcData->dataManager()->pixelSize();
+ const int pixelSize = srcDataManager->pixelSize();
int rowsAccumulated = 0;
int columnsAccumulated = 0;
KoMixColorsOp *mixOp = colorSpace()->mixColorsOp();
QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]);
quint8 *blendDataPtr = blendData.data();
int blendDataOffset = 0;
const int srcCellSize = srcStepSize * srcStepSize;
const int srcCellStride = srcCellSize * pixelSize;
const int srcStepStride = srcStepSize * pixelSize;
const int srcColumnStride = (srcStepSize - 1) * srcStepStride;
QScopedArrayPointer weights(new qint16[srcCellSize]);
{
const qint16 averageWeight = qCeil(255.0 / srcCellSize);
const qint16 extraWeight = averageWeight * srcCellSize - 255;
KIS_ASSERT_RECOVER_NOOP(extraWeight == 1);
for (int i = 0; i < srcCellSize - 1; i++) {
weights[i] = averageWeight;
}
weights[srcCellSize - 1] = averageWeight - extraWeight;
}
- InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect);
- InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect);
+ InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect);
+ InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect);
int rowsRemaining = srcRect.height();
while (rowsRemaining > 0) {
int colsRemaining = srcRect.width();
while (colsRemaining > 0 && srcIntIt.nextPixel()) {
memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize);
blendDataPtr += pixelSize;
columnsAccumulated++;
if (columnsAccumulated >= srcStepSize) {
blendDataPtr += srcColumnStride;
columnsAccumulated = 0;
}
colsRemaining--;
}
rowsAccumulated++;
if (rowsAccumulated >= srcStepSize) {
// blend and write the final data
blendDataPtr = blendData.data();
int colsRemaining = dstRect.width();
while (colsRemaining > 0 && dstIntIt.nextPixel()) {
mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData());
blendDataPtr += srcCellStride;
colsRemaining--;
}
// reset counters
rowsAccumulated = 0;
blendDataPtr = blendData.data();
blendDataOffset = 0;
} else {
blendDataOffset += srcStepStride;
blendDataPtr = blendData.data() + blendDataOffset;
}
rowsRemaining--;
}
}
+void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect)
+{
+ LodDataStructImpl *dst = dynamic_cast(_dst);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(dst);
+
+ Data *lodData = dst->lodData.data();
+ Data *srcData = currentNonLodData();
+
+ const int lod = lodData->levelOfDetail();
+
+ updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(),
+ QPoint(srcData->x(), srcData->y()),
+ QPoint(lodData->x(), lodData->y()),
+ originalRect, lod);
+}
+
+void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst));
+
+ Data *srcData = currentNonLodData();
+ updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(),
+ QPoint(srcData->x(), srcData->y()),
+ QPoint(dst->x(), dst->y()),
+ originalRect, lod);
+}
+
void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst)
{
LodDataStructImpl *dst = dynamic_cast(_dst);
KIS_SAFE_ASSERT_RECOVER_RETURN(dst);
KIS_SAFE_ASSERT_RECOVER_RETURN(
dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail());
ensureLodDataPresent();
m_lodData->prepareClone(dst->lodData.data());
m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent());
}
void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice)
{
QRect extent = data->dataManager()->extent();
extent.translate(data->x(), data->y());
targetDevice->m_d->prepareCloneImpl(q, data);
targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent);
}
void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice)
{
DataSP data = m_frames[frameId];
transferFromData(data.data(), targetDevice);
}
void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice)
{
DataSP dstData = m_frames[dstFrameId];
KIS_ASSERT_RECOVER_RETURN(dstData);
DataSP srcData = srcDevice->m_d->m_frames[srcFrameId];
KIS_ASSERT_RECOVER_RETURN(srcData);
uploadFrameData(srcData, dstData);
}
void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice)
{
DataSP dstData = m_frames[dstFrameId];
KIS_ASSERT_RECOVER_RETURN(dstData);
DataSP srcData = srcDevice->m_d->m_data;
KIS_ASSERT_RECOVER_RETURN(srcData);
uploadFrameData(srcData, dstData);
}
void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData)
{
if (srcData->colorSpace() != dstData->colorSpace() &&
*srcData->colorSpace() != *dstData->colorSpace()) {
KUndo2Command tempCommand;
srcData = toQShared(new Data(srcData.data(), true));
srcData->convertDataColorSpace(dstData->colorSpace(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags(),
&tempCommand);
}
dstData->dataManager()->clear();
dstData->cache()->invalidate();
const QRect rect = srcData->dataManager()->extent();
dstData->dataManager()->bitBltRough(srcData->dataManager(), rect);
dstData->setX(srcData->x());
dstData->setY(srcData->y());
}
void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice)
{
Data *data = m_lodData.data();
Q_ASSERT(data);
transferFromData(data, targetDevice);
}
KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
class DeviceChangeColorSpaceCommand : public KUndo2Command
{
public:
DeviceChangeColorSpaceCommand(KisPaintDeviceSP device)
: m_firstRun(true),
m_device(device)
{
}
void emitNotifications()
{
m_device->emitColorSpaceChanged();
m_device->setDirty();
}
void redo() override
{
KUndo2Command::redo();
if (!m_firstRun) {
m_firstRun = false;
return;
}
emitNotifications();
}
void undo() override
{
KUndo2Command::undo();
emitNotifications();
}
private:
bool m_firstRun;
KisPaintDeviceSP m_device;
};
KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q);
QList dataObjects = allDataObjects();
Q_FOREACH (Data *data, dataObjects) {
if (!data) continue;
data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand);
}
if (!parentCommand->childCount()) {
delete parentCommand;
parentCommand = 0;
} else {
q->emitColorSpaceChanged();
}
return parentCommand;
}
bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile)
{
if (!profile) return false;
const KoColorSpace *dstColorSpace =
KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
if (!dstColorSpace) return false;
QList dataObjects = allDataObjects();
Q_FOREACH (Data *data, dataObjects) {
if (!data) continue;
data->assignColorSpace(dstColorSpace);
}
q->emitProfileChanged();
// no undo information is provided here
return true;
}
void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel)
{
QList dataObjects = allDataObjects();
Q_FOREACH (Data *data, dataObjects) {
if (!data) continue;
KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel);
data->init(cs, dataManager);
}
}
KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, m_d(new Private(this))
{
init(colorSpace, new KisDefaultBounds(), 0, name);
}
KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name)
: QObject(0)
, m_d(new Private(this))
{
init(colorSpace, defaultBounds, parent, name);
}
void KisPaintDevice::init(const KoColorSpace *colorSpace,
KisDefaultBoundsBaseSP defaultBounds,
KisNodeWSP parent, const QString& name)
{
Q_ASSERT(colorSpace);
setObjectName(name);
// temporary def. bounds object for the initialization phase only
m_d->defaultBounds = m_d->transitionalDefaultBounds;
if (!defaultBounds) {
// Reuse transitionalDefaultBounds here. Change if you change
// semantics of transitionalDefaultBounds
defaultBounds = m_d->transitionalDefaultBounds;
}
QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]);
colorSpace->fromQColor(Qt::transparent, defaultPixel.data());
m_d->init(colorSpace, defaultPixel.data());
Q_ASSERT(m_d->colorSpace());
setDefaultBounds(defaultBounds);
setParentNode(parent);
}
KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode)
: QObject()
, KisShared()
, m_d(new Private(this))
{
if (this != &rhs) {
// temporary def. bounds object for the initialization phase only
m_d->defaultBounds = m_d->transitionalDefaultBounds;
// copy data objects with or without frames
m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames);
if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) {
KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface);
KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel);
m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this));
m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this));
}
setDefaultBounds(rhs.m_d->defaultBounds);
setParentNode(newParentNode);
}
}
KisPaintDevice::~KisPaintDevice()
{
delete m_d;
}
void KisPaintDevice::setProjectionDevice(bool value)
{
m_d->isProjectionDevice = value;
}
void KisPaintDevice::prepareClone(KisPaintDeviceSP src)
{
m_d->prepareClone(src);
Q_ASSERT(fastBitBltPossible(src));
}
void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect)
{
prepareClone(src);
// we guarantee that *this is totally empty, so copy pixels that
// are areally present on the source image only
const QRect optimizedRect = rect & src->extent();
fastBitBlt(src, optimizedRect);
}
void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect)
{
prepareClone(src);
// we guarantee that *this is totally empty, so copy pixels that
// are areally present on the source image only
const QRect optimizedRect = minimalRect & src->extent();
fastBitBltRough(src, optimizedRect);
}
void KisPaintDevice::setDirty()
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty();
}
void KisPaintDevice::setDirty(const QRect & rc)
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty(rc);
}
void KisPaintDevice::setDirty(const QRegion & region)
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty(region);
}
void KisPaintDevice::setDirty(const QVector rects)
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty(rects);
}
void KisPaintDevice::requestTimeSwitch(int time)
{
if (m_d->parent.isValid()) {
m_d->parent->requestTimeSwitch(time);
}
}
int KisPaintDevice::sequenceNumber() const
{
return m_d->cache()->sequenceNumber();
}
void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const
{
m_d->estimateMemoryStats(imageData, temporaryData, lodData);
}
void KisPaintDevice::setParentNode(KisNodeWSP parent)
{
m_d->parent = parent;
}
// for testing purposes only
KisNodeWSP KisPaintDevice::parentNode() const
{
return m_d->parent;
}
void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds)
{
m_d->defaultBounds = defaultBounds;
m_d->cache()->invalidate();
}
KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const
{
return m_d->defaultBounds;
}
void KisPaintDevice::moveTo(const QPoint &pt)
{
m_d->currentStrategy()->move(pt);
m_d->cache()->invalidate();
}
QPoint KisPaintDevice::offset() const
{
return QPoint(x(), y());
}
void KisPaintDevice::moveTo(qint32 x, qint32 y)
{
moveTo(QPoint(x, y));
}
void KisPaintDevice::setX(qint32 x)
{
moveTo(QPoint(x, m_d->y()));
}
void KisPaintDevice::setY(qint32 y)
{
moveTo(QPoint(m_d->x(), y));
}
qint32 KisPaintDevice::x() const
{
return m_d->x();
}
qint32 KisPaintDevice::y() const
{
return m_d->y();
}
void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const
{
QRect rc = extent();
x = rc.x();
y = rc.y();
w = rc.width();
h = rc.height();
}
QRect KisPaintDevice::extent() const
{
return m_d->currentStrategy()->extent();
}
QRegion KisPaintDevice::region() const
{
return m_d->currentStrategy()->region();
}
QRect KisPaintDevice::nonDefaultPixelArea() const
{
return m_d->cache()->nonDefaultPixelArea();
}
QRect KisPaintDevice::exactBounds() const
{
return m_d->cache()->exactBounds();
}
QRect KisPaintDevice::exactBoundsAmortized() const
{
return m_d->cache()->exactBoundsAmortized();
}
namespace Impl
{
struct CheckFullyTransparent {
CheckFullyTransparent(const KoColorSpace *colorSpace)
: m_colorSpace(colorSpace)
{
}
bool isPixelEmpty(const quint8 *pixelData)
{
return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8;
}
private:
const KoColorSpace *m_colorSpace;
};
struct CheckNonDefault {
CheckNonDefault(int pixelSize, const quint8 *defaultPixel)
: m_pixelSize(pixelSize),
m_defaultPixel(defaultPixel)
{
}
bool isPixelEmpty(const quint8 *pixelData)
{
return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0;
}
private:
int m_pixelSize;
const quint8 *m_defaultPixel;
};
template
QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp)
{
if (startRect == endRect) return startRect;
// the passed extent might have weird invalid structure that
// can overflow integer precision when calling startRect.right()
if (!startRect.isValid()) return QRect();
// Solution n°2
int x, y, w, h;
int boundLeft, boundTop, boundRight, boundBottom;
int endDirN, endDirE, endDirS, endDirW;
startRect.getRect(&x, &y, &w, &h);
if (endRect.isEmpty()) {
endDirS = startRect.bottom();
endDirN = startRect.top();
endDirE = startRect.right();
endDirW = startRect.left();
startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom);
} else {
endDirS = endRect.top() - 1;
endDirN = endRect.bottom() + 1;
endDirE = endRect.left() - 1;
endDirW = endRect.right() + 1;
endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom);
}
// XXX: a small optimization is possible by using H/V line iterators in the first
// and third cases, at the cost of making the code a bit more complex
KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y);
bool found = false;
{
for (qint32 y2 = y; y2 <= endDirS; ++y2) {
for (qint32 x2 = x; x2 < x + w || found; ++ x2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundTop = y2;
found = true;
break;
}
}
if (found) break;
}
}
/**
* If the first pass hasn't found any opaque pixel, there is no
* reason to check that 3 more times. They will not appear in the
* meantime. Just return an empty bounding rect.
*/
if (!found && endRect.isEmpty()) {
return QRect();
}
found = false;
for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) {
for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundBottom = y2;
found = true;
break;
}
}
if (found) break;
}
found = false;
{
for (qint32 x2 = x; x2 <= endDirE ; ++x2) {
for (qint32 y2 = y; y2 < y + h || found; ++y2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundLeft = x2;
found = true;
break;
}
}
if (found) break;
}
}
found = false;
// Look for right edge )
{
for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) {
for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundRight = x2;
found = true;
break;
}
}
if (found) break;
}
}
return QRect(boundLeft, boundTop,
boundRight - boundLeft + 1,
boundBottom - boundTop + 1);
}
}
QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const
{
QRect startRect = extent();
QRect endRect;
quint8 defaultOpacity = defaultPixel().opacityU8();
if (defaultOpacity != OPACITY_TRANSPARENT_U8) {
if (!nonDefaultOnly) {
/**
* We will calculate exact bounds only outside of the
* image bounds, and that'll be nondefault area only.
*/
endRect = defaultBounds()->bounds();
nonDefaultOnly = true;
} else {
startRect = region().boundingRect();
}
}
if (nonDefaultOnly) {
const KoColor defaultPixel = this->defaultPixel();
Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data());
endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp);
} else {
Impl::CheckFullyTransparent compareOp(m_d->colorSpace());
endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp);
}
return endRect;
}
QRegion KisPaintDevice::regionExact() const
{
QRegion resultRegion;
QVector rects = region().rects();
const KoColor defaultPixel = this->defaultPixel();
Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data());
Q_FOREACH (const QRect &rc1, rects) {
const int patchSize = 64;
QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize));
Q_FOREACH (const QRect &rc2, smallerRects) {
const QRect result =
Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp);
if (!result.isEmpty()) {
resultRegion += result;
}
}
}
return resultRegion;
}
void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h)
{
crop(QRect(x, y, w, h));
}
void KisPaintDevice::crop(const QRect &rect)
{
m_d->currentStrategy()->crop(rect);
}
void KisPaintDevice::purgeDefaultPixels()
{
KisDataManagerSP dm = m_d->dataManager();
dm->purge(dm->extent());
}
void KisPaintDevice::setDefaultPixel(const KoColor &defPixel)
{
KoColor color(defPixel);
color.convertTo(colorSpace());
m_d->dataManager()->setDefaultPixel(color.data());
m_d->cache()->invalidate();
}
KoColor KisPaintDevice::defaultPixel() const
{
return KoColor(m_d->dataManager()->defaultPixel(), colorSpace());
}
void KisPaintDevice::clear()
{
m_d->dataManager()->clear();
m_d->cache()->invalidate();
}
void KisPaintDevice::clear(const QRect & rc)
{
m_d->currentStrategy()->clear(rc);
}
void KisPaintDevice::fill(const QRect & rc, const KoColor &color)
{
KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace());
m_d->currentStrategy()->fill(rc, color.data());
}
void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel)
{
m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel);
}
bool KisPaintDevice::write(KisPaintDeviceWriter &store)
{
return m_d->dataManager()->write(store);
}
bool KisPaintDevice::read(QIODevice *stream)
{
bool retval;
retval = m_d->dataManager()->read(stream);
m_d->cache()->invalidate();
return retval;
}
void KisPaintDevice::emitColorSpaceChanged()
{
emit colorSpaceChanged(m_d->colorSpace());
}
void KisPaintDevice::emitProfileChanged()
{
emit profileChanged(m_d->colorSpace()->profile());
}
KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags);
return command;
}
bool KisPaintDevice::setProfile(const KoColorProfile * profile)
{
return m_d->assignProfile(profile);
}
KisDataManagerSP KisPaintDevice::dataManager() const
{
return m_d->dataManager();
}
void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile,
qint32 offsetX, qint32 offsetY)
{
QImage image = _image;
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
// Don't convert if not no profile is given and both paint dev and qimage are rgba.
if (!profile && colorSpace()->id() == "RGBA") {
writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height());
} else {
try {
quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()];
KoColorSpaceRegistry::instance()
->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile)
->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
writeBytes(dstData, offsetX, offsetY, image.width(), image.height());
delete[] dstData;
} catch (std::bad_alloc) {
warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes";
return;
}
}
m_d->cache()->invalidate();
}
QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
qint32 x1;
qint32 y1;
qint32 w;
qint32 h;
QRect rc = exactBounds();
x1 = rc.x();
y1 = rc.y();
w = rc.width();
h = rc.height();
return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags);
}
QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile,
const QRect &rc,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
return convertToQImage(dstProfile,
rc.x(), rc.y(), rc.width(), rc.height(),
renderingIntent, conversionFlags);
}
QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
if (w < 0)
return QImage();
if (h < 0)
return QImage();
quint8 *data = 0;
try {
data = new quint8 [w * h * pixelSize()];
} catch (std::bad_alloc) {
warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize();
//delete[] data; // data is not allocated, so don't free it
return QImage();
}
Q_CHECK_PTR(data);
// XXX: Is this really faster than converting line by line and building the QImage directly?
// This copies potentially a lot of data.
readBytes(data, x1, y1, w, h);
QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags);
delete[] data;
return image;
}
inline bool moveBy(KisSequentialConstIterator& iter, int numPixels)
{
int pos = 0;
while (pos < numPixels) {
int step = std::min(iter.nConseqPixels(), numPixels - pos);
if (!iter.nextPixels(step))
return false;
pos += step;
}
return true;
}
static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect)
{
KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace());
qint32 pixelSize = srcDev->pixelSize();
KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0);
KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0);
for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) {
qint32 iY = srcY0 + (y * srcHeight) / h;
for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) {
qint32 iX = srcX0 + (x * srcWidth) / w;
srcIter->moveTo(iX, iY);
dstIter->moveTo(x, y);
memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize);
}
}
return thumbnail;
}
QSize fixThumbnailSize(QSize size)
{
if (!size.width() && size.height()) {
size.setWidth(1);
}
if (size.width() && !size.height()) {
size.setHeight(1);
}
return size;
}
KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const
{
QSize thumbnailSize(w, h);
QRect imageRect = rect.isValid() ? rect : extent();
if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) {
thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio);
}
thumbnailSize = fixThumbnailSize(thumbnailSize);
//can't create thumbnail for an empty device, e.g. layer thumbnail for empty image
if (imageRect.isEmpty() || thumbnailSize.isEmpty()) {
return new KisPaintDevice(colorSpace());
}
int srcWidth, srcHeight;
int srcX0, srcY0;
imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight);
if (!outputRect.isValid()) {
outputRect = QRect(0, 0, w, h);
}
KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(),
thumbnailSize.width(), thumbnailSize.height(), outputRect);
return thumbnail;
}
KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const
{
QSize thumbnailSize(w, h);
qreal oversampleAdjusted = qMax(oversample, 1.);
QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize;
QRect outputRect;
QRect imageRect = rect.isValid() ? rect : extent();
qint32 hstart = thumbnailOversampledSize.height();
if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) {
thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio);
}
thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize);
//can't create thumbnail for an empty device, e.g. layer thumbnail for empty image
if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) {
return new KisPaintDevice(colorSpace());
}
oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size
outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height());
if (outputTileRect.isValid()) {
//compensating output rectangle for oversampling
outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight());
outputRect = outputRect.intersected(outputTileRect);
}
KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(),
thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect);
if (oversample != 1. && oversampleAdjusted != 1.) {
KoDummyUpdater updater;
KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
&updater, KisFilterStrategyRegistry::instance()->value("Bilinear"));
worker.run();
}
return thumbnail;
}
QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
QSize size = fixThumbnailSize(QSize(w, h));
KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect);
QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags);
return thumbnail;
}
QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
QSize size = fixThumbnailSize(QSize(w, h));
return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags);
}
KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
{
m_d->cache()->invalidate();
return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y());
}
KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
{
return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y());
}
KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w)
{
m_d->cache()->invalidate();
return m_d->currentStrategy()->createVLineIteratorNG(x, y, w);
}
KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
{
return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w);
}
KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const
{
return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator());
}
KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const
{
return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator());
}
KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y)
{
m_d->cache()->invalidate();
return m_d->currentStrategy()->createRandomAccessorNG(x, y);
}
KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const
{
return m_d->currentStrategy()->createRandomConstAccessorNG(x, y);
}
KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const
{
KisPaintDevice* pd = const_cast(this);
return new KisRandomSubAccessor(pd);
}
void KisPaintDevice::clearSelection(KisSelectionSP selection)
{
const KoColorSpace *colorSpace = m_d->colorSpace();
QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds();
if (r.isValid()) {
KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width());
KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width());
const KoColor defaultPixel = this->defaultPixel();
bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8);
for (qint32 y = 0; y < r.height(); y++) {
do {
// XXX: Optimize by using stretches
colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1);
if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) {
memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize());
}
} while (devIt->nextPixel() && selectionIt->nextPixel());
devIt->nextRow();
selectionIt->nextRow();
}
m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y()));
setDirty(r);
}
}
bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const
{
KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1);
const quint8 *pix = iter->rawDataConst();
if (!pix) return false;
colorSpace()->toQColor(pix, c);
return true;
}
bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const
{
KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1);
const quint8 *pix = iter->rawDataConst();
if (!pix) return false;
kc->setColor(pix, m_d->colorSpace());
return true;
}
bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c)
{
KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1);
colorSpace()->fromQColor(c, iter->rawData());
m_d->cache()->invalidate();
return true;
}
bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc)
{
const quint8 * pix;
KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1);
if (kc.colorSpace() != m_d->colorSpace()) {
KoColor kc2(kc, m_d->colorSpace());
pix = kc2.data();
memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize());
} else {
pix = kc.data();
memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize());
}
m_d->cache()->invalidate();
return true;
}
bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src)
{
return m_d->fastBitBltPossible(src);
}
void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBlt(src, rect);
}
void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBltOldData(src, rect);
}
void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBltRough(src, rect);
}
void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBltRoughOldData(src, rect);
}
void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const
{
readBytes(data, QRect(x, y, w, h));
}
void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const
{
m_d->currentStrategy()->readBytes(data, rect);
}
void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
{
writeBytes(data, QRect(x, y, w, h));
}
void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect)
{
m_d->currentStrategy()->writeBytes(data, rect);
}
QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const
{
return m_d->currentStrategy()->readPlanarBytes(x, y, w, h);
}
void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h)
{
m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h);
}
quint32 KisPaintDevice::pixelSize() const
{
quint32 _pixelSize = m_d->colorSpace()->pixelSize();
Q_ASSERT(_pixelSize > 0);
return _pixelSize;
}
quint32 KisPaintDevice::channelCount() const
{
quint32 _channelCount = m_d->colorSpace()->channelCount();
Q_ASSERT(_channelCount > 0);
return _channelCount;
}
KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id)
{
Q_ASSERT(!m_d->framesInterface);
m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this));
Q_ASSERT(!m_d->contentChannel);
m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds));
// Raster channels always have at least one frame (representing a static image)
KUndo2Command tempParentCommand;
m_d->contentChannel->addKeyframe(0, &tempParentCommand);
return m_d->contentChannel.data();
}
KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const
{
Q_ASSERT(m_d->contentChannel);
return m_d->contentChannel.data();
}
const KoColorSpace* KisPaintDevice::colorSpace() const
{
Q_ASSERT(m_d->colorSpace() != 0);
return m_d->colorSpace();
}
KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const
{
KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace());
device->setDefaultBounds(defaultBounds());
return device;
}
KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const
{
KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource);
clone->setDefaultBounds(defaultBounds());
clone->convertTo(compositionSourceColorSpace(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return clone;
}
KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const
{
KisPaintDeviceSP clone = new KisPaintDevice(colorSpace());
clone->setDefaultBounds(defaultBounds());
clone->makeCloneFromRough(cloneSource, roughRect);
clone->convertTo(compositionSourceColorSpace(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return clone;
}
KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const
{
return new KisFixedPaintDevice(compositionSourceColorSpace());
}
const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const
{
return colorSpace();
}
QVector KisPaintDevice::channelSizes() const
{
QVector sizes;
QList channels = colorSpace()->channels();
std::sort(channels.begin(), channels.end());
Q_FOREACH (KoChannelInfo * channelInfo, channels) {
sizes.append(channelInfo->size());
}
return sizes;
}
KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject()
{
KisDataManager::releaseInternalPools();
}
KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject()
{
return new MemoryReleaseObject();
}
KisPaintDevice::LodDataStruct::~LodDataStruct()
{
}
QRegion KisPaintDevice::regionForLodSyncing() const
{
return m_d->regionForLodSyncing();
}
KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod)
{
return m_d->createLodDataStruct(lod);
}
void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect)
{
m_d->updateLodDataStruct(dst, srcRect);
}
void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst)
{
m_d->uploadLodDataStruct(dst);
}
+void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod)
+{
+ m_d->generateLodCloneDevice(dst, originalRect, lod);
+}
+
KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface()
{
return m_d->framesInterface.data();
}
/******************************************************************/
/* KisPaintDeviceFramesInterface */
/******************************************************************/
KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice)
: q(parentDevice)
{
}
QList KisPaintDeviceFramesInterface::frames()
{
return q->m_d->frameIds();
}
int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand)
{
return q->m_d->createFrame(copy, copySrc, offset, parentCommand);
}
void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand)
{
return q->m_d->deleteFrame(frame, parentCommand);
}
void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice)
{
q->m_d->fetchFrame(frameId, targetDevice);
}
void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice)
{
q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice);
}
void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice)
{
q->m_d->uploadFrame(dstFrameId, srcDevice);
}
QRect KisPaintDeviceFramesInterface::frameBounds(int frameId)
{
return q->m_d->frameBounds(frameId);
}
QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const
{
return q->m_d->frameOffset(frameId);
}
void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId)
{
KIS_ASSERT_RECOVER_RETURN(frameId >= 0);
q->m_d->setFrameDefaultPixel(defPixel, frameId);
}
KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return KoColor(Qt::red, q->m_d->colorSpace());
}
return q->m_d->frameDefaultPixel(frameId);
}
bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId)
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return false;
}
return q->m_d->writeFrame(store, frameId);
}
bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId)
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return false;
}
return q->m_d->readFrame(stream, frameId);
}
int KisPaintDeviceFramesInterface::currentFrameId() const
{
return q->m_d->currentFrameId();
}
KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return q->m_d->dataManager();
}
return q->m_d->frameDataManager(frameId);
}
void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId)
{
KIS_ASSERT_RECOVER_RETURN(frameId >= 0);
return q->m_d->invalidateFrameCache(frameId);
}
void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset)
{
KIS_ASSERT_RECOVER_RETURN(frameId >= 0);
return q->m_d->setFrameOffset(frameId, offset);
}
KisPaintDeviceFramesInterface::TestingDataObjects
KisPaintDeviceFramesInterface::testingGetDataObjects() const
{
TestingDataObjects objects;
objects.m_data = q->m_d->m_data.data();
objects.m_lodData = q->m_d->m_lodData.data();
objects.m_externalFrameData = q->m_d->m_externalFrameData.data();
typedef KisPaintDevice::Private::FramesHash FramesHash;
FramesHash::const_iterator it = q->m_d->m_frames.constBegin();
FramesHash::const_iterator end = q->m_d->m_frames.constEnd();
for (; it != end; ++it) {
objects.m_frames.insert(it.key(), it.value().data());
}
objects.m_currentData = q->m_d->currentData();
return objects;
}
QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const
{
return q->m_d->allDataObjects();
}
void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice)
{
m_d->tesingFetchLodDevice(targetDevice);
}
diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h
index 674ee3138d..9a4c833391 100644
--- a/libs/image/kis_paint_device.h
+++ b/libs/image/kis_paint_device.h
@@ -1,878 +1,880 @@
/*
* Copyright (c) 2002 patrick julien
* Copyright (c) 2006 Boudewijn Rempt
*
* 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 KIS_PAINT_DEVICE_IMPL_H_
#define KIS_PAINT_DEVICE_IMPL_H_
#include
#include
#include
#include "kis_debug.h"
#include
#include "kis_types.h"
#include "kis_shared.h"
#include "kis_default_bounds_base.h"
#include
class KUndo2Command;
class QRect;
class QImage;
class QPoint;
class QString;
class QColor;
class QIODevice;
class KoColor;
class KoColorSpace;
class KoColorProfile;
class KisDataManager;
class KisPaintDeviceWriter;
class KisKeyframe;
class KisRasterKeyframeChannel;
class KisPaintDeviceFramesInterface;
typedef KisSharedPtr KisDataManagerSP;
namespace KritaUtils {
enum DeviceCopyMode {
CopySnapshot = 0,
CopyAllFrames
};
}
/**
* A paint device contains the actual pixel data and offers methods
* to read and write pixels. A paint device has an integer x, y position
* (it is not positioned on the image with sub-pixel accuracy).
* A KisPaintDevice doesn't have any fixed size, the size changes dynamically
* when pixels are accessed by an iterator.
*/
class KRITAIMAGE_EXPORT KisPaintDevice
: public QObject
, public KisShared
{
Q_OBJECT
public:
/**
* Create a new paint device with the specified colorspace.
*
* @param colorSpace the colorspace of this paint device
* @param name for debugging purposes
*/
explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString());
/**
* Create a new paint device with the specified colorspace. The
* parent node will be notified of changes to this paint device.
*
* @param parent the node that contains this paint device
* @param colorSpace the colorspace of this paint device
* @param defaultBounds boundaries of the device in case it is empty
* @param name for debugging purposes
*/
KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString());
/**
* Creates a copy of this device.
*
* If \p copyMode is CopySnapshot, the newly created device clones the
* current frame of \p rhs only (default and efficient
* behavior). If \p copyFrames is CopyAllFrames, the new device is a deep
* copy of the source with all the frames included.
*/
KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0);
~KisPaintDevice() override;
protected:
/**
* A special constructor for usage in KisPixelSelection. It allows
* two paint devices to share a data manager.
*
* @param explicitDataManager data manager to use inside paint device
* @param src source paint device to copy parameters from
* @param name for debugging purposes
*/
KisPaintDevice(KisDataManagerSP explicitDataManager,
KisPaintDeviceSP src, const QString& name = QString());
public:
/**
* Write the pixels of this paint device into the specified file store.
*/
bool write(KisPaintDeviceWriter &store);
/**
* Fill this paint device with the pixels from the specified file store.
*/
bool read(QIODevice *stream);
public:
/**
* set the parent node of the paint device
*/
void setParentNode(KisNodeWSP parent);
/**
* set the default bounds for the paint device when
* the default pixel is not completely transparent
*/
void setDefaultBounds(KisDefaultBoundsBaseSP bounds);
/**
* the default bounds rect of the paint device
*/
KisDefaultBoundsBaseSP defaultBounds() const;
/**
* Moves the device to these new coordinates (no incremental move)
*/
void moveTo(qint32 x, qint32 y);
/**
* Convenience method for the above.
*/
virtual void moveTo(const QPoint& pt);
/**
* Return an X,Y offset of the device in a convenient form
*/
QPoint offset() const;
/**
* The X offset of the paint device
*/
qint32 x() const;
/**
* The Y offset of the paint device
*/
qint32 y() const;
/**
* set the X offset of the paint device
*/
void setX(qint32 x);
/**
* set the Y offset of the paint device
*/
void setY(qint32 y);
/**
* Retrieve the bounds of the paint device. The size is not exact,
* but may be larger if the underlying datamanager works that way.
* For instance, the tiled datamanager keeps the extent to the nearest
* multiple of 64.
*
* If default pixel is not transparent, then the actual extent
* rect is united with the defaultBounds()->bounds() value
* (the size of the image, usually).
*/
QRect extent() const;
/// Convenience method for the above
void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const;
/**
* Get the exact bounds of this paint device. The real solution is
* very slow because it does a linear scanline search, but it
* uses caching, so calling to this function without changing
* the device is quite cheap.
*
* Exactbounds follows these rules:
*
*
* - if default pixel is transparent, then exact bounds
* of actual pixel data are returned
*
- if default pixel is not transparent, then the union
* (defaultBounds()->bounds() | nonDefaultPixelArea()) is
* returned
*
* \see calculateExactBounds()
*/
QRect exactBounds() const;
/**
* Relaxed version of the exactBounds() that can be used in tight
* loops. If the exact bounds value is present in the paint
* device cache, returns this value. If the cache is invalidated,
* returns extent() and tries to recalculate the exact bounds not
* faster than once in 1000 ms.
*/
QRect exactBoundsAmortized() const;
/**
* Returns exact rectangle of the paint device that contains
* non-default pixels. For paint devices with fully transparent
* default pixel is equivalent to exactBounds().
*
* nonDefaultPixelArea() follows these rules:
*
*
* - if default pixel is transparent, then exact bounds
* of actual pixel data are returned. The same as exactBounds()
*
- if default pixel is not transparent, then calculates the
* rectangle of non-default pixels. May be smaller or greater
* than image bounds
*
* \see calculateExactBounds()
*/
QRect nonDefaultPixelArea() const;
/**
* Returns a rough approximation of region covered by device.
* For tiled data manager, it region will consist of a number
* of rects each corresponding to a tile.
*/
QRegion region() const;
/**
* The slow version of region() that searches for exact bounds of
* each rectangle in the region
*/
QRegion regionExact() const;
/**
* Cut the paint device down to the specified rect. If the crop
* area is bigger than the paint device, nothing will happen.
*/
void crop(qint32 x, qint32 y, qint32 w, qint32 h);
/// Convenience method for the above
void crop(const QRect & r);
/**
* Complete erase the current paint device. Its size will become 0. This
* does not take the selection into account.
*/
virtual void clear();
/**
* Clear the given rectangle to transparent black. The paint device will expand to
* contain the given rect.
*/
void clear(const QRect & rc);
/**
* Frees the memory occupied by the pixels containing default
* values. The extents() and exactBounds() of the image will
* probably also shrink
*/
void purgeDefaultPixels();
/**
* Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the
* caller still owns the pointer and needs to delete it to avoid memory leaks.
* If frame ID is given, set default pixel for that frame. Otherwise use active frame.
*/
void setDefaultPixel(const KoColor &defPixel);
/**
* Get a pointer to the default pixel.
* If the frame parameter is given, get the default pixel of
* specified frame. Otherwise use currently active frame.
*/
KoColor defaultPixel() const;
/**
* Fill the given rectangle with the given pixel. The paint device will expand to
* contain the given rect.
*/
void fill(const QRect & rc, const KoColor &color);
/**
* Overloaded function. For legacy purposes only.
* Please use fill(const QRect & rc, const KoColor &color) instead
*/
void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel);
public:
/**
* Prepares the device for fastBitBlt opreration. It clears
* the device, switches x,y shifts and colorspace if needed.
* After this call fastBitBltPossible will return true.
* May be used for initialization of temporary devices.
*/
void prepareClone(KisPaintDeviceSP src);
/**
* Make this device to become a clone of \a src. It will have the same
* x,y shifts, colorspace and will share pixels inside \a rect.
* After calling this function:
* (this->extent() >= this->exactBounds() == rect).
*
* Rule of thumb:
*
* "Use makeCloneFrom() or makeCloneFromRough() if and only if you
* are the only owner of the destination paint device and you are
* 100% sure no other thread has access to it"
*/
void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect);
/**
* Make this device to become a clone of \a src. It will have the same
* x,y shifts, colorspace and will share pixels inside \a rect.
* Be careful, this function will copy *at least* \a rect
* of pixels. Actual copy area will be a bigger - it will
* be aligned by tiles borders. So after calling this function:
* (this->extent() == this->exactBounds() >= rect).
*
* Rule of thumb:
*
* "Use makeCloneFrom() or makeCloneFromRough() if and only if you
* are the only owner of the destination paint device and you are
* 100% sure no other thread has access to it"
*/
void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect);
protected:
friend class KisPaintDeviceTest;
friend class DataReaderThread;
/**
* Checks whether a src paint device can be used as source
* of fast bitBlt operation. The result of the check may
* depend on whether color spaces coinside, whether there is
* any shift of tiles between the devices and etc.
*
* WARNING: This check must be done before performing any
* fast bitBlt operation!
*
* \see fastBitBlt
* \see fastBitBltRough
*/
bool fastBitBltPossible(KisPaintDeviceSP src);
/**
* Clones rect from another paint device. The cloned area will be
* shared between both paint devices as much as possible using
* copy-on-write. Parts of the rect that cannot be shared
* (cross tiles) are deep-copied,
*
* \see fastBitBltPossible
* \see fastBitBltRough
*/
void fastBitBlt(KisPaintDeviceSP src, const QRect &rect);
/**
* The same as \ref fastBitBlt() but reads old data
*/
void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect);
/**
* Clones rect from another paint device in a rough and fast way.
* All the tiles touched by rect will be shared, between both
* devices, that means it will copy a bigger area than was
* requested. This method is supposed to be used for bitBlt'ing
* into temporary paint devices.
*
* \see fastBitBltPossible
* \see fastBitBlt
*/
void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect);
/**
* The same as \ref fastBitBltRough() but reads old data
*/
void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect);
public:
/**
* Read the bytes representing the rectangle described by x, y, w, h into
* data. If data is not big enough, Krita will gladly overwrite the rest
* of your precious memory.
*
* Since this is a copy, you need to make sure you have enough memory.
*
* Reading from areas not previously initialized will read the default
* pixel value into data but not initialize that region.
*/
void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const;
/**
* Read the bytes representing the rectangle rect into
* data. If data is not big enough, Krita will gladly overwrite the rest
* of your precious memory.
*
* Since this is a copy, you need to make sure you have enough memory.
*
* Reading from areas not previously initialized will read the default
* pixel value into data but not initialize that region.
* @param data The address of the memory to receive the bytes read
* @param rect The rectangle in the paint device to read from
*/
void readBytes(quint8 * data, const QRect &rect) const;
/**
* Copy the bytes in data into the rect specified by x, y, w, h. If the
* data is too small or uninitialized, Krita will happily read parts of
* memory you never wanted to be read.
*
* If the data is written to areas of the paint device not previously initialized,
* the paint device will grow.
*/
void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h);
/**
* Copy the bytes in data into the rectangle rect. If the
* data is too small or uninitialized, Krita will happily read parts of
* memory you never wanted to be read.
*
* If the data is written to areas of the paint device not previously initialized,
* the paint device will grow.
* @param data The address of the memory to write bytes from
* @param rect The rectangle in the paint device to write to
*/
void writeBytes(const quint8 * data, const QRect &rect);
/**
* Copy the bytes in the paint device into a vector of arrays of bytes,
* where the number of arrays is the number of channels in the
* paint device. If the specified area is larger than the paint
* device's extent, the default pixel will be read.
*/
QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const;
/**
* Write the data in the separate arrays to the channes. If there
* are less vectors than channels, the remaining channels will not
* be copied. If any of the arrays points to 0, the channel in
* that location will not be touched. If the specified area is
* larger than the paint device, the paint device will be
* extended. There are no guards: if the area covers more pixels
* than there are bytes in the arrays, krita will happily fill
* your paint device with areas of memory you never wanted to be
* read. Krita may also crash.
*
* XXX: what about undo?
*/
void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h);
/**
* Converts the paint device to a different colorspace
*
* @return a command that can be used to undo the conversion.
*/
KUndo2Command* convertTo(const KoColorSpace * dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags());
/**
* Changes the profile of the colorspace of this paint device to the given
* profile. If the given profile is 0, nothing happens.
*/
bool setProfile(const KoColorProfile * profile);
/**
* Fill this paint device with the data from image; starting at (offsetX, offsetY)
* @param srcProfileName name of the RGB profile to interpret the image as. 0 is interpreted as sRGB
*/
void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0);
/**
* Create an RGBA QImage from a rectangle in the paint device.
*
* @param x Left coordinate of the rectangle
* @param y Top coordinate of the rectangle
* @param w Width of the rectangle in pixels
* @param h Height of the rectangle in pixels
* @param dstProfile RGB profile to use in conversion. May be 0, in which
* case it's up to the color strategy to choose a profile (most
* like sRGB).
*/
QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const;
/**
* Overridden method for convenience
*/
QImage convertToQImage(const KoColorProfile *dstProfile,
const QRect &rc,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const;
/**
* Create an RGBA QImage from a rectangle in the paint device. The
* rectangle is defined by the parent image's bounds.
*
* @param dstProfile RGB profile to use in conversion. May be 0, in which
* case it's up to the color strategy to choose a profile (most
* like sRGB).
*/
QImage convertToQImage(const KoColorProfile * dstProfile,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const;
/**
* Creates a paint device thumbnail of the paint device, retaining
* the aspect ratio. The width and height of the returned device
* won't exceed \p maxw and \p maxw, but they may be smaller.
*
* @param maxw: maximum width
* @param maxh: maximum height
* @param rect: only this rect will be used for the thumbnail
*
*/
KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const;
KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const;
/**
* Creates a thumbnail of the paint device, retaining the aspect ratio.
* The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller.
* The colors are not corrected for display!
*
* @param maxw: maximum width
* @param maxh: maximum height
* @param rect: only this rect will be used for the thumbnail
* @param oversample: ratio used for antialiasing
*/
QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags());
/**
* Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect)
*/
QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags());
/**
* Fill c and opacity with the values found at x and y.
*
* The color values will be transformed from the profile of
* this paint device to the display profile.
*
* @return true if the operation was successful.
*/
bool pixel(qint32 x, qint32 y, QColor *c) const;
/**
* Fill kc with the values found at x and y. This method differs
* from the above in using KoColor, which can be of any colorspace
*
* The color values will be transformed from the profile of
* this paint device to the display profile.
*
* @return true if the operation was successful.
*/
bool pixel(qint32 x, qint32 y, KoColor * kc) const;
/**
* Set the specified pixel to the specified color. Note that this
* bypasses KisPainter. the PaintDevice is here used as an equivalent
* to QImage, not QPixmap. This means that this is not undoable; also,
* there is no compositing with an existing value at this location.
*
* The color values will be transformed from the display profile to
* the paint device profile.
*
* Note that this will use 8-bit values and may cause a significant
* degradation when used on 16-bit or hdr quality images.
*
* @return true if the operation was successful
*/
bool setPixel(qint32 x, qint32 y, const QColor& c);
/// Convenience method for the above
bool setPixel(qint32 x, qint32 y, const KoColor& kc);
/**
* @return the colorspace of the pixels in this paint device
*/
const KoColorSpace* colorSpace() const;
/**
* There is quite a common technique in Krita. It is used in
* cases, when we want to paint something over a paint device
* using the composition, opacity or selection. E.g. painting a
* dab in a paint op, filling the selection in the Fill Tool.
* Such work is usually done in the following way:
*
* 1) Create a paint device
*
* 2) Fill it with the desired color or data
*
* 3) Create a KisPainter and set all the properties of the
* transaction: selection, compositeOp, opacity and etc.
*
* 4) Paint a newly created paint device over the destination
* device.
*
* The following two methods (createCompositionSourceDevice() or
* createCompositionSourceDeviceFixed())should be used for the
* accomplishing the step 1). The point is that the desired color
* space of the temporary device may not coincide with the color
* space of the destination. That is the case, for example, for
* the alpha8() colorspace used in the selections. So for such
* devices the temporary target would have a different (grayscale)
* color space.
*
* So there are two rules of thumb:
*
* 1) If you need a temporary device which you are going to fill
* with some data and then paint over the paint device, create
* it with either createCompositionSourceDevice() or
* createCompositionSourceDeviceFixed().
*
* 2) Do *not* expect that the color spaces of the destination and
* the temporary device would coincide. If you need to copy a
* single pixel from one device to another, you can use
* KisCrossDeviceColorPicker class, that will handle all the
* necessary conversions for you.
*
* \see createCompositionSourceDeviceFixed()
* \see compositionSourceColorSpace()
* \see KisCrossDeviceColorPicker
* \see KisCrossDeviceColorPickerInt
*/
KisPaintDeviceSP createCompositionSourceDevice() const;
/**
* The same as createCompositionSourceDevice(), but initializes the
* newly created device with the content of \p cloneSource
*
* \see createCompositionSourceDevice()
*/
KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const;
/**
* The same as createCompositionSourceDevice(), but initializes
* the newly created device with the *rough* \p roughRect of
* \p cloneSource.
*
* "Rough rect" means that it may copy a bit more than
* requested. It is expected that the caller will not use the area
* outside \p roughRect.
*
* \see createCompositionSourceDevice()
*/
KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const;
/**
* This is a convenience method for createCompositionSourceDevice()
*
* \see createCompositionSourceDevice()
*/
KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const;
/**
* This is a lowlevel method for the principle used in
* createCompositionSourceDevice(). In most of the cases the paint
* device creation methods should be used instead of this function.
*
* \see createCompositionSourceDevice()
* \see createCompositionSourceDeviceFixed()
*/
virtual const KoColorSpace* compositionSourceColorSpace() const;
/**
* @return the internal datamanager that keeps the pixels.
*/
KisDataManagerSP dataManager() const;
/**
* Replace the pixel data, color strategy, and profile.
*/
void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0);
/**
* Return the number of bytes a pixel takes.
*/
quint32 pixelSize() const;
/**
* Return the number of channels a pixel takes
*/
quint32 channelCount() const;
/**
* Create a keyframe channel for the content on this device.
* @param id identifier for the channel
* @param node the parent node for the channel
* @return keyframe channel
*/
KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id);
KisRasterKeyframeChannel* keyframeChannel() const;
/**
* An interface to modify/load/save frames stored inside this device
*/
KisPaintDeviceFramesInterface* framesInterface();
public:
/**
* Add the specified rect to the parent layer's set of dirty rects
* (if there is a parent layer)
*/
void setDirty(const QRect & rc);
/**
* Add the specified region to the parent layer's dirty region
* (if there is a parent layer)
*/
void setDirty(const QRegion & region);
/**
* Set the parent layer completely dirty, if this paint device has
* as parent layer.
*/
void setDirty();
void setDirty(const QVector rects);
/**
* Called by KisTransactionData when it thinks current time should
* be changed. And the requests is forwarded to the image if
* needed.
*/
void requestTimeSwitch(int time);
/**
* \return a sequence number corresponding to the current paint
* device state. Every time the paint device is changed,
* the sequence number is increased
*/
int sequenceNumber() const;
void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const;
public:
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w);
KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const;
KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h);
KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const;
KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y);
KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const;
/**
* Create an iterator that will "artificially" extend the paint device with the
* value of the border when trying to access values outside the range of data.
*
* @param rc indicates the rectangle that truly contains data
*/
KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const;
/**
* Create an iterator that will "artificially" extend the paint device with the
* value of the border when trying to access values outside the range of data.
*
* @param rc indicates the rectangle that trully contains data
*/
KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const;
/**
* This function create a random accessor which can easily access to sub pixel values.
* @param selection an up-to-date selection that has the same origin as the paint device
*/
KisRandomSubAccessorSP createRandomSubAccessor() const;
/** Clear the selected pixels from the paint device */
void clearSelection(KisSelectionSP selection);
Q_SIGNALS:
void profileChanged(const KoColorProfile * profile);
void colorSpaceChanged(const KoColorSpace *colorspace);
public:
friend class PaintDeviceCache;
/**
* Caclculates exact bounds of the device. Used internally
* by a transparent caching system. The solution is very slow
* because it does a linear scanline search. So the complexity
* is n*n at worst.
*
* \see exactBounds(), nonDefaultPixelArea()
*/
QRect calculateExactBounds(bool nonDefaultOnly) const;
public:
struct MemoryReleaseObject : public QObject {
~MemoryReleaseObject() override;
};
static MemoryReleaseObject* createMemoryReleaseObject();
public:
struct LodDataStruct {
virtual ~LodDataStruct();
};
QRegion regionForLodSyncing() const;
LodDataStruct* createLodDataStruct(int lod);
void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect);
void uploadLodDataStruct(LodDataStruct *dst);
+ void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod);
+
void setProjectionDevice(bool value);
void tesingFetchLodDevice(KisPaintDeviceSP targetDevice);
private:
KisPaintDevice& operator=(const KisPaintDevice&);
void init(const KoColorSpace *colorSpace,
KisDefaultBoundsBaseSP defaultBounds,
KisNodeWSP parent, const QString& name);
// Only KisPainter is allowed to have access to these low-level methods
friend class KisPainter;
/**
* Return a vector with in order the size in bytes of the channels
* in the colorspace of this paint device.
*/
QVector channelSizes() const;
void emitColorSpaceChanged();
void emitProfileChanged();
private:
friend class KisPaintDeviceFramesInterface;
protected:
friend class KisSelectionTest;
KisNodeWSP parentNode() const;
private:
struct Private;
Private * const m_d;
};
#endif // KIS_PAINT_DEVICE_IMPL_H_
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index c388f23b79..38b6e288e8 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,592 +1,599 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
${EXIV2_INCLUDE_DIR}
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_paintop_transformation_connector.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/kis_dlg_internal_color_selector.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
KisPaintopPropertiesBase.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
kis_config_notifier.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
kis_histogram_view.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
KisDecorationsManager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
KisResourceServerProvider.cpp
KisResourceBundleServerProvider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
KisActionPlugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
opengl/kis_texture_tile_info_pool.cpp
+ opengl/KisOpenGLUpdateInfoBuilder.cpp
kis_fps_decoration.cpp
recorder/kis_node_query_path_editor.cc
recorder/kis_recorded_action_creator.cc
recorder/kis_recorded_action_creator_factory.cc
recorder/kis_recorded_action_creator_factory_registry.cc
recorder/kis_recorded_action_editor_factory.cc
recorder/kis_recorded_action_editor_factory_registry.cc
recorder/kis_recorded_filter_action_editor.cc
recorder/kis_recorded_filter_action_creator.cpp
recorder/kis_recorded_paint_action_editor.cc
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/kis_recording_adapter.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
tool/strokes/KisFreehandStrokeInfo.cpp
tool/strokes/KisMaskedFreehandStrokePainter.cpp
tool/strokes/KisMaskingBrushRenderer.cpp
tool/strokes/KisMaskingBrushCompositeOpFactory.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_paintop_preset_icon_library.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_popup_button.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/KisSelectionPropertySlider.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_spinbox_color_selector.cpp
widgets/kis_screen_color_picker.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KoDualColorButton.cpp
widgets/kis_color_input.cpp
widgets/kis_color_button.cpp
widgets/KisVisualColorSelector.cpp
widgets/KisVisualColorSelectorShape.cpp
widgets/KisVisualEllipticalSelectorShape.cpp
widgets/KisVisualRectangleSelectorShape.cpp
widgets/KisVisualTriangleSelectorShape.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
input/KisInputActionGroup.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
actions/KisPasteActionFactory.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_transaction_based_command.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisCloneDocumentStroke.cpp
KisNodeDelegate.cpp
kis_node_view_visibility_delegate.cpp
KisNodeToolTip.cpp
KisNodeView.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisFilterEntry.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoActionsUpdateManager.cpp
KisView.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisPaletteModel.cpp
kis_palette_delegate.cpp
kis_palette_view.cpp
KisColorsetChooser.cpp
KisSaveGroupVisitor.cpp
KisWindowLayoutResource.cpp
KisWindowLayoutManager.cpp
KisSessionResource.cpp
KisReferenceImagesDecoration.cpp
KisReferenceImage.cpp
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
)
if(WIN32)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
qtlockedfile/qtlockedfile_win.cpp
input/wintab/kis_tablet_support_win8.cpp
opengl/kis_opengl_win.cpp
)
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
+ KisFrameDataSerializer.cpp
+ KisFrameCacheStore.cpp
+ KisFrameCacheSwapper.cpp
+ KisAbstractFrameCacheSwapper.cpp
+ KisInMemoryFrameCacheSwapper.cpp
+
input/wintab/drawpile_tablettester/tablettester.cpp
input/wintab/drawpile_tablettester/tablettest.cpp
)
if(UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support.cpp
qtlockedfile/qtlockedfile_unix.cpp
)
if(NOT APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/qxcbconnection_xi2.cpp
input/wintab/qxcbconnection.cpp
input/wintab/kis_xi2_event_filter.cpp
)
endif()
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgpaintactioneditor.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgpreseticonlibrary.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
forms/wdgsessionmanager.ui
forms/wdgnewwindowlayout.ui
brushhud/kis_dlg_brush_hud_config.ui
forms/wdgdlginternalcolorselector.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
input/wintab/drawpile_tablettester/tablettest.ui
)
QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h)
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES}
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (HAVE_KIO)
target_link_libraries(kritaui KF5::KIOCore)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$
$
$
$
$
$
$
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisAbstractFrameCacheSwapper.cpp b/libs/ui/KisAbstractFrameCacheSwapper.cpp
new file mode 100644
index 0000000000..1540d8f32b
--- /dev/null
+++ b/libs/ui/KisAbstractFrameCacheSwapper.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 "KisAbstractFrameCacheSwapper.h"
+
+KisAbstractFrameCacheSwapper::~KisAbstractFrameCacheSwapper()
+{
+}
diff --git a/libs/ui/KisAbstractFrameCacheSwapper.h b/libs/ui/KisAbstractFrameCacheSwapper.h
new file mode 100644
index 0000000000..c01db7953c
--- /dev/null
+++ b/libs/ui/KisAbstractFrameCacheSwapper.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 KISABSTRACTFRAMECACHESWAPPER_H
+#define KISABSTRACTFRAMECACHESWAPPER_H
+
+#include "kritaui_export.h"
+
+class QRect;
+
+template
+class KisSharedPtr;
+
+class KisOpenGLUpdateInfo;
+typedef KisSharedPtr KisOpenGLUpdateInfoSP;
+
+
+class KRITAUI_EXPORT KisAbstractFrameCacheSwapper
+{
+public:
+ virtual ~KisAbstractFrameCacheSwapper();
+
+ // WARNING: after transferring \p info to saveFrame() the object becomes invalid
+ virtual void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) = 0;
+ virtual KisOpenGLUpdateInfoSP loadFrame(int frameId) = 0;
+
+ virtual void moveFrame(int srcFrameId, int dstFrameId) = 0;
+ virtual void forgetFrame(int frameId) = 0;
+
+ virtual bool hasFrame(int frameId) const = 0;
+
+ virtual int frameLevelOfDetail(int frameId) const = 0;
+ virtual QRect frameDirtyRect(int frameId) const = 0;
+};
+
+#endif // KISABSTRACTFRAMECACHESWAPPER_H
diff --git a/libs/ui/KisAsyncAnimationCacheRenderer.cpp b/libs/ui/KisAsyncAnimationCacheRenderer.cpp
index 1e49f3720a..d41b7dcd02 100644
--- a/libs/ui/KisAsyncAnimationCacheRenderer.cpp
+++ b/libs/ui/KisAsyncAnimationCacheRenderer.cpp
@@ -1,84 +1,84 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 "KisAsyncAnimationCacheRenderer.h"
#include "kis_animation_frame_cache.h"
#include "kis_update_info.h"
struct KisAsyncAnimationCacheRenderer::Private
{
KisAnimationFrameCacheSP requestedCache;
KisOpenGLUpdateInfoSP requestInfo;
};
KisAsyncAnimationCacheRenderer::KisAsyncAnimationCacheRenderer()
: m_d(new Private)
{
connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(slotCompleteRegenerationInternal(int)));
}
KisAsyncAnimationCacheRenderer::~KisAsyncAnimationCacheRenderer()
{
}
void KisAsyncAnimationCacheRenderer::setFrameCache(KisAnimationFrameCacheSP cache)
{
m_d->requestedCache = cache;
}
-void KisAsyncAnimationCacheRenderer::frameCompletedCallback(int frame)
+void KisAsyncAnimationCacheRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion)
{
KisAnimationFrameCacheSP cache = m_d->requestedCache;
KisImageSP image = requestedImage();
if (!cache || !image) return;
- m_d->requestInfo = cache->fetchFrameData(frame, image);
+ m_d->requestInfo = cache->fetchFrameData(frame, image, requestedRegion);
emit sigCompleteRegenerationInternal(frame);
}
void KisAsyncAnimationCacheRenderer::slotCompleteRegenerationInternal(int frame)
{
if (!isActive()) return;
KIS_SAFE_ASSERT_RECOVER(m_d->requestInfo) {
frameCancelledCallback(frame);
return;
}
m_d->requestedCache->addConvertedFrameData(m_d->requestInfo, frame);
notifyFrameCompleted(frame);
}
void KisAsyncAnimationCacheRenderer::frameCancelledCallback(int frame)
{
notifyFrameCancelled(frame);
}
void KisAsyncAnimationCacheRenderer::clearFrameRegenerationState(bool isCancelled)
{
m_d->requestInfo.clear();
m_d->requestedCache.clear();
KisAsyncAnimationRendererBase::clearFrameRegenerationState(isCancelled);
}
diff --git a/libs/ui/KisAsyncAnimationCacheRenderer.h b/libs/ui/KisAsyncAnimationCacheRenderer.h
index 9701203dfc..4d581c6a59 100644
--- a/libs/ui/KisAsyncAnimationCacheRenderer.h
+++ b/libs/ui/KisAsyncAnimationCacheRenderer.h
@@ -1,50 +1,50 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 KISASYNCANIMATIONCACHERENDERER_H
#define KISASYNCANIMATIONCACHERENDERER_H
#include
class KisAsyncAnimationCacheRenderer : public KisAsyncAnimationRendererBase
{
Q_OBJECT
public:
KisAsyncAnimationCacheRenderer();
~KisAsyncAnimationCacheRenderer();
void setFrameCache(KisAnimationFrameCacheSP cache);
protected:
- void frameCompletedCallback(int frame) override;
+ void frameCompletedCallback(int frame, const QRegion &requestedRegion) override;
void frameCancelledCallback(int frame) override;
void clearFrameRegenerationState(bool isCancelled) override;
Q_SIGNALS:
void sigCompleteRegenerationInternal(int frame);
private Q_SLOTS:
void slotCompleteRegenerationInternal(int frame);
private:
struct Private;
const QScopedPointer m_d;
};
#endif // KISASYNCANIMATIONCACHERENDERER_H
diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
index 442a9fbc6b..76809a84fd 100644
--- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
+++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
@@ -1,127 +1,132 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 "KisAsyncAnimationFramesSavingRenderer.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "KisImportExportFilter.h"
#include "KisPart.h"
#include "KisDocument.h"
#include "kis_time_range.h"
#include "kis_paint_layer.h"
struct KisAsyncAnimationFramesSavingRenderer::Private
{
Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration)
: savingDoc(KisPart::instance()->createDocument()),
range(_range),
sequenceNumberingOffset(_sequenceNumberingOffset),
exportConfiguration(_exportConfiguration)
{
savingDoc->setInfiniteAutoSaveInterval();
savingDoc->setFileBatchMode(true);
KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(),
image->bounds().width(),
image->bounds().height(),
image->colorSpace(),
QString());
savingImage->setResolution(image->xRes(), image->yRes());
savingDoc->setCurrentImage(savingImage);
KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255);
savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0));
savingDevice = paintLayer->paintDevice();
}
QScopedPointer savingDoc;
KisPaintDeviceSP savingDevice;
KisTimeRange range;
int sequenceNumberingOffset = 0;
QString filenamePrefix;
QString filenameSuffix;
QByteArray outputMimeType;
KisPropertiesConfigurationSP exportConfiguration;
};
KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image,
const QString &fileNamePrefix,
const QString &fileNameSuffix,
const QByteArray &outputMimeType,
const KisTimeRange &range,
const int sequenceNumberingOffset,
KisPropertiesConfigurationSP exportConfiguration)
: m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration))
{
m_d->filenamePrefix = fileNamePrefix;
m_d->filenameSuffix = fileNameSuffix;
m_d->outputMimeType = outputMimeType;
connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int)));
connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int)));
}
KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer()
{
}
-void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame)
+void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion)
{
KisImageSP image = requestedImage();
if (!image) return;
+ KIS_SAFE_ASSERT_RECOVER (requestedRegion == image->bounds()) {
+ emit sigCancelRegenerationInternal(frame);
+ return;
+ }
+
m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds());
KisTimeRange range(frame, 1);
KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
for (int i = range.start(); i <= range.end(); i++) {
QString frameNumber = QString("%1").arg(i + m_d->sequenceNumberingOffset, 4, 10, QChar('0'));
QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix;
if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) {
status = KisImportExportFilter::InternalError;
break;
}
}
if (status == KisImportExportFilter::OK) {
emit sigCompleteRegenerationInternal(frame);
} else {
emit sigCancelRegenerationInternal(frame);
}
}
void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame)
{
notifyFrameCancelled(frame);
}
diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.h b/libs/ui/KisAsyncAnimationFramesSavingRenderer.h
index 6dffff69a5..efa5fcaf5e 100644
--- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.h
+++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.h
@@ -1,53 +1,53 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 KISASYNCANIMATIONFRAMESSAVINGRENDERER_H
#define KISASYNCANIMATIONFRAMESSAVINGRENDERER_H
#include
class KisDocument;
class KisTimeRange;
class KisAsyncAnimationFramesSavingRenderer : public KisAsyncAnimationRendererBase
{
Q_OBJECT
public:
KisAsyncAnimationFramesSavingRenderer(KisImageSP image,
const QString &fileNamePrefix,
const QString &fileNameSuffix,
const QByteArray &outputMimeType,
const KisTimeRange &range,
int sequenceNumberingOffset,
KisPropertiesConfigurationSP exportConfiguration);
~KisAsyncAnimationFramesSavingRenderer();
protected:
- void frameCompletedCallback(int frame) override;
+ void frameCompletedCallback(int frame, const QRegion &requestedRegion) override;
void frameCancelledCallback(int frame) override;
Q_SIGNALS:
void sigCompleteRegenerationInternal(int frame);
void sigCancelRegenerationInternal(int frame);
private:
struct Private;
const QScopedPointer m_d;
};
#endif // KISASYNCANIMATIONFRAMESSAVINGRENDERER_H
diff --git a/libs/ui/KisAsyncAnimationRendererBase.cpp b/libs/ui/KisAsyncAnimationRendererBase.cpp
index 4216a923b7..1ba2f92c5f 100644
--- a/libs/ui/KisAsyncAnimationRendererBase.cpp
+++ b/libs/ui/KisAsyncAnimationRendererBase.cpp
@@ -1,160 +1,168 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 "KisAsyncAnimationRendererBase.h"
#include
#include
#include "kis_image.h"
#include "kis_image_animation_interface.h"
#include "kis_signal_auto_connection.h"
-struct KisAsyncAnimationRendererBase::Private
+struct KRITAUI_NO_EXPORT KisAsyncAnimationRendererBase::Private
{
KisSignalAutoConnectionsStore imageRequestConnections;
QTimer regenerationTimeout;
KisImageSP requestedImage;
int requestedFrame = -1;
bool isCancelled = false;
+ QRegion requestedRegion;
static const int WAITING_FOR_FRAME_TIMEOUT = 10000;
};
KisAsyncAnimationRendererBase::KisAsyncAnimationRendererBase(QObject *parent)
: QObject(parent),
m_d(new Private())
{
connect(&m_d->regenerationTimeout, SIGNAL(timeout()), SLOT(slotFrameRegenerationCancelled()));
m_d->regenerationTimeout.setSingleShot(true);
m_d->regenerationTimeout.setInterval(Private::WAITING_FOR_FRAME_TIMEOUT);
}
KisAsyncAnimationRendererBase::~KisAsyncAnimationRendererBase()
{
}
-void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame)
+void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame, const QRegion ®ionOfInterest)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
m_d->requestedImage = image;
m_d->requestedFrame = frame;
m_d->isCancelled = false;
+ m_d->requestedRegion = !regionOfInterest.isEmpty() ? regionOfInterest : image->bounds();
KisImageAnimationInterface *animation = m_d->requestedImage->animationInterface();
m_d->imageRequestConnections.clear();
m_d->imageRequestConnections.addConnection(
animation, SIGNAL(sigFrameReady(int)),
this, SLOT(slotFrameRegenerationFinished(int)),
Qt::DirectConnection);
m_d->imageRequestConnections.addConnection(
animation, SIGNAL(sigFrameCancelled()),
this, SLOT(slotFrameRegenerationCancelled()),
Qt::AutoConnection);
m_d->regenerationTimeout.start();
- animation->requestFrameRegeneration(m_d->requestedFrame, image->bounds());
+ animation->requestFrameRegeneration(m_d->requestedFrame, m_d->requestedRegion);
+}
+
+void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame)
+{
+ startFrameRegeneration(image, frame, QRegion());
}
bool KisAsyncAnimationRendererBase::isActive() const
{
return m_d->requestedImage;
}
void KisAsyncAnimationRendererBase::cancelCurrentFrameRendering()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
frameCancelledCallback(m_d->requestedFrame);
}
void KisAsyncAnimationRendererBase::slotFrameRegenerationCancelled()
{
// the timeout can arrive in async way
if (!m_d->requestedImage) return;
frameCancelledCallback(m_d->requestedFrame);
}
void KisAsyncAnimationRendererBase::slotFrameRegenerationFinished(int frame)
{
// We might have already cancelled the regeneration. We don't check
// isCancelled flag here because this code runs asynchronously.
if (!m_d->requestedImage) return;
// WARNING: executed in the context of image worker thread!
// probably a bit too strict...
KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != this->thread());
- frameCompletedCallback(frame);
+ frameCompletedCallback(frame, m_d->requestedRegion);
}
void KisAsyncAnimationRendererBase::notifyFrameCompleted(int frame)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
// the image events can come with a delay, even after
// the processing was cancelled
if (m_d->isCancelled) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame);
clearFrameRegenerationState(false);
emit sigFrameCompleted(frame);
}
void KisAsyncAnimationRendererBase::notifyFrameCancelled(int frame)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
// the image events can come with a delay, even after
// the processing was cancelled
if (m_d->isCancelled) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame);
clearFrameRegenerationState(true);
emit sigFrameCancelled(frame);
}
void KisAsyncAnimationRendererBase::clearFrameRegenerationState(bool isCancelled)
{
// TODO: for some reason we mark the process as cancelled in any case, and it
// seem to be a correct behavior
Q_UNUSED(isCancelled);
m_d->imageRequestConnections.clear();
m_d->requestedImage = 0;
m_d->requestedFrame = -1;
m_d->regenerationTimeout.stop();
m_d->isCancelled = true;
+ m_d->requestedRegion = QRegion();
}
KisImageSP KisAsyncAnimationRendererBase::requestedImage() const
{
return m_d->requestedImage;
}
diff --git a/libs/ui/KisAsyncAnimationRendererBase.h b/libs/ui/KisAsyncAnimationRendererBase.h
index 388d586b61..48074838a5 100644
--- a/libs/ui/KisAsyncAnimationRendererBase.h
+++ b/libs/ui/KisAsyncAnimationRendererBase.h
@@ -1,136 +1,145 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 KISASYNCANIMATIONRENDERERBASE_H
#define KISASYNCANIMATIONRENDERERBASE_H
#include
#include "kis_types.h"
+#include "kritaui_export.h"
+
/**
* KisAsyncAnimationRendererBase is a special class represinting a
* single worker thread inside KisAsyncAnimationRenderDialogBase. It connects
* the specified image using correct Qt::DirectConnection connections and
* reacts on them. On sigFrameReady() signal it calls frameCompletedCallback(),
* so the derived class can fetch a frame from the image and process it. On
* sigFrameCancelled() it calls frameCancelledCallback(). The derived class
* should override these two methods to do the actual work.
*/
-class KisAsyncAnimationRendererBase : public QObject
+class KRITAUI_EXPORT KisAsyncAnimationRendererBase : public QObject
{
Q_OBJECT
public:
explicit KisAsyncAnimationRendererBase(QObject *parent = 0);
virtual ~KisAsyncAnimationRendererBase();
/**
- * Initiates the rendering of the frame \p frame on an image \p image
+ * Initiates the rendering of the frame \p frame on an image \p image.
+ * Only \p regionOfInterest is regenerated. If \p regionOfInterest is
+ * empty, then entire bounds of the image is regenerated.
+ */
+ void startFrameRegeneration(KisImageSP image, int frame, const QRegion ®ionOfInterest);
+
+ /**
+ * Convenience overload that regenerates the full image
*/
- virtual void startFrameRegeneration(KisImageSP image, int frame);
+ void startFrameRegeneration(KisImageSP image, int frame);
/**
* @return true if the regeneration process is in progress
*/
bool isActive() const;
public Q_SLOTS:
/**
* @brief cancels current rendering operation
*
* After calling this slot requestedImage() becomes invalid.
* @see requestedImage()
*/
void cancelCurrentFrameRendering();
Q_SIGNALS:
void sigFrameCompleted(int frame);
void sigFrameCancelled(int frame);
private Q_SLOTS:
void slotFrameRegenerationCancelled();
void slotFrameRegenerationFinished(int frame);
protected Q_SLOTS:
/**
* Called by a derived class to continue processing of the frames
*/
void notifyFrameCompleted(int frame);
/**
* Called by a derived class to cancel processing of the frames. After calling
* this method, the dialog will stop processing the frames and close.
*/
void notifyFrameCancelled(int frame);
protected:
/**
* @brief frameCompletedCallback is called by the renderer when
* a new frame becomes ready
*
* NOTE1: the callback is called from the context of a image
* worker thread! So it is asynchronous from the GUI thread.
* NOTE2: in case of successful processing of the frame, the callback
* must issue some signal, connected to notifyFrameCompleted()
* via auto connection, to continue processing. Please do not
* call the method directly, because notifyFame*() slots should
* be called from the context of the GUI thread.
* NOTE3: In case of failure, notifyFrameCancelled(). The same threading
* rules apply.
*/
- virtual void frameCompletedCallback(int frame) = 0;
+ virtual void frameCompletedCallback(int frame, const QRegion &requestedRegion) = 0;
/**
* @brief frameCancelledCallback is called when the rendering of
* the frame was cancelled.
*
* The rendering of the frame can be either cancelled by the image itself or
* by receiving a timeout signal (10 seconds).
*
* NOTE: the slot is called in the GUI thread. Don't forget to call
* notifyFrameCancelled() in he end of your call.
*/
virtual void frameCancelledCallback(int frame) = 0;
/**
* Called by KisAsyncAnimationRendererBase when the processing has been completed
* and the internal state of the populator should be cleared
*
* @param isCancelled tells if frame regeneration has failed to be regenerated
*/
virtual void clearFrameRegenerationState(bool isCancelled);
protected:
/**
* @return the image that for which the rendering was requested using
* startFrameRegeneration(). Should be used by the derived classes only.
*
* Please note that requestedImage() will become null as soon as the user
* cancels the processing. That happends in the GUI thread so
* frameCompletedCallback() should be extremely careful when requesting the
* value (check the shared pointer after fetching).
*/
KisImageSP requestedImage() const;
private:
struct Private;
const QScopedPointer m_d;
};
#endif // KISASYNCANIMATIONRENDERERBASE_H
diff --git a/libs/ui/KisCloneDocumentStroke.cpp b/libs/ui/KisCloneDocumentStroke.cpp
index a6290c91c9..17071af1dc 100644
--- a/libs/ui/KisCloneDocumentStroke.cpp
+++ b/libs/ui/KisCloneDocumentStroke.cpp
@@ -1,59 +1,60 @@
/*
* Copyright (c) 2018 Dmitry Kazakov
*
* 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 "KisCloneDocumentStroke.h"
#include "KisDocument.h"
#include "kis_layer_utils.h"
+#include
+
+
struct KRITAIMAGE_NO_EXPORT KisCloneDocumentStroke::Private
{
Private(KisDocument *_document)
: document(_document)
{
}
KisDocument *document = 0;
};
KisCloneDocumentStroke::KisCloneDocumentStroke(KisDocument *document)
: KisSimpleStrokeStrategy("clone-document-stroke", kundo2_i18n("Clone Document")),
m_d(new Private(document))
{
setClearsRedoOnStart(false);
- setRequestsOtherStrokesToEnd(false); // TODO: ???
+ setRequestsOtherStrokesToEnd(false);
enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
}
KisCloneDocumentStroke::~KisCloneDocumentStroke()
{
}
void KisCloneDocumentStroke::initStrokeCallback()
{
KisLayerUtils::forceAllDelayedNodesUpdate(m_d->document->image()->root());
}
-#include
-
void KisCloneDocumentStroke::finishStrokeCallback()
{
KisDocument *doc = m_d->document->clone();
doc->moveToThread(qApp->thread());
emit sigDocumentCloned(doc);
}
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index e3946c0317..b2b8b6cba5 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1791 +1,1797 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include // XXX: remove
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_layer_utils.h"
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisPart.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include "KisReferenceImagesLayer.h"
#include
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
#include "KisCloneDocumentStroke.h"
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
using namespace std;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private(KisDocument *q)
: docInfo(new KoDocumentInfo(q)) // deleted by QObject
, importExportManager(new KisImportExportManager(q)) // deleted manually
, autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q)) // deleted by QObject
, m_bAutoDetectedMime(false)
, modified(false)
, readwrite(true)
, firstMod(QDateTime::currentDateTime())
, lastMod(firstMod)
, nserver(new KisNameServer(1))
, imageIdleWatcher(2000 /*ms*/)
, savingLock(&savingMutex)
, batchMode(false)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
Private(const Private &rhs, KisDocument *q)
: docInfo(new KoDocumentInfo(*rhs.docInfo, q))
, unit(rhs.unit)
, importExportManager(new KisImportExportManager(q))
, mimeType(rhs.mimeType)
, outputMimeType(rhs.outputMimeType)
, autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q))
, guidesConfig(rhs.guidesConfig)
, m_bAutoDetectedMime(rhs.m_bAutoDetectedMime)
, m_url(rhs.m_url)
, m_file(rhs.m_file)
, modified(rhs.modified)
, readwrite(rhs.readwrite)
, firstMod(rhs.firstMod)
, lastMod(rhs.lastMod)
, nserver(new KisNameServer(*rhs.nserver))
, preActivatedNode(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
, assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document!
, gridConfig(rhs.gridConfig)
, savingLock(&savingMutex)
, batchMode(rhs.batchMode)
{
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
QTimer *autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
int autoSaveFailureCount = 0;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
QList assistants;
KisSharedPtr referenceImagesLayer;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
bool modifiedWhileSaving = false;
QScopedPointer backgroundSaveDocument;
QPointer savingUpdater;
QFuture childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
bool batchMode { false };
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class StrippedSafeSavingLocker;
};
class KisDocument::Private::StrippedSafeSavingLocker {
public:
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
: m_locked(false)
, m_image(image)
, m_savingLock(savingMutex)
, m_imageLock(image, true)
{
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
if (!m_locked) {
m_image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
}
}
~StrippedSafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
m_savingLock.unlock();
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
bool m_locked;
KisImageSP m_image;
StdLockableWrapper m_savingLock;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(rhs.objectName());
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(true), false);
if (rhs.d->preActivatedNode) {
// since we clone uuid's, we can use them for lacating new
// nodes. Otherwise we would need to use findSymmetricClone()
d->preActivatedNode =
KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
}
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer->disconnect(this);
d->autoSaveTimer->stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
slotCompleteSavingDocument(job,
KisImportExportFilter::CreationError,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
return false;
}
KisConfig cfg;
if (cfg.backupFile() && filePathInfo.exists()) {
KBackup::backupFile(job.filePath);
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)),
job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
return exportDocumentImpl(ExportFileJob(url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
if (status == KisImportExportFilter::UserCancelled)
return;
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
d->undoStack->setClean();
}
setRecovered(false);
removeAutoSaveFiles();
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->batchMode;
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->batchMode = batchMode;
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
// force update of all the asynchronous nodes before cloning
QApplication::processEvents();
KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
return 0;
}
}
}
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportFilter::ConversionStatus status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status == KisImportExportFilter::OK;
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
{
return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
job, exportConfiguration, std::unique_ptr());
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
QScopedPointer clonedDocument;
if (!optionalClonedDocument) {
clonedDocument.reset(lockAndCloneForSaving());
} else {
clonedDocument.reset(optionalClonedDocument.release());
}
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
if (!started) {
// the state should have been deinitialized in slotChildCompletedSavingInBackground()
KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
d->backgroundSaveJob = KritaUtils::ExportFileJob();
}
}
return started;
}
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
d->savingMutex.unlock();
return;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument)
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
0,
std::move(optionalClonedDocument));
} else {
emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
}
if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
Qt::BlockingQueuedConnection);
KisStrokeId strokeId = d->image->startStroke(stroke);
d->image->endStroke(strokeId);
setInfiniteAutoSaveInterval();
} else if (!started) {
setEmergencyAutoSaveInterval();
} else {
d->modifiedAfterAutosave = false;
}
}
void KisDocument::slotAutoSave()
{
slotAutoSaveImpl(std::unique_ptr());
}
void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
{
slotAutoSaveImpl(std::unique_ptr(clonedDocument));
}
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg;
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
d->autoSaveTimer->stop(); // until the next change
d->autoSaveFailureCount = 0;
} else {
setNormalAutoSaveInterval();
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
KisImportExportFilter::ConversionStatus initializationStatus;
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage());
return false;
}
typedef QFutureWatcher StatusWatcher;
StatusWatcher *watcher = new StatusWatcher();
watcher->setFuture(d->childSavingFuture);
connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return true;
}
void KisDocument::finishExportInBackground()
{
KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, "");
return;
}
KisImportExportFilter::ConversionStatus status =
d->childSavingFuture.result();
const QString errorMessage = this->errorMessage();
d->savingImage.clear();
d->childSavingFuture = QFuture();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setNormalAutoSaveInterval();
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
if (isReadWrite() && delay > 0) {
d->autoSaveTimer->start(delay * 1000);
} else {
d->autoSaveTimer->stop();
}
}
void KisDocument::setNormalAutoSaveInterval()
{
setAutoSaveDelay(d->autoSaveDelay);
d->autoSaveFailureCount = 0;
}
void KisDocument::setEmergencyAutoSaveInterval()
{
const int emergencyAutoSaveInterval = 10; /* sec */
setAutoSaveDelay(emergencyAutoSaveInterval);
d->autoSaveFailureCount++;
}
void KisDocument::setInfiniteAutoSaveInterval()
{
setAutoSaveDelay(-1);
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
QRegularExpression autosavePattern("^\\..+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
setRecovered(false);
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "";
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window && window->viewManager()) {
KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified()
{
d->modified = true;
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg;
d->undoStack->setUndoLimit(cfg.undoStackLimit());
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisConfig cfg;
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList &value)
{
d->assistants = value;
}
-KisSharedPtr KisDocument::createReferenceImagesLayer(KisImageSP targetImage)
+KisSharedPtr KisDocument::getOrCreateReferenceImagesLayer(KisImageSP targetImage)
{
if (!d->referenceImagesLayer) {
if (targetImage.isNull()) targetImage = d->image;
d->referenceImagesLayer = new KisReferenceImagesLayer(shapeController(), targetImage);
targetImage->addNode(d->referenceImagesLayer, targetImage->root());
}
return d->referenceImagesLayer;
}
+// TODO: change signature to return a shared pointer
KisReferenceImagesLayer *KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
+void KisDocument::setReferenceImagesLayer(KisSharedPtr layer)
+{
+ d->referenceImagesLayer = layer;
+}
+
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index 22130bfb27..71232d2df7 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,644 +1,648 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KISDOCUMENT_H
#define KISDOCUMENT_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kritaui_export.h"
#include
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeBasedDocumentBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class QDomDocument;
class KisReferenceImagesLayer;
#define KIS_MIME_TYPE "application/x-krita"
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
protected:
explicit KisDocument();
/**
* @brief KisDocument makes a deep copy of the document \p rhs.
* The caller *must* ensure that the image is properly
* locked and is in consistent state before asking for
* cloning.
* @param rhs the source document to copy from
*/
explicit KisDocument(const KisDocument &rhs);
public:
enum OpenFlag {
None = 0,
DontAddToRecent = 0x1,
RecoveryFile = 0x2
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
~KisDocument() override;
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief creates a clone of the document and returns it. Please make sure that you
* hold all the necessary locks on the image before asking for a clone!
*/
KisDocument* clone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
bool openUrl(const QUrl &url, OpenFlags flags = None);
/**
* Opens the document given by @p url, without storing the URL
* in the KisDocument.
* Call this instead of openUrl() to implement KisMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KisDocument (URL, modified flag etc.). Call this instead of
* KisParts::ReadWritePart::saveAs() to implement KisMainWindow's
* File --> Export feature.
*/
bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
private:
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
public:
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KisView::updateReadWrite is called for every attached
* view.
*/
void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; }
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KisDocument without a filter, in *addition* to the main one
static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; }
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const override;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType) override;
/**
* @return true if file operations should inhibit the option dialog
*/
bool fileBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog for file operations.
*/
void setFileBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KisDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Sets the warning message to be shown to the user (use i18n()!)
* when loading or saving fails.
*/
void setWarningMessage(const QString& warningMsg);
/**
* Return the last warning message set by loading or saving. Warnings
* mean that the document could not be completely loaded, but the errors
* were not absolutely fatal.
*/
QString warningMessage() const;
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
QPixmap generatePreview(const QSize& size);
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
void setEmpty(bool empty = true);
/**
* Return a correctly created QDomDocument for this KisDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
bool loadNativeFormat(const QString & file);
/**
* Set standard autosave interval that is set by a config file
*/
void setNormalAutoSaveInterval();
/**
* Set emergency interval that autosave uses when the image is busy,
* by default it is 10 sec
*/
void setEmergencyAutoSaveInterval();
/**
* Disable autosave
*/
void setInfiniteAutoSaveInterval();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles();
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const override;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
void setModified(bool _mod);
void setRecovered(bool value);
bool isRecovered() const;
void updateEditingTime(bool forceStoreElapsed);
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* @brief importExportManager gives access to the internal import/export manager
* @return the document's import/export manager
*/
KisImportExportManager *importExportManager() const;
/**
* @brief serializeToNativeByteArray daves the document into a .kra file wtitten
* to a memory-based byte-array
* @return a byte array containing the .kra file
*/
QByteArray serializeToNativeByteArray();
/**
* @brief isInSaving shown if the document has any (background) saving process or not
* @return true if there is some saving in action
*/
bool isInSaving() const;
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KisDocument and used by KisView to display a statusbar message
*/
void statusBarMessage(const QString& text, int timeout = 0);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KisDocument and used by KisView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
void sigLoadingFinished();
void sigSavingFinished();
void sigGuidesConfigChanged(const KisGuidesConfig &config);
void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
private Q_SLOTS:
void finishExportInBackground();
void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
private:
friend class KisPart;
friend class SafeSavingLocker;
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument);
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSaveDelay(int delay);
/**
* Generate a name for the document.
*/
QString newObjectName();
QString generateAutoSaveFileName(const QString & path) const;
/**
* Loads a document
*
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KisView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
bool openFile();
/** @internal */
void setModified();
public:
bool isAutosaving() const override;
public:
QString localFilePath() const override;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const override;
void setUrl(const QUrl &url) override;
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0);
/**
* Create a new image that has this document as a parent and
* replace the current image with this image.
*/
bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers, const QString &imageDescription, const double imageResolution);
bool isSaving() const;
void waitForSavingToComplete();
KisImageWSP image() const;
/**
* @brief savingImage provides a detached, shallow copy of the original image that must be used when saving.
* Any strokes in progress will not be applied to this image, so the result might be missing some data. On
* the other hand, it won't block.
*
* @return a shallow copy of the original image, or 0 is saving is not in progress
*/
KisImageSP savingImage() const;
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true);
/**
* Set the image of the document preliminary, before the document
* has completed loading. Some of the document items (shapes) may want
* to access image properties (bounds and resolution), so we should provide
* it to them even before the entire image is loaded.
*
* Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove
* after it is deprecated.
*/
void hackPreliminarySetImage(KisImageSP image);
KisUndoStore* createUndoStore();
/**
* The shape controller matches internal krita image layers with
* the flake shape hierarchy.
*/
KoShapeBasedDocumentBase * shapeController() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
/**
* Set the list of nodes that was marked as currently active. Used *only*
* for saving loading. Never use it for tools or processing.
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading. Used *only*
* for saving loading. Never use it for tools or processing.
*/
KisNodeSP preActivatedNode() const;
/// @return the list of assistants associated with this document
QList assistants() const;
/// @replace the current list of assistants with @param value
void setAssistants(const QList &value);
/**
* Get existing reference images layer or create new if none exists.
+ *
+ * TODO: use setReferenceImagesLayer() combined with undo commands instead
*/
- KisSharedPtr createReferenceImagesLayer(KisImageSP targetImage = KisImageSP());
+ KRITAUI_DEPRECATED KisSharedPtr getOrCreateReferenceImagesLayer(KisImageSP targetImage = KisImageSP());
/**
* Get existing reference images layer or null if none exists.
*/
KisReferenceImagesLayer *referenceImagesLayer() const;
+ void setReferenceImagesLayer(KisSharedPtr layer);
+
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
private:
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *lockAndCloneForSaving();
QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument);
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/libs/ui/KisFrameCacheStore.cpp b/libs/ui/KisFrameCacheStore.cpp
new file mode 100644
index 0000000000..e00d3e9b80
--- /dev/null
+++ b/libs/ui/KisFrameCacheStore.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 "KisFrameCacheStore.h"
+
+#include
+#include "kis_update_info.h"
+#include "KisFrameDataSerializer.h"
+#include "opengl/KisOpenGLUpdateInfoBuilder.h"
+
+#define SANITY_CHECK
+
+namespace {
+enum FrameType {
+ FrameFull,
+ FrameCopy,
+ FrameDiff
+};
+
+struct FrameInfo;
+typedef QSharedPointer FrameInfoSP;
+
+struct FrameInfo {
+ // full frame
+ FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, const KisFrameDataSerializer::Frame &frame);
+ // diff frame
+ FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame, const KisFrameDataSerializer::Frame &frame);
+ // copy frame
+ FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame);
+
+ ~FrameInfo();
+
+ FrameType type() const {
+ return m_type;
+ }
+
+ int levelOfDetail() const {
+ return m_levelOfDetail;
+ }
+
+ QRect dirtyImageRect() const {
+ return m_dirtyImageRect;
+ }
+
+ QRect imageBounds() const {
+ return m_imageBounds;
+ }
+
+ int frameDataId() const {
+ return m_savedFrameDataId;
+ }
+
+ FrameInfoSP baseFrame() const {
+ return m_baseFrame;
+ }
+
+ int m_levelOfDetail = 0;
+ QRect m_dirtyImageRect;
+ QRect m_imageBounds;
+ FrameInfoSP m_baseFrame;
+ FrameType m_type = FrameFull;
+ int m_savedFrameDataId = -1;
+ KisFrameDataSerializer &m_serializer;
+};
+
+// full frame
+FrameInfo::FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, const KisFrameDataSerializer::Frame &frame)
+ : m_levelOfDetail(levelOfDetail),
+ m_dirtyImageRect(dirtyImageRect),
+ m_imageBounds(imageBounds),
+ m_baseFrame(0),
+ m_type(FrameFull),
+ m_serializer(serializer)
+{
+ m_savedFrameDataId = m_serializer.saveFrame(frame);
+}
+
+// diff frame
+FrameInfo::FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame, const KisFrameDataSerializer::Frame &frame)
+ : m_levelOfDetail(levelOfDetail),
+ m_dirtyImageRect(dirtyImageRect),
+ m_imageBounds(imageBounds),
+ m_baseFrame(baseFrame),
+ m_type(FrameDiff),
+ m_serializer(serializer)
+{
+ m_savedFrameDataId = m_serializer.saveFrame(frame);
+}
+
+// copy frame
+FrameInfo::FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame)
+ : m_levelOfDetail(levelOfDetail),
+ m_dirtyImageRect(dirtyImageRect),
+ m_imageBounds(imageBounds),
+ m_baseFrame(baseFrame),
+ m_type(FrameCopy),
+ m_savedFrameDataId(-1),
+ m_serializer(serializer)
+{
+}
+
+FrameInfo::~FrameInfo()
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedFrameDataId >= 0 || m_type == FrameCopy);
+
+ if (m_savedFrameDataId >= 0) {
+ m_serializer.forgetFrame(m_savedFrameDataId);
+ }
+}
+
+}
+
+
+struct KRITAUI_NO_EXPORT KisFrameCacheStore::Private
+{
+ Private(const QString &frameCachePath)
+ : serializer(frameCachePath)
+ {
+ }
+
+ // the serializer should be killed after *all* the frame info objects
+ // got destroyed, because they use it in their own destruction
+ KisFrameDataSerializer serializer;
+
+ KisFrameDataSerializer::Frame lastSavedFullFrame;
+ int lastSavedFullFrameId = -1;
+
+ KisFrameDataSerializer::Frame lastLoadedBaseFrame;
+ FrameInfoSP lastLoadedBaseFrameInfo;
+
+ QMap savedFrames;
+};
+
+KisFrameCacheStore::KisFrameCacheStore()
+ : KisFrameCacheStore(QString())
+{
+}
+
+KisFrameCacheStore::KisFrameCacheStore(const QString &frameCachePath)
+ : m_d(new Private(frameCachePath))
+{
+}
+
+
+KisFrameCacheStore::~KisFrameCacheStore()
+{
+}
+
+void KisFrameCacheStore::saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds)
+{
+ int pixelSize = 0;
+
+ Q_FOREACH (auto tile, info->tileList) {
+#ifdef SANITY_CHECK
+ if (!pixelSize) {
+ pixelSize = tile->pixelSize();
+ } else {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(pixelSize == tile->pixelSize());
+ }
+#else
+ pixelSize = tile->pixelSize();
+ break;
+#endif
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(pixelSize);
+
+ // TODO: assert that dirty image rect is equal to the full image rect
+ // TODO: assert tile color space coicides with the destination color space
+
+ KisFrameDataSerializer::Frame frame;
+ frame.pixelSize = pixelSize;
+
+ for (auto it = info->tileList.begin(); it != info->tileList.end(); ++it) {
+ KisFrameDataSerializer::FrameTile tile(0); // TODO: fix null!
+ tile.col = (*it)->tileCol();
+ tile.row = (*it)->tileRow();
+ tile.rect = (*it)->realPatchRect();
+ tile.data = std::move((*it)->takePixelData());
+
+ frame.frameTiles.push_back(std::move(tile));
+ }
+
+ FrameInfoSP frameInfo;
+
+ if (m_d->lastSavedFullFrame.isValid()) {
+ boost::optional uniqueness = KisFrameDataSerializer::estimateFrameUniqueness(m_d->lastSavedFullFrame, frame, 0.01);
+
+ if (uniqueness) {
+ if (*uniqueness == 0.0) {
+ FrameInfoSP baseFrameInfo = m_d->savedFrames[m_d->lastSavedFullFrameId];
+ frameInfo = toQShared(new FrameInfo(info->dirtyImageRect(),
+ imageBounds,
+ info->levelOfDetail(),
+ m_d->serializer,
+ baseFrameInfo));
+
+ } else if (*uniqueness < 0.5) {
+ FrameInfoSP baseFrameInfo = m_d->savedFrames[m_d->lastSavedFullFrameId];
+
+ KisFrameDataSerializer::subtractFrames(frame, m_d->lastSavedFullFrame);
+ frameInfo = toQShared(new FrameInfo(info->dirtyImageRect(),
+ imageBounds,
+ info->levelOfDetail(),
+ m_d->serializer,
+ baseFrameInfo,
+ frame));
+ }
+ }
+ }
+
+ if (!frameInfo) {
+ frameInfo = toQShared(new FrameInfo(info->dirtyImageRect(),
+ imageBounds,
+ info->levelOfDetail(),
+ m_d->serializer,
+ frame));
+ }
+
+ m_d->savedFrames.insert(frameId, frameInfo);
+
+ if (frameInfo->type() == FrameFull) {
+ m_d->lastSavedFullFrame = std::move(frame);
+ m_d->lastSavedFullFrameId = frameId;
+ }
+}
+
+KisOpenGLUpdateInfoSP KisFrameCacheStore::loadFrame(int frameId, const KisOpenGLUpdateInfoBuilder &builder)
+{
+ KisOpenGLUpdateInfoSP info = new KisOpenGLUpdateInfo();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->savedFrames.contains(frameId), info);
+
+ FrameInfoSP frameInfo = m_d->savedFrames[frameId];
+
+ info->assignDirtyImageRect(frameInfo->dirtyImageRect());
+ info->assignLevelOfDetail(frameInfo->levelOfDetail());
+
+ KisFrameDataSerializer::Frame frame;
+
+ switch (frameInfo->type()) {
+ case FrameFull:
+ frame = m_d->serializer.loadFrame(frameInfo->frameDataId(), builder.textureInfoPool());
+ m_d->lastLoadedBaseFrame = frame.clone();
+ m_d->lastLoadedBaseFrameInfo = frameInfo;
+ break;
+ case FrameCopy: {
+ FrameInfoSP baseFrameInfo = frameInfo->baseFrame();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(baseFrameInfo, KisOpenGLUpdateInfoSP());
+
+ if (baseFrameInfo == m_d->lastLoadedBaseFrameInfo) {
+ frame = m_d->lastLoadedBaseFrame.clone();
+ } else {
+ frame = m_d->serializer.loadFrame(baseFrameInfo->frameDataId(), builder.textureInfoPool());
+ m_d->lastLoadedBaseFrame = frame.clone();
+ m_d->lastLoadedBaseFrameInfo = baseFrameInfo;
+ }
+ break;
+ }
+ case FrameDiff: {
+ FrameInfoSP baseFrameInfo = frameInfo->baseFrame();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(baseFrameInfo, KisOpenGLUpdateInfoSP());
+
+ if (baseFrameInfo == m_d->lastLoadedBaseFrameInfo) {
+ // noop
+ } else {
+ m_d->lastLoadedBaseFrame = m_d->serializer.loadFrame(baseFrameInfo->frameDataId(), builder.textureInfoPool());
+ m_d->lastLoadedBaseFrameInfo = baseFrameInfo;
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->lastLoadedBaseFrame.isValid(), KisOpenGLUpdateInfoSP());
+ }
+
+ const KisFrameDataSerializer::Frame &baseFrame = m_d->lastLoadedBaseFrame;
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(baseFrame.isValid(), KisOpenGLUpdateInfoSP());
+
+ frame = m_d->serializer.loadFrame(frameInfo->frameDataId(), builder.textureInfoPool());
+ KisFrameDataSerializer::addFrames(frame, baseFrame);
+ break;
+ }
+ }
+
+ for (auto it = frame.frameTiles.begin(); it != frame.frameTiles.end(); ++it) {
+ KisFrameDataSerializer::FrameTile &tile = *it;
+
+ QRect patchRect = tile.rect;
+
+ if (frameInfo->levelOfDetail()) {
+ patchRect = KisLodTransform::upscaledRect(patchRect, frameInfo->levelOfDetail());
+ }
+
+ const QRect fullSizeTileRect =
+ builder.calculatePhysicalTileRect(tile.col, tile.row,
+ frameInfo->imageBounds(),
+ frameInfo->levelOfDetail());
+
+ KisTextureTileUpdateInfoSP tileInfo(
+ new KisTextureTileUpdateInfo(tile.col, tile.row,
+ fullSizeTileRect, patchRect,
+ frameInfo->imageBounds(),
+ frameInfo->levelOfDetail(),
+ builder.textureInfoPool()));
+
+ tileInfo->putPixelData(std::move(tile.data), builder.destinationColorSpace());
+
+ info->tileList << tileInfo;
+ }
+
+ return info;
+}
+
+void KisFrameCacheStore::moveFrame(int srcFrameId, int dstFrameId)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(srcFrameId != dstFrameId);
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->savedFrames.contains(srcFrameId));
+
+ KIS_SAFE_ASSERT_RECOVER(!m_d->savedFrames.contains(dstFrameId)) {
+ m_d->savedFrames.remove(dstFrameId);
+ }
+
+ m_d->savedFrames.insert(dstFrameId, m_d->savedFrames[srcFrameId]);
+ m_d->savedFrames.remove(srcFrameId);
+
+ if (m_d->lastSavedFullFrameId == srcFrameId) {
+ m_d->lastSavedFullFrameId = dstFrameId;
+ }
+}
+
+void KisFrameCacheStore::forgetFrame(int frameId)
+{
+ KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->savedFrames.contains(frameId));
+
+ if (m_d->lastSavedFullFrameId == frameId) {
+ m_d->lastSavedFullFrame = KisFrameDataSerializer::Frame();
+ m_d->lastSavedFullFrameId = -1;
+ }
+
+ m_d->savedFrames.remove(frameId);
+}
+
+bool KisFrameCacheStore::hasFrame(int frameId) const
+{
+ return m_d->savedFrames.contains(frameId);
+}
+
+int KisFrameCacheStore::frameLevelOfDetail(int frameId) const
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->savedFrames.contains(frameId), 0);
+ return m_d->savedFrames[frameId]->levelOfDetail();
+}
+
+QRect KisFrameCacheStore::frameDirtyRect(int frameId) const
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->savedFrames.contains(frameId), QRect());
+ return m_d->savedFrames[frameId]->dirtyImageRect();
+}
diff --git a/libs/ui/KisFrameCacheStore.h b/libs/ui/KisFrameCacheStore.h
new file mode 100644
index 0000000000..58db2681ee
--- /dev/null
+++ b/libs/ui/KisFrameCacheStore.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 KISFRAMECACHESTORE_H
+#define KISFRAMECACHESTORE_H
+
+#include "kritaui_export.h"
+#include
+#include "kis_types.h"
+
+#include "opengl/kis_texture_tile_info_pool.h"
+
+class KisOpenGLUpdateInfoBuilder;
+
+class KisOpenGLUpdateInfo;
+typedef KisSharedPtr KisOpenGLUpdateInfoSP;
+
+/**
+ * KisFrameCacheStore is a middle-level class for reading/writing
+ * animation frames on disk. Its main responsibilities:
+ *
+ * 1) Convert frames from KisOpenGLUpdateInfo format into a serializable
+ * KisFrameDataSerializer::Frame format.
+ *
+ * 2) Calculate differences between the frames and decide which
+ * frame will be a keyframe for other frames.
+ *
+ * 3) The keyframes will be used as a base for difference
+ * calculation and stored in a short in-memory cache to avoid
+ * fetching them from disk too often.
+ *
+ * 4) The in-memory cache of the keyframes is stored in serializable
+ * KisFrameDataSerializer::Frame format.
+ */
+
+class KRITAUI_EXPORT KisFrameCacheStore
+{
+public:
+ KisFrameCacheStore();
+ KisFrameCacheStore(const QString &frameCachePath);
+
+ ~KisFrameCacheStore();
+
+ // WARNING: after transferring \p info to saveFrame() the object becomes invalid
+ void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds);
+ KisOpenGLUpdateInfoSP loadFrame(int frameId, const KisOpenGLUpdateInfoBuilder &builder);
+
+ void moveFrame(int srcFrameId, int dstFrameId);
+
+ void forgetFrame(int frameId);
+ bool hasFrame(int frameId) const;
+
+ int frameLevelOfDetail(int frameId) const;
+ QRect frameDirtyRect(int frameId) const;
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+#endif // KISFRAMECACHESTORE_H
diff --git a/libs/ui/KisFrameCacheSwapper.cpp b/libs/ui/KisFrameCacheSwapper.cpp
new file mode 100644
index 0000000000..15c138fcd1
--- /dev/null
+++ b/libs/ui/KisFrameCacheSwapper.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 "KisFrameCacheSwapper.h"
+
+#include "KisFrameCacheStore.h"
+
+#include "kis_update_info.h"
+#include "opengl/KisOpenGLUpdateInfoBuilder.h"
+
+struct KisFrameCacheSwapper::Private
+{
+ Private(const KisOpenGLUpdateInfoBuilder &_builder, const QString &frameCachePath)
+ : frameStore(frameCachePath),
+ builder(_builder)
+ {
+ }
+
+ KisFrameCacheStore frameStore;
+ const KisOpenGLUpdateInfoBuilder &builder;
+};
+
+KisFrameCacheSwapper::KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder)
+ : KisFrameCacheSwapper(builder, "")
+{
+}
+
+KisFrameCacheSwapper::KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder, const QString &frameCachePath)
+ : m_d(new Private(builder, frameCachePath))
+{
+}
+
+KisFrameCacheSwapper::~KisFrameCacheSwapper()
+{
+}
+
+void KisFrameCacheSwapper::saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds)
+{
+ m_d->frameStore.saveFrame(frameId, info, imageBounds);
+}
+
+KisOpenGLUpdateInfoSP KisFrameCacheSwapper::loadFrame(int frameId)
+{
+ return m_d->frameStore.loadFrame(frameId, m_d->builder);
+}
+
+void KisFrameCacheSwapper::moveFrame(int srcFrameId, int dstFrameId)
+{
+ m_d->frameStore.moveFrame(srcFrameId, dstFrameId);
+}
+
+void KisFrameCacheSwapper::forgetFrame(int frameId)
+{
+ m_d->frameStore.forgetFrame(frameId);
+}
+
+bool KisFrameCacheSwapper::hasFrame(int frameId) const
+{
+ return m_d->frameStore.hasFrame(frameId);
+}
+
+int KisFrameCacheSwapper::frameLevelOfDetail(int frameId) const
+{
+ return m_d->frameStore.frameLevelOfDetail(frameId);
+}
+
+QRect KisFrameCacheSwapper::frameDirtyRect(int frameId) const
+{
+ return m_d->frameStore.frameDirtyRect(frameId);
+}
diff --git a/libs/ui/KisFrameCacheSwapper.h b/libs/ui/KisFrameCacheSwapper.h
new file mode 100644
index 0000000000..17cd32390c
--- /dev/null
+++ b/libs/ui/KisFrameCacheSwapper.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 KISFRAMECACHESWAPPER_H
+#define KISFRAMECACHESWAPPER_H
+
+#include
+
+#include "KisAbstractFrameCacheSwapper.h"
+
+class KisOpenGLUpdateInfoBuilder;
+
+
+/**
+ * KisFrameCacheSwapper is the most highlevel facade of the frame
+ * swapping infrastructure. The main responsibilities of the class:
+ *
+ * 1) Asynchronously predict and prefetch the pending frames from disk
+ * and maintain a short in-memory cache of these frames (already
+ * converted into KisOpenGLUpdateInfo)
+ *
+ * 2) Pass all the other requests to the lower-level API,
+ * like KisFrameCacheStore
+ */
+
+class KRITAUI_EXPORT KisFrameCacheSwapper : public KisAbstractFrameCacheSwapper
+{
+public:
+ KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder);
+ KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder, const QString &frameCachePath);
+ ~KisFrameCacheSwapper();
+
+ // WARNING: after transferring \p info to saveFrame() the object becomes invalid
+ void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) override;
+ KisOpenGLUpdateInfoSP loadFrame(int frameId) override;
+
+ void moveFrame(int srcFrameId, int dstFrameId) override;
+
+ void forgetFrame(int frameId) override;
+ bool hasFrame(int frameId) const override;
+
+ int frameLevelOfDetail(int frameId) const override;
+
+ QRect frameDirtyRect(int frameId) const override;
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+#endif // KISFRAMECACHESWAPPER_H
diff --git a/libs/ui/KisFrameDataSerializer.cpp b/libs/ui/KisFrameDataSerializer.cpp
new file mode 100644
index 0000000000..67f637e901
--- /dev/null
+++ b/libs/ui/KisFrameDataSerializer.cpp
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 "KisFrameDataSerializer.h"
+
+#include
+#include
+#include "tiles3/swap/kis_lzf_compression.h"
+
+
+struct KRITAUI_NO_EXPORT KisFrameDataSerializer::Private
+{
+ Private(const QString &frameCachePath)
+ : framesDir(
+ (!frameCachePath.isEmpty() ? frameCachePath : QDir::tempPath()) +
+ QDir::separator() + "KritaFrameCacheXXXXXX")
+ {
+ KIS_SAFE_ASSERT_RECOVER_NOOP(framesDir.isValid());
+ framesDirObject = QDir(framesDir.path());
+ framesDirObject.makeAbsolute();
+ }
+
+ QString subfolderNameForFrame(int frameId)
+ {
+ const int subfolderIndex = frameId & 0xff00;
+ return QString::number(subfolderIndex);
+ }
+
+ QString fileNameForFrame(int frameId) {
+ return QString("frame_%1").arg(frameId);
+ }
+
+ QString filePathForFrame(int frameId)
+ {
+ return framesDirObject.filePath(
+ subfolderNameForFrame(frameId) + QDir::separator() +
+ fileNameForFrame(frameId));
+ }
+
+ int generateFrameId() {
+ // TODO: handle wrapping and range compression
+ return nextFrameId++;
+ }
+
+ quint8* getCompressionBuffer(int size) {
+ if (compressionBuffer.size() < size) {
+ compressionBuffer.resize(size);
+ }
+ return reinterpret_cast(compressionBuffer.data());
+ }
+
+ QTemporaryDir framesDir;
+ QDir framesDirObject;
+ int nextFrameId = 0;
+
+ QByteArray compressionBuffer;
+};
+
+KisFrameDataSerializer::KisFrameDataSerializer()
+ : KisFrameDataSerializer(QString())
+{
+}
+
+KisFrameDataSerializer::KisFrameDataSerializer(const QString &frameCachePath)
+ : m_d(new Private(frameCachePath))
+{
+}
+
+KisFrameDataSerializer::~KisFrameDataSerializer()
+{
+}
+
+int KisFrameDataSerializer::saveFrame(const KisFrameDataSerializer::Frame &frame)
+{
+ KisLzfCompression compression;
+
+ const int frameId = m_d->generateFrameId();
+
+ const QString frameSubfolder = m_d->subfolderNameForFrame(frameId);
+
+ if (!m_d->framesDirObject.exists(frameSubfolder)) {
+ m_d->framesDirObject.mkpath(frameSubfolder);
+ }
+
+ const QString frameRelativePath = frameSubfolder + QDir::separator() + m_d->fileNameForFrame(frameId);
+
+ if (m_d->framesDirObject.exists(frameRelativePath)) {
+ qWarning() << "WARNING: overwriting existing frame file!" << frameRelativePath;
+ forgetFrame(frameId);
+ }
+
+ const QString frameFilePath = m_d->framesDirObject.filePath(frameRelativePath);
+
+ QFile file(frameFilePath);
+ file.open(QFile::WriteOnly);
+
+ QDataStream stream(&file);
+ stream << frameId;
+ stream << frame.pixelSize;
+
+ stream << int(frame.frameTiles.size());
+
+ for (int i = 0; i < int(frame.frameTiles.size()); i++) {
+ const FrameTile &tile = frame.frameTiles[i];
+
+ stream << tile.col;
+ stream << tile.row;
+ stream << tile.rect;
+
+ const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
+ const int maxBufferSize = compression.outputBufferSize(frameByteSize);
+ quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
+
+ const int compressedSize =
+ compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize);
+
+ ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize);
+
+ const bool isCompressed = compressedSize < frameByteSize;
+ stream << isCompressed;
+
+ if (isCompressed) {
+ stream << compressedSize;
+ stream.writeRawData((char*)buffer, compressedSize);
+ } else {
+ stream << frameByteSize;
+ stream.writeRawData((char*)tile.data.data(), frameByteSize);
+ }
+ }
+
+ file.close();
+
+ return frameId;
+}
+
+#include "../sdk/tests/testutil.h"
+#include
+
+TestUtil::MeasureAvgPortion CC(10);
+
+KisFrameDataSerializer::Frame KisFrameDataSerializer::loadFrame(int frameId, KisTextureTileInfoPoolSP pool)
+{
+ KisLzfCompression compression;
+
+ QElapsedTimer loadingTime;
+ loadingTime.start();
+
+ int loadedFrameId = -1;
+ KisFrameDataSerializer::Frame frame;
+
+ qint64 compressionTime = 0;
+
+ const QString framePath = m_d->filePathForFrame(frameId);
+
+ QFile file(framePath);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists());
+ if (!file.open(QFile::ReadOnly)) return frame;
+
+ QDataStream stream(&file);
+
+ int numTiles = 0;
+
+ stream >> loadedFrameId;
+ stream >> frame.pixelSize;
+ stream >> numTiles;
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(loadedFrameId == frameId, KisFrameDataSerializer::Frame());
+
+
+
+ for (int i = 0; i < numTiles; i++) {
+ FrameTile tile(pool);
+ stream >> tile.col;
+ stream >> tile.row;
+ stream >> tile.rect;
+
+ const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize),
+ KisFrameDataSerializer::Frame());
+
+ bool isCompressed = false;
+ int inputSize = -1;
+
+ stream >> isCompressed;
+ stream >> inputSize;
+
+ if (isCompressed) {
+ const int maxBufferSize = compression.outputBufferSize(inputSize);
+ quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
+ stream.readRawData((char*)buffer, inputSize);
+
+ tile.data.allocate(frame.pixelSize);
+
+ QElapsedTimer compTime;
+ compTime.start();
+
+ const int decompressedSize =
+ compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize);
+
+ compressionTime += compTime.nsecsElapsed();
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize,
+ KisFrameDataSerializer::Frame());
+
+ } else {
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize,
+ KisFrameDataSerializer::Frame());
+
+ tile.data.allocate(frame.pixelSize);
+ stream.readRawData((char*)tile.data.data(), inputSize);
+ }
+
+ frame.frameTiles.push_back(std::move(tile));
+ }
+
+ file.close();
+
+ CC.addVal(compressionTime / 1000);
+ CC.addTotal(loadingTime.nsecsElapsed() / 1000);
+
+ return frame;
+}
+
+void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId)
+{
+ const QString srcFramePath = m_d->filePathForFrame(srcFrameId);
+ const QString dstFramePath = m_d->filePathForFrame(dstFrameId);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists());
+
+ KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) {
+ QFile::remove(dstFramePath);
+ }
+
+ QFile::rename(srcFramePath, dstFramePath);
+}
+
+bool KisFrameDataSerializer::hasFrame(int frameId) const
+{
+ const QString framePath = m_d->filePathForFrame(frameId);
+ return QFileInfo(framePath).exists();
+}
+
+void KisFrameDataSerializer::forgetFrame(int frameId)
+{
+ const QString framePath = m_d->filePathForFrame(frameId);
+ QFile::remove(framePath);
+}
+
+boost::optional KisFrameDataSerializer::estimateFrameUniqueness(const KisFrameDataSerializer::Frame &lhs, const KisFrameDataSerializer::Frame &rhs, qreal portion)
+{
+ if (lhs.pixelSize != rhs.pixelSize) return boost::none;
+ if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none;
+
+ const int pixelSize = lhs.pixelSize;
+ int numSampledPixels = 0;
+ int numUniquePixels = 0;
+ const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0;
+
+ for (int i = 0; i < int(lhs.frameTiles.size()); i++) {
+ const FrameTile &lhsTile = lhs.frameTiles[i];
+ const FrameTile &rhsTile = rhs.frameTiles[i];
+
+ if (lhsTile.col != rhsTile.col ||
+ lhsTile.row != rhsTile.row ||
+ lhsTile.rect != rhsTile.rect) {
+
+ return boost::none;
+ }
+
+ if (sampleStep > 0) {
+ const int numPixels = lhsTile.rect.width() * lhsTile.rect.height();
+ for (int j = 0; j < numPixels; j += sampleStep) {
+ quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize;
+ quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize;
+
+ if (std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) {
+ numUniquePixels++;
+ }
+ numSampledPixels++;
+ }
+ }
+ }
+
+ return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0;
+}
+
+template class OpPolicy, typename T>
+bool processData(T *dst, const T *src, int numUnits)
+{
+ OpPolicy op;
+
+ bool unitsAreSame = true;
+
+ for (int j = 0; j < numUnits; j++) {
+ *dst = op(*dst, *src);
+
+ if (*dst != 0) {
+ unitsAreSame = false;
+ }
+
+ src++;
+ dst++;
+ }
+ return unitsAreSame;
+}
+
+
+template class OpPolicy>
+bool KisFrameDataSerializer::processFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
+{
+ bool framesAreSame = true;
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(estimateFrameUniqueness(src, dst, 0.0), false);
+
+ for (int i = 0; i < int(src.frameTiles.size()); i++) {
+ const FrameTile &srcTile = src.frameTiles[i];
+ FrameTile &dstTile = dst.frameTiles[i];
+
+ const int numBytes = srcTile.rect.width() * srcTile.rect.height() * src.pixelSize;
+ const int numQWords = numBytes / 8;
+
+ const quint64 *srcDataPtr = reinterpret_cast(srcTile.data.data());
+ quint64 *dstDataPtr = reinterpret_cast(dstTile.data.data());
+
+ framesAreSame &= processData(dstDataPtr, srcDataPtr, numQWords);
+
+
+ const int tailBytes = numBytes % 8;
+ const quint8 *srcTailDataPtr = srcTile.data.data() + numBytes - tailBytes;
+ quint8 *dstTailDataPtr = dstTile.data.data() + numBytes - tailBytes;
+
+ framesAreSame &= processData(dstTailDataPtr, srcTailDataPtr, tailBytes);
+ }
+
+ return framesAreSame;
+}
+
+bool KisFrameDataSerializer::subtractFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
+{
+ return processFrames(dst, src);
+}
+
+void KisFrameDataSerializer::addFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
+{
+ // TODO: don't spend time on calculation of "framesAreSame" in this case
+ (void) processFrames(dst, src);
+}
diff --git a/libs/ui/KisFrameDataSerializer.h b/libs/ui/KisFrameDataSerializer.h
new file mode 100644
index 0000000000..b2f83c9098
--- /dev/null
+++ b/libs/ui/KisFrameDataSerializer.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 KISFRAMEDATASERIALIZER_H
+#define KISFRAMEDATASERIALIZER_H
+
+#include "kritaui_export.h"
+#include
+#include "opengl/kis_texture_tile_info_pool.h"
+
+// TODO: extract DataBuffer into a separate file
+#include "opengl/kis_texture_tile_update_info.h"
+
+#include
+#include
+
+class QString;
+
+
+/**
+ * KisFrameDataSerializer is the lowest level class for storing frame
+ * data on disk. Its responsibilities are simple:
+ *
+ * 1) Accept low-level frame data object (KisFrameDataSerializer::Frame),
+ * which contains raw data in it (the data may be not a pixel data,
+ * but a preprocessed pixel differences)
+ *
+ * 2) Compress this data and save it on disk
+ */
+
+class KRITAUI_EXPORT KisFrameDataSerializer
+{
+public:
+ struct FrameTile
+ {
+ FrameTile(KisTextureTileInfoPoolSP pool) : data(pool) {}
+
+ FrameTile(FrameTile &&rhs) = default;
+ FrameTile& operator=(FrameTile &&rhs) = default;
+
+ FrameTile(const FrameTile &rhs) = delete;
+ FrameTile& operator=(FrameTile &rhs) = delete;
+
+ bool isValid() const {
+ return data.data();
+ }
+
+ FrameTile clone() const{
+ FrameTile tile(data.pool());
+ tile.col = col;
+ tile.row = row;
+ tile.rect = rect;
+ tile.data.allocate(data.pixelSize());
+
+ const int bufferSize = data.pixelSize() * rect.width() * rect.height();
+ memcpy(tile.data.data(), data.data(), bufferSize);
+
+ return tile;
+ }
+
+ int col = -1;
+ int row = -1;
+ bool isCompressed = false;
+ QRect rect;
+ DataBuffer data;
+ };
+
+ struct Frame
+ {
+ Frame() = default;
+
+ Frame(Frame&&rhs) = default;
+ Frame& operator=(Frame &&rhs) = default;
+
+ Frame(const Frame &rhs) = delete;
+ Frame& operator=(Frame &rhs) = delete;
+
+ Frame clone() const {
+ Frame frame;
+ frame.pixelSize = pixelSize;
+ for (auto it = frameTiles.begin(); it != frameTiles.end(); ++it) {
+ frame.frameTiles.push_back(it->clone());
+ }
+ return frame;
+ }
+
+ int pixelSize = 0;
+ std::vector frameTiles;
+
+ bool isValid() const {
+ return pixelSize > 0;
+ }
+ };
+
+public:
+ KisFrameDataSerializer();
+ KisFrameDataSerializer(const QString &frameCachePath);
+ ~KisFrameDataSerializer();
+
+ int saveFrame(const Frame &frame);
+ Frame loadFrame(int frameId, KisTextureTileInfoPoolSP pool);
+
+ void moveFrame(int srcFrameId, int dstFrameId);
+
+ bool hasFrame(int frameId) const;
+ void forgetFrame(int frameId);
+
+ static boost::optional estimateFrameUniqueness(const Frame &lhs, const Frame &rhs, qreal portion);
+ static bool subtractFrames(Frame &dst, const Frame &src);
+ static void addFrames(Frame &dst, const Frame &src);
+
+private:
+ template class OpPolicy>
+ static bool processFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src);
+
+private:
+ Q_DISABLE_COPY(KisFrameDataSerializer)
+
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+#endif // KISFRAMEDATASERIALIZER_H
diff --git a/libs/ui/KisInMemoryFrameCacheSwapper.cpp b/libs/ui/KisInMemoryFrameCacheSwapper.cpp
new file mode 100644
index 0000000000..299ebdfa49
--- /dev/null
+++ b/libs/ui/KisInMemoryFrameCacheSwapper.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 "KisInMemoryFrameCacheSwapper.h"
+
+#include
+#include
+
+
+struct KRITAUI_NO_EXPORT KisInMemoryFrameCacheSwapper::Private
+{
+ QMap framesMap;
+};
+
+KisInMemoryFrameCacheSwapper::KisInMemoryFrameCacheSwapper()
+ : m_d(new Private)
+{
+}
+
+KisInMemoryFrameCacheSwapper::~KisInMemoryFrameCacheSwapper()
+{
+}
+
+void KisInMemoryFrameCacheSwapper::saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds)
+{
+ Q_UNUSED(imageBounds);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->framesMap.contains(frameId));
+
+ m_d->framesMap.insert(frameId, info);
+}
+
+KisOpenGLUpdateInfoSP KisInMemoryFrameCacheSwapper::loadFrame(int frameId)
+{
+ KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->framesMap.contains(frameId));
+ return m_d->framesMap.value(frameId, KisOpenGLUpdateInfoSP());
+}
+
+void KisInMemoryFrameCacheSwapper::moveFrame(int srcFrameId, int dstFrameId)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->framesMap.contains(srcFrameId));
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->framesMap.contains(dstFrameId));
+
+ m_d->framesMap[dstFrameId] = m_d->framesMap[srcFrameId];
+ m_d->framesMap.remove(srcFrameId);
+}
+
+void KisInMemoryFrameCacheSwapper::forgetFrame(int frameId)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->framesMap.contains(frameId));
+ m_d->framesMap.remove(frameId);
+}
+
+bool KisInMemoryFrameCacheSwapper::hasFrame(int frameId) const
+{
+ return m_d->framesMap.contains(frameId);
+}
+
+int KisInMemoryFrameCacheSwapper::frameLevelOfDetail(int frameId) const
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->framesMap.contains(frameId), 0);
+ return m_d->framesMap[frameId]->levelOfDetail();
+}
+
+QRect KisInMemoryFrameCacheSwapper::frameDirtyRect(int frameId) const
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->framesMap.contains(frameId), QRect());
+ return m_d->framesMap[frameId]->dirtyImageRect();
+}
diff --git a/libs/ui/KisInMemoryFrameCacheSwapper.h b/libs/ui/KisInMemoryFrameCacheSwapper.h
new file mode 100644
index 0000000000..9a5ae1263f
--- /dev/null
+++ b/libs/ui/KisInMemoryFrameCacheSwapper.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * 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 KISINMEMORYFRAMECACHESWAPPER_H
+#define KISINMEMORYFRAMECACHESWAPPER_H
+
+#include
+
+#include "KisAbstractFrameCacheSwapper.h"
+#include "opengl/kis_texture_tile_info_pool.h"
+
+class KisOpenGLUpdateInfoBuilder;
+
+
+class KRITAUI_EXPORT KisInMemoryFrameCacheSwapper : public KisAbstractFrameCacheSwapper
+{
+public:
+ KisInMemoryFrameCacheSwapper();
+ ~KisInMemoryFrameCacheSwapper();
+
+ // WARNING: after transferring \p info to saveFrame() the object becomes invalid
+ void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) override;
+ KisOpenGLUpdateInfoSP loadFrame(int frameId) override;
+
+ void moveFrame(int srcFrameId, int dstFrameId) override;
+
+ void forgetFrame(int frameId) override;
+ bool hasFrame(int frameId) const override;
+
+ int frameLevelOfDetail(int frameId) const override;
+
+ QRect frameDirtyRect(int frameId) const override;
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+#endif // KISINMEMORYFRAMECACHESWAPPER_H
diff --git a/libs/ui/KisReferenceImagesDecoration.cpp b/libs/ui/KisReferenceImagesDecoration.cpp
index 989b76c30d..d14366c1b2 100644
--- a/libs/ui/KisReferenceImagesDecoration.cpp
+++ b/libs/ui/KisReferenceImagesDecoration.cpp
@@ -1,138 +1,138 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisReferenceImagesDecoration.h"
#include "KoShapeManager.h"
#include "kis_algebra_2d.h"
#include "KisDocument.h"
#include "KisReferenceImagesLayer.h"
struct KisReferenceImagesDecoration::Private {
struct Buffer
{
/// Top left corner of the buffer relative to the viewport
QPointF position;
QImage image;
QRectF bounds() const
{
return QRectF(position, image.size());
}
};
KisReferenceImagesDecoration *q;
KisWeakSharedPtr layer;
Buffer buffer;
QTransform previousTransform;
explicit Private(KisReferenceImagesDecoration *q)
: q(q)
{}
void updateBufferByImageCoordinates(const QRectF &dirtyImageRect)
{
QRectF dirtyWidgetRect = q->view()->viewConverter()->imageToWidget(dirtyImageRect);
updateBuffer(dirtyWidgetRect, dirtyImageRect);
}
void updateBufferByWidgetCoordinates(const QRectF &dirtyWidgetRect)
{
QRectF dirtyImageRect = q->view()->viewConverter()->widgetToImage(dirtyWidgetRect);
updateBuffer(dirtyWidgetRect, dirtyImageRect);
}
private:
void updateBuffer(const QRectF &widgetRect, const QRectF &imageRect)
{
KisCoordinatesConverter *viewConverter = q->view()->viewConverter();
QTransform transform = viewConverter->imageToWidgetTransform();
if (buffer.image.isNull() || !buffer.bounds().contains(widgetRect)) {
// TODO: only use enough buffer to cover the BB of the shapes
buffer.position = QPointF();
buffer.image = QImage(q->view()->width(), q->view()->height(), QImage::Format_ARGB32);
buffer.image.fill(Qt::transparent);
}
QPainter gc(&buffer.image);
gc.setTransform(transform);
gc.save();
gc.setCompositionMode(QPainter::CompositionMode_Source);
gc.fillRect(imageRect, Qt::transparent);
gc.restore();
gc.setClipRect(imageRect);
layer->paintReferences(gc);
}
};
KisReferenceImagesDecoration::KisReferenceImagesDecoration(QPointer parent)
: KisCanvasDecoration("referenceImagesDecoration", parent)
, d(new Private(this))
{}
KisReferenceImagesDecoration::~KisReferenceImagesDecoration()
{}
void KisReferenceImagesDecoration::addReferenceImage(KisReferenceImage *referenceImage)
{
- KisSharedPtr layer = view()->document()->createReferenceImagesLayer();
+ KisSharedPtr layer = view()->document()->getOrCreateReferenceImagesLayer();
KIS_SAFE_ASSERT_RECOVER_RETURN(layer);
KUndo2Command *cmd = layer->addReferenceImage(referenceImage);
view()->document()->addCommand(cmd);
}
bool KisReferenceImagesDecoration::documentHasReferenceImages() const
{
return view()->document()->referenceImagesLayer() != nullptr;
}
void KisReferenceImagesDecoration::drawDecoration(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter */*converter*/, KisCanvas2 */*canvas*/)
{
KisSharedPtr layer = d->layer.toStrongRef();
if (layer.isNull()) {
layer = d->layer = view()->document()->referenceImagesLayer();
if (layer.isNull()) return;
connect(layer.data(), SIGNAL(sigUpdateCanvas(const QRectF&)), this, SLOT(slotReferenceImagesChanged(const QRectF&)));
d->updateBufferByWidgetCoordinates(updateRect);
} else {
QTransform transform = view()->viewConverter()->imageToWidgetTransform();
if (!KisAlgebra2D::fuzzyMatrixCompare(transform, d->previousTransform, 1e-4)) {
d->previousTransform = transform;
d->updateBufferByWidgetCoordinates(QRectF(0, 0, view()->width(), view()->height()));
}
}
gc.drawImage(d->buffer.position, d->buffer.image);
}
void KisReferenceImagesDecoration::slotReferenceImagesChanged(const QRectF &dirtyRect)
{
d->updateBufferByImageCoordinates(dirtyRect);
QRectF documentRect = view()->viewConverter()->imageToDocument(dirtyRect);
view()->canvasBase()->updateCanvas(documentRect);
}
diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp
index 7db6d793ee..7d4e95d96d 100644
--- a/libs/ui/KisViewManager.cpp
+++ b/libs/ui/KisViewManager.cpp
@@ -1,1402 +1,1402 @@
/*
* This file is part of KimageShop^WKrayon^WKrita
*
* Copyright (c) 1999 Matthias Elter
* 1999 Michael Koch
* 1999 Carsten Pfeiffer
* 2002 Patrick Julien
* 2003-2011 Boudewijn Rempt
* 2004 Clarence Dang
* 2011 José Luis Vergara
* 2017 L. E. Segovia
*
* 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
#include "KisViewManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "input/kis_input_manager.h"
#include "canvas/kis_canvas2.h"
#include "canvas/kis_canvas_controller.h"
#include "canvas/kis_grid_manager.h"
#include "dialogs/kis_dlg_blacklist_cleanup.h"
#include "input/kis_input_profile_manager.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_canvas_controls_manager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_composite_progress_proxy.h"
#include
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_control_frame.h"
#include "kis_coordinates_converter.h"
#include "KisDocument.h"
#include "kis_favorite_resource_manager.h"
#include "kis_filter_manager.h"
#include "kis_group_layer.h"
#include
#include
#include "kis_image_manager.h"
#include
#include "kis_mainwindow_observer.h"
#include "kis_mask_manager.h"
#include "kis_mimedata.h"
#include "kis_mirror_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_node.h"
#include "kis_node_manager.h"
#include "KisDecorationsManager.h"
#include
#include "kis_paintop_box.h"
#include
#include "KisPart.h"
#include "KisPrintJob.h"
#include
#include "KisResourceServerProvider.h"
#include "kis_selection.h"
#include "kis_selection_manager.h"
#include "kis_shape_controller.h"
#include "kis_shape_layer.h"
#include
#include "kis_statusbar.h"
#include
#include
#include "kis_tooltip_manager.h"
#include
#include "KisView.h"
#include "kis_zoom_manager.h"
#include "widgets/kis_floating_message.h"
#include "kis_signal_auto_connection.h"
#include "kis_icon_utils.h"
#include "kis_guides_manager.h"
#include "kis_derived_resources.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include
#include
#include "kis_signals_blocker.h"
class BlockingUserInputEventFilter : public QObject
{
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched);
if(dynamic_cast(event)
|| dynamic_cast(event)
|| dynamic_cast(event)) {
return true;
}
else {
return false;
}
}
};
class KisViewManager::KisViewManagerPrivate
{
public:
KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent)
: filterManager(_q)
, createTemplate(0)
, saveIncremental(0)
, saveIncrementalBackup(0)
, openResourcesDirectory(0)
, rotateCanvasRight(0)
, rotateCanvasLeft(0)
, resetCanvasRotation(0)
, wrapAroundAction(0)
, levelOfDetailAction(0)
, showRulersAction(0)
, rulersTrackMouseAction(0)
, zoomTo100pct(0)
, zoomIn(0)
, zoomOut(0)
, selectionManager(_q)
, statusBar(_q)
, controlFrame(_q, _q_parent)
, nodeManager(_q)
, imageManager(_q)
, gridManager(_q)
, canvasControlsManager(_q)
, paintingAssistantsManager(_q)
, actionManager(_q, _actionCollection)
, mainWindow(0)
, showFloatingMessage(true)
, currentImageView(0)
, canvasResourceProvider(_q)
, canvasResourceManager()
, guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q)
, actionCollection(_actionCollection)
, mirrorManager(_q)
, inputManager(_q)
, actionAuthor(0)
, showPixelGrid(0)
{
KisViewManager::initializeResourceManager(&canvasResourceManager);
}
public:
KisFilterManager filterManager;
KisAction *createTemplate;
KisAction *createCopy;
KisAction *saveIncremental;
KisAction *saveIncrementalBackup;
KisAction *openResourcesDirectory;
KisAction *rotateCanvasRight;
KisAction *rotateCanvasLeft;
KisAction *resetCanvasRotation;
KisAction *wrapAroundAction;
KisAction *levelOfDetailAction;
KisAction *showRulersAction;
KisAction *rulersTrackMouseAction;
KisAction *zoomTo100pct;
KisAction *zoomIn;
KisAction *zoomOut;
KisAction *softProof;
KisAction *gamutCheck;
KisSelectionManager selectionManager;
KisGuidesManager guidesManager;
KisStatusBar statusBar;
QPointer persistentImageProgressUpdater;
QScopedPointer persistentUnthreadedProgressUpdaterRouter;
QPointer persistentUnthreadedProgressUpdater;
KisControlFrame controlFrame;
KisNodeManager nodeManager;
KisImageManager imageManager;
KisGridManager gridManager;
KisCanvasControlsManager canvasControlsManager;
KisDecorationsManager paintingAssistantsManager;
BlockingUserInputEventFilter blockingEventFilter;
KisActionManager actionManager;
QMainWindow* mainWindow;
QPointer savedFloatingMessage;
bool showFloatingMessage;
QPointer currentImageView;
KisCanvasResourceProvider canvasResourceProvider;
KoCanvasResourceManager canvasResourceManager;
KisSignalCompressor guiUpdateCompressor;
KActionCollection *actionCollection;
KisMirrorManager mirrorManager;
KisInputManager inputManager;
KisSignalAutoConnectionsStore viewConnections;
KSelectAction *actionAuthor; // Select action for author profile.
KisAction *showPixelGrid;
QByteArray canvasState;
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
QFlags windowFlags;
#endif
bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force);
};
KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection)
: d(new KisViewManagerPrivate(this, _actionCollection, parent))
{
d->actionCollection = _actionCollection;
d->mainWindow = dynamic_cast(parent);
d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager);
connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout()));
createActions();
setupManagers();
// These initialization functions must wait until KisViewManager ctor is complete.
d->statusBar.setup();
d->persistentImageProgressUpdater =
d->statusBar.progressUpdater()->startSubtask(1, "", true);
// reset state to "completed"
d->persistentImageProgressUpdater->setRange(0,100);
d->persistentImageProgressUpdater->setValue(100);
d->persistentUnthreadedProgressUpdater =
d->statusBar.progressUpdater()->startSubtask(1, "", true);
// reset state to "completed"
d->persistentUnthreadedProgressUpdater->setRange(0,100);
d->persistentUnthreadedProgressUpdater->setValue(100);
d->persistentUnthreadedProgressUpdaterRouter.reset(
new KoProgressUpdater(d->persistentUnthreadedProgressUpdater,
KoProgressUpdater::Unthreaded));
d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true);
d->controlFrame.setup(parent);
//Check to draw scrollbars after "Canvas only mode" toggle is created.
this->showHideScrollbars();
QScopedPointer dummy(new KoDummyCanvasController(actionCollection()));
KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data());
QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility()));
connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)),
d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice)));
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)),
d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int)));
connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)),
resourceProvider(), SLOT(slotNodeActivated(KisNodeSP)));
connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant)));
connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*)));
connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions()));
connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction()));
KisInputProfileManager::instance()->loadProfiles();
KisConfig cfg;
d->showFloatingMessage = cfg.showCanvasMessages();
}
KisViewManager::~KisViewManager()
{
KisConfig cfg;
if (resourceProvider() && resourceProvider()->currentPreset()) {
cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name());
cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor());
cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor());
}
cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength());
delete d;
}
void KisViewManager::initializeResourceManager(KoCanvasResourceManager *resourceManager)
{
resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter));
resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator));
}
KActionCollection *KisViewManager::actionCollection() const
{
return d->actionCollection;
}
void KisViewManager::slotViewAdded(KisView *view)
{
// WARNING: this slot is called even when a view from another main windows is added!
// Don't expect \p view be a child of this view manager!
Q_UNUSED(view);
if (viewCount() == 0) {
d->statusBar.showAllStatusBarItems();
}
}
void KisViewManager::slotViewRemoved(KisView *view)
{
// WARNING: this slot is called even when a view from another main windows is removed!
// Don't expect \p view be a child of this view manager!
Q_UNUSED(view);
if (viewCount() == 0) {
d->statusBar.hideAllStatusBarItems();
}
}
void KisViewManager::setCurrentView(KisView *view)
{
bool first = true;
if (d->currentImageView) {
d->currentImageView->notifyCurrentStateChanged(false);
d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor));
first = false;
KisDocument* doc = d->currentImageView->document();
if (doc) {
doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater);
doc->disconnect(this);
}
d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar);
d->viewConnections.clear();
}
QPointer imageView = qobject_cast(view);
d->currentImageView = imageView;
if (imageView) {
d->softProof->setChecked(imageView->softProofing());
d->gamutCheck->setChecked(imageView->gamutCheck());
// Wait for the async image to have loaded
KisDocument* doc = view->document();
// connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF)));
// Restore the last used brush preset, color and background color.
if (first) {
KisConfig cfg;
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
QString defaultPresetName = "basic_tip_default";
bool foundTip = false;
for (int i=0; iresourceCount(); i++) {
KisPaintOpPresetSP resource = rserver->resources().at(i);
if (resource->name().toLower().contains("basic_tip_default")) {
defaultPresetName = resource->name();
foundTip = true;
} else if (foundTip == false && (resource->name().toLower().contains("default") ||
resource->filename().toLower().contains("default"))) {
defaultPresetName = resource->name();
foundTip = true;
}
}
QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName);
KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset);
if (!preset) {
preset = rserver->resourceByName(defaultPresetName);
}
- if (!preset) {
+ if (!preset && !rserver->resources().isEmpty()) {
preset = rserver->resources().first();
}
if (preset) {
paintOpBox()->restoreResource(preset.data());
}
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoColor foreground(Qt::black, cs);
d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground));
KoColor background(Qt::white, cs);
d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background));
}
KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController());
d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode()));
d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15()));
d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15()));
d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation()));
d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool)));
d->wrapAroundAction->setChecked(canvasController->wrapAroundMode());
d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool)));
d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode());
d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*)));
d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool)));
d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool)));
d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100()));
d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn()));
d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut()));
d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) );
d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) );
// set up progrress reporting
doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater);
d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation()));
d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool)));
imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked());
imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked());
showHideScrollbars();
}
d->filterManager.setView(imageView);
d->selectionManager.setView(imageView);
d->guidesManager.setView(imageView);
d->nodeManager.setView(imageView);
d->imageManager.setView(imageView);
d->canvasControlsManager.setView(imageView);
d->actionManager.setView(imageView);
d->gridManager.setView(imageView);
d->statusBar.setView(imageView);
d->paintingAssistantsManager.setView(imageView);
d->mirrorManager.setView(imageView);
if (d->currentImageView) {
d->currentImageView->notifyCurrentStateChanged(true);
d->currentImageView->canvasController()->activate();
d->currentImageView->canvasController()->setFocus();
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)),
resourceProvider(), SLOT(slotImageSizeChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigResolutionChanged(double,double)),
resourceProvider(), SLOT(slotOnScreenResolutionChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigNodeChanged(KisNodeSP)),
this, SLOT(updateGUI()));
d->viewConnections.addUniqueConnection(
d->currentImageView->zoomManager()->zoomController(),
SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
resourceProvider(), SLOT(slotOnScreenResolutionChanged()));
}
d->actionManager.updateGUI();
resourceProvider()->slotImageSizeChanged();
resourceProvider()->slotOnScreenResolutionChanged();
Q_EMIT viewChanged();
}
KoZoomController *KisViewManager::zoomController() const
{
if (d->currentImageView) {
return d->currentImageView->zoomController();
}
return 0;
}
KisImageWSP KisViewManager::image() const
{
if (document()) {
return document()->image();
}
return 0;
}
KisCanvasResourceProvider * KisViewManager::resourceProvider()
{
return &d->canvasResourceProvider;
}
KisCanvas2 * KisViewManager::canvasBase() const
{
if (d && d->currentImageView) {
return d->currentImageView->canvasBase();
}
return 0;
}
QWidget* KisViewManager::canvas() const
{
if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) {
return d->currentImageView->canvasBase()->canvasWidget();
}
return 0;
}
KisStatusBar * KisViewManager::statusBar() const
{
return &d->statusBar;
}
void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent)
{
d->statusBar.addStatusBarItem(widget, stretch, permanent);
}
void KisViewManager::removeStatusBarItem(QWidget *widget)
{
d->statusBar.removeStatusBarItem(widget);
}
KisPaintopBox* KisViewManager::paintOpBox() const
{
return d->controlFrame.paintopBox();
}
QPointer KisViewManager::createUnthreadedUpdater(const QString &name)
{
return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false);
}
QPointer KisViewManager::createThreadedUpdater(const QString &name)
{
return d->statusBar.progressUpdater()->startSubtask(1, name, false);
}
KisSelectionManager * KisViewManager::selectionManager()
{
return &d->selectionManager;
}
KisNodeSP KisViewManager::activeNode()
{
return d->nodeManager.activeNode();
}
KisLayerSP KisViewManager::activeLayer()
{
return d->nodeManager.activeLayer();
}
KisPaintDeviceSP KisViewManager::activeDevice()
{
return d->nodeManager.activePaintDevice();
}
KisZoomManager * KisViewManager::zoomManager()
{
if (d->currentImageView) {
return d->currentImageView->zoomManager();
}
return 0;
}
KisFilterManager * KisViewManager::filterManager()
{
return &d->filterManager;
}
KisImageManager * KisViewManager::imageManager()
{
return &d->imageManager;
}
KisInputManager* KisViewManager::inputManager() const
{
return &d->inputManager;
}
KisSelectionSP KisViewManager::selection()
{
if (d->currentImageView) {
return d->currentImageView->selection();
}
return 0;
}
bool KisViewManager::selectionEditable()
{
KisLayerSP layer = activeLayer();
if (layer) {
KoProperties properties;
QList masks = layer->childNodes(QStringList("KisSelectionMask"), properties);
if (masks.size() == 1) {
return masks[0]->isEditable();
}
}
// global selection is always editable
return true;
}
KisUndoAdapter * KisViewManager::undoAdapter()
{
if (!document()) return 0;
KisImageWSP image = document()->image();
Q_ASSERT(image);
return image->undoAdapter();
}
void KisViewManager::createActions()
{
KisConfig cfg;
d->saveIncremental = actionManager()->createAction("save_incremental_version");
connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental()));
d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup");
connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup()));
connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved()));
d->saveIncremental->setEnabled(false);
d->saveIncrementalBackup->setEnabled(false);
KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger");
connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger()));
d->createTemplate = actionManager()->createAction("create_template");
connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate()));
d->createCopy = actionManager()->createAction("create_copy");
connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy()));
d->openResourcesDirectory = actionManager()->createAction("open_resources_directory");
connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory()));
d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right");
d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left");
d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation");
d->wrapAroundAction = actionManager()->createAction("wrap_around_mode");
d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode");
d->softProof = actionManager()->createAction("softProof");
d->gamutCheck = actionManager()->createAction("gamutCheck");
KisAction *tAction = actionManager()->createAction("showStatusBar");
tAction->setChecked(cfg.showStatusBar());
connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool)));
tAction = actionManager()->createAction("view_show_canvas_only");
tAction->setChecked(false);
connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool)));
//Workaround, by default has the same shortcut as mirrorCanvas
KisAction *a = dynamic_cast(actionCollection()->action("format_italic"));
if (a) {
a->setDefaultShortcut(QKeySequence());
}
a = actionManager()->createAction("edit_blacklist_cleanup");
connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup()));
actionManager()->createAction("ruler_pixel_multiple2");
d->showRulersAction = actionManager()->createAction("view_ruler");
d->showRulersAction->setChecked(cfg.showRulers());
connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool)));
d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse");
d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse());
connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool)));
d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct");
d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, "");
d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, "");
d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this);
connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &)));
actionCollection()->addAction("settings_active_author", d->actionAuthor);
slotUpdateAuthorProfileActions();
d->showPixelGrid = actionManager()->createAction("view_pixel_grid");
slotUpdatePixelGridAction();
}
void KisViewManager::setupManagers()
{
// Create the managers for filters, selections, layers etc.
// XXX: When the currentlayer changes, call updateGUI on all
// managers
d->filterManager.setup(actionCollection(), actionManager());
d->selectionManager.setup(actionManager());
d->guidesManager.setup(actionManager());
d->nodeManager.setup(actionCollection(), actionManager());
d->imageManager.setup(actionManager());
d->gridManager.setup(actionManager());
d->paintingAssistantsManager.setup(actionManager());
d->canvasControlsManager.setup(actionManager());
d->mirrorManager.setup(actionCollection());
}
void KisViewManager::updateGUI()
{
d->guiUpdateCompressor.start();
}
void KisViewManager::slotBlacklistCleanup()
{
KisDlgBlacklistCleanup dialog;
dialog.exec();
}
KisNodeManager * KisViewManager::nodeManager() const
{
return &d->nodeManager;
}
KisActionManager* KisViewManager::actionManager() const
{
return &d->actionManager;
}
KisGridManager * KisViewManager::gridManager() const
{
return &d->gridManager;
}
KisGuidesManager * KisViewManager::guidesManager() const
{
return &d->guidesManager;
}
KisDocument *KisViewManager::document() const
{
if (d->currentImageView && d->currentImageView->document()) {
return d->currentImageView->document();
}
return 0;
}
int KisViewManager::viewCount() const
{
KisMainWindow *mw = qobject_cast(d->mainWindow);
if (mw) {
return mw->viewCount();
}
return 0;
}
bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force)
{
const int busyWaitDelay = 1000;
KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow);
dialog.blockIfImageIsBusy();
return dialog.result() == QDialog::Accepted;
}
bool KisViewManager::blockUntilOperationsFinished(KisImageSP image)
{
return d->blockUntilOperationsFinishedImpl(image, false);
}
void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image)
{
d->blockUntilOperationsFinishedImpl(image, true);
}
void KisViewManager::slotCreateTemplate()
{
if (!document()) return;
KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow());
}
void KisViewManager::slotCreateCopy()
{
KisDocument *srcDoc = document();
if (!srcDoc) return;
if (!this->blockUntilOperationsFinished(srcDoc->image())) return;
KisDocument *doc = 0;
{
KisImageBarrierLocker l(srcDoc->image());
doc = srcDoc->clone();
}
KIS_SAFE_ASSERT_RECOVER_RETURN(doc);
QString name = srcDoc->documentInfo()->aboutInfo("name");
if (name.isEmpty()) {
name = document()->url().toLocalFile();
}
name = i18n("%1 (Copy)", name);
doc->documentInfo()->setAboutInfo("title", name);
KisPart::instance()->addDocument(doc);
KisMainWindow *mw = qobject_cast(d->mainWindow);
mw->addViewAndNotifyLoadingCompleted(doc);
}
QMainWindow* KisViewManager::qtMainWindow() const
{
if (d->mainWindow)
return d->mainWindow;
//Fallback for when we have not yet set the main window.
QMainWindow* w = qobject_cast(qApp->activeWindow());
if(w)
return w;
return mainWindow();
}
void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow)
{
d->mainWindow = newMainWindow;
}
void KisViewManager::slotDocumentSaved()
{
d->saveIncremental->setEnabled(true);
d->saveIncrementalBackup->setEnabled(true);
}
void KisViewManager::slotSaveIncremental()
{
if (!document()) return;
if (document()->url().isEmpty()) {
KisMainWindow *mw = qobject_cast(d->mainWindow);
mw->saveDocument(document(), true, false);
return;
}
bool foundVersion;
bool fileAlreadyExists;
bool isBackup;
QString version = "000";
QString newVersion;
QString letter;
QString fileName = document()->localFilePath();
// Find current version filenames
// v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well
// Considering our incremental version and backup scheme, format is filename_001~001.ext
QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
regex.indexIn(fileName); // Perform the search
QStringList matches = regex.capturedTexts();
foundVersion = matches.at(0).isEmpty() ? false : true;
// Ensure compatibility with Save Incremental Backup
// If this regex is not kept separate, the entire algorithm needs modification;
// It's simpler to just add this.
QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
regexAux.indexIn(fileName); // Perform the search
QStringList matchesAux = regexAux.capturedTexts();
isBackup = matchesAux.at(0).isEmpty() ? false : true;
// If the filename has a version, prepare it for incrementation
if (foundVersion) {
version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches
if (version.contains(QRegExp("[a-z]"))) {
version.chop(1); // Trim "."
letter = version.right(1); // Save letter
version.chop(1); // Trim letter
} else {
version.chop(1); // Trim "."
}
version.remove(0, 1); // Trim "_"
} else {
// TODO: this will not work with files extensions like jp2
// ...else, simply add a version to it so the next loop works
QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension
regex2.indexIn(fileName);
QStringList matches2 = regex2.capturedTexts();
QString extensionPlusVersion = matches2.at(0);
extensionPlusVersion.prepend(version);
extensionPlusVersion.prepend("_");
fileName.replace(regex2, extensionPlusVersion);
}
// Prepare the base for new version filename
int intVersion = version.toInt(0);
++intVersion;
QString baseNewVersion = QString::number(intVersion);
while (baseNewVersion.length() < version.length()) {
baseNewVersion.prepend("0");
}
// Check if the file exists under the new name and search until options are exhausted (test appending a to z)
do {
newVersion = baseNewVersion;
newVersion.prepend("_");
if (!letter.isNull()) newVersion.append(letter);
if (isBackup) {
newVersion.append("~");
} else {
newVersion.append(".");
}
fileName.replace(regex, newVersion);
fileAlreadyExists = QFile(fileName).exists();
if (fileAlreadyExists) {
if (!letter.isNull()) {
char letterCh = letter.at(0).toLatin1();
++letterCh;
letter = QString(QChar(letterCh));
} else {
letter = 'a';
}
}
} while (fileAlreadyExists && letter != "{"); // x, y, z, {...
if (letter == "{") {
QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number"));
return;
}
document()->setFileBatchMode(true);
document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true);
document()->setFileBatchMode(false);
if (mainWindow()) {
mainWindow()->updateCaption();
}
}
void KisViewManager::slotSaveIncrementalBackup()
{
if (!document()) return;
if (document()->url().isEmpty()) {
KisMainWindow *mw = qobject_cast(d->mainWindow);
mw->saveDocument(document(), true, false);
return;
}
bool workingOnBackup;
bool fileAlreadyExists;
QString version = "000";
QString newVersion;
QString letter;
QString fileName = document()->localFilePath();
// First, discover if working on a backup file, or a normal file
QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]");
regex.indexIn(fileName); // Perform the search
QStringList matches = regex.capturedTexts();
workingOnBackup = matches.at(0).isEmpty() ? false : true;
if (workingOnBackup) {
// Try to save incremental version (of backup), use letter for alt versions
version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches
if (version.contains(QRegExp("[a-z]"))) {
version.chop(1); // Trim "."
letter = version.right(1); // Save letter
version.chop(1); // Trim letter
} else {
version.chop(1); // Trim "."
}
version.remove(0, 1); // Trim "~"
// Prepare the base for new version filename
int intVersion = version.toInt(0);
++intVersion;
QString baseNewVersion = QString::number(intVersion);
QString backupFileName = document()->localFilePath();
while (baseNewVersion.length() < version.length()) {
baseNewVersion.prepend("0");
}
// Check if the file exists under the new name and search until options are exhausted (test appending a to z)
do {
newVersion = baseNewVersion;
newVersion.prepend("~");
if (!letter.isNull()) newVersion.append(letter);
newVersion.append(".");
backupFileName.replace(regex, newVersion);
fileAlreadyExists = QFile(backupFileName).exists();
if (fileAlreadyExists) {
if (!letter.isNull()) {
char letterCh = letter.at(0).toLatin1();
++letterCh;
letter = QString(QChar(letterCh));
} else {
letter = 'a';
}
}
} while (fileAlreadyExists && letter != "{"); // x, y, z, {...
if (letter == "{") {
QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number"));
return;
}
QFile::copy(fileName, backupFileName);
document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true);
if (mainWindow()) mainWindow()->updateCaption();
}
else { // if NOT working on a backup...
// Navigate directory searching for latest backup version, ignore letters
const quint8 HARDCODED_DIGIT_COUNT = 3;
QString baseNewVersion = "000";
QString backupFileName = document()->localFilePath();
QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension
regex2.indexIn(backupFileName);
QStringList matches2 = regex2.capturedTexts();
QString extensionPlusVersion = matches2.at(0);
extensionPlusVersion.prepend(baseNewVersion);
extensionPlusVersion.prepend("~");
backupFileName.replace(regex2, extensionPlusVersion);
// Save version with 1 number higher than the highest version found ignoring letters
do {
newVersion = baseNewVersion;
newVersion.prepend("~");
newVersion.append(".");
backupFileName.replace(regex, newVersion);
fileAlreadyExists = QFile(backupFileName).exists();
if (fileAlreadyExists) {
// Prepare the base for new version filename, increment by 1
int intVersion = baseNewVersion.toInt(0);
++intVersion;
baseNewVersion = QString::number(intVersion);
while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) {
baseNewVersion.prepend("0");
}
}
} while (fileAlreadyExists);
// Save both as backup and on current file for interapplication workflow
document()->setFileBatchMode(true);
QFile::copy(fileName, backupFileName);
document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true);
document()->setFileBatchMode(false);
if (mainWindow()) mainWindow()->updateCaption();
}
}
void KisViewManager::disableControls()
{
// prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel
// this is for Bug 250944
// the solution blocks all wheel, mouse and key event, while dragging with the freehand tool
// see KisToolFreehand::initPaint() and endPaint()
d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter);
Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
child->installEventFilter(&d->blockingEventFilter);
}
}
void KisViewManager::enableControls()
{
d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter);
Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
child->removeEventFilter(&d->blockingEventFilter);
}
}
void KisViewManager::showStatusBar(bool toggled)
{
KisMainWindow *mw = mainWindow();
if(mw && mw->statusBar()) {
mw->statusBar()->setVisible(toggled);
KisConfig cfg;
cfg.setShowStatusBar(toggled);
}
}
void KisViewManager::switchCanvasOnly(bool toggled)
{
KisConfig cfg;
KisMainWindow* main = mainWindow();
if(!main) {
dbgUI << "Unable to switch to canvas-only mode, main window not found";
return;
}
if (toggled) {
d->canvasState = qtMainWindow()->saveState();
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
d->windowFlags = main->windowState();
#endif
}
if (cfg.hideStatusbarFullscreen()) {
if (main->statusBar()) {
if (!toggled) {
if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) {
if (main->statusBar()->property("wasvisible").toBool()) {
main->statusBar()->setVisible(true);
}
}
}
else {
main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible());
main->statusBar()->setVisible(false);
}
}
}
if (cfg.hideDockersFullscreen()) {
KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers"));
if (action) {
action->setCheckable(true);
if (toggled) {
if (action->isChecked()) {
cfg.setShowDockers(action->isChecked());
action->setChecked(false);
} else {
cfg.setShowDockers(false);
}
} else {
action->setChecked(cfg.showDockers());
}
}
}
// QT in windows does not return to maximized upon 4th tab in a row
// https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/
if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) {
if(toggled) {
main->setWindowState( main->windowState() | Qt::WindowFullScreen);
} else {
main->setWindowState( main->windowState() & ~Qt::WindowFullScreen);
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
// If window was maximized prior to fullscreen, restore that
if (d->windowFlags & Qt::WindowMaximized) {
main->setWindowState( main->windowState() | Qt::WindowMaximized);
}
#endif
}
}
if (cfg.hideMenuFullscreen()) {
if (!toggled) {
if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) {
if (main->menuBar()->property("wasvisible").toBool()) {
main->menuBar()->setVisible(true);
}
}
}
else {
main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible());
main->menuBar()->setVisible(false);
}
}
if (cfg.hideToolbarFullscreen()) {
QList toolBars = main->findChildren();
Q_FOREACH (QToolBar* toolbar, toolBars) {
if (!toggled) {
if (toolbar->dynamicPropertyNames().contains("wasvisible")) {
if (toolbar->property("wasvisible").toBool()) {
toolbar->setVisible(true);
}
}
}
else {
toolbar->setProperty("wasvisible", toolbar->isVisible());
toolbar->setVisible(false);
}
}
}
showHideScrollbars();
if (toggled) {
// show a fading heads-up display about the shortcut to go back
showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.",
actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon());
}
else {
main->restoreState(d->canvasState);
}
}
void KisViewManager::toggleTabletLogger()
{
d->inputManager.toggleTabletLogger();
}
void KisViewManager::openResourcesDirectory()
{
QString dir = KoResourcePaths::locateLocal("data", "");
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
}
void KisViewManager::updateIcons()
{
if (mainWindow()) {
QList dockers = mainWindow()->dockWidgets();
Q_FOREACH (QDockWidget* dock, dockers) {
dbgKrita << "name " << dock->objectName();
QObjectList objects;
objects.append(dock);
while (!objects.isEmpty()) {
QObject* object = objects.takeFirst();
objects.append(object->children());
KisIconUtils::updateIconCommon(object);
}
}
}
}
void KisViewManager::initializeStatusBarVisibility()
{
KisConfig cfg;
d->mainWindow->statusBar()->setVisible(cfg.showStatusBar());
}
void KisViewManager::guiUpdateTimeout()
{
d->nodeManager.updateGUI();
d->selectionManager.updateGUI();
d->filterManager.updateGUI();
if (zoomManager()) {
zoomManager()->updateGUI();
}
d->gridManager.updateGUI();
d->actionManager.updateGUI();
}
void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
{
if (!d->currentImageView) return;
d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment);
emit floatingMessageRequested(message, icon.name());
}
KisMainWindow *KisViewManager::mainWindow() const
{
return qobject_cast(d->mainWindow);
}
void KisViewManager::showHideScrollbars()
{
if (!d->currentImageView) return;
if (!d->currentImageView->canvasController()) return;
KisConfig cfg;
bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked();
if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) {
d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
} else {
d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}
}
void KisViewManager::slotSaveShowRulersState(bool value)
{
KisConfig cfg;
cfg.setShowRulers(value);
}
void KisViewManager::slotSaveRulersTrackMouseState(bool value)
{
KisConfig cfg;
cfg.setRulersTrackMouse(value);
}
void KisViewManager::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisViewManager::changeAuthorProfile(const QString &profileName)
{
KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author");
if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) {
appAuthorGroup.writeEntry("active-profile", "");
} else {
appAuthorGroup.writeEntry("active-profile", profileName);
}
appAuthorGroup.sync();
Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) {
doc->documentInfo()->updateParameters();
}
}
void KisViewManager::slotUpdateAuthorProfileActions()
{
Q_ASSERT(d->actionAuthor);
if (!d->actionAuthor) {
return;
}
d->actionAuthor->clear();
d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous"));
KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/";
QStringList filters = QStringList() << "*.authorinfo";
QDir dir(authorInfo);
Q_FOREACH(QString entry, dir.entryList(filters)) {
int ln = QString(".authorinfo").size();
entry.chop(ln);
if (!profiles.contains(entry)) {
profiles.append(entry);
}
}
Q_FOREACH (const QString &profile , profiles) {
d->actionAuthor->addAction(profile);
}
KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author");
QString profileName = appAuthorGroup.readEntry("active-profile", "");
if (profileName == "anonymous" || profileName.isEmpty()) {
d->actionAuthor->setCurrentItem(0);
} else if (profiles.contains(profileName)) {
d->actionAuthor->setCurrentAction(profileName);
}
}
void KisViewManager::slotUpdatePixelGridAction()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid);
KisSignalsBlocker b(d->showPixelGrid);
KisConfig cfg;
d->showPixelGrid->setChecked(cfg.pixelGridEnabled());
}
diff --git a/libs/ui/actions/KisPasteActionFactory.cpp b/libs/ui/actions/KisPasteActionFactory.cpp
index 8d9128b802..646feb5631 100644
--- a/libs/ui/actions/KisPasteActionFactory.cpp
+++ b/libs/ui/actions/KisPasteActionFactory.cpp
@@ -1,241 +1,242 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* 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 "KisPasteActionFactory.h"
#include "kis_image.h"
#include "KisViewManager.h"
#include "kis_tool_proxy.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_shape_layer.h"
#include "kis_import_catcher.h"
#include "kis_clipboard.h"
#include "commands/kis_image_layer_add_command.h"
#include "kis_processing_applicator.h"
#include
#include
#include
#include
#include
#include "kis_algebra_2d.h"
#include
#include
#include "kis_time_range.h"
#include "kis_keyframe_channel.h"
#include "kis_raster_keyframe_channel.h"
+#include "kis_painter.h"
namespace {
QPointF getFittingOffset(QList shapes,
const QPointF &shapesOffset,
const QRectF &documentRect,
const qreal fitRatio)
{
QPointF accumulatedFitOffset;
Q_FOREACH (KoShape *shape, shapes) {
const QRectF bounds = shape->boundingRect();
const QPointF center = bounds.center() + shapesOffset;
const qreal wMargin = (0.5 - fitRatio) * bounds.width();
const qreal hMargin = (0.5 - fitRatio) * bounds.height();
const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin);
const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect);
accumulatedFitOffset += fittedCenter - center;
}
return accumulatedFitOffset;
}
bool tryPasteShapes(bool pasteAtCursorPosition, KisViewManager *view)
{
bool result = false;
KoSvgPaste paste;
if (paste.hasShapes()) {
KoCanvasBase *canvas = view->canvasBase();
QSizeF fragmentSize;
QList shapes =
paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
canvas->shapeController()->pixelsPerInch(), &fragmentSize);
if (!shapes.isEmpty()) {
KoShapeManager *shapeManager = canvas->shapeManager();
shapeManager->selection()->deselectAll();
// adjust z-index of the shapes so that they would be
// pasted on the top of the stack
QList topLevelShapes = shapeManager->topLevelShapes();
auto it = std::max_element(topLevelShapes.constBegin(), topLevelShapes.constEnd(), KoShape::compareShapeZIndex);
if (it != topLevelShapes.constEnd()) {
const int zIndexOffset = (*it)->zIndex();
std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QList indexedShapes;
std::transform(shapes.constBegin(), shapes.constEnd(),
std::back_inserter(indexedShapes),
[zIndexOffset] (KoShape *shape) {
KoShapeReorderCommand::IndexedShape indexedShape(shape);
indexedShape.zIndex += zIndexOffset;
return indexedShape;
});
indexedShapes = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedShapes);
KoShapeReorderCommand cmd(indexedShapes);
cmd.redo();
}
KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
canvas->shapeController()->addShapesDirect(shapes, 0, parentCommand);
QPointF finalShapesOffset;
if (pasteAtCursorPosition) {
QRectF boundingRect = KoShape::boundingRect(shapes);
const QPointF cursorPos = canvas->canvasController()->currentCursorPosition();
finalShapesOffset = cursorPos - boundingRect.center();
} else {
bool foundOverlapping = false;
QRectF boundingRect = KoShape::boundingRect(shapes);
const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height());
QPointF offset;
Q_FOREACH (KoShape *shape, shapes) {
QRectF br1 = shape->boundingRect();
bool hasOverlappingShape = false;
do {
hasOverlappingShape = false;
// we cannot use shapesAt() here, because the groups are not
// handled in the shape manager's tree
QList conflicts = shapeManager->shapes();
Q_FOREACH (KoShape *intersectedShape, conflicts) {
if (intersectedShape == shape) continue;
QRectF br2 = intersectedShape->boundingRect();
const qreal tolerance = 2.0; /* pt */
if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) {
br1.translate(offsetStep.x(), offsetStep.y());
offset += offsetStep;
hasOverlappingShape = true;
foundOverlapping = true;
break;
}
}
} while (hasOverlappingShape);
if (foundOverlapping) break;
}
if (foundOverlapping) {
finalShapesOffset = offset;
}
}
const QRectF documentRect = canvas->shapeController()->documentRect();
finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1);
if (!finalShapesOffset.isNull()) {
new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand);
}
canvas->addCommand(parentCommand);
Q_FOREACH (KoShape *shape, shapes) {
canvas->selectedShapesProxy()->selection()->select(shape);
}
result = true;
}
}
return result;
}
}
#include "kis_painter.h"
void KisPasteActionFactory::run(bool pasteAtCursorPosition, KisViewManager *view)
{
KisImageSP image = view->image();
if (!image) return;
if (tryPasteShapes(pasteAtCursorPosition, view)) {
return;
}
KisTimeRange range;
const QRect fittingBounds = pasteAtCursorPosition ? QRect() : image->bounds();
KisPaintDeviceSP clip = KisClipboard::instance()->clip(fittingBounds, true, &range);
if (clip) {
if (pasteAtCursorPosition) {
const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition();
const QPointF imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos);
const QPointF offset = (imagePos - QRectF(clip->exactBounds()).center()).toPoint();
clip->setX(clip->x() + offset.x());
clip->setY(clip->y() + offset.y());
}
KisImportCatcher::adaptClipToImageColorSpace(clip, image);
KisPaintLayerSP newLayer = new KisPaintLayer(image.data(),
image->nextLayerName() + i18n("(pasted)"),
OPACITY_OPAQUE_U8);
KisNodeSP aboveNode = view->activeLayer();
KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root();
if (range.isValid()) {
newLayer->enableAnimation();
KisKeyframeChannel *channel = newLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
KisRasterKeyframeChannel *rasterChannel = dynamic_cast(channel);
rasterChannel->importFrame(range.start(), clip, 0);
rasterChannel->addKeyframe(range.end() + 1, 0);
} else {
const QRect rc = clip->extent();
KisPainter::copyAreaOptimized(rc.topLeft(), clip, newLayer->paintDevice(), rc);
}
KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode);
KisProcessingApplicator *ap = beginAction(view, cmd->text());
ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
endAction(ap, KisOperationConfiguration(id()).toXML());
} else {
// XXX: "Add saving of XML data for Paste of shapes"
view->canvasBase()->toolProxy()->paste();
}
}
diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp
index 9e72f292f8..6570900809 100644
--- a/libs/ui/canvas/kis_animation_player.cpp
+++ b/libs/ui/canvas/kis_animation_player.cpp
@@ -1,546 +1,588 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* 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 "kis_animation_player.h"
#include
#include
#include
//#define PLAYER_DEBUG_FRAMERATE
#include "kis_global.h"
+#include "kis_algebra_2d.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_image.h"
#include "kis_canvas2.h"
#include "kis_animation_frame_cache.h"
#include "kis_signal_auto_connection.h"
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_signal_compressor.h"
#include
#include
#include "KisSyncedAudioPlayback.h"
#include "kis_signal_compressor_with_param.h"
+#include "kis_image_config.h"
+#include
+
#include "KisViewManager.h"
#include "kis_icon_utils.h"
#include "KisPart.h"
#include "dialogs/KisAsyncAnimationCacheRenderDialog.h"
#include "KisRollingMeanAccumulatorWrapper.h"
struct KisAnimationPlayer::Private
{
public:
Private(KisAnimationPlayer *_q)
: q(_q),
realFpsAccumulator(24),
droppedFpsAccumulator(24),
droppedFramesPortion(24),
dropFramesMode(true),
nextFrameExpectedTime(0),
expectedInterval(0),
expectedFrame(0),
lastTimerInterval(0),
lastPaintedFrame(0),
playbackStatisticsCompressor(1000, KisSignalCompressor::FIRST_INACTIVE),
stopAudioOnScrubbingCompressor(100, KisSignalCompressor::POSTPONE),
audioOffsetTolerance(-1)
{}
KisAnimationPlayer *q;
bool useFastFrameUpload;
bool playing;
QTimer *timer;
int initialFrame;
int firstFrame;
int lastFrame;
qreal playbackSpeed;
KisCanvas2 *canvas;
KisSignalAutoConnectionsStore cancelStrokeConnections;
QElapsedTimer realFpsTimer;
KisRollingMeanAccumulatorWrapper realFpsAccumulator;
KisRollingMeanAccumulatorWrapper droppedFpsAccumulator;
KisRollingMeanAccumulatorWrapper droppedFramesPortion;
bool dropFramesMode;
QElapsedTimer playbackTime;
int nextFrameExpectedTime;
int expectedInterval;
int expectedFrame;
int lastTimerInterval;
int lastPaintedFrame;
KisSignalCompressor playbackStatisticsCompressor;
QScopedPointer syncedAudio;
QScopedPointer > audioSyncScrubbingCompressor;
KisSignalCompressor stopAudioOnScrubbingCompressor;
int audioOffsetTolerance;
void stopImpl(bool doUpdates);
int incFrame(int frame, int inc) {
frame += inc;
if (frame > lastFrame) {
frame = firstFrame + frame - lastFrame - 1;
}
return frame;
}
qint64 frameToMSec(int value, int fps) {
return qreal(value) / fps * 1000.0;
}
int msecToFrame(qint64 value, int fps) {
return qreal(value) * fps / 1000.0;
}
};
KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
: QObject(canvas)
, m_d(new Private(this))
{
m_d->useFastFrameUpload = false;
m_d->playing = false;
m_d->canvas = canvas;
m_d->playbackSpeed = 1.0;
m_d->timer = new QTimer(this);
connect(m_d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate()));
m_d->timer->setSingleShot(true);
connect(KisConfigNotifier::instance(),
SIGNAL(dropFramesModeChanged()),
SLOT(slotUpdateDropFramesMode()));
slotUpdateDropFramesMode();
connect(&m_d->playbackStatisticsCompressor, SIGNAL(timeout()),
this, SIGNAL(sigPlaybackStatisticsUpdated()));
using namespace std::placeholders;
std::function callback(
std::bind(&KisAnimationPlayer::slotSyncScrubbingAudio, this, _1));
const int defaultScrubbingUdpatesDelay = 40; /* 40 ms == 25 fps */
m_d->audioSyncScrubbingCompressor.reset(
new KisSignalCompressorWithParam(defaultScrubbingUdpatesDelay, callback, KisSignalCompressor::FIRST_ACTIVE));
m_d->stopAudioOnScrubbingCompressor.setDelay(defaultScrubbingUdpatesDelay);
connect(&m_d->stopAudioOnScrubbingCompressor, SIGNAL(timeout()), SLOT(slotTryStopScrubbingAudio()));
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(slotUpdateAudioChunkLength()));
slotUpdateAudioChunkLength();
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioChannelChanged()), SLOT(slotAudioChannelChanged()));
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SLOT(slotAudioVolumeChanged()));
slotAudioChannelChanged();
}
KisAnimationPlayer::~KisAnimationPlayer()
{}
void KisAnimationPlayer::slotUpdateDropFramesMode()
{
KisConfig cfg;
m_d->dropFramesMode = cfg.animationDropFrames();
}
void KisAnimationPlayer::slotSyncScrubbingAudio(int msecTime)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
if (!m_d->syncedAudio->isPlaying()) {
m_d->syncedAudio->play(msecTime);
} else {
m_d->syncedAudio->syncWithVideo(msecTime);
}
if (!isPlaying()) {
m_d->stopAudioOnScrubbingCompressor.start();
}
}
void KisAnimationPlayer::slotTryStopScrubbingAudio()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
if (m_d->syncedAudio && !isPlaying()) {
m_d->syncedAudio->stop();
}
}
void KisAnimationPlayer::slotAudioChannelChanged()
{
KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface();
QString fileName = interface->audioChannelFileName();
QFileInfo info(fileName);
if (info.exists() && !interface->isAudioMuted()) {
m_d->syncedAudio.reset(new KisSyncedAudioPlayback(info.absoluteFilePath()));
m_d->syncedAudio->setVolume(interface->audioVolume());
m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance);
connect(m_d->syncedAudio.data(), SIGNAL(error(const QString &, const QString &)), SLOT(slotOnAudioError(const QString &, const QString &)));
} else {
m_d->syncedAudio.reset();
}
}
void KisAnimationPlayer::slotAudioVolumeChanged()
{
KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface();
if (m_d->syncedAudio) {
m_d->syncedAudio->setVolume(interface->audioVolume());
}
}
void KisAnimationPlayer::slotOnAudioError(const QString &fileName, const QString &message)
{
QString errorMessage(i18nc("floating on-canvas message", "Cannot open audio: \"%1\"\nError: %2", fileName, message));
m_d->canvas->viewManager()->showFloatingMessage(errorMessage, KisIconUtils::loadIcon("warning"));
}
void KisAnimationPlayer::connectCancelSignals()
{
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigUndoDuringStrokeRequested()),
this, SLOT(slotCancelPlayback()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigStrokeCancellationRequested()),
this, SLOT(slotCancelPlayback()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigStrokeEndRequested()),
this, SLOT(slotCancelPlaybackSafe()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigFullClipRangeChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigPlaybackRangeChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
}
void KisAnimationPlayer::disconnectCancelSignals()
{
m_d->cancelStrokeConnections.clear();
}
void KisAnimationPlayer::slotUpdateAudioChunkLength()
{
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const int animationFramePeriod = qMax(1, 1000 / animation->framerate());
KisConfig cfg;
int scrubbingAudioUdpatesDelay = cfg.scrubbingAudioUpdatesDelay();
if (scrubbingAudioUdpatesDelay < 0) {
scrubbingAudioUdpatesDelay = qMax(1, animationFramePeriod);
}
m_d->audioSyncScrubbingCompressor->setDelay(scrubbingAudioUdpatesDelay);
m_d->stopAudioOnScrubbingCompressor.setDelay(scrubbingAudioUdpatesDelay);
m_d->audioOffsetTolerance = cfg.audioOffsetTolerance();
if (m_d->audioOffsetTolerance < 0) {
m_d->audioOffsetTolerance = animationFramePeriod;
}
if (m_d->syncedAudio) {
m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance);
}
}
void KisAnimationPlayer::slotUpdatePlaybackTimer()
{
m_d->timer->stop();
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const KisTimeRange &playBackRange = animation->playbackRange();
if (!playBackRange.isValid()) return;
const int fps = animation->framerate();
m_d->initialFrame = animation->currentUITime();
m_d->firstFrame = playBackRange.start();
m_d->lastFrame = playBackRange.end();
m_d->expectedFrame = qBound(m_d->firstFrame, m_d->expectedFrame, m_d->lastFrame);
m_d->expectedInterval = qreal(1000) / fps / m_d->playbackSpeed;
m_d->lastTimerInterval = m_d->expectedInterval;
if (m_d->syncedAudio) {
m_d->syncedAudio->setSpeed(m_d->playbackSpeed);
}
m_d->timer->start(m_d->expectedInterval);
if (m_d->playbackTime.isValid()) {
m_d->playbackTime.restart();
} else {
m_d->playbackTime.start();
}
m_d->nextFrameExpectedTime = m_d->playbackTime.elapsed() + m_d->expectedInterval;
}
void KisAnimationPlayer::play()
{
{
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const KisTimeRange &range = animation->playbackRange();
if (!range.isValid()) return;
// when openGL is disabled, there is no animation cache
if (m_d->canvas->frameCache()) {
+ KisImageConfig cfg;
+
+ const int dimensionLimit =
+ cfg.useAnimationCacheFrameSizeLimit() ?
+ cfg.animationCacheFrameSizeLimit() :
+ std::numeric_limits::max();
+
+ const int maxImageDimension = KisAlgebra2D::maxDimension(m_d->canvas->image()->bounds());
+
+ const QRect regionOfInterest =
+ cfg.useAnimationCacheRegionOfInterest() && maxImageDimension > dimensionLimit ?
+ m_d->canvas->regionOfInterest() :
+ m_d->canvas->coordinatesConverter()->imageRectInImagePixels();
+
+ const QRect minimalNeedRect =
+ m_d->canvas->coordinatesConverter()->widgetRectInImagePixels().toAlignedRect() &
+ m_d->canvas->coordinatesConverter()->imageRectInImagePixels();
+
+ m_d->canvas->frameCache()->dropLowQualityFrames(range, regionOfInterest, minimalNeedRect);
+
KisAsyncAnimationCacheRenderDialog dlg(m_d->canvas->frameCache(),
range,
200);
+ dlg.setRegionOfInterest(regionOfInterest);
+
KisAsyncAnimationCacheRenderDialog::Result result =
dlg.regenerateRange(m_d->canvas->viewManager());
if (result != KisAsyncAnimationCacheRenderDialog::RenderComplete) {
return;
}
+
+ m_d->canvas->setRenderingLimit(regionOfInterest);
}
}
m_d->playing = true;
slotUpdatePlaybackTimer();
m_d->expectedFrame = m_d->firstFrame;
- m_d->lastPaintedFrame = m_d->firstFrame;
+ m_d->lastPaintedFrame = -1;
connectCancelSignals();
if (m_d->syncedAudio) {
KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface();
m_d->syncedAudio->play(m_d->frameToMSec(m_d->firstFrame, animationInterface->framerate()));
}
+
+ emit sigPlaybackStarted();
}
void KisAnimationPlayer::Private::stopImpl(bool doUpdates)
{
if (syncedAudio) {
syncedAudio->stop();
}
q->disconnectCancelSignals();
timer->stop();
playing = false;
+ canvas->setRenderingLimit(QRect());
if (doUpdates) {
KisImageAnimationInterface *animation = canvas->image()->animationInterface();
if (animation->currentUITime() == initialFrame) {
canvas->refetchDataFromImage();
} else {
animation->switchCurrentTimeAsync(initialFrame);
}
}
emit q->sigPlaybackStopped();
}
void KisAnimationPlayer::stop()
{
m_d->stopImpl(true);
}
void KisAnimationPlayer::forcedStopOnExit()
{
m_d->stopImpl(false);
}
bool KisAnimationPlayer::isPlaying()
{
return m_d->playing;
}
int KisAnimationPlayer::currentTime()
{
return m_d->lastPaintedFrame;
}
void KisAnimationPlayer::displayFrame(int time)
{
uploadFrame(time);
}
void KisAnimationPlayer::slotUpdate()
{
uploadFrame(-1);
}
void KisAnimationPlayer::uploadFrame(int frame)
{
KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface();
if (frame < 0) {
const int currentTime = m_d->playbackTime.elapsed();
const int framesDiff = currentTime - m_d->nextFrameExpectedTime;
const qreal framesDiffNorm = qreal(framesDiff) / m_d->expectedInterval;
// qDebug() << ppVar(framesDiff)
// << ppVar(m_d->expectedFrame)
// << ppVar(framesDiffNorm)
// << ppVar(m_d->lastTimerInterval);
if (m_d->dropFramesMode) {
const int numDroppedFrames = qMax(0, qRound(framesDiffNorm));
frame = m_d->incFrame(m_d->expectedFrame, numDroppedFrames);
} else {
frame = m_d->expectedFrame;
}
m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval;
m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff);
m_d->expectedFrame = m_d->incFrame(frame, 1);
m_d->timer->start(m_d->lastTimerInterval);
m_d->playbackStatisticsCompressor.start();
}
if (m_d->syncedAudio) {
const int msecTime = m_d->frameToMSec(frame, animationInterface->framerate());
if (isPlaying()) {
slotSyncScrubbingAudio(msecTime);
} else {
m_d->audioSyncScrubbingCompressor->start(msecTime);
}
}
- if (m_d->canvas->frameCache() && m_d->canvas->frameCache()->uploadFrame(frame)) {
- m_d->canvas->updateCanvas();
- m_d->useFastFrameUpload = true;
- emit sigFrameChanged();
- } else {
+ bool useFallbackUploadMethod = !m_d->canvas->frameCache();
+
+ if (m_d->canvas->frameCache() &&
+ m_d->canvas->frameCache()->shouldUploadNewFrame(frame, m_d->lastPaintedFrame)) {
+
+
+ if (m_d->canvas->frameCache()->uploadFrame(frame)) {
+ m_d->canvas->updateCanvas();
+
+ m_d->useFastFrameUpload = true;
+ } else {
+ useFallbackUploadMethod = true;
+ }
+ }
+
+ if (useFallbackUploadMethod) {
m_d->useFastFrameUpload = false;
m_d->canvas->image()->barrierLock(true);
m_d->canvas->image()->unlock();
// no OpenGL cache or the frame just not cached yet
animationInterface->switchCurrentTimeAsync(frame);
-
- emit sigFrameChanged();
}
if (!m_d->realFpsTimer.isValid()) {
m_d->realFpsTimer.start();
} else {
const int elapsed = m_d->realFpsTimer.restart();
m_d->realFpsAccumulator(elapsed);
- int numFrames = frame - m_d->lastPaintedFrame;
- if (numFrames < 0) {
- numFrames += m_d->lastFrame - m_d->firstFrame + 1;
- }
+ if (m_d->lastPaintedFrame >= 0) {
+ int numFrames = frame - m_d->lastPaintedFrame;
+ if (numFrames < 0) {
+ numFrames += m_d->lastFrame - m_d->firstFrame + 1;
+ }
- m_d->droppedFramesPortion(qreal(int(numFrames != 1)));
+ m_d->droppedFramesPortion(qreal(int(numFrames != 1)));
- if (numFrames > 0) {
- m_d->droppedFpsAccumulator(qreal(elapsed) / numFrames);
- }
+ if (numFrames > 0) {
+ m_d->droppedFpsAccumulator(qreal(elapsed) / numFrames);
+ }
#ifdef PLAYER_DEBUG_FRAMERATE
- qDebug() << " RFPS:" << 1000.0 / m_d->realFpsAccumulator.rollingMean()
- << "DFPS:" << 1000.0 / m_d->droppedFpsAccumulator.rollingMean() << ppVar(numFrames);
+ qDebug() << " RFPS:" << 1000.0 / m_d->realFpsAccumulator.rollingMean()
+ << "DFPS:" << 1000.0 / m_d->droppedFpsAccumulator.rollingMean() << ppVar(numFrames);
#endif /* PLAYER_DEBUG_FRAMERATE */
+ }
}
m_d->lastPaintedFrame = frame;
-
+ emit sigFrameChanged();
}
qreal KisAnimationPlayer::effectiveFps() const
{
return 1000.0 / m_d->droppedFpsAccumulator.rollingMean();
}
qreal KisAnimationPlayer::realFps() const
{
return 1000.0 / m_d->realFpsAccumulator.rollingMean();
}
qreal KisAnimationPlayer::framesDroppedPortion() const
{
return m_d->droppedFramesPortion.rollingMean();
}
void KisAnimationPlayer::slotCancelPlayback()
{
stop();
}
void KisAnimationPlayer::slotCancelPlaybackSafe()
{
/**
* If there is no openGL support, then we have no (!) cache at
* all. Therefore we should regenerate frame on every time switch,
* which, yeah, can be very slow. What is more important, when
* regenerating a frame animation interface will emit a
* sigStrokeEndRequested() signal and we should ignore it. That is
* not an ideal solution, because the user will be able to paint
* on random frames while playing, but it lets users with faulty
* GPUs see at least some preview of their animation.
*/
if (m_d->useFastFrameUpload) {
stop();
}
}
qreal KisAnimationPlayer::playbackSpeed()
{
return m_d->playbackSpeed;
}
void KisAnimationPlayer::slotUpdatePlaybackSpeed(double value)
{
m_d->playbackSpeed = value;
if (m_d->playing) {
slotUpdatePlaybackTimer();
}
}
diff --git a/libs/ui/canvas/kis_animation_player.h b/libs/ui/canvas/kis_animation_player.h
index be25cdc00a..8af2d8977e 100644
--- a/libs/ui/canvas/kis_animation_player.h
+++ b/libs/ui/canvas/kis_animation_player.h
@@ -1,87 +1,88 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* 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 KIS_ANIMATION_PLAYER_H
#define KIS_ANIMATION_PLAYER_H
#include
#include
#include "kritaui_export.h"
class KisCanvas2;
class KRITAUI_EXPORT KisAnimationPlayer : public QObject
{
Q_OBJECT
public:
KisAnimationPlayer(KisCanvas2 *canvas);
~KisAnimationPlayer() override;
void play();
void stop();
void displayFrame(int time);
bool isPlaying();
int currentTime();
qreal playbackSpeed();
void forcedStopOnExit();
qreal effectiveFps() const;
qreal realFps() const;
qreal framesDroppedPortion() const;
public Q_SLOTS:
void slotUpdate();
void slotCancelPlayback();
void slotCancelPlaybackSafe();
void slotUpdatePlaybackSpeed(double value);
void slotUpdatePlaybackTimer();
void slotUpdateDropFramesMode();
private Q_SLOTS:
void slotSyncScrubbingAudio(int msecTime);
void slotTryStopScrubbingAudio();
void slotUpdateAudioChunkLength();
void slotAudioChannelChanged();
void slotAudioVolumeChanged();
void slotOnAudioError(const QString &fileName, const QString &message);
Q_SIGNALS:
void sigFrameChanged();
+ void sigPlaybackStarted();
void sigPlaybackStopped();
void sigPlaybackStatisticsUpdated();
void sigFullClipRangeChanged();
private:
void connectCancelSignals();
void disconnectCancelSignals();
void uploadFrame(int time);
private:
struct Private;
QScopedPointer m_d;
};
#endif
diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp
index 477e2347d7..64c8db5d9f 100644
--- a/libs/ui/canvas/kis_canvas2.cpp
+++ b/libs/ui/canvas/kis_canvas2.cpp
@@ -1,1098 +1,1145 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2010 Boudewijn Rempt
* Copyright (C) Lukáš Tvrdý , (C) 2010
* Copyright (C) 2011 Silvio Heinrich
*
* 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 "kis_canvas2.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_tool_proxy.h"
#include "kis_coordinates_converter.h"
#include "kis_prescaled_projection.h"
#include "kis_image.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_adapter.h"
#include "KisDocument.h"
#include "flake/kis_shape_layer.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_abstract_canvas_widget.h"
#include "kis_qpainter_canvas.h"
#include "kis_group_layer.h"
#include "flake/kis_shape_controller.h"
#include "kis_node_manager.h"
#include "kis_selection.h"
#include "kis_selection_component.h"
#include "flake/kis_shape_selection.h"
#include "kis_image_config.h"
#include "kis_infinity_manager.h"
#include "kis_signal_compressor.h"
#include "kis_display_color_converter.h"
#include "kis_exposure_gamma_correction_interface.h"
#include "KisView.h"
#include "kis_canvas_controller.h"
#include "kis_grid_config.h"
#include "kis_animation_player.h"
#include "kis_animation_frame_cache.h"
#include "opengl/kis_opengl_canvas2.h"
#include "opengl/kis_opengl.h"
#include "kis_fps_decoration.h"
#include "KoColorConversionTransformation.h"
#include "KisProofingConfiguration.h"
#include
#include
#include "input/kis_input_manager.h"
#include "kis_painting_assistants_decoration.h"
#include "kis_canvas_updates_compressor.h"
#include "KoZoomController.h"
#include
#include "opengl/kis_opengl_canvas_debugger.h"
+#include "kis_algebra_2d.h"
+
class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private
{
public:
KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceManager* resourceManager)
: coordinatesConverter(coordConverter)
, view(view)
, shapeManager(parent)
, selectedShapesProxy(&shapeManager)
, toolProxy(parent)
, displayColorConverter(resourceManager, view)
+ , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
{
}
KisCoordinatesConverter *coordinatesConverter;
QPointerview;
KisAbstractCanvasWidget *canvasWidget = 0;
KoShapeManager shapeManager;
KisSelectedShapesProxy selectedShapesProxy;
bool currentCanvasIsOpenGL;
int openGLFilterMode;
KisToolProxy toolProxy;
KisPrescaledProjectionSP prescaledProjection;
bool vastScrolling;
KisSignalCompressor canvasUpdateCompressor;
QRect savedUpdateRect;
QBitArray channelFlags;
KisProofingConfigurationSP proofingConfig;
bool softProofing = false;
bool gamutCheck = false;
bool proofingConfigUpdated = false;
KisPopupPalette *popupPalette = 0;
KisDisplayColorConverter displayColorConverter;
KisCanvasUpdatesCompressor projectionUpdatesCompressor;
KisAnimationPlayer *animationPlayer;
KisAnimationFrameCacheSP frameCache;
- bool lodAllowedInCanvas;
+ bool lodAllowedInImage = false;
bool bootstrapLodBlocked;
QPointer currentlyActiveShapeManager;
KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup;
KisSignalCompressor frameRenderStartCompressor;
- bool effectiveLodAllowedInCanvas() {
- return lodAllowedInCanvas && !bootstrapLodBlocked;
+ KisSignalCompressor regionOfInterestUpdateCompressor;
+ QRect regionOfInterest;
+
+ QRect renderingLimit;
+
+ bool effectiveLodAllowedInImage() {
+ return lodAllowedInImage && !bootstrapLodBlocked;
}
void setActiveShapeManager(KoShapeManager *shapeManager);
};
namespace {
KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node)
{
KoShapeManager *shapeManager = 0;
KisLayer *layer = dynamic_cast(node.data());
if (layer) {
KisShapeLayer *shapeLayer = dynamic_cast(layer);
if (shapeLayer) {
shapeManager = shapeLayer->shapeManager();
} else {
KisSelectionSP selection = layer->selection();
if (selection && selection->hasShapeSelection()) {
KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection());
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0);
shapeManager = shapeSelection->shapeManager();
}
}
}
return shapeManager;
}
}
KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc)
: KoCanvasBase(sc, resourceManager)
, m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager))
{
/**
* While loading LoD should be blocked. Only when GUI has finished
* loading and zoom level settled down, LoD is given a green
* light.
*/
m_d->bootstrapLodBlocked = true;
connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished()));
KisImageConfig config;
m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit());
m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit());
m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
}
void KisCanvas2::setup()
{
// a bit of duplication from slotConfigChanged()
KisConfig cfg;
m_d->vastScrolling = cfg.vastScrolling();
- m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled();
+ m_d->lodAllowedInImage = cfg.levelOfDetailEnabled();
createCanvas(cfg.useOpenGL());
- setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
+ setLodAllowedInCanvas(m_d->lodAllowedInImage);
m_d->animationPlayer = new KisAnimationPlayer(this);
connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
/**
* We switch the shape manager every time vector layer or
* shape selection is activated. Flake does not expect this
* and connects all the signals of the global shape manager
* to the clients in the constructor. To workaround this we
* forward the signals of local shape managers stored in the
* vector layers to the signals of global shape manager. So the
* sequence of signal deliveries is the following:
*
* shapeLayer.m_d.canvas.m_shapeManager.selection() ->
* shapeLayer ->
* shapeController ->
* globalShapeManager.selection()
*/
KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase());
connect(kritaShapeController, SIGNAL(selectionChanged()),
this, SLOT(slotSelectionChanged()));
connect(kritaShapeController, SIGNAL(selectionContentChanged()),
globalShapeManager(), SIGNAL(selectionContentChanged()));
connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate()));
connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start()));
connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection()));
connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32)));
+ connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest()));
+
initializeFpsDecoration();
}
void KisCanvas2::initializeFpsDecoration()
{
KisConfig cfg;
const bool shouldShowDebugOverlay =
(canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) ||
cfg.enableBrushSpeedLogging();
if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) {
addDecoration(new KisFpsDecoration(imageView()));
if (cfg.enableBrushSpeedLogging()) {
connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
}
} else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) {
m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag);
disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
}
}
KisCanvas2::~KisCanvas2()
{
if (m_d->animationPlayer->isPlaying()) {
m_d->animationPlayer->forcedStopOnExit();
}
delete m_d;
}
void KisCanvas2::setCanvasWidget(QWidget * widget)
{
KisAbstractCanvasWidget *tmp = dynamic_cast(widget);
Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget");
if (m_d->popupPalette) {
m_d->popupPalette->setParent(widget);
}
if(m_d->canvasWidget != 0)
{
tmp->setDecorations(m_d->canvasWidget->decorations());
// Redundant check for the constructor case, see below
if(viewManager() != 0)
viewManager()->inputManager()->removeTrackedCanvas(this);
}
m_d->canvasWidget = tmp;
// Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView
// constructor, so the view manager still doesn't exists.
if(m_d->canvasWidget != 0 && viewManager() != 0)
viewManager()->inputManager()->addTrackedCanvas(this);
if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) {
KisInfinityManager *manager = new KisInfinityManager(m_d->view, this);
manager->setVisible(true);
m_d->canvasWidget->addDecoration(manager);
}
widget->setAutoFillBackground(false);
widget->setAttribute(Qt::WA_OpaquePaintEvent);
widget->setMouseTracking(true);
widget->setAcceptDrops(true);
KoCanvasControllerWidget *controller = dynamic_cast(canvasController());
if (controller) {
Q_ASSERT(controller->canvas() == this);
controller->changeCanvasWidget(widget);
}
}
bool KisCanvas2::canvasIsOpenGL() const
{
return m_d->currentCanvasIsOpenGL;
}
KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const
{
return KisOpenGL::FilterMode(m_d->openGLFilterMode);
}
void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const
{
QTransform transform = coordinatesConverter()->imageToDocumentTransform();
const QPoint intSpacing = m_d->view->document()->gridConfig().spacing();
const QPoint intOffset = m_d->view->document()->gridConfig().offset();
QPointF size = transform.map(QPointF(intSpacing));
spacing->rwidth() = size.x();
spacing->rheight() = size.y();
*offset = transform.map(QPointF(intOffset));
}
bool KisCanvas2::snapToGrid() const
{
return m_d->view->document()->gridConfig().snapToGrid();
}
qreal KisCanvas2::rotationAngle() const
{
return m_d->coordinatesConverter->rotationAngle();
}
bool KisCanvas2::xAxisMirrored() const
{
return m_d->coordinatesConverter->xAxisMirrored();
}
bool KisCanvas2::yAxisMirrored() const
{
return m_d->coordinatesConverter->yAxisMirrored();
}
void KisCanvas2::channelSelectionChanged()
{
KisImageSP image = this->image();
m_d->channelFlags = image->rootLayer()->channelFlags();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags);
startUpdateInPatches(image->bounds());
image->unlock();
}
void KisCanvas2::addCommand(KUndo2Command *command)
{
// This method exists to support flake-related operations
m_d->view->document()->addCommand(command);
}
void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager)
{
if (shapeManager != currentlyActiveShapeManager) {
currentlyActiveShapeManager = shapeManager;
selectedShapesProxy.setShapeManager(shapeManager);
}
}
KoShapeManager* KisCanvas2::shapeManager() const
{
KisNodeSP node = m_d->view->currentNode();
KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node);
if (localShapeManager != m_d->currentlyActiveShapeManager) {
m_d->setActiveShapeManager(localShapeManager);
}
// sanity check for consistency of the local shape manager
KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) {
localShapeManager = globalShapeManager();
}
return localShapeManager ? localShapeManager : globalShapeManager();
}
KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const
{
return &m_d->selectedShapesProxy;
}
KoShapeManager* KisCanvas2::globalShapeManager() const
{
return &m_d->shapeManager;
}
void KisCanvas2::updateInputMethodInfo()
{
// TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget...
}
const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const
{
return m_d->coordinatesConverter;
}
KoViewConverter* KisCanvas2::viewConverter() const
{
return m_d->coordinatesConverter;
}
KisInputManager* KisCanvas2::globalInputManager() const
{
return m_d->view->globalInputManager();
}
KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const
{
return m_d->inputActionGroupsMask;
}
void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask)
{
m_d->inputActionGroupsMask = mask;
}
QWidget* KisCanvas2::canvasWidget()
{
return m_d->canvasWidget->widget();
}
const QWidget* KisCanvas2::canvasWidget() const
{
return m_d->canvasWidget->widget();
}
KoUnit KisCanvas2::unit() const
{
KoUnit unit(KoUnit::Pixel);
KisImageWSP image = m_d->view->image();
if (image) {
if (!qFuzzyCompare(image->xRes(), image->yRes())) {
warnKrita << "WARNING: resolution of the image is anisotropic"
<< ppVar(image->xRes())
<< ppVar(image->yRes());
}
const qreal resolution = image->xRes();
unit.setFactor(resolution);
}
return unit;
}
KoToolProxy * KisCanvas2::toolProxy() const
{
return &m_d->toolProxy;
}
void KisCanvas2::createQPainterCanvas()
{
m_d->currentCanvasIsOpenGL = false;
KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view);
m_d->prescaledProjection = new KisPrescaledProjection();
m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter);
m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(),
m_d->displayColorConverter.renderingIntent(),
m_d->displayColorConverter.conversionFlags());
m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter());
canvasWidget->setPrescaledProjection(m_d->prescaledProjection);
setCanvasWidget(canvasWidget);
}
void KisCanvas2::createOpenGLCanvas()
{
KisConfig cfg;
m_d->openGLFilterMode = cfg.openGLFilteringMode();
m_d->currentCanvasIsOpenGL = true;
KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter);
m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures());
setCanvasWidget(canvasWidget);
}
void KisCanvas2::createCanvas(bool useOpenGL)
{
// deinitialize previous canvas structures
m_d->prescaledProjection = 0;
m_d->frameCache = 0;
KisConfig cfg;
QDesktopWidget dw;
const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView()));
m_d->displayColorConverter.setMonitorProfile(profile);
if (useOpenGL) {
if (KisOpenGL::hasOpenGL()) {
createOpenGLCanvas();
if (cfg.canvasState() == "OPENGL_FAILED") {
// Creating the opengl canvas failed, fall back
warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter.";
createQPainterCanvas();
}
} else {
warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n";
createQPainterCanvas();
}
}
else {
createQPainterCanvas();
}
if (m_d->popupPalette) {
m_d->popupPalette->setParent(m_d->canvasWidget->widget());
}
}
void KisCanvas2::initializeImage()
{
KisImageSP image = m_d->view->image();
m_d->coordinatesConverter->setImage(image);
m_d->toolProxy.initializeImage(image);
connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection);
connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig()));
connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection);
connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager()));
connectCurrentCanvas();
}
void KisCanvas2::connectCurrentCanvas()
{
KisImageWSP image = m_d->view->image();
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->setImage(image);
}
startResizingImage();
+ setLodAllowedInCanvas(m_d->lodAllowedInImage);
- emit imageChanged(image);
- setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
+ emit sigCanvasEngineChanged();
}
void KisCanvas2::resetCanvas(bool useOpenGL)
{
// we cannot reset the canvas before it's created, but this method might be called,
// for instance when setting the monitor profile.
if (!m_d->canvasWidget) {
return;
}
KisConfig cfg;
bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) ||
(m_d->currentCanvasIsOpenGL &&
m_d->openGLFilterMode != cfg.openGLFilteringMode());
if (needReset) {
createCanvas(useOpenGL);
connectCurrentCanvas();
notifyZoomChanged();
}
updateCanvasWidgetImpl();
}
void KisCanvas2::startUpdateInPatches(const QRect &imageRect)
{
if (m_d->currentCanvasIsOpenGL) {
startUpdateCanvasProjection(imageRect);
} else {
KisImageConfig imageConfig;
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < imageRect.height(); y += patchHeight) {
for (int x = 0; x < imageRect.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
startUpdateCanvasProjection(patchRect);
}
}
}
}
void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter)
{
m_d->displayColorConverter.setDisplayFilter(displayFilter);
KisImageSP image = this->image();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->setDisplayFilter(displayFilter);
image->unlock();
}
QSharedPointer KisCanvas2::displayFilter() const
{
return m_d->displayColorConverter.displayFilter();
}
KisDisplayColorConverter* KisCanvas2::displayColorConverter() const
{
return &m_d->displayColorConverter;
}
KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const
{
QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter();
return displayFilter ?
displayFilter->correctionInterface() :
KisDumbExposureGammaCorrectionInterface::instance();
}
void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck)
{
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
qDebug()<<"Canvas: No proofing config found, generating one.";
KisImageConfig cfg;
m_d->proofingConfig = cfg.defaultProofingconfiguration();
}
KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags;
#if QT_VERSION >= 0x050700
if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof);
if (softProof) {
conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck);
}
}
#else
if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::SoftProofing;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing;
}
if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::GamutCheck;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck;
}
#endif
m_d->proofingConfig->conversionFlags = conversionFlags;
m_d->proofingConfigUpdated = true;
startUpdateInPatches(this->image()->bounds());
}
void KisCanvas2::slotSoftProofing(bool softProofing)
{
m_d->softProofing = softProofing;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotGamutCheck(bool gamutCheck)
{
m_d->gamutCheck = gamutCheck;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotChangeProofingConfig()
{
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::setProofingConfigUpdated(bool updated)
{
m_d->proofingConfigUpdated = updated;
}
bool KisCanvas2::proofingConfigUpdated()
{
return m_d->proofingConfigUpdated;
}
KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const
{
if (!m_d->proofingConfig) {
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
m_d->proofingConfig = KisImageConfig().defaultProofingconfiguration();
}
}
return m_d->proofingConfig;
}
void KisCanvas2::startResizingImage()
{
KisImageWSP image = this->image();
qint32 w = image->width();
qint32 h = image->height();
emit sigContinueResizeImage(w, h);
QRect imageBounds(0, 0, w, h);
startUpdateInPatches(imageBounds);
}
void KisCanvas2::finishResizingImage(qint32 w, qint32 h)
{
m_d->canvasWidget->finishResizingImage(w, h);
}
void KisCanvas2::startUpdateCanvasProjection(const QRect & rc)
{
KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags);
if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) {
emit sigCanvasCacheUpdated();
}
}
void KisCanvas2::updateCanvasProjection()
{
QVector infoObjects;
while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) {
infoObjects << info;
}
QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects);
const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(),
QRect(), std::bit_or());
// TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas
if (m_d->currentCanvasIsOpenGL) {
m_d->savedUpdateRect = QRect();
// we already had a compression in frameRenderStartCompressor, so force the update directly
slotDoCanvasUpdate();
} else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) {
m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect();
// we already had a compression in frameRenderStartCompressor, so force the update directly
slotDoCanvasUpdate();
}
}
void KisCanvas2::slotDoCanvasUpdate()
{
/**
* WARNING: in isBusy() we access openGL functions without making the painting
* context current. We hope that currently active context will be Qt's one,
* which is shared with our own.
*/
if (m_d->canvasWidget->isBusy()) {
// just restarting the timer
updateCanvasWidgetImpl(m_d->savedUpdateRect);
return;
}
if (m_d->savedUpdateRect.isEmpty()) {
m_d->canvasWidget->widget()->update();
emit updateCanvasRequested(m_d->canvasWidget->widget()->rect());
} else {
emit updateCanvasRequested(m_d->savedUpdateRect);
m_d->canvasWidget->widget()->update(m_d->savedUpdateRect);
}
m_d->savedUpdateRect = QRect();
}
void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc)
{
if (!m_d->canvasUpdateCompressor.isActive() ||
!m_d->savedUpdateRect.isEmpty()) {
m_d->savedUpdateRect |= rc;
}
m_d->canvasUpdateCompressor.start();
}
void KisCanvas2::updateCanvas()
{
updateCanvasWidgetImpl();
}
void KisCanvas2::updateCanvas(const QRectF& documentRect)
{
if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) {
updateCanvasWidgetImpl();
}
else {
// updateCanvas is called from tools, never from the projection
// updates, so no need to prescale!
QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect();
widgetRect.adjust(-2, -2, 2, 2);
if (!widgetRect.isEmpty()) {
updateCanvasWidgetImpl(widgetRect);
}
}
}
void KisCanvas2::disconnectCanvasObserver(QObject *object)
{
KoCanvasBase::disconnectCanvasObserver(object);
m_d->view->disconnect(object);
}
void KisCanvas2::notifyZoomChanged()
{
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->notifyZoomChanged();
}
notifyLevelOfDetailChange();
updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction
+
+ m_d->regionOfInterestUpdateCompressor.start();
+}
+
+QRect KisCanvas2::regionOfInterest() const
+{
+ return m_d->regionOfInterest;
+}
+
+void KisCanvas2::slotUpdateRegionOfInterest()
+{
+ const QRect oldRegionOfInterest = m_d->regionOfInterest;
+
+ const qreal ratio = 0.25;
+ const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect();
+
+ const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels();
+
+ m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect;
+
+ if (m_d->regionOfInterest != oldRegionOfInterest) {
+ emit sigRegionOfInterestChanged(m_d->regionOfInterest);
+ }
+}
+
+void KisCanvas2::setRenderingLimit(const QRect &rc)
+{
+ m_d->renderingLimit = rc;
+}
+
+QRect KisCanvas2::renderingLimit() const
+{
+ return m_d->renderingLimit;
}
void KisCanvas2::slotTrySwitchShapeManager()
{
KisNodeSP node = m_d->view->currentNode();
QPointer newManager;
newManager = fetchShapeManagerFromNode(node);
m_d->setActiveShapeManager(newManager);
}
void KisCanvas2::notifyLevelOfDetailChange()
{
- if (!m_d->effectiveLodAllowedInCanvas()) return;
-
- KisImageSP image = this->image();
+ if (!m_d->effectiveLodAllowedInImage()) return;
const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom();
KisConfig cfg;
const int maxLod = cfg.numMipmapLevels();
- int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod);
- image->setDesiredLevelOfDetail(lod);
+ const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod);
+
+ if (m_d->effectiveLodAllowedInImage()) {
+ KisImageSP image = this->image();
+ image->setDesiredLevelOfDetail(lod);
+ }
}
const KoColorProfile * KisCanvas2::monitorProfile()
{
return m_d->displayColorConverter.monitorProfile();
}
KisViewManager* KisCanvas2::viewManager() const
{
if (m_d->view) {
return m_d->view->viewManager();
}
return 0;
}
QPointerKisCanvas2::imageView() const
{
return m_d->view;
}
KisImageWSP KisCanvas2::image() const
{
return m_d->view->image();
}
KisImageWSP KisCanvas2::currentImage() const
{
return m_d->view->image();
}
void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset)
{
QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
m_d->coordinatesConverter->setDocumentOffset(documentOffset);
QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
QPointF moveOffset = offsetAfter - offsetBefore;
if (!m_d->currentCanvasIsOpenGL)
m_d->prescaledProjection->viewportMoved(moveOffset);
emit documentOffsetUpdateFinished();
updateCanvas();
+
+ m_d->regionOfInterestUpdateCompressor.start();
}
void KisCanvas2::slotConfigChanged()
{
KisConfig cfg;
m_d->vastScrolling = cfg.vastScrolling();
resetCanvas(cfg.useOpenGL());
slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget())));
initializeFpsDecoration();
}
void KisCanvas2::refetchDataFromImage()
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
startUpdateInPatches(image->bounds());
}
void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile)
{
if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return;
m_d->displayColorConverter.setMonitorProfile(monitorProfile);
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter);
}
refetchDataFromImage();
}
void KisCanvas2::addDecoration(KisCanvasDecorationSP deco)
{
m_d->canvasWidget->addDecoration(deco);
}
KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const
{
return m_d->canvasWidget->decoration(id);
}
QPoint KisCanvas2::documentOrigin() const
{
/**
* In Krita we don't use document origin anymore.
* All the centering when needed (vastScrolling < 0.5) is done
* automatically by the KisCoordinatesConverter.
*/
return QPoint();
}
QPoint KisCanvas2::documentOffset() const
{
return m_d->coordinatesConverter->documentOffset();
}
void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager)
{
m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(),
m_d->view->resourceProvider(), m_d->canvasWidget->widget());
- connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotZoomChanged(int)));
+ connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int)));
connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas()));
connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons()));
m_d->popupPalette->showPopupPalette(false);
}
-void KisCanvas2::slotZoomChanged(int zoom ) {
+void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) {
m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom
notifyZoomChanged();
}
void KisCanvas2::setCursor(const QCursor &cursor)
{
canvasWidget()->setCursor(cursor);
}
KisAnimationFrameCacheSP KisCanvas2::frameCache() const
{
return m_d->frameCache;
}
KisAnimationPlayer *KisCanvas2::animationPlayer() const
{
return m_d->animationPlayer;
}
void KisCanvas2::slotSelectionChanged()
{
KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data());
if (!shapeLayer) {
return;
}
m_d->shapeManager.selection()->deselectAll();
Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) {
m_d->shapeManager.selection()->select(shape);
}
}
bool KisCanvas2::isPopupPaletteVisible() const
{
if (!m_d->popupPalette) {
return false;
}
return m_d->popupPalette->isVisible();
}
void KisCanvas2::setWrapAroundViewingMode(bool value)
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
infinityDecoration->setVisible(!value);
}
m_d->canvasWidget->setWrapAroundViewingMode(value);
}
bool KisCanvas2::wrapAroundViewingMode() const
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
return !(infinityDecoration->visible());
}
return false;
}
void KisCanvas2::bootstrapFinished()
{
if (!m_d->bootstrapLodBlocked) return;
m_d->bootstrapLodBlocked = false;
- setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
+ setLodAllowedInCanvas(m_d->lodAllowedInImage);
}
void KisCanvas2::setLodAllowedInCanvas(bool value)
{
if (!KisOpenGL::supportsLoD()) {
qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support";
}
- m_d->lodAllowedInCanvas =
- value &&
- m_d->currentCanvasIsOpenGL &&
- KisOpenGL::supportsLoD() &&
- (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode ||
- m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering);
+ m_d->lodAllowedInImage =
+ value &&
+ m_d->currentCanvasIsOpenGL &&
+ KisOpenGL::supportsLoD() &&
+ (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode ||
+ m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering);
KisImageSP image = this->image();
- if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) {
-
- image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas());
- notifyLevelOfDetailChange();
+ if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) {
+ image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage());
}
+ notifyLevelOfDetailChange();
+
KisConfig cfg;
- cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas);
+ cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage);
}
bool KisCanvas2::lodAllowedInCanvas() const
{
- return m_d->lodAllowedInCanvas;
+ return m_d->lodAllowedInImage;
}
void KisCanvas2::slotShowPopupPalette(const QPoint &p)
{
if (!m_d->popupPalette) {
return;
}
m_d->popupPalette->showPopupPalette(p);
}
KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const
{
KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration");
return qobject_cast(deco.data());
}
KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const
{
KisCanvasDecorationSP deco = decoration("referenceImagesDecoration");
return qobject_cast(deco.data());
}
diff --git a/libs/ui/canvas/kis_canvas2.h b/libs/ui/canvas/kis_canvas2.h
index be9854b337..5ca115794a 100644
--- a/libs/ui/canvas/kis_canvas2.h
+++ b/libs/ui/canvas/kis_canvas2.h
@@ -1,320 +1,341 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2010 Boudewijn Rempt
* Copyright (C) 2011 Silvio Heinrich
*
* 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 KIS_CANVAS_H
#define KIS_CANVAS_H
#include
#include
#include
#include
#include
#include