diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index e76d7d7ee1..cf0c812bb7 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1730 +1,1729 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 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_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , recorder(_q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg; if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; KisActionRecorder recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); setObjectName(rhs.objectName()); m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; if (rhs.m_d->proofingConfig) { m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); if (exactCopy) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); node->setUuid(refNode->uuid()); }); } Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { stopIsolatedMode(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { stopIsolatedMode(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, shearOrigin); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, shearOrigin); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint((int)pixelCoord.x(), (int)pixelCoord.y()); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QRect KisImage::documentToIntPixel(const QRectF &documentRect) const { return documentToPixel(documentRect).toAlignedRect(); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return &m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { stopIsolatedMode(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { if (!tryBarrierLock()) return false; unlock(); m_d->isolatedRootNode = node; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; KisNodeSP oldRootNode = m_d->isolatedRootNode; m_d->isolatedRootNode = 0; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // udpate filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { - if (!m_d->proofingConfig) { - KisImageConfig cfg; - m_d->proofingConfig = cfg.defaultProofingconfiguration(); + if (m_d->proofingConfig) { + return m_d->proofingConfig; } - return m_d->proofingConfig; + return 0; } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index 026a30d32b..0580d67aa1 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1159 +1,1160 @@ /* * Copyright (c) 2005 Adrian Page * * 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_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); + KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "testutil.h" #include "kis_group_layer.h" #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ExternalImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ExternalImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ExternalImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } QTEST_MAIN(KisImageTest) diff --git a/libs/ui/dialogs/kis_dlg_image_properties.cc b/libs/ui/dialogs/kis_dlg_image_properties.cc index 7243891c01..43b5f6c4f6 100644 --- a/libs/ui/dialogs/kis_dlg_image_properties.cc +++ b/libs/ui/dialogs/kis_dlg_image_properties.cc @@ -1,177 +1,196 @@ /* * 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_dlg_image_properties.h" #include #include #include #include #include #include #include #include #include #include #include -#include "KoColorProfile.h" -#include "KoColorSpaceRegistry.h" -#include "KoColor.h" -#include "KoColorConversionTransformation.h" -#include "KoColorPopupAction.h" -#include "kis_icon_utils.h" -#include "KoID.h" -#include "kis_image.h" -#include "kis_annotation.h" -#include "kis_config.h" -#include "kis_signal_compressor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "widgets/kis_cmb_idlist.h" #include "widgets/squeezedcombobox.h" #include "kis_layer_utils.h" + KisDlgImageProperties::KisDlgImageProperties(KisImageWSP image, QWidget *parent, const char *name) : KoDialog(parent) { setButtons(Ok | Cancel); setDefaultButton(Ok); setObjectName(name); setCaption(i18n("Image Properties")); m_page = new WdgImageProperties(this); m_image = image; setMainWidget(m_page); resize(m_page->sizeHint()); KisConfig cfg; m_page->lblWidthValue->setText(QString::number(image->width())); m_page->lblHeightValue->setText(QString::number(image->height())); m_page->lblResolutionValue->setText(QLocale().toString(image->xRes()*72, 2)); // XXX: separate values for x & y? //Set the canvas projection color: backgroundColor KoColor background = m_image->defaultProjectionColor(); background.setOpacity(1.0); m_page->bnBackgroundColor->setColor(background); m_page->sldBackgroundColor->setRange(0.0,1.0,2); m_page->sldBackgroundColor->setSingleStep(0.05); m_page->sldBackgroundColor->setValue(m_image->defaultProjectionColor().opacityF()); KisSignalCompressor *compressor = new KisSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_page->bnBackgroundColor, SIGNAL(changed(KoColor)), compressor, SLOT(start())); connect(m_page->sldBackgroundColor, SIGNAL(valueChanged(qreal)), compressor, SLOT(start())); connect(compressor, SIGNAL(timeout()), this, SLOT(setCurrentColor())); //Set the color space m_page->colorSpaceSelector->setCurrentColorSpace(image->colorSpace()); //set the proofing space m_proofingConfig = m_image->proofingConfiguration(); - m_page->proofSpaceSelector->setCurrentColorSpace(KoColorSpaceRegistry::instance()->colorSpace(m_proofingConfig->proofingModel, m_proofingConfig->proofingDepth, m_proofingConfig->proofingProfile)); + if (!m_proofingConfig) { + m_page->chkSaveProofing->setChecked(false); + m_proofingConfig = KisImageConfig().defaultProofingconfiguration(); + } + else { + m_page->chkSaveProofing->setChecked(true); + } + m_page->proofSpaceSelector->setCurrentColorSpace(KoColorSpaceRegistry::instance()->colorSpace(m_proofingConfig->proofingModel, m_proofingConfig->proofingDepth, m_proofingConfig->proofingProfile)); m_page->cmbIntent->setCurrentIndex((int)m_proofingConfig->intent); - m_page->ckbBlackPointComp->setChecked(m_proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->gamutAlarm->setColor(m_proofingConfig->warningColor); m_page->gamutAlarm->setToolTip(i18n("Set color used for warning")); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)m_proofingConfig->adaptationState*20); KisSignalCompressor *softProofConfigCompressor = new KisSignalCompressor(500, KisSignalCompressor::POSTPONE,this); connect(m_page->gamutAlarm, SIGNAL(changed(KoColor)), softProofConfigCompressor, SLOT(start())); connect(m_page->proofSpaceSelector, SIGNAL(colorSpaceChanged(const KoColorSpace*)), softProofConfigCompressor, SLOT(start())); connect(m_page->cmbIntent, SIGNAL(currentIndexChanged(int)), softProofConfigCompressor, SLOT(start())); connect(m_page->ckbBlackPointComp, SIGNAL(stateChanged(int)), softProofConfigCompressor, SLOT(start())); connect(m_page->sldAdaptationState, SIGNAL(valueChanged(int)), softProofConfigCompressor, SLOT(start())); connect(softProofConfigCompressor, SIGNAL(timeout()), this, SLOT(setProofingConfig())); //annotations vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); vKisAnnotationSP_it it = beginIt; while (it != endIt) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } m_page->cmbAnnotations->addItem((*it) -> type()); it++; } connect(m_page->cmbAnnotations, SIGNAL(activated(QString)), SLOT(setAnnotation(QString))); setAnnotation(m_page->cmbAnnotations->currentText()); } KisDlgImageProperties::~KisDlgImageProperties() { delete m_page; } const KoColorSpace * KisDlgImageProperties::colorSpace() { return m_page->colorSpaceSelector->currentColorSpace(); } void KisDlgImageProperties::setCurrentColor() { KoColor background = m_page->bnBackgroundColor->color(); background.setOpacity(m_page->sldBackgroundColor->value()); KisLayerUtils::changeImageDefaultProjectionColor(m_image, background); } void KisDlgImageProperties::setProofingConfig() { - m_proofingConfig->conversionFlags = KoColorConversionTransformation::HighQuality; - if (m_page->ckbBlackPointComp) m_proofingConfig->conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; - m_proofingConfig->intent = (KoColorConversionTransformation::Intent)m_page->cmbIntent->currentIndex(); - m_proofingConfig->proofingProfile = m_page->proofSpaceSelector->currentColorSpace()->profile()->name(); - m_proofingConfig->proofingModel = m_page->proofSpaceSelector->currentColorSpace()->colorModelId().id(); - m_proofingConfig->proofingDepth = "U8";//default to this - m_proofingConfig->warningColor = m_page->gamutAlarm->color(); - m_proofingConfig->adaptationState = (double)m_page->sldAdaptationState->value()/20.0; - qDebug()<<"set proofing config in properties: "<proofingProfile; - m_image->setProofingConfiguration(m_proofingConfig); + if (m_firstProofingConfigChange) { + m_page->chkSaveProofing->setChecked(true); + m_firstProofingConfigChange = false; + } + if (m_page->chkSaveProofing->isChecked()) { + + m_proofingConfig->conversionFlags = KoColorConversionTransformation::HighQuality; + if (m_page->ckbBlackPointComp) m_proofingConfig->conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; + m_proofingConfig->intent = (KoColorConversionTransformation::Intent)m_page->cmbIntent->currentIndex(); + m_proofingConfig->proofingProfile = m_page->proofSpaceSelector->currentColorSpace()->profile()->name(); + m_proofingConfig->proofingModel = m_page->proofSpaceSelector->currentColorSpace()->colorModelId().id(); + m_proofingConfig->proofingDepth = "U8";//default to this + m_proofingConfig->warningColor = m_page->gamutAlarm->color(); + m_proofingConfig->adaptationState = (double)m_page->sldAdaptationState->value()/20.0; + + m_image->setProofingConfiguration(m_proofingConfig); + } + else { + m_image->setProofingConfiguration(0); + } } void KisDlgImageProperties::setAnnotation(const QString &type) { KisAnnotationSP annotation = m_image->annotation(type); if (annotation) { m_page->lblDescription->clear(); m_page->txtAnnotation->clear(); m_page->lblDescription->setText(annotation->description()); m_page->txtAnnotation->appendPlainText(annotation->displayText()); } else { m_page->lblDescription->clear(); m_page->txtAnnotation->clear(); } } diff --git a/libs/ui/dialogs/kis_dlg_image_properties.h b/libs/ui/dialogs/kis_dlg_image_properties.h index 1c9c50e26f..5084e61ccd 100644 --- a/libs/ui/dialogs/kis_dlg_image_properties.h +++ b/libs/ui/dialogs/kis_dlg_image_properties.h @@ -1,65 +1,66 @@ /* * 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. */ #ifndef KIS_DLG_IMAGE_PROPERTIES_H_ #define KIS_DLG_IMAGE_PROPERTIES_H_ #include #include "KisProofingConfiguration.h" #include #include "ui_wdgimageproperties.h" class KoColorSpace; class WdgImageProperties : public QWidget, public Ui::WdgImageProperties { Q_OBJECT public: WdgImageProperties(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class KisDlgImageProperties : public KoDialog { Q_OBJECT public: KisDlgImageProperties(KisImageWSP image, QWidget *parent = 0, const char *name = 0); ~KisDlgImageProperties() override; const KoColorSpace * colorSpace(); private Q_SLOTS: void setAnnotation(const QString& type); void setCurrentColor(); void setProofingConfig(); private: - WdgImageProperties * m_page; + WdgImageProperties *m_page; KisImageWSP m_image; KisProofingConfigurationSP m_proofingConfig; + bool m_firstProofingConfigChange {true}; }; #endif // KIS_DLG_IMAGE_PROPERTIES_H_ diff --git a/libs/ui/forms/wdgimageproperties.ui b/libs/ui/forms/wdgimageproperties.ui index 6253bc15ab..b2a67d7b18 100644 --- a/libs/ui/forms/wdgimageproperties.ui +++ b/libs/ui/forms/wdgimageproperties.ui @@ -1,373 +1,400 @@ WdgImageProperties 0 0 449 322 New Image 0 Dimensions 12 12 0 0 Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel 0 0 Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel 0 0 Resolution: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 TextLabel pixels-per-inch ppi Qt::Horizontal 40 20 0 0 Background Color: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 20 0 0 Background Opacity: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Image Color Space - - + <html><head/><body><p><span style=" font-weight:600;">Note:</span> This changes only the colorspace of the rendered image. To convert the colorspace of the layers, use Convert Image Colorspace.</p></body></html> Qt::RichText true Softproofing - - - - - Gamut Warning: - - - - - - - Qt::LeftToRight - - - Adaptation State: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - <html><head/><body><p>Set how much you wish to correct the adaptation state. This will affect how <span style=" font-style:italic;">Absolute Colorimetric</span> changes the whites of your image. In Layman's terms: how much do you wish to have the color management correct the paper-color to screen white while using <span style=" font-style:italic;">Absolute Colorimetric</span>?</p></body></html> - - - Qt::Horizontal - - - - - - - <html><head/><body><p>Black Point compensation matches the darkest color of the source device to the darkest color of the destination device. Relative Colorimetric without Black Point Compensation will show the difference between the darkest values. With blackpoint compensation, black is black.</p></body></html> - + + + - Black Point Compensation + Store Softproofing configuration in the image - + - + Rendering Intent 0 Perceptual Relative Colorimetric Saturation Absolute Colorimetric - - - - + + + + + + Qt::LeftToRight + + + Adaptation State: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + <html><head/><body><p>Set how much you wish to correct the adaptation state. This will affect how <span style=" font-style:italic;">Absolute Colorimetric</span> changes the whites of your image. In Layman's terms: how much do you wish to have the color management correct the paper-color to screen white while using <span style=" font-style:italic;">Absolute Colorimetric</span>?</p></body></html> + + + Qt::Horizontal + + + + + + + + + + + Gamut Warning: + + + + + + + + + + + + + + <html><head/><body><p>Black Point compensation matches the darkest color of the source device to the darkest color of the destination device. Relative Colorimetric without Black Point Compensation will show the difference between the darkest values. With blackpoint compensation, black is black.</p></body></html> + + + Black Point Compensation + + + + + + + + + Qt::Vertical - + + + 20 + 40 + + + Annotations Type: 0 0 TextLabel true KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisColorButton QPushButton
kis_color_button.h
KisColorSpaceSelector QWidget
widgets/kis_color_space_selector.h
1
diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index 3ad6aba6f2..2ed38dcf05 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1154 +1,1155 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * 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 "kis_kra_loader.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 "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); - if (proofingProfileRes) - { - const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(image->proofingConfiguration()->proofingModel, image->proofingConfiguration()->proofingDepth, proofingData); - if (proofingProfile->valid()){ - //if (KoColorSpaceEngineRegistry::instance()->get("icc")) { - // KoColorSpaceEngineRegistry::instance()->get("icc")->addProfile(proofingProfile->fileName()); - //} + KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); + if (!proofingConfig) { + proofingConfig = KisImageConfig().defaultProofingconfiguration(); + } + + if (proofingProfileRes) { + const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); + if (proofingProfile->valid()){ KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image, parent); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element, parent); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element, parent); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element, parent); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element, parent); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, parent, colorSpace); else if (nodeType == FILE_LAYER) { node = loadFileLayer(element, image, name, opacity); } else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename)); // Entering the event loop to show the messagebox will delete the image, so up the ref by one image->ref(); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeBasedDocumentBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisCloneInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisCloneInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisCloneInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent, const KoColorSpace *colorSpace) { Q_UNUSED(parent); KisColorizeMaskSP mask = new KisColorizeMask(); bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index 18802e4d2d..857f9beb98 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,452 +1,452 @@ /* This file is part of the KDE project * Copyright 2008 (C) 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 "kis_kra_saver.h" #include "kis_kra_tags.h" #include "kis_kra_save_visitor.h" #include "kis_kra_savexml_visitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include #include "kis_dom_utils.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "KisProofingConfiguration.h" #include #include using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document) : m_d(new Private) { m_d->doc = document; m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title"); if (m_d->imageName.isEmpty()) { m_d->imageName = i18n("Unnamed"); } } KisKraSaver::~KisKraSaver() { delete m_d; } QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image) { QDomElement imageElement = doc.createElement("IMAGE"); // Legacy! Q_ASSERT(image); imageElement.setAttribute(NAME, m_d->imageName); imageElement.setAttribute(MIME, NATIVE_MIMETYPE); imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width())); imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height())); imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id()); imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment")); // XXX: Save profile as blob inside the image, instead of the product name. if (image->profile() && image->profile()-> valid()) { imageElement.setAttribute(PROFILE, image->profile()->name()); } imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0)); imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0)); //now the proofing options: - imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); - imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); - imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); - imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); - imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); + if (image->proofingConfiguration()) { + imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); + imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); + imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); + imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); + imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); + } quint32 count = 1; // We don't save the root layer, but it does count KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true); visitor.setSelectedNodes({m_d->doc->preActivatedNode()}); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); m_d->nodeFileNames = visitor.nodeFileNames(); m_d->keyframeFilenames = visitor.keyframeFileNames(); saveBackgroundColor(doc, imageElement, image); saveWarningColor(doc, imageElement, image); saveCompositions(doc, imageElement, image); saveAssistantsList(doc, imageElement); saveGrid(doc,imageElement); saveGuides(doc,imageElement); saveAudio(doc,imageElement); QDomElement animationElement = doc.createElement("animation"); KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate()); KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange()); KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime()); imageElement.appendChild(animationElement); return imageElement; } bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external) { QMap::iterator it; for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) { const KisNode *node = it.key(); QString filename = it.value(); QString location = (external ? QString() : uri) + m_d->imageName + LAYER_PATH + filename; if (!saveNodeKeyframes(store, location, node)) { return false; } } return true; } bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node) { QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0"); QDomElement root = doc.documentElement(); KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]); root.appendChild(element); } if (store->open(location)) { QByteArray xml = doc.toByteArray(); store->write(xml); store->close(); } else { m_d->errorMessages << i18n("could not save keyframes"); return false; } return true; } bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave) { QString location; // Save the layers data KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames); if (external) visitor.setExternalUri(uri); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); if (!m_d->errorMessages.isEmpty()) { return false; } // saving annotations // XXX this only saves EXIF and ICC info. This would probably need // a redesign of the dtd of the krita file to do this more generally correct // e.g. have tags or so. KisAnnotationSP annotation = image->annotation("exif"); if (annotation) { location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } if (image->profile()) { const KoColorProfile *profile = image->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } //This'll embed the profile used for proofing into the kra file. if (image->proofingConfiguration()) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile); if (proofingProfile && proofingProfile->valid()) { QByteArray proofingProfileRaw = proofingProfile->rawData(); if (!proofingProfileRaw.isEmpty()) { annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData()); } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } { KisPSDLayerStyleCollectionResource collection("not-nexists.asl"); KIS_ASSERT_RECOVER_NOOP(!collection.valid()); collection.collectAllLayerStyles(image->root()); if (collection.valid()) { location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->open(location)) { QBuffer aslBuffer; aslBuffer.open(QIODevice::WriteOnly); collection.saveToDevice(&aslBuffer); aslBuffer.close(); store->write(aslBuffer.buffer()); store->close(); } } } if (!autosave) { KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); } saveAssistants(store, uri,external); return true; } QStringList KisKraSaver::errorMessages() const { return m_d->errorMessages; } void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR); KoColor color = image->defaultProjectionColor(); QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64())); element.appendChild(e); } void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (image->proofingConfiguration()) { QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR); KoColor color = image->proofingConfiguration()->warningColor; color.toXML(doc, e); - //QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); - //e.setAttribute("ColorData", QString(colorData.toBase64())); element.appendChild(e); } } void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (!image->compositions().isEmpty()) { QDomElement e = doc.createElement("compositions"); Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { composition->save(doc, e); } element.appendChild(e); } } bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external) { QString location; QMap assistantcounters; QByteArray data; QList assistants = m_d->doc->assistants(); QMap handlemap; if (!assistants.isEmpty()) { Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (!assistantcounters.contains(assist->id())){ assistantcounters.insert(assist->id(),0); } location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]); data = assist->saveXml(handlemap); store->open(location); store->write(data); store->close(); assistantcounters[assist->id()]++; } } return true; } bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element) { int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; QList assistants = m_d->doc->assistants(); if (!assistants.isEmpty()) { QDomElement assistantsElement = doc.createElement("assistants"); Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (assist->id() == "ellipse"){ assist->saveXmlList(doc, assistantsElement, count_ellipse); count_ellipse++; } else if (assist->id() == "spline"){ assist->saveXmlList(doc, assistantsElement, count_spline); count_spline++; } else if (assist->id() == "perspective"){ assist->saveXmlList(doc, assistantsElement, count_perspective); count_perspective++; } else if (assist->id() == "vanishing point"){ assist->saveXmlList(doc, assistantsElement, count_vanishingpoint); count_vanishingpoint++; } else if (assist->id() == "infinite ruler"){ assist->saveXmlList(doc, assistantsElement, count_infiniteruler); count_infiniteruler++; } else if (assist->id() == "parallel ruler"){ assist->saveXmlList(doc, assistantsElement, count_parallelruler); count_parallelruler++; } else if (assist->id() == "concentric ellipse"){ assist->saveXmlList(doc, assistantsElement, count_concentricellipse); count_concentricellipse++; } else if (assist->id() == "fisheye-point"){ assist->saveXmlList(doc, assistantsElement, count_fisheyepoint); count_fisheyepoint++; } else if (assist->id() == "ruler"){ assist->saveXmlList(doc, assistantsElement, count_ruler); count_ruler++; } } element.appendChild(assistantsElement); } return true; } bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element) { KisGridConfig config = m_d->doc->gridConfig(); if (!config.isDefault()) { QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid"); element.appendChild(gridElement); } return true; } bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element) { KisGuidesConfig guides = m_d->doc->guidesConfig(); if (guides.hasGuides()) { QDomElement guidesElement = guides.saveToXml(doc, "guides"); element.appendChild(guidesElement); } return true; } bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element) { const KisImageAnimationInterface *interface = m_d->doc->image()->animationInterface(); QString fileName = interface->audioChannelFileName(); if (fileName.isEmpty()) return true; if (!QFileInfo::exists(fileName)) { m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName); return false; } const QDir documentDir = QFileInfo(m_d->doc->localFilePath()).absoluteDir(); KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false); fileName = documentDir.relativeFilePath(fileName); fileName = QDir::fromNativeSeparators(fileName); KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false); QDomElement audioElement = doc.createElement("audio"); KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName); KisDomUtils::saveValue(&audioElement, "audioMuted", interface->isAudioMuted()); KisDomUtils::saveValue(&audioElement, "audioVolume", interface->audioVolume()); element.appendChild(audioElement); return true; }