Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

kis_image.cc 44 KB
Newer Older
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1
/*
2
 *  Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
3
 *  Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 *  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
17
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 */
Boudewijn Rempt's avatar
Boudewijn Rempt committed
19

20
#include "kis_image.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
21

22
#include <KoConfig.h> // WORDS_BIGENDIAN
23

24
#include <stdlib.h>
25
#include <math.h>
26

27 28 29 30
#include <QImage>
#include <QPainter>
#include <QSize>
#include <QDateTime>
31 32
#include <QRect>
#include <QRegion>
33
#include <QtConcurrent>
34

35
#include <klocalizedstring.h>
36

37
#include "KoColorSpaceRegistry.h"
38
#include "KoColor.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
39
#include "KoColorProfile.h"
40
#include <KoCompositeOpRegistry.h>
41
#include "KisProofingConfiguration.h"
42

43
#include "recorder/kis_action_recorder.h"
44
#include "kis_adjustment_layer.h"
45 46
#include "kis_annotation.h"
#include "kis_change_profile_visitor.h"
47
#include "kis_colorspace_convert_visitor.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
48
#include "kis_count_visitor.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
49
#include "kis_filter_strategy.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
50
#include "kis_group_layer.h"
51
#include "commands/kis_image_commands.h"
52
#include "kis_layer.h"
53
#include "kis_meta_data_merge_strategy_registry.h"
54
#include "kis_name_server.h"
55 56 57 58
#include "kis_paint_layer.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_transaction.h"
59
#include "kis_meta_data_merge_strategy.h"
60
#include "kis_memory_statistics_server.h"
61

62
#include "kis_image_config.h"
63
#include "kis_update_scheduler.h"
64
#include "kis_image_signal_router.h"
65
#include "kis_image_animation_interface.h"
66
#include "kis_stroke_strategy.h"
67 68
#include "kis_image_barrier_locker.h"

69

Dmitry Kazakov's avatar
Dmitry Kazakov committed
70 71 72 73
#include "kis_undo_stores.h"
#include "kis_legacy_undo_adapter.h"
#include "kis_post_execution_undo_adapter.h"

74
#include "kis_transform_worker.h"
Dmitry Kazakov's avatar
Dmitry Kazakov committed
75 76
#include "kis_processing_applicator.h"
#include "processing/kis_crop_processing_visitor.h"
77
#include "processing/kis_crop_selections_processing_visitor.h"
78
#include "processing/kis_transform_processing_visitor.h"
79
#include "commands_new/kis_image_resize_command.h"
80
#include "commands_new/kis_image_set_resolution_command.h"
81
#include "commands_new/kis_activate_selection_mask_command.h"
82
#include "kis_composite_progress_proxy.h"
Sven Langkamp's avatar
Sven Langkamp committed
83
#include "kis_layer_composition.h"
84
#include "kis_wrapped_rect.h"
85
#include "kis_crop_saved_extra_data.h"
86
#include "kis_layer_utils.h"
87

88
#include "kis_lod_transform.h"
89

90
#include "kis_suspend_projection_updates_stroke_strategy.h"
91 92
#include "kis_sync_lod_cache_stroke_strategy.h"

93 94
#include "kis_projection_updates_filter.h"

95 96
#include "kis_layer_projection_plane.h"

97
#include "kis_update_time_monitor.h"
98 99
#include "kis_image_barrier_locker.h"

100
#include <QtCore>
101 102

#include <functional>
103

104
#include "kis_time_range.h"
105

106 107 108 109
// #define SANITY_CHECKS

#ifdef SANITY_CHECKS
#define SANITY_CHECK_LOCKED(name)                                       \
110 111 112
    if (!locked()) warnKrita() << "Locking policy failed:" << name          \
                               << "has been called without the image"       \
                                  "being locked";
113 114 115 116 117
#else
#define SANITY_CHECK_LOCKED(name)
#endif


Boudewijn Rempt's avatar
Boudewijn Rempt committed
118 119
class KisImage::KisImagePrivate
{
120
public:
121 122
    KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *u)
        : q(_q)
123
        , lockedForReadOnly(false)
