diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index b923d92f40..fc61678ea7 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1607 +1,1613 @@ /* * 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 class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *u) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c) , nserver(1) , undoStore(u) , legacyUndoAdapter(u, _q) , postExecutionUndoAdapter(u, _q) , recorder(_q) , signalRouter(_q) , animationInterface(0) , scheduler(_q) {} KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfiguration *proofingConfig = 0; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList dirtyLayers; // for thumbnails QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; KisUndoStore *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; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() { setObjectName(name); // Handle undoStore == 0 and colorSpace == 0 cases if (!undoStore) { undoStore = new KisDumbUndoStore(); } const KoColorSpace *c; if (colorSpace != 0) { c = colorSpace; } else { c = KoColorSpaceRegistry::instance()->rgb8(); } m_d = new KisImagePrivate(this, width, height, c, undoStore); { KisImageConfig cfg; if (cfg.enableProgressReporting()) { m_d->scheduler.setProgressProxy(&m_d->compositeProgressProxy); } // Each of these lambdas defines a new factory function. m_d->scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(this), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(this))); }); m_d->scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(this))); }); m_d->scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(this))); }); } setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); m_d->animationInterface = new KisImageAnimationInterface(this); connect(this, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); /** * Stop animation interface. It may use the rootLayer. */ delete m_d->animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ m_d->rootLayer = 0; delete m_d->undoStore; delete m_d; disconnect(); // in case Qt gets confused } 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() { return !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 = dynamic_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) { 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(); 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); return m_d->rootLayer->accept(visitor); } 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 { return &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); delete m_d->undoStore; m_d->undoStore = undoStore; } KisUndoStore* KisImage::undoStore() { return m_d->undoStore; } 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.setColor(original->defaultPixel(), original->colorSpace()); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); defaultProjectionColor.convertTo(newOriginal->colorSpace()); newOriginal->setDefaultPixel(defaultProjectionColor.data()); 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(); } } 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::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 QRect &rect, const QRect &cropRect) { if (rect.isEmpty()) return; KisNodeGraphListener::requestProjectionUpdate(node, rect); m_d->scheduler.updateProjection(node, rect, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rect)) { return; } m_d->animationInterface->notifyNodeChanged(node, rect, 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) { const QRect boundRect = effectiveLodBounds(); KisWrappedRect splitRect(rect, boundRect); Q_FOREACH (const QRect &rc, splitRect) { requestProjectionUpdateImpl(node, rc, boundRect); } } else { requestProjectionUpdateImpl(node, rect, bounds()); } } 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(KisLayerComposition* composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerComposition* composition) { m_d->compositions.removeAll(composition); delete 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) { 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(KisProofingConfiguration *proofingConfig) +{ + m_d->proofingConfig = proofingConfig; + emit sigProofingConfigChanged(); +} + KisProofingConfiguration *KisImage::proofingConfiguration() const { if (!m_d->proofingConfig) { qDebug()<<"Image: No proofing config found, generating one."; m_d->proofingConfig = new KisProofingConfiguration(); } return m_d->proofingConfig; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index 88e39b7e30..ae55056b0b 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,914 +1,926 @@ /* * 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. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_paint_device.h" // msvc cannot handle forward declarations, so include kis_paint_device here #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include class KisDocument; class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisActionRecorder; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @param colorSpace can be null. in that case it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name); virtual ~KisImage(); public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index); void nodeHasBeenAdded(KisNode *parent, int index); void aboutToRemoveANode(KisNode *parent, int index); void nodeChanged(KisNode * node); void invalidateAllFrames(); void notifySelectionChanged(); void requestProjectionUpdate(KisNode *node, const QRect& rect); void invalidateFrames(const KisTimeRange &range, const QRect &rect); void requestTimeSwitch(int time); public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc); public: /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * XXX: docs! */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * Calls KisUpdateScheduler::lock */ void lock(); /** * Calls KisUpdateScheduler::unlock */ void unlock(); /** * Returns true if lock() has been called more often than unlock(). */ bool locked() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * Resize the image to the specified rect. The resize * method handles the creating on an undo step itself. * * @param newRect the rect describing the new width, height and offset * of the image */ void resizeImage(const QRect& newRect); /** * Crop the image to the specified rect. The crop * method handles the creating on an undo step itself. * * @param newRect the rect describing the new width, height and offset * of the image */ void cropImage(const QRect& newRect); /** * Crop a node to @newRect. The node will *not* be moved anywhere, * it just drops some content */ void cropNode(KisNodeSP node, const QRect& newRect); /// XXX: ApiDox void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /// XXX: ApiDox void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy); /** * Execute a rotate transform on all layers in this image. * Image is resized to fit rotated image. */ void rotateImage(double radians); /** * Execute a rotate transform on on a subtree of this image. * Image is not resized. */ void rotateNode(KisNodeSP node, double radians); /** * Execute a shear transform on all layers in this image. */ void shear(double angleX, double angleY); /** * Shear a node and all its children. * @param angleX, @param angleY are given in degrees. */ void shearNode(KisNodeSP node, double angleX, double angleY); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * NOTE: Note conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * adn executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * @return the action recorder associated with this image */ KisActionRecorder* actionRecorder() const; /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToIntPixel(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRect documentToIntPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is delted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerComposition* composition); /** * Remove the layer compostion */ void removeComposition(KisLayerComposition* composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return curent level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; + /** + * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals + * the proofingConfiguration has changed. + * @param proofingConfig - the kis proofing config that will be used instead. + */ + void setProofingConfiguration(KisProofingConfiguration *proofingConfig); /** * @brief proofingConfiguration - * @return + * @return the proofing configuration of the image. */ KisProofingConfiguration *proofingConfiguration() const; public: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. * * @param rc The rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); + /** + * Emitted when the proofing configuration of the image is being changed. + * + */ + void sigProofingConfigChanged(); + public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(); void barrierLock(bool readOnly = false); bool tryBarrierLock(bool readOnly = false); void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy); void addJob(KisStrokeId id, KisStrokeJobData *data); void endStroke(KisStrokeId id); bool cancelStroke(KisStrokeId id); void blockUpdates(); void unblockUpdates(); /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. */ void disableUIUpdates(); /** * \see disableUIUpdates */ void enableUIUpdates(); /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests(); /** * \see disableDirtyRequests() */ void enableDirtyRequests(); /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter); /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const; void refreshGraphAsync(KisNodeSP root = 0); void refreshGraphAsync(KisNodeSP root, const QRect &rc); void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect); /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = 0); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to dintinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QRect& rect, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp index d4250555dd..90c2831462 100644 --- a/libs/pigment/KoColorSpace.cpp +++ b/libs/pigment/KoColorSpace.cpp @@ -1,836 +1,836 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoColorSpace.h" #include "KoColorSpace_p.h" #include "KoChannelInfo.h" #include "DebugPigment.h" #include "KoCompositeOp.h" #include "KoColorTransformation.h" #include "KoColorTransformationFactory.h" #include "KoColorTransformationFactoryRegistry.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "KoCopyColorConversionTransformation.h" #include "KoFallBackColorTransformation.h" #include "KoUniqueNumberForIdServer.h" #include "KoMixColorsOp.h" #include "KoConvolutionOp.h" #include "KoCompositeOpRegistry.h" #include "KoColorSpaceEngine.h" #include #include #include #include #include #include KoColorSpace::KoColorSpace() : d(new Private()) { } KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp) : d(new Private()) { d->id = id; d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id); d->name = name; d->mixColorsOp = mixColorsOp; d->convolutionOp = convolutionOp; d->transfoToRGBA16 = 0; d->transfoFromRGBA16 = 0; d->transfoToLABA16 = 0; d->transfoFromLABA16 = 0; d->gamutXYY = QPolygonF(); d->TRCXYY = QPolygonF(); d->colorants = QVector (0); d->lumaCoefficients = QVector (0); d->iccEngine = 0; d->proofingSpace = ""; d->softProofing = false; d->gamutCheck = false; d->proofingTransform = 0; d->deletability = NotOwnedByRegistry; } KoColorSpace::~KoColorSpace() { Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete); qDeleteAll(d->compositeOps); Q_FOREACH (KoChannelInfo * channel, d->channels) { delete channel; } if (d->deletability == NotOwnedByRegistry) { KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache(); if (cache) { cache->colorSpaceIsDestroyed(this); } } delete d->mixColorsOp; delete d->convolutionOp; delete d->transfoToRGBA16; delete d->transfoFromRGBA16; delete d->transfoToLABA16; delete d->transfoFromLABA16; delete d->proofingTransform; delete d; } bool KoColorSpace::operator==(const KoColorSpace& rhs) const { const KoColorProfile* p1 = rhs.profile(); const KoColorProfile* p2 = profile(); return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2)); } QString KoColorSpace::id() const { return d->id; } QString KoColorSpace::name() const { return d->name; } //Color space info stuff. QPolygonF KoColorSpace::gamutXYY() const { if (d->gamutXYY.empty()) { //now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases. //first make a list of colors. qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. max = this->channels()[0]->getUIMax(); } int samples = 5;//amount of samples in our color space. QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF32")->defaultProfile(); const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32", name); quint8 *data = new quint8[pixelSize()]; quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel. //QVector sampleCoordinates(pow(colorChannelCount(),samples)); //sampleCoordinates.fill(0.0); // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for(int x=0;xnormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY << QPointF(x,y); } else { for(int y=0;ynormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } else { channelValuesF[0]=(max/(samples-1))*(x); channelValuesF[1]=(max/(samples-1))*(y); channelValuesF[2]=(max/(samples-1))*(z); channelValuesF[3]=max; if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } } } } delete[] data; //if we ever implement a boundary-checking thing I'd add it here. return d->gamutXYY; } else { return d->gamutXYY; } } QPolygonF KoColorSpace::estimatedTRCXYY() const { if (d->TRCXYY.empty()){ qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. max = this->channels()[0]->getUIMax(); } QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF16")->defaultProfile(); const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F16", name); quint8 *data = new quint8[pixelSize()]; quint8 data2[8]; // xyza is 8 bytes per pixel. // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for (quint32 i=0; i0; j--){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(5-j)); if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(5-j))); } } else { for (int j=0; j<5; j++){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(j)); fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j))); } } } delete[] data; return d->TRCXYY; } else { return d->TRCXYY; } } QVector KoColorSpace::colorants() const { if (d->colorants.size()>1){ return d->colorants; } else if (profile() && profile()->hasColorants()) { d->colorants.resize(3*colorChannelCount()); d->colorants = profile()->getColorantsxyY(); return d->colorants; } else { estimatedTRCXYY(); return d->colorants; } } QVector KoColorSpace::lumaCoefficients() const { if (d->lumaCoefficients.size()>1){ return d->lumaCoefficients; } else { d->lumaCoefficients.resize(3); if (colorModelId().id()!="RGBA") { d->lumaCoefficients.fill(0.33); } else { colorants(); if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) { d->lumaCoefficients[0]=0.2126; d->lumaCoefficients[1]=0.7152; d->lumaCoefficients[2]=0.0722; } else { d->lumaCoefficients[0]=d->colorants[2]; d->lumaCoefficients[1]=d->colorants[5]; d->lumaCoefficients[2]=d->colorants[8]; } } return d->lumaCoefficients; } } QList KoColorSpace::channels() const { return d->channels; } QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const { QBitArray ba(d->channels.size()); if (!color && !alpha) return ba; for (int i = 0; i < d->channels.size(); ++i) { KoChannelInfo * channel = d->channels.at(i); if ((color && channel->channelType() == KoChannelInfo::COLOR) || (alpha && channel->channelType() == KoChannelInfo::ALPHA)) ba.setBit(i, true); } return ba; } void KoColorSpace::addChannel(KoChannelInfo * ci) { d->channels.push_back(ci); } bool KoColorSpace::hasCompositeOp(const QString& id) const { return d->compositeOps.contains(id); } QList KoColorSpace::compositeOps() const { return d->compositeOps.values(); } KoMixColorsOp* KoColorSpace::mixColorsOp() const { return d->mixColorsOp; } KoConvolutionOp* KoColorSpace::convolutionOp() const { return d->convolutionOp; } const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const { const QHash::ConstIterator it = d->compositeOps.constFind(id); if (it != d->compositeOps.constEnd()) { return it.value(); } else { warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER; return d->compositeOps.value(COMPOSITE_OVER); } } void KoColorSpace::addCompositeOp(const KoCompositeOp * op) { if (op->colorSpace()->id() == id()) { d->compositeOps.insert(op->id(), const_cast(op)); } } const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const { if (!d->transfoToLABA16) { d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToLABA16; } const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const { if (!d->transfoFromLABA16) { d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromLABA16; } const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const { if (!d->transfoToRGBA16) { d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToRGBA16; } const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const { if (!d->transfoFromRGBA16) { d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromRGBA16; } void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toRgbA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromRgbA16Converter()->transform(src, dst, nPixels); } KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { return new KoCopyColorConversionTransformation(this); } else { return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags); } } bool KoColorSpace::convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { if (src != dst) { memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize()); } } else { KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags); cct.transformation()->transform(src, dst, numPixels); } return true; } bool KoColorSpace::proofPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { /*if (*this == *dstColorSpace) { if (src != dst) { memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize()); } return true; }*/ // Only the icc engine can do this kind of stuff //Can we cache this, maybe??? if (!d->iccEngine) { d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); qDebug() << ">>>>>>>>>>>>>>>>>>>> we got a proofing engine"; } if (!d->iccEngine) return false; - if (d->proofingSpace!=proofingSpace->name()+dstColorSpace->name() || d->softProofing!=conversionFlags.testFlag(KoColorConversionTransformation::SoftProofing) || d->gamutCheck!=conversionFlags.testFlag(KoColorConversionTransformation::GamutCheck)) { + if (d->proofingSpace!=proofingSpace->profile()->name()+dstColorSpace->profile()->name() || d->softProofing!=conversionFlags.testFlag(KoColorConversionTransformation::SoftProofing) || d->gamutCheck!=conversionFlags.testFlag(KoColorConversionTransformation::GamutCheck)) { d->proofingTransform = d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, conversionFlags); - d->proofingSpace = proofingSpace->name()+dstColorSpace->name(); + d->proofingSpace = proofingSpace->profile()->name()+dstColorSpace->profile()->name(); d->softProofing = conversionFlags.testFlag(KoColorConversionTransformation::SoftProofing); d->gamutCheck = conversionFlags.testFlag(KoColorConversionTransformation::GamutCheck); } //Q_UNUSED(transform); d->proofingTransform->transform(src, dst, numPixels); //the transform is deleted in the destructor. return true; } void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1()); if(params.rows <= 0 || params.cols <= 0) return; if(!(*this == *srcSpace)) { if (preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(op->id())) { quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize(); QVector * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride); quint8* conversionDstData = conversionDstCache->data(); for(qint32 row=0; rowcompositeOp(op->id()); KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.dstRowStart = conversionDstData; paramInfo.dstRowStride = conversionDstBufferStride; otherOp->composite(paramInfo); for(qint32 row=0; rowconvertPixelsTo(conversionDstData + row * conversionDstBufferStride, params.dstRowStart + row * params.dstRowStride, this, params.cols, renderingIntent, conversionFlags); } } else { quint32 conversionBufferStride = params.cols * pixelSize(); QVector * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride); quint8* conversionData = conversionCache->data(); for(qint32 row=0; rowconvertPixelsTo(params.srcRowStart + row * params.srcRowStride, conversionData + row * conversionBufferStride, this, params.cols, renderingIntent, conversionFlags); } KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.srcRowStart = conversionData; paramInfo.srcRowStride = conversionBufferStride; op->composite(paramInfo); } } else { op->composite(params); } } QVector * KoColorSpace::threadLocalConversionCache(quint32 size) const { QVector * ba = 0; if (!d->conversionCache.hasLocalData()) { ba = new QVector(size, '0'); d->conversionCache.setLocalData(ba); } else { ba = d->conversionCache.localData(); if ((quint8)ba->size() < size) ba->resize(size); } return ba; } KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash & parameters) const { KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id); if (!factory) return 0; QPair model(colorModelId(), colorDepthId()); QList< QPair > models = factory->supportedModels(); if (models.isEmpty() || models.contains(model)) { return factory->createTransformation(this, parameters); } else { // Find the best solution // TODO use the color conversion cache KoColorConversionTransformation* csToFallBack = 0; KoColorConversionTransformation* fallBackToCs = 0; KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverters(this, models, csToFallBack, fallBackToCs); Q_ASSERT(csToFallBack); Q_ASSERT(fallBackToCs); KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters); return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo); } } void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{ int channelnumber = channelCount(); QVector channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); luma = qMin(1.0, luma + step); luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = qMin(1.0, luma + step); channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat += step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat -= step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue+step>1.0){ hue=(hue+step)- 1.0; } else { hue += step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue-step<0.0){ hue=1.0-(step-hue); } else { hue -= step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u += step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u -= step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v += step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v -= step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;irgb8(dstProfile); if (data) this->convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags); return img; } bool KoColorSpace::preferCompositionInSourceColorSpace() const { return false; } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 294d51d966..5e877f45ab 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,951 +1,956 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "KisDocument.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceManager* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , toolProxy(parent) , displayColorConverter(resourceManager, view) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor updateSignalCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfiguration *proofingConfig = 0; bool softProofing = false; bool gamutCheck = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInCanvas; bool bootstrapLodBlocked; bool effectiveLodAllowedInCanvas() { return lodAllowedInCanvas && !bootstrapLodBlocked; } }; KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); m_d->updateSignalCompressor.setDelay(10); m_d->updateSignalCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), globalShapeManager(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); - connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(QWidget * widget) { KisAbstractCanvasWidget *tmp = dynamic_cast(widget); Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget"); if (m_d->popupPalette) { m_d->popupPalette->setParent(widget); } if(m_d->canvasWidget != 0) { tmp->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = tmp; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->setAutoFillBackground(false); widget->setAttribute(Qt::WA_OpaquePaintEvent); widget->setMouseTracking(true); widget->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller) { Q_ASSERT(controller->canvas() == this); controller->changeCanvasWidget(widget); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageWSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } KoShapeManager* KisCanvas2::shapeManager() const { if (!viewManager()) return globalShapeManager(); if (!viewManager()->nodeManager()) return globalShapeManager(); KisLayerSP activeLayer = viewManager()->nodeManager()->activeLayer(); if (activeLayer && activeLayer->isEditable()) { KisShapeLayer * shapeLayer = dynamic_cast(activeLayer.data()); if (shapeLayer) { return shapeLayer->shapeManager(); } KisSelectionSP selection = activeLayer->selection(); if (selection && !selection.isNull()) { if (selection->hasShapeSelection()) { KoShapeManager* m = dynamic_cast(selection->shapeSelection())->shapeManager(); return m; } } } return globalShapeManager(); } KoShapeManager * KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg; m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); if (canvasWidget->needsFpsDebugging() && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); } } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg; QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL) { if (KisOpenGL::hasOpenGL()) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; createQPainterCanvas(); } } else { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageWSP image = m_d->view->image(); m_d->coordinatesConverter->setImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(this, SIGNAL(sigCanvasCacheUpdated()), SLOT(updateCanvasProjection())); + connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); emit imageChanged(image); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg; bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(KisDisplayFilter *displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageWSP image = this->image(); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } KisDisplayFilter *KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { KisDisplayFilter *displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { qDebug()<<"Canvas: No proofing config found, generating one."; m_d->proofingConfig = new KisProofingConfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; if (softProof) { conversionFlags |= KoColorConversionTransformation::SoftProofing; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing; } if (gamutCheck) { conversionFlags |= KoColorConversionTransformation::GamutCheck; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck; } m_d->proofingConfig->conversionFlags = conversionFlags; qDebug()<<"setting softproofing in canvas: "<image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } +void KisCanvas2::slotChangeProofingConfig() +{ + setProofingOptions(m_d->softProofing, m_d->gamutCheck); +} + KisProofingConfiguration *KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { qDebug()<<"No proofing config found, generating one"; m_d->proofingConfig = new KisProofingConfiguration(); } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) { QRect vRect = m_d->canvasWidget->updateCanvasProjection(info); if (!vRect.isEmpty()) { updateCanvasWidgetImpl(m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect()); } } // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { updateCanvasWidgetImpl(); } } void KisCanvas2::slotDoCanvasUpdate() { if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->updateSignalCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->updateSignalCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInCanvas()) return; KisImageSP image = this->image(); const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg; const int maxLod = cfg.numMipmapLevels(); int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); image->setDesiredLevelOfDetail(lod); } void KisCanvas2::preScale() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->preScale(); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); m_d->coordinatesConverter->setDocumentOffset(documentOffset); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); KisImageWSP image = this->image(); image->barrierLock(); m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter); refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->canvasWidget->widget()); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsGLSL13()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInCanvas = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsGLSL13() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas()); notifyLevelOfDetailChange(); } KisConfig cfg; cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInCanvas; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/canvas/kis_canvas2.h b/libs/ui/canvas/kis_canvas2.h index e60f8398db..1494ef4088 100644 --- a/libs/ui/canvas/kis_canvas2.h +++ b/libs/ui/canvas/kis_canvas2.h @@ -1,280 +1,281 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CANVAS_H #define KIS_CANVAS_H #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl.h" #include "kis_ui_types.h" #include "kis_coordinates_converter.h" #include "kis_canvas_decoration.h" #include "kis_painting_assistants_decoration.h" class KoToolProxy; class KoColorProfile; class KisViewManager; class KisFavoriteResourceManager; class KisDisplayFilter; class KisDisplayColorConverter; struct KisExposureGammaCorrectionInterface; class KisView; class KisInputManager; class KisAnimationPlayer; class KisShapeController; class KisCoordinatesConverter; class KoViewConverter; /** * KisCanvas2 is not an actual widget class, but rather an adapter for * the widget it contains, which may be either a QPainter based * canvas, or an OpenGL based canvas: that are the real widgets. */ class KRITAUI_EXPORT KisCanvas2 : public QObject, public KoCanvasBase { Q_OBJECT public: /** * Create a new canvas. The canvas manages a widget that will do * the actual painting: the canvas itself is not a widget. * * @param viewConverter the viewconverter for converting between * window and document coordinates. */ KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc); virtual ~KisCanvas2(); void notifyZoomChanged(); virtual void disconnectCanvasObserver(QObject *object); public: // KoCanvasBase implementation bool canvasIsOpenGL() const; KisOpenGL::FilterMode openGLFilterMode() const; void gridSize(QPointF *offset, QSizeF *spacing) const; bool snapToGrid() const; // XXX: Why? void addCommand(KUndo2Command *command); virtual QPoint documentOrigin() const; QPoint documentOffset() const; /** * Return the right shape manager for the current layer. That is * to say, if the current layer is a vector layer, return the shape * layer's canvas' shapemanager, else the shapemanager associated * with the global krita canvas. */ KoShapeManager * shapeManager() const; /** * Return the shape manager associated with this canvas */ KoShapeManager * globalShapeManager() const; void updateCanvas(const QRectF& rc); virtual void updateInputMethodInfo(); const KisCoordinatesConverter* coordinatesConverter() const; virtual KoViewConverter *viewConverter() const; virtual QWidget* canvasWidget(); virtual const QWidget* canvasWidget() const; virtual KoUnit unit() const; virtual KoToolProxy* toolProxy() const; const KoColorProfile* monitorProfile(); /** * Prescale the canvas represention of the image (if necessary, it * is for QPainter, not for OpenGL). */ void preScale(); // FIXME: // Temporary! Either get the current layer and image from the // resource provider, or use this, which gets them from the // current shape selection. KisImageWSP currentImage() const; /** * Filters events and sends them to canvas actions. Shared * among all the views/canvases * * NOTE: May be null while initialization! */ KisInputManager* globalInputManager() const; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const; public: // KisCanvas2 methods KisImageWSP image() const; KisViewManager* viewManager() const; QPointer imageView() const; /// @return true if the canvas image should be displayed in vertically mirrored mode void addDecoration(KisCanvasDecorationSP deco); KisCanvasDecorationSP decoration(const QString& id) const; void setDisplayFilter(KisDisplayFilter *displayFilter); KisDisplayFilter *displayFilter() const; KisDisplayColorConverter *displayColorConverter() const; KisExposureGammaCorrectionInterface* exposureGammaCorrectionInterface() const; void setProofingOptions(bool softProof, bool gamutCheck); KisProofingConfiguration *proofingConfiguration() const; void setCursor(const QCursor &cursor); KisAnimationFrameCacheSP frameCache() const; KisAnimationPlayer *animationPlayer() const; void refetchDataFromImage(); Q_SIGNALS: void imageChanged(KisImageWSP image); void sigCanvasCacheUpdated(); void sigContinueResizeImage(qint32 w, qint32 h); void documentOffsetUpdateFinished(); // emitted whenever the canvas widget thinks sketch should update void updateCanvasRequested(const QRect &rc); public Q_SLOTS: /// Update the entire canvas area void updateCanvas(); void startResizingImage(); void finishResizingImage(qint32 w, qint32 h); /// canvas rotation in degrees qreal rotationAngle() const; /// Bools indicating canvasmirroring. bool xAxisMirrored() const; bool yAxisMirrored() const; void slotSoftProofing(bool softProofing); void slotGamutCheck(bool gamutCheck); + void slotChangeProofingConfig(); void channelSelectionChanged(); /** * Called whenever the display monitor profile resource changes */ void slotSetDisplayProfile(const KoColorProfile *profile); void startUpdateInPatches(const QRect &imageRect); private Q_SLOTS: /// The image projection has changed, now start an update /// of the canvas representation. void startUpdateCanvasProjection(const QRect & rc); void updateCanvasProjection(); /** * Called whenever the view widget needs to show a different part of * the document * * @param documentOffset the offset in widget pixels */ void documentOffsetMoved(const QPoint &documentOffset); /** * Called whenever the configuration settings change. */ void slotConfigChanged(); void slotSelectionChanged(); void slotDoCanvasUpdate(); void bootstrapFinished(); public: bool isPopupPaletteVisible() const; void slotShowPopupPalette(const QPoint& = QPoint(0,0)); // interface for KisCanvasController only void setWrapAroundViewingMode(bool value); bool wrapAroundViewingMode() const; void setLodAllowedInCanvas(bool value); bool lodAllowedInCanvas() const; void initializeImage(); void setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager); private: Q_DISABLE_COPY(KisCanvas2) void connectCurrentCanvas(); void createCanvas(bool useOpenGL); void createQPainterCanvas(); void createOpenGLCanvas(); void updateCanvasWidgetImpl(const QRect &rc = QRect()); void setCanvasWidget(QWidget *widget); void resetCanvas(bool useOpenGL); void notifyLevelOfDetailChange(); // Completes construction of canvas. // To be called by KisView in its constructor, once it has been setup enough // (to be defined what that means) for things KisCanvas2 expects from KisView // TODO: see to avoid that void setup(); private: friend class KisView; // calls setup() class KisCanvas2Private; KisCanvas2Private * const m_d; }; #endif diff --git a/libs/ui/dialogs/kis_dlg_image_properties.cc b/libs/ui/dialogs/kis_dlg_image_properties.cc index 8a795e55d9..833d050190 100644 --- a/libs/ui/dialogs/kis_dlg_image_properties.cc +++ b/libs/ui/dialogs/kis_dlg_image_properties.cc @@ -1,132 +1,172 @@ /* * 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 "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: m_defaultColorAction = new KoColorPopupAction(this); m_defaultColorAction->setCurrentColor(m_image->defaultProjectionColor()); - m_defaultColorAction->setIcon(KisIconUtils::loadIcon("format-stroke-color")); m_defaultColorAction->setToolTip(i18n("Change the background color of the image")); m_page->bnBackgroundColor->setDefaultAction(m_defaultColorAction); KisSignalCompressor *compressor = new KisSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_defaultColorAction, SIGNAL(colorChanged(const KoColor&)), compressor, SLOT(start())); connect(compressor, SIGNAL(timeout()), this, SLOT(setCurrentColor())); connect(m_defaultColorAction, SIGNAL(colorChanged(const KoColor&)), 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)); + + m_page->cmbIntent->setCurrentIndex((int)m_proofingConfig->intent); + + m_page->ckbBlackPointComp->setChecked(m_proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); + + m_gamutWarning = new KoColorPopupAction(this); + m_gamutWarning->setCurrentColor(m_proofingConfig->warningColor); + m_gamutWarning->setToolTip(i18n("Set color used for warning")); + m_page->gamutAlarm->setDefaultAction(m_gamutWarning); + + KisSignalCompressor *softProofConfigCompressor = new KisSignalCompressor(500, KisSignalCompressor::POSTPONE,this); + + connect(m_gamutWarning, SIGNAL(colorChanged(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(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() { KisLayerUtils::changeImageDefaultProjectionColor(m_image, m_defaultColorAction->currentKoColor()); } +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_gamutWarning->currentKoColor(); + + qDebug()<<"set proofing config in properties: "<proofingProfile; + m_image->setProofingConfiguration(m_proofingConfig); +} + 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 eff9da1ee2..5302ea636a 100644 --- a/libs/ui/dialogs/kis_dlg_image_properties.h +++ b/libs/ui/dialogs/kis_dlg_image_properties.h @@ -1,66 +1,69 @@ /* * 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 KoColorPopupAction; 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); virtual ~KisDlgImageProperties(); const KoColorSpace * colorSpace(); private Q_SLOTS: void setAnnotation(const QString& type); void setCurrentColor(); + void setProofingConfig(); private: WdgImageProperties * m_page; KisImageWSP m_image; KoColorPopupAction *m_defaultColorAction; + KoColorPopupAction *m_gamutWarning; + KisProofingConfiguration *m_proofingConfig; }; #endif // KIS_DLG_IMAGE_PROPERTIES_H_ diff --git a/libs/ui/forms/wdgimageproperties.ui b/libs/ui/forms/wdgimageproperties.ui index ddc54d9198..b9de8660b4 100644 --- a/libs/ui/forms/wdgimageproperties.ui +++ b/libs/ui/forms/wdgimageproperties.ui @@ -1,234 +1,306 @@ WdgImageProperties 0 0 448 - 340 + 568 New Image Qt::Horizontal 95 61 Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel Resolution: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel pixels-per-inch ppi Background Color ... QToolButton::InstantPopup Qt::Horizontal QSizePolicy::Expanding 95 61 - - - - Mode - - - - - - - - - <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 - - - - - - Annotations Type: 0 0 TextLabel true Qt::Vertical 20 40 + + + + 1 + + + + 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 Alarm: + + + + + + + BlackPoint Compenstation + + + + + + + Rendering Intent + + + + + + 2 + + + + Perceptual + + + + + Relative Colorimetric + + + + + Absolute Colorimetric + + + + + Saturation + + + + + + + + + + + + + + KisColorSpaceSelector QWidget
widgets/kis_color_space_selector.h
1