diff --git a/krita/image/commands/kis_deselect_global_selection_command.cpp b/krita/image/commands/kis_deselect_global_selection_command.cpp index 4b92d4020c6..14f7a35e75d 100644 --- a/krita/image/commands/kis_deselect_global_selection_command.cpp +++ b/krita/image/commands/kis_deselect_global_selection_command.cpp @@ -1,47 +1,48 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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_selection_commands.h" #include #include "kis_image.h" #include "kis_selection.h" #include "kis_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_pixel_selection.h" KisDeselectGlobalSelectionCommand::KisDeselectGlobalSelectionCommand(KisImageWSP image, KUndo2Command * parent) : KUndo2Command(i18nc("(qtundo-format)", "Deselect"), parent) , m_image(image) { } KisDeselectGlobalSelectionCommand::~KisDeselectGlobalSelectionCommand() { } void KisDeselectGlobalSelectionCommand::redo() { + m_oldSelection = m_image->globalSelection(); m_image->deselectGlobalSelection(); } void KisDeselectGlobalSelectionCommand::undo() { - m_image->reselectGlobalSelection(); + m_image->setGlobalSelection(m_oldSelection); } diff --git a/krita/image/commands/kis_deselect_global_selection_command.h b/krita/image/commands/kis_deselect_global_selection_command.h index 7e3a0700721..644beb7d308 100644 --- a/krita/image/commands/kis_deselect_global_selection_command.h +++ b/krita/image/commands/kis_deselect_global_selection_command.h @@ -1,46 +1,47 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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_DESELECT_GLOBAL_SELECTION_COMMAND_H_ #define KIS_DESELECT_GLOBAL_SELECTION_COMMAND_H_ #include #include #include "kis_types.h" /// The command for deselection the global selection of KisImage class KRITAIMAGE_EXPORT KisDeselectGlobalSelectionCommand : public KUndo2Command { public: /** * Constructor * @param image the image * @param parent the parent command */ KisDeselectGlobalSelectionCommand(KisImageWSP image, KUndo2Command * parent = 0); virtual ~KisDeselectGlobalSelectionCommand(); virtual void redo(); virtual void undo(); private: KisImageWSP m_image; + KisSelectionSP m_oldSelection; }; #endif diff --git a/krita/image/kis_image.cc b/krita/image/kis_image.cc index 46dcf17f40b..087fce355a0 100644 --- a/krita/image/kis_image.cc +++ b/krita/image/kis_image.cc @@ -1,1392 +1,1377 @@ /* * 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 "KoUnit.h" #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorConversionTransformation.h" #include "KoColorProfile.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_background.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_iterators_pixel.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_perspective_grid.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_transform_visitor.h" #include "kis_types.h" #include "kis_meta_data_merge_strategy.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_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 "kis_composite_progress_proxy.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: KisBackgroundSP backgroundPattern; quint32 lockCount; bool sizeChangedWhileLocked; KisPerspectiveGrid* perspectiveGrid; qint32 width; qint32 height; double xres; double yres; KoUnit unit; const KoColorSpace * colorSpace; + KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList dirtyLayers; // for thumbnails KisNameServer *nserver; KisUndoStore *undoStore; KisUndoAdapter *legacyUndoAdapter; KisPostExecutionUndoAdapter *postExecutionUndoAdapter; KisActionRecorder *recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisImageSignalRouter *signalRouter; KisUpdateScheduler *scheduler; KisCompositeProgressProxy *compositeProgressProxy; bool startProjection; }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name, bool startProjection) : QObject(0) , KisShared() , m_d(new KisImagePrivate()) { setObjectName(name); dbgImage << "creating" << name; m_d->startProjection = startProjection; init(undoStore, width, height, colorSpace); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * First delete the nodes, while strokes * and undo are still alive */ m_d->rootLayer = 0; KisUpdateScheduler *scheduler = m_d->scheduler; m_d->scheduler = 0; delete scheduler; delete m_d->postExecutionUndoAdapter; delete m_d->legacyUndoAdapter; delete m_d->undoStore; delete m_d->compositeProgressProxy; delete m_d->signalRouter; delete m_d->perspectiveGrid; delete m_d->nserver; delete m_d; disconnect(); // in case Qt gets confused } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter->emitNodeHasBeenAdded(parent, index); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter->emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); m_d->signalRouter->emitNodeChanged(node); } 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); addNode(selectionMask); selectionMask->setActive(true); } 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() { - KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); - if (selectionMask) { - selectionMask->setActive(false); - } - - m_d->legacyUndoAdapter->emitSelectionChanged(); + KisSelectionSP savedSelection = globalSelection(); + setGlobalSelection(0); + m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { - return deselectedMask(); + return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { - KisSelectionMaskSP mask = deselectedMask(); - if (mask) { - mask->setActive(true); + if(m_d->deselectedGlobalSelection) { + setGlobalSelection(m_d->deselectedGlobalSelection); } - - m_d->legacyUndoAdapter->emitSelectionChanged(); -} - -KisSelectionMaskSP KisImage::deselectedMask() -{ - // Get a list of non-active masks - KoProperties properties; - properties.setProperty("active", false); - QList masks = root()->childNodes(QStringList("KisSelectionMask"), properties); - - return masks.size() > 0 ? - static_cast(masks.last().data()) : 0; } KisBackgroundSP KisImage::backgroundPattern() const { return m_d->backgroundPattern; } void KisImage::setBackgroundPattern(KisBackgroundSP background) { if (background != m_d->backgroundPattern) { m_d->backgroundPattern = background; emit sigImageUpdated(bounds()); } } QString KisImage::nextLayerName() const { if (m_d->nserver->currentSeed() == 0) { m_d->nserver->number(); return i18n("background"); } return i18n("Layer %1", m_d->nserver->number()); } void KisImage::rollBackLayerName() { m_d->nserver->rollback(); } void KisImage::init(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace) { if (colorSpace == 0) { colorSpace = KoColorSpaceRegistry::instance()->rgb8(); } m_d->lockCount = 0; m_d->sizeChangedWhileLocked = false; m_d->perspectiveGrid = 0; m_d->signalRouter = new KisImageSignalRouter(this); if (!undoStore) { undoStore = new KisDumbUndoStore(); } m_d->undoStore = undoStore; m_d->legacyUndoAdapter = new KisLegacyUndoAdapter(m_d->undoStore, this); m_d->postExecutionUndoAdapter = new KisPostExecutionUndoAdapter(m_d->undoStore, this); m_d->nserver = new KisNameServer(1); m_d->colorSpace = colorSpace; setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); m_d->xres = 1.0; m_d->yres = 1.0; m_d->unit = KoUnit::Point; m_d->width = width; m_d->height = height; m_d->recorder = new KisActionRecorder(this); m_d->compositeProgressProxy = new KisCompositeProgressProxy(); m_d->scheduler = 0; if (m_d->startProjection) { m_d->scheduler = new KisUpdateScheduler(this); m_d->scheduler->setProgressProxy(m_d->compositeProgressProxy); } } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock() { if (!locked()) { if (m_d->scheduler) { m_d->scheduler->barrierLock(); } m_d->sizeChangedWhileLocked = false; } m_d->lockCount++; } bool KisImage::tryBarrierLock() { bool result = true; if (!locked()) { if (m_d->scheduler) { result = m_d->scheduler->tryBarrierLock(); } if (result) { m_d->sizeChangedWhileLocked = false; } } if (result) { m_d->lockCount++; } return result; } void KisImage::lock() { if (!locked()) { if (m_d->scheduler) { m_d->scheduler->lock(); } m_d->sizeChangedWhileLocked = false; } m_d->lockCount++; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { if (m_d->sizeChangedWhileLocked) { m_d->signalRouter->emitNotification(SizeChangedSignal); } if (m_d->scheduler) { m_d->scheduler->unlock(); } } } } void KisImage::blockUpdates() { m_d->scheduler->blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler->unblockUpdates(); } void KisImage::notifyLayerUpdated(KisLayerSP layer) { // Add the layer to the list of layers that need to be // rescanned for the thumbnails in the layerbox KisLayer *l = layer.data(); while (l) { if (!m_d->dirtyLayers.contains(l)) m_d->dirtyLayers.append(l); l = dynamic_cast(l->parent().data()); } } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); emitSizeChanged(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds()) return; QString actionName = cropLayers ? i18n("Crop Image") : i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << SizeChangedSignal << ModifiedSignal; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitor(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) { QString actionName = i18n("Crop Node"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::emitSizeChanged() { if (!locked()) { m_d->signalRouter->emitNotification(SizeChangedSignal); } else { m_d->sizeChangedWhileLocked = true; } } 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 << SizeChangedSignal; emitSignals << ModifiedSignal; // XXX: Translate after 2.4 is released QString actionName = sizeChanged ? "Scale Image" : "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.applyVisitor(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::rotate(double radians) { qint32 w = width(); qint32 h = height(); qint32 tx = qint32((w * cos(radians) - h * sin(radians) - w) / 2 + 0.5); qint32 ty = qint32((h * cos(radians) + w * sin(radians) - h) / 2 + 0.5); w = (qint32)(width() * qAbs(cos(radians)) + height() * qAbs(sin(radians)) + 0.5); h = (qint32)(height() * qAbs(cos(radians)) + width() * qAbs(sin(radians)) + 0.5); tx -= (w - width()) / 2; ty -= (h - height()) / 2; bool sizeChanged = w != width() || h != height(); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << SizeChangedSignal; emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = (emitSignals.contains(SizeChangedSignal)) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; // XXX i18n("Rotate Image") after 2.4 KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, "Rotate Image"); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Triangle"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, -tx, -ty, filter); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, QSize(w,h))); } applicator.end(); } void KisImage::shearImpl(const QString &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 << SizeChangedSignal; 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("Triangle"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = 0.5 * (QPointF(1.0,1.0) + bounds().bottomRight()); shearImpl(i18n("Shear layer"), node, false, angleX, angleY, shearOrigin); } void KisImage::shear(double angleX, double angleY) { shearImpl(i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent) { if (*m_d->colorSpace == *dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(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); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); m_d->rootLayer->accept(visitor); } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(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() { 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(); } QRect KisImage::realNodeExtent(KisNodeSP rootNode, QRect currentRect) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExtent(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->changeRect(rootNode->extent()); return currentRect; } void KisImage::refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExtent(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { refreshGraph(rootNode, rc, realNodeRect); } } } void KisImage::flatten() { KisGroupLayerSP oldRootLayer = m_d->rootLayer; KisGroupLayerSP newRootLayer = new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8); refreshHiddenArea(oldRootLayer, bounds()); lock(); KisPaintDeviceSP projectionCopy = new KisPaintDevice(*oldRootLayer->projection()); unlock(); KisPaintLayerSP flattenLayer = new KisPaintLayer(this, nextLayerName(), OPACITY_OPAQUE_U8, projectionCopy); Q_CHECK_PTR(flattenLayer); addNode(flattenLayer, newRootLayer, 0); undoAdapter()->beginMacro(i18n("Flatten Image")); // NOTE: KisImageChangeLayersCommand performs all the locking for us undoAdapter()->addCommand(new KisImageChangeLayersCommand(KisImageWSP(this), oldRootLayer, newRootLayer, "")); undoAdapter()->endMacro(); setModified(); } KisLayerSP KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return 0; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = dynamic_cast(layer->prevSibling().data()); if (!prevLayer) return 0; refreshHiddenArea(layer, bounds()); refreshHiddenArea(prevLayer, bounds()); QRect layerProjectionExtent = layer->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); lock(); KisPaintDeviceSP mergedDevice = new KisPaintDevice(*prevLayer->projection()); unlock(); KisPainter gc(mergedDevice); gc.setChannelFlags(layer->channelFlags()); gc.setCompositeOp(mergedDevice->colorSpace()->compositeOp(layer->compositeOpId())); gc.setOpacity(layer->opacity()); gc.bitBlt(layerProjectionExtent.topLeft(), layer->projection(), layerProjectionExtent); KisPaintLayerSP mergedLayer = new KisPaintLayer(this, prevLayer->name(), OPACITY_OPAQUE_U8, mergedDevice); Q_CHECK_PTR(mergedLayer); mergedLayer->setCompositeOp(prevLayer->compositeOp()->id()); mergedLayer->setOpacity(prevLayer->opacity()); mergedLayer->setChannelFlags(prevLayer->channelFlags()); // Merge meta data QList srcs; srcs.append(prevLayer->metaData()); srcs.append(layer->metaData()); QList scores; int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); strategy->merge(mergedLayer->metaData(), srcs, scores); KisNodeSP parent = layer->parent(); // parent is set to null when the layer is removed from the node dbgImage << ppVar(parent); // XXX: merge the masks! // AAA: do you really think you need it? ;) -- yes, we don't want to lose the masks // FIXME: "Merge Down"? undoAdapter()->beginMacro(i18n("Merge with Layer Below")); undoAdapter()->addCommand(new KisImageLayerAddCommand(this, mergedLayer, parent, layer)); undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, prevLayer)); undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, layer)); undoAdapter()->endMacro(); return mergedLayer; } KisLayerSP KisImage::flattenLayer(KisLayerSP layer) { if (!layer->firstChild()) return layer; refreshHiddenArea(layer, bounds()); lock(); KisPaintDeviceSP mergedDevice = new KisPaintDevice(*layer->projection()); unlock(); KisPaintLayerSP newLayer = new KisPaintLayer(this, layer->name(), layer->opacity(), mergedDevice); newLayer->setCompositeOp(layer->compositeOp()->id()); undoAdapter()->beginMacro(i18n("Flatten Layer")); undoAdapter()->addCommand(new KisImageLayerAddCommand(this, newLayer, layer->parent(), layer)); undoAdapter()->addCommand(new KisImageLayerRemoveCommand(this, layer)); QList srcs; srcs.append(layer->metaData()); const KisMetaData::MergeStrategy* strategy = KisMetaData::MergeStrategyRegistry::instance()->get("Smart"); QList scores; scores.append(1.0); //Just give some score, there only is one layer strategy->merge(newLayer->metaData(), srcs, scores); undoAdapter()->endMacro(); return newLayer; } void KisImage::setModified() { m_d->signalRouter->emitNotification(ModifiedSignal); } void KisImage::renderToPainter(qint32 srcX, qint32 srcY, qint32 dstX, qint32 dstY, qint32 width, qint32 height, QPainter &painter, const KoColorProfile * monitorProfile) { QImage image = convertToQImage(srcX, srcY, width, height, monitorProfile); painter.drawImage(dstX, dstY, image, 0, 0, width, height); } 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 = m_d->rootLayer->projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h); if (m_d->backgroundPattern) { m_d->backgroundPattern->paintBackground(image, QRect(x, y, w, h)); } if (!image.isNull()) { #ifdef WORDS_BIGENDIAN uchar * data = image.bits(); for (int i = 0; i < w * h; ++i) { uchar r, g, b, a; a = data[0]; b = data[1]; g = data[2]; r = data[3]; data[0] = r; data[1] = g; data[2] = b; data[3] = a; data += 4; } #endif return image; } return QImage(); } QImage KisImage::convertToQImage(const QRect& scaledRect, const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledRect.isEmpty() || scaledImageSize.isEmpty()) { return QImage(); } qint32 imageWidth = width(); qint32 imageHeight = height(); quint32 pixelSize = colorSpace()->pixelSize(); double xScale = static_cast(imageWidth) / scaledImageSize.width(); double yScale = static_cast(imageHeight) / scaledImageSize.height(); QRect srcRect; srcRect.setLeft(static_cast(scaledRect.left() * xScale)); srcRect.setRight(static_cast(ceil((scaledRect.right() + 1) * xScale)) - 1); srcRect.setTop(static_cast(scaledRect.top() * yScale)); srcRect.setBottom(static_cast(ceil((scaledRect.bottom() + 1) * yScale)) - 1); KisPaintDeviceSP mergedImage = m_d->rootLayer->projection(); quint8 *scaledImageData = new quint8[scaledRect.width() * scaledRect.height() * pixelSize]; quint8 *imageRow = new quint8[srcRect.width() * pixelSize]; const qint32 imageRowX = srcRect.x(); for (qint32 y = 0; y < scaledRect.height(); ++y) { qint32 dstY = scaledRect.y() + y; qint32 dstX = scaledRect.x(); qint32 srcY = (dstY * imageHeight) / scaledImageSize.height(); mergedImage->readBytes(imageRow, imageRowX, srcY, srcRect.width(), 1); quint8 *dstPixel = scaledImageData + (y * scaledRect.width() * pixelSize); quint32 columnsRemaining = scaledRect.width(); while (columnsRemaining > 0) { qint32 srcX = (dstX * imageWidth) / scaledImageSize.width(); memcpy(dstPixel, imageRow + ((srcX - imageRowX) * pixelSize), pixelSize); ++dstX; dstPixel += pixelSize; --columnsRemaining; } } delete [] imageRow; QImage image = colorSpace()->convertToQImage(scaledImageData, scaledRect.width(), scaledRect.height(), const_cast(profile), KoColorConversionTransformation::IntentPerceptual); if (m_d->backgroundPattern) { m_d->backgroundPattern->paintBackground(image, scaledRect, scaledImageSize, QSize(imageWidth, imageHeight)); } delete [] scaledImageData; #ifdef __BIG_ENDIAN__ uchar * data = image.bits(); for (int i = 0; i < image.width() * image.height(); ++i) { uchar r, g, b, a; a = data[0]; b = data[1]; g = data[2]; r = data[3]; data[0] = r; data[1] = g; data[2] = b; data[3] = a; data += 4; } #endif return image; } KisPaintDeviceSP KisImage::mergedImage() { refreshGraph(); return m_d->rootLayer->projection(); } void KisImage::notifyLayersChanged() { m_d->signalRouter->emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } 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::setRootLayer(KisGroupLayerSP rootLayer) { if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); 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() { const KoColorProfile * profile = colorSpace()->profile(); KisAnnotationSP annotation; if (profile) { #ifdef __GNUC__ #warning "KisImage::beginAnnotations: make it possible to save any profile, not just icc profiles." #endif #if 0 // XXX we hardcode icc, this is correct for icc? // XXX productName(), or just "ICC Profile"? if (profile->valid() && profile->type() == "icc" && !profile->rawData().isEmpty()) { annotation = new KisAnnotation("icc", profile->name(), profile->rawData()); } } #endif } if (annotation) addAnnotation(annotation); else removeAnnotation("icc"); return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisPerspectiveGrid* KisImage::perspectiveGrid() { if (m_d->perspectiveGrid == 0) m_d->perspectiveGrid = new KisPerspectiveGrid(); return m_d->perspectiveGrid; } KisImageSignalRouter* KisImage::signalRouter() { return m_d->signalRouter; } void KisImage::waitForDone() { if (m_d->scheduler) { m_d->scheduler->waitForDone(); } } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { KisStrokeId id; if (m_d->scheduler) { id = m_d->scheduler->startStroke(strokeStrategy); } return id; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { if (m_d->scheduler) { m_d->scheduler->addJob(id, data); } } void KisImage::endStroke(KisStrokeId id) { if (m_d->scheduler) { m_d->scheduler->endStroke(id); } } bool KisImage::cancelStroke(KisStrokeId id) { bool result = false; if (m_d->scheduler) { result = m_d->scheduler->cancelStroke(id); } return result; } 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; if (m_d->scheduler) { 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; if (m_d->scheduler) { m_d->scheduler->fullRefreshAsync(root, rc, cropRect); } } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { if (!m_d->disableUIUpdateSignals) { emit sigImageUpdated(rc); } } void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect) { KisNodeGraphListener::requestProjectionUpdate(node, rect); if (m_d->scheduler) { m_d->scheduler->updateProjection(node, rect, bounds()); } } #include "kis_image.moc" diff --git a/krita/image/kis_image.h b/krita/image/kis_image.h index 09d754bda0c..07537a199da 100644 --- a/krita/image/kis_image.h +++ b/krita/image/kis_image.h @@ -1,637 +1,635 @@ /* * 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_default_bounds.h" #include "kis_image_interfaces.h" class KoDocument; class KoColorSpace; class KoCompositeOp; class KoColor; class KisCompositeProgressProxy; class KisActionRecorder; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KoUpdater; class KisPerspectiveGrid; 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, bool startProjection = true); virtual ~KisImage(); public: // KisNodeGraphListener implementation void nodeHasBeenAdded(KisNode *parent, int index); void aboutToRemoveANode(KisNode *parent, int index); void nodeChanged(KisNode * node); void requestProjectionUpdate(KisNode *node, const QRect& rect); public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc); public: /** * Paint the specified rect onto the painter, adjusting the colors * using the given profile. */ void renderToPainter(qint32 srcX, qint32 srcY, qint32 dstX, qint32 dstY, qint32 width, qint32 height, QPainter &painter, const KoColorProfile *profile); /** * 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 QRect& scaledRect, 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 image that is used as background tile. */ KisBackgroundSP backgroundPattern() const; /** * Set a 64x64 tile for the background of the image. */ void setBackgroundPattern(KisBackgroundSP image); /** * @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; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @return the perspective grid associated to this image */ KisPerspectiveGrid* perspectiveGrid(); /** * 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); void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * Execute a rotate transform on all layers in this image. */ void rotate(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::IntentPerceptual); /** * 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. */ void 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 KisDoc2 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()); } /** * Starting form 2.3 mergedImage() is declared deprecated. * If you want to get a projection of the image, please use * something like: * * image->lock(); * read_something_from_the_image(image->projection()); * image->unlock(); * * or if you want to get a full refresh of the image graph * performed beforehand (do you really want it?) (sure?) then * you can add a call to image->refreshGraph() before locking * the image. */ KDE_DEPRECATED KisPaintDeviceSP mergedImage(); /** * @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(); /** * 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. */ KisLayerSP 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. */ KisLayerSP flattenLayer(KisLayerSP layer); /// This overrides interface for KisDefaultBounds /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Called whenever a layer has changed. The layer is added to a * list of dirty layers and as soon as the document stores the * command that is now in progress, the image will be notified. * Then the image will notify the dirty layers, the dirty layers * will notify their parents & emit a signal that will be caught * by the layer box, which will request a new thumbnail. */ void notifyLayerUpdated(KisLayerSP layer); 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(); 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(); void sigSizeChanged(qint32 w, qint32 h); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); /** * 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 reciever should reload information * about the image */ void sigLayersChangedAsync(); public slots: KisCompositeProgressProxy* compositeProgressProxy(); void barrierLock(); bool tryBarrierLock(); void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy); void addJob(KisStrokeId id, KisStrokeJobData *data); void endStroke(KisStrokeId id); bool cancelStroke(KisStrokeId id); void blockUpdates(); void unblockUpdates(); void disableUIUpdates(); void enableUIUpdates(); 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(); private: KisImage(const KisImage& rhs); KisImage& operator=(const KisImage& rhs); void init(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void shearImpl(const QString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); static QRect realNodeExtent(KisNodeSP rootNode, QRect currentRect = QRect()); 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(); - KisSelectionMaskSP deselectedMask(); - private: class KisImagePrivate; KisImagePrivate * const m_d; }; #endif // KIS_IMAGE_H_ diff --git a/krita/image/tests/kis_image_test.cpp b/krita/image/tests/kis_image_test.cpp index eca7c8d8546..5089d3e6acc 100644 --- a/krita/image/tests/kis_image_test.cpp +++ b/krita/image/tests/kis_image_test.cpp @@ -1,141 +1,136 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfiguration *configuration = filter->defaultConfiguration(0); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); - QCOMPARE(image->root()->childCount(), 1U); + QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); - QCOMPARE(image->root()->childCount(), 1U); + QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); - QCOMPARE(image->canReselectGlobalSelection(), true); - QCOMPARE(image->root()->childCount(), 2U); - - image->reselectGlobalSelection(); - QCOMPARE(image->globalSelection(), selection2); - QCOMPARE(image->canReselectGlobalSelection(), true); - QCOMPARE(image->root()->childCount(), 2U); + QCOMPARE(image->canReselectGlobalSelection(), false); + QCOMPARE(image->root()->childCount(), 1U); } QTEST_KDEMAIN(KisImageTest, NoGUI) #include "kis_image_test.moc"