124 125 126 127 128 129 130 131 132
        , width(w)
        , height(h)
        , colorSpace(c)
        , nserver(1)
        , undoStore(u)
        , legacyUndoAdapter(u, _q)
        , postExecutionUndoAdapter(u, _q)
        , recorder(_q)
        , signalRouter(_q)
133
        , animationInterface(0)
134 135
        , scheduler(_q)
    {}
136 137 138

    KisImage *q;

139
    quint32 lockCount = 0;
140
    bool lockedForReadOnly;
141 142 143 144

    qint32 width;
    qint32 height;

145 146
    double xres = 1.0;
    double yres = 1.0;
147

148
    const KoColorSpace * colorSpace;
149
    KisProofingConfiguration *proofingConfig = 0;
150

151
    KisSelectionSP deselectedGlobalSelection;
152 153
    KisGroupLayerSP rootLayer; // The layers are contained in here
    QList<KisLayer*> dirtyLayers; // for thumbnails
Sven Langkamp's avatar
Sven Langkamp committed
154
    QList<KisLayerComposition*> compositions;
155
    KisNodeSP isolatedRootNode;
156
    bool wrapAroundModePermitted = false;
157

158
    KisNameServer nserver;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
159 160

    KisUndoStore *undoStore;
161 162
    KisLegacyUndoAdapter legacyUndoAdapter;
    KisPostExecutionUndoAdapter postExecutionUndoAdapter;
163

164
    KisActionRecorder recorder;
165 166 167

    vKisAnnotationSP annotations;

168
    QAtomicInt disableUIUpdateSignals;
169
    KisProjectionUpdatesFilterSP projectionUpdatesFilter;
170
    KisImageSignalRouter signalRouter;
171
    KisImageAnimationInterface *animationInterface;
172 173 174
    KisUpdateScheduler scheduler;
    QAtomicInt disableDirtyRequests;

175

176
    KisCompositeProgressProxy compositeProgressProxy;
177

178
    bool blockLevelOfDetail = false;
179 180

    bool tryCancelCurrentStrokeAsync();
181 182

    void notifyProjectionUpdatedInPatches(const QRect &rc);
183
};
184

185
KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name)
Boudewijn Rempt's avatar
Boudewijn Rempt committed
186 187
        : QObject(0)
        , KisShared()
Patrick Julien's avatar
Patrick Julien committed
188
{
Adrian Page's avatar
Adrian Page committed
189
    setObjectName(name);
190 191 192 193
    // Handle undoStore == 0 and colorSpace == 0 cases
    if (!undoStore) {
        undoStore = new KisDumbUndoStore();
    }
194

195
    const KoColorSpace *c;
196
    if (colorSpace != 0) {
197 198 199
        c = colorSpace;
    } else {
        c = KoColorSpaceRegistry::instance()->rgb8();
200
    }
201
    m_d = new KisImagePrivate(this, width, height, c, undoStore);
202

203

204
    {
205 206
        KisImageConfig cfg;
        if (cfg.enableProgressReporting()) {
207
            m_d->scheduler.setProgressProxy(&m_d->compositeProgressProxy);
208 209
        }

210 211
        // Each of these lambdas defines a new factory function.
        m_d->scheduler.setLod0ToNStrokeStrategyFactory(
212 213 214 215 216 217
            [=](bool forgettable) {
                return KisLodSyncPair(
                    new KisSyncLodCacheStrokeStrategy(KisImageWSP(this), forgettable),
                    KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(this)));
            });

218
        m_d->scheduler.setSuspendUpdatesStrokeStrategyFactory(
219 220 221 222 223 224
            [=]() {
                return KisSuspendResumePair(
                    new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), true),
                    KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(this)));
            });

225
        m_d->scheduler.setResumeUpdatesStrokeStrategyFactory(
226 227 228 229 230
            [=]() {
                return KisSuspendResumePair(
                    new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), false),
                    KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(this)));
            });
231 232
    }

233 234
    setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));

235 236
    m_d->animationInterface = new KisImageAnimationInterface(this);

237
    connect(this, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
Patrick Julien's avatar
Patrick Julien committed
238
}
Patrick Julien's avatar
Patrick Julien committed
239

Boudewijn Rempt's avatar
Boudewijn Rempt committed
240
KisImage::~KisImage()
Patrick Julien's avatar
Patrick Julien committed
241
{
242
    dbgImage << "deleting kisimage" << objectName();
243

244 245 246 247 248
    /**
     * Request the tools to end currently running strokes
     */
    waitForDone();

249 250 251 252 253
    /**
     * Stop animation interface. It may use the rootLayer.
     */
    delete m_d->animationInterface;

Dmitry Kazakov's avatar
Dmitry Kazakov committed
254 255 256 257 258 259
    /**
     * First delete the nodes, while strokes
     * and undo are still alive
     */
    m_d->rootLayer = 0;

Stefano Bonicatti's avatar
Stefano Bonicatti committed
260
    delete m_d->undoStore;
261
    delete m_d;
262
    disconnect(); // in case Qt gets confused
Patrick Julien's avatar
Patrick Julien committed
263
}
Patrick Julien's avatar
Patrick Julien committed
264

265 266 267 268 269 270
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
    KisNodeGraphListener::aboutToAddANode(parent, index);
    SANITY_CHECK_LOCKED("aboutToAddANode");
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
271
void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
Boudewijn Rempt's avatar
Boudewijn Rempt committed
272
{
273 274
    KisNodeGraphListener::nodeHasBeenAdded(parent, index);

275
    SANITY_CHECK_LOCKED("nodeHasBeenAdded");
276
    m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
277 278 279 280 281

    KisNodeSP newNode = parent->at(index);
    if (!dynamic_cast<KisSelectionMask*>(newNode.data())) {
        stopIsolatedMode();
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
282 283
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
284
void KisImage::aboutToRemoveANode(KisNode *parent, int index)
Boudewijn Rempt's avatar
Boudewijn Rempt committed
285
{
286 287 288 289 290
    KisNodeSP deletedNode = parent->at(index);
    if (!dynamic_cast<KisSelectionMask*>(deletedNode.data())) {
        stopIsolatedMode();
    }

291 292
    KisNodeGraphListener::aboutToRemoveANode(parent, index);

293
    SANITY_CHECK_LOCKED("aboutToRemoveANode");
294
    m_d->signalRouter.emitAboutToRemoveANode(parent, index);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
295 296
}

297 298
void KisImage::nodeChanged(KisNode* node)
{
299
    KisNodeGraphListener::nodeChanged(node);
300
    requestStrokeEnd();
301
    m_d->signalRouter.emitNodeChanged(node);
302
}
303

304 305
void KisImage::invalidateAllFrames()
{
306
    invalidateFrames(KisTimeRange::infinite(0), QRect());
307
}
Boudewijn Rempt's avatar
Boudewijn Rempt committed
308

309 310
KisSelectionSP KisImage::globalSelection() const
{
311 312 313
    KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
    if (selectionMask) {
        return selectionMask->selection();
314
    } else {
315 316
        return 0;
    }
317 318
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
319
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
320
{
321
    KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
Dmitry Kazakov's avatar
Dmitry Kazakov committed
322

323 324
    if (!globalSelection) {
        if (selectionMask) {
Dmitry Kazakov's avatar
Dmitry Kazakov committed
325
            removeNode(selectionMask);
326
        }
327 328
    }
    else {
329
        if (!selectionMask) {
Dmitry Kazakov's avatar
Dmitry Kazakov committed
330
            selectionMask = new KisSelectionMask(this);
331
            selectionMask->initSelection(m_d->rootLayer);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
332
            addNode(selectionMask);
333 334 335
            // 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);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
336 337
            selectionMask->setActive(true);
        }
338 339 340
        else {
            selectionMask->setSelection(globalSelection);
        }
Dmitry Kazakov's avatar
Dmitry Kazakov committed
341 342 343

        Q_ASSERT(m_d->rootLayer->childCount() > 0);
        Q_ASSERT(m_d->rootLayer->selectionMask());
344
    }
Dmitry Kazakov's avatar
Dmitry Kazakov committed
345

346
    m_d->deselectedGlobalSelection = 0;
347
    m_d->legacyUndoAdapter.emitSelectionChanged();
348 349
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
350
void KisImage::deselectGlobalSelection()
Boudewijn Rempt's avatar
Boudewijn Rempt committed
351
{
352 353 354
    KisSelectionSP savedSelection = globalSelection();
    setGlobalSelection(0);
    m_d->deselectedGlobalSelection = savedSelection;
355
}
356

Dmitry Kazakov's avatar
Dmitry Kazakov committed
357
bool KisImage::canReselectGlobalSelection()
358
{
359
    return m_d->deselectedGlobalSelection;
360 361
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
362
void KisImage::reselectGlobalSelection()
363
{
364 365
    if(m_d->deselectedGlobalSelection) {
        setGlobalSelection(m_d->deselectedGlobalSelection);
366
    }
367 368
}

369
QString KisImage::nextLayerName(const QString &_baseName) const
Patrick Julien's avatar
Patrick Julien committed
370
{
371 372
    QString baseName = _baseName;

373 374
    if (m_d->nserver.currentSeed() == 0) {
        m_d->nserver.number();
375 376
        return i18n("background");
    }
Patrick Julien's avatar
Patrick Julien committed
377

378 379 380 381 382
    if (baseName.isEmpty()) {
        baseName = i18n("Layer");
    }

    return QString("%1 %2").arg(baseName).arg(m_d->nserver.number());
Patrick Julien's avatar
Patrick Julien committed
383 384
}

385 386
void KisImage::rollBackLayerName()
{
387
    m_d->nserver.rollback();
388 389
}

390 391
KisCompositeProgressProxy* KisImage::compositeProgressProxy()
{
392
    return &m_d->compositeProgressProxy;
393 394
}

395 396
bool KisImage::locked() const
{
397
    return m_d->lockCount != 0;
398 399
}

400
void KisImage::barrierLock(bool readOnly)
401 402
{
    if (!locked()) {
403
        requestStrokeEnd();
404
        m_d->scheduler.barrierLock();
405 406 407
        m_d->lockedForReadOnly = readOnly;
    } else {
        m_d->lockedForReadOnly &= readOnly;
408
    }
409

410 411 412
    m_d->lockCount++;
}

413
bool KisImage::tryBarrierLock(bool readOnly)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
414 415 416 417
{
    bool result = true;

    if (!locked()) {
418
        result = m_d->scheduler.tryBarrierLock();
419
        m_d->lockedForReadOnly = readOnly;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
420 421
    }

422
    if (result) {
Dmitry Kazakov's avatar
Dmitry Kazakov committed
423
        m_d->lockCount++;
424
        m_d->lockedForReadOnly &= readOnly;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
425 426 427 428 429
    }

    return result;
}

430 431
bool KisImage::isIdle()
{
432
    return !locked() && m_d->scheduler.isIdle();
433 434
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
435 436
void KisImage::lock()
{
437
    if (!locked()) {
438
        requestStrokeEnd();
439
        m_d->scheduler.lock();
440
    }
441
    m_d->lockCount++;
442
    m_d->lockedForReadOnly = false;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
443 444 445 446
}

void KisImage::unlock()
{
447 448
    Q_ASSERT(locked());

449
    if (locked()) {
450
        m_d->lockCount--;
451

452
        if (m_d->lockCount == 0) {
453
            m_d->scheduler.unlock(!m_d->lockedForReadOnly);
454
        }
455 456 457
    }
}

458 459
void KisImage::blockUpdates()
{
460
    m_d->scheduler.blockUpdates();
461 462 463 464
}

void KisImage::unblockUpdates()
{
465
    m_d->scheduler.unblockUpdates();
466 467
}

468
void KisImage::setSize(const QSize& size)
469
{
470 471
    m_d->width = size.width();
    m_d->height = size.height();
472 473
}

474
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
Patrick Julien's avatar
Patrick Julien committed
475
{
476
    if (newRect == bounds() && !cropLayers) return;
477

478 479 480
    KUndo2MagicString actionName = cropLayers ?
        kundo2_i18n("Crop Image") :
        kundo2_i18n("Resize Image");
481 482

    KisImageSignalVector emitSignals;
483 484
    emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
    emitSignals << ModifiedSignal;
485

486 487 488 489 490 491
    KisCropSavedExtraData *extraData =
        new KisCropSavedExtraData(cropLayers ?
                                  KisCropSavedExtraData::CROP_IMAGE :
                                  KisCropSavedExtraData::RESIZE_IMAGE,
                                  newRect);

492
    KisProcessingApplicator applicator(this, m_d->rootLayer,
493 494
                                       KisProcessingApplicator::RECURSIVE |
                                       KisProcessingApplicator::NO_UI_UPDATES,
495
                                       emitSignals, actionName, extraData);
496

497
    if (cropLayers || !newRect.topLeft().isNull()) {
498
        KisProcessingVisitorSP visitor =
499
            new KisCropProcessingVisitor(newRect, cropLayers, true);
500
        applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
501
    }
502 503
    applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
    applicator.end();
504
}
505

506
void KisImage::resizeImage(const QRect& newRect)
507
{
508
    resizeImageImpl(newRect, false);
Patrick Julien's avatar
Patrick Julien committed
509 510
}

511
void KisImage::cropImage(const QRect& newRect)
512
{
513 514 515 516 517 518
    resizeImageImpl(newRect, true);
}


void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
{
519
    bool isLayer = dynamic_cast<KisLayer*>(node.data());
520 521 522
    KUndo2MagicString actionName = isLayer ?
        kundo2_i18n("Crop Layer") :
        kundo2_i18n("Crop Mask");
523 524 525 526

    KisImageSignalVector emitSignals;
    emitSignals << ModifiedSignal;

527 528 529 530
    KisCropSavedExtraData *extraData =
        new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
                                  newRect, node);

531
    KisProcessingApplicator applicator(this, node,
532
                                       KisProcessingApplicator::RECURSIVE,
533
                                       emitSignals, actionName, extraData);
534

535 536
    KisProcessingVisitorSP visitor =
        new KisCropProcessingVisitor(newRect, true, false);
537
    applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
538
    applicator.end();
539
}
540

541
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
542
{
543 544
    bool resolutionChanged = xres != xRes() && yres != yRes();
    bool sizeChanged = size != this->size();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
545

546
    if (!resolutionChanged && !sizeChanged) return;
547

548
    KisImageSignalVector emitSignals;
549
    if (resolutionChanged) emitSignals << ResolutionChangedSignal;
550
    if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
551 552
    emitSignals << ModifiedSignal;

553 554 555
    KUndo2MagicString actionName = sizeChanged ?
        kundo2_i18n("Scale Image") :
        kundo2_i18n("Change Image Resolution");
556

557 558
    KisProcessingApplicator::ProcessingFlags signalFlags =
        (resolutionChanged || sizeChanged) ?
559 560
                KisProcessingApplicator::NO_UI_UPDATES :
                KisProcessingApplicator::NONE;
561

562
    KisProcessingApplicator applicator(this, m_d->rootLayer,
563
                                       KisProcessingApplicator::RECURSIVE | signalFlags,
564
                                       emitSignals, actionName);
565

566 567 568
    qreal sx = qreal(size.width()) / this->size().width();
    qreal sy = qreal(size.height()) / this->size().height();

569 570
    QTransform shapesCorrection;

571
    if (resolutionChanged) {
572 573
        shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
    }
574

575 576 577 578 579 580 581
    KisProcessingVisitorSP visitor =
        new KisTransformProcessingVisitor(sx, sy,
                                          0, 0,
                                          QPointF(),
                                          0,
                                          0, 0,
                                          filterStrategy,
582
                                          shapesCorrection);
583

584
    applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
585

586
    if (resolutionChanged) {
587 588 589 590
        KUndo2Command *parent =
            new KisResetShapesCommand(m_d->rootLayer);
        new KisImageSetResolutionCommand(this, xres, yres, parent);
        applicator.applyCommand(parent);
591 592
    }

593
    if (sizeChanged) {
594 595 596
        applicator.applyCommand(new KisImageResizeCommand(this, size));
    }

597
    applicator.end();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
598
}
599

600
void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy)
601
{
602
    KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
603 604 605 606 607 608 609 610
    KisImageSignalVector emitSignals;
    emitSignals << ModifiedSignal;

    KisProcessingApplicator applicator(this, node,
                                       KisProcessingApplicator::RECURSIVE,
                                       emitSignals, actionName);

    KisProcessingVisitorSP visitor =
611
        new KisTransformProcessingVisitor(scaleX, scaleY,
612 613 614 615 616 617
                                          0, 0,
                                          QPointF(),
                                          0,
                                          0, 0,
                                          filterStrategy);

618
    applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
619 620 621
    applicator.end();
}

622
void KisImage::rotateImpl(const KUndo2MagicString &actionName,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
623 624 625
                          KisNodeSP rootNode,
                          bool resizeImage,
                          double radians)
626
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
627 628 629 630 631 632 633 634 635 636
    QPointF offset;
    QSize newSize;

    {
        KisTransformWorker worker(0,
                                  1.0, 1.0,
                                  0, 0, 0, 0,
                                  radians,
                                  0, 0, 0, 0);
        QTransform transform = worker.transform();
637

Dmitry Kazakov's avatar
Dmitry Kazakov committed
638 639 640 641 642 643 644 645 646 647 648 649
        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);
        }
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
650

Dmitry Kazakov's avatar
Dmitry Kazakov committed
651 652
    bool sizeChanged = resizeImage &&
        (newSize.width() != width() || newSize.height() != height());
653

654 655
    // These signals will be emitted after processing is done
    KisImageSignalVector emitSignals;
656
    if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
657 658 659 660
    emitSignals << ModifiedSignal;

    // These flags determine whether updates are transferred to the UI during processing
    KisProcessingApplicator::ProcessingFlags signalFlags =
Dmitry Kazakov's avatar
Dmitry Kazakov committed
661 662 663
        sizeChanged ?
        KisProcessingApplicator::NO_UI_UPDATES :
        KisProcessingApplicator::NONE;
664 665


Dmitry Kazakov's avatar
Dmitry Kazakov committed
666
    KisProcessingApplicator applicator(this, rootNode,
667
                                       KisProcessingApplicator::RECURSIVE | signalFlags,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
668
                                       emitSignals, actionName);
669

670
    KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
671

672 673 674 675
    KisProcessingVisitorSP visitor =
            new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
                                              QPointF(),
                                              radians,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
676 677
                                              offset.x(), offset.y(),
                                              filter);
678

679
    applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
680

681
    if (sizeChanged) {
Dmitry Kazakov's avatar
Dmitry Kazakov committed
682
        applicator.applyCommand(new KisImageResizeCommand(this, newSize));
683 684
    }
    applicator.end();
685 686
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
687 688 689

void KisImage::rotateImage(double radians)
{
690
    rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
691 692 693 694
}

void KisImage::rotateNode(KisNodeSP node, double radians)
{
695
    rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
696 697
}

698
void KisImage::shearImpl(const KUndo2MagicString &actionName,
699 700 701 702
                         KisNodeSP rootNode,
                         bool resizeImage,
                         double angleX, double angleY,
                         const QPointF &origin)
703
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
704
    //angleX, angleY are in degrees
705 706
    const qreal pi = 3.1415926535897932385;
    const qreal deg2rad = pi / 180.0;
707

708 709
    qreal tanX = tan(angleX * deg2rad);
    qreal tanY = tan(angleY * deg2rad);
710

711 712
    QPointF offset;
    QSize newSize;
713

714 715 716
    {
        KisTransformWorker worker(0,
                                  1.0, 1.0,
717
                                  tanX, tanY, origin.x(), origin.y(),
718 719 720 721 722
                                  0,
                                  0, 0, 0, 0);

        QRect newRect = worker.transform().mapRect(bounds());
        newSize = newRect.size();
723
        if (resizeImage) offset = -newRect.topLeft();
724
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
725

726
    if (newSize == size()) return;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
727

728
    KisImageSignalVector emitSignals;
729
    if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
730
    emitSignals << ModifiedSignal;
731

732 733
    KisProcessingApplicator::ProcessingFlags signalFlags =
        KisProcessingApplicator::RECURSIVE;
734
    if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
735

736
    KisProcessingApplicator applicator(this, rootNode,
737
                                       signalFlags,
738
                                       emitSignals, actionName);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
739

740
    KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
741 742 743

    KisProcessingVisitorSP visitor =
            new KisTransformProcessingVisitor(1.0, 1.0,
744
                                              tanX, tanY, origin,
745 746 747 748
                                              0,
                                              offset.x(), offset.y(),
                                              filter);

749
    applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
750

751
    if (resizeImage) {
752 753 754
        applicator.applyCommand(new KisImageResizeCommand(this, newSize));
    }

755
    applicator.end();
756 757
}

758 759
void KisImage::shearNode(KisNodeSP node, double angleX, double angleY)
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
760
    QPointF shearOrigin = QRectF(bounds()).center();
761

762
    shearImpl(kundo2_i18n("Shear layer"), node, false,
763 764 765 766 767
              angleX, angleY, shearOrigin);
}

void KisImage::shear(double angleX, double angleY)
{
768
    shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
769 770 771
              angleX, angleY, QPointF());
}

772 773 774
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
                                      KoColorConversionTransformation::Intent renderingIntent,
                                      KoColorConversionTransformation::ConversionFlags conversionFlags)
775
{
776
    if (!dstColorSpace) return;
777

778 779
    const KoColorSpace *srcColorSpace = m_d->colorSpace;

780
    undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space"));
781 782
    undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
    undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
783

784
    KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
785
    m_d->rootLayer->accept(visitor);
786

787 788
    undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
    undoAdapter()->endMacro();
789

790
    setModified();
791 792
}

793
bool KisImage::assignImageProfile(const KoColorProfile *profile)
794
{
795
    if (!profile) return false;
796 797 798 799

    const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
    const KoColorSpace *srcCs = colorSpace();

800
    if (!dstCs) return false;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
801

Dmitry Kazakov's avatar
Dmitry Kazakov committed
802
    m_d->colorSpace = dstCs;
803

804
    KisChangeProfileVisitor visitor(srcCs, dstCs);
805
    return m_d->rootLayer->accept(visitor);
806

807 808
}

809
void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace)
810
{
811
    if (*m_d->colorSpace == *dstColorSpace) return;
812

813
    undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space"));
814 815 816 817
    undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
    undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
    undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
    undoAdapter()->endMacro();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
818

819 820
    setModified();
}
821

822 823 824 825
void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
{
    m_d->colorSpace = colorSpace;
    m_d->rootLayer->resetCache();
826
    m_d->signalRouter.emitNotification(ColorSpaceChangedSignal);
827
}
828

829 830 831 832
const KoColorSpace * KisImage::colorSpace() const
{
    return m_d->colorSpace;
}
Boudewijn Rempt's avatar
Boudewijn Rempt committed
833

834 835 836
const KoColorProfile * KisImage::profile() const
{
    return colorSpace()->profile();
Patrick Julien's avatar
Patrick Julien committed
837 838
}

Thomas Zander's avatar
Thomas Zander committed
839
double KisImage::xRes() const
840
{
841
    return m_d->xres;
842 843
}

Thomas Zander's avatar
Thomas Zander committed
844
double KisImage::yRes() const
845
{
846
    return m_d->yres;
847 848 849 850
}

void KisImage::setResolution(double xres, double yres)
{
851 852
    m_d->xres = xres;
    m_d->yres = yres;
853
    m_d->signalRouter.emitNotification(ResolutionChangedSignal);
854 855
}

856
QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
857
{
858 859 860 861 862 863 864 865 866
    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());
}

867 868 869 870 871 872 873 874 875 876
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();
}

877 878 879 880 881 882 883 884
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());
885 886
}

Cyrille Berger's avatar