Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_scratch_pad.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * Copyright 2010 (C) Boudewijn Rempt <boud@valdyas.org>
3 * Copyright 2011 (C) Dmitry Kazakov <dimula73@gmail.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7#include "kis_scratch_pad.h"
8
9#include <QApplication>
10#include <QScreen>
11#include <QMutex>
12#include <QMutexLocker>
13#include <QWheelEvent>
14#include <QPaintEvent>
15
16#include <KoColorSpace.h>
17#include <KoColorProfile.h>
19#include <KoPointerEvent.h>
21
22#include <KisPortingUtils.h>
23
24#include <kis_cursor.h>
25#include <kis_tool_utils.h>
26#include <kis_paint_layer.h>
27#include <kis_paint_device.h>
29#include <kis_fill_painter.h>
30#include <kis_default_bounds.h>
32#include <KisPortingUtils.h>
33
34#include "kis_config.h"
35#include "kis_image.h"
36#include "kis_undo_stores.h"
42#include "kis_image_patch.h"
46#include "kis_transaction.h"
47#include "kis_algebra_2d.h"
48#include <KisAdaptedLock.h>
51#include <kis_config_notifier.h>
52
54#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
56#endif /* KRITA_USE_SURFACE_COLOR_MANAGEMENT_API */
57
58namespace {
59class KisUpdateSchedulerLockAdapter
60{
61public:
62 KisUpdateSchedulerLockAdapter(KisUpdateScheduler *scheduler)
63 : m_scheduler(scheduler)
64 {
65 }
66
67 void lock() {
68 m_scheduler->barrierLock();
69 }
70
71 bool try_lock() {
72 return m_scheduler->tryBarrierLock();
73 }
74
75 void unlock() {
76 m_scheduler->unlock();
77 }
78
79private:
80 KisUpdateScheduler *m_scheduler;
81};
82
86KIS_DECLARE_ADAPTED_LOCK(KisUpdateSchedulerLockWithFeedback,
88
89}
90
91
93{
94public:
96 : m_scratchPad(scratchPad)
97 {
98 }
99
100 void requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, KisProjectionUpdateFlags flags) override {
102
103 QMutexLocker locker(&m_lock);
104
105 Q_FOREACH (const QRect &rc, rects) {
107 }
108 }
109
110private:
112 QMutex m_lock;
113};
114
115
117{
118public:
119
121 : m_scratchPad(scratchPad)
122 {
123 }
124
126
127 QRect bounds() const override {
128 return m_scratchPad->imageBounds();
129 }
130
131 void * sourceCookie() const override {
132 return m_scratchPad;
133 }
134
135private:
136 Q_DISABLE_COPY(KisScratchPadDefaultBounds)
137
139};
140
142{
143 Q_OBJECT
144
145public:
147 : m_scratchPad(scratchPad)
148 {
149 }
150
151protected:
152 QPointF imageToView(const QPointF &point) override {
153 return m_scratchPad->documentToWidget().map(point);
154 }
155
156private:
158};
159
161 : QWidget(parent)
162 , m_toolMode(HOVERING)
163 , isModeManuallySet(false)
164 , isMouseDown(false)
165 , m_linkCanvasZoomLevel(true)
166 , m_canvasScaleX(1.0)
167 , m_canvasScaleY(1.0)
168 , m_scratchpadScaleX(1.0)
169 , m_scratchpadScaleY(1.0)
170 , m_accumulatedMouseDelta(0)
171 , m_paintLayer(0)
172 , m_resourceProvider(0)
173{
174
175 setAutoFillBackground(false);
176 setMouseTracking(true);
177
178 m_cursor = KisCursor::load("tool_freehand_cursor.xpm", 2, 2);
179 m_colorSamplerCursor = KisCursor::load("tool_color_sampler_cursor.xpm", 2, 2);
180 setCursor(m_cursor);
181
182
183 KisConfig cfg(true);
184 QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize());
185 m_checkBrush = QBrush(checkImage);
186
187
188 // We are not supposed to use updates here,
189 // so just set the listener to null
194
195 connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection);
196
197 // filter will be deleted by the QObject hierarchy
199
201
203
205
208
211
212 QScreen *screen = m_screenMigrationTracker->currentScreenSafe();
213 const int canvasScreenNumber = qApp->screens().indexOf(screen);
214
215#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
216 if (KisPlatformPluginInterfaceFactory::instance()->surfaceColorManagedByOS()) {
217 // proxy's lifetime is managed by QObject hierarchy
218 KisRootSurfaceInfoProxy *rootSurfaceInfoProxy = new KisRootSurfaceInfoProxy(this, this);
219 m_multiSurfaceStateManager.setRootSurfaceInfoProxy(rootSurfaceInfoProxy);
220 connect(rootSurfaceInfoProxy,
222 parent,
223 [this] (const KoColorProfile *profile) {
225 assignNewSurfaceState(newState);
226 });
227 }
228#endif
229
230 m_multiSurfaceState = m_multiSurfaceStateManager.createInitializingConfig(false, canvasScreenNumber, nullptr);
231}
232
234{
235 delete m_infoBuilder;
236
237 delete m_undoAdapter;
238 delete m_undoStore;
239 delete m_updateScheduler;
240 delete m_nodeListener;
241}
242
244{
245 return
246 button == Qt::NoButton ? HOVERING :
247 button == Qt::MiddleButton ? PANNING :
248 button == Qt::RightButton ? SAMPLING :
249 PAINTING;
250}
251
252void KisScratchPad::wheelDelta(QWheelEvent *event)
253{
254 // if linked to canvas zoom level, ignore wheel event scale
255 if (!isEnabled() || m_linkCanvasZoomLevel) return;
256
257 const int angleDelta = event->angleDelta().y();
258
259#ifdef Q_OS_MACOS
260 // --> from KisInputManager::eventFilterImpl()
261
262 // Some QT wheel events are actually touch pad pan events. From the QT docs:
263 // "Wheel events are generated for both mouse wheels and trackpad scroll gestures."
264 // We differentiate between touchpad events and real mouse wheels by inspecting the
265 // event source.
266 if (event->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) {
267 return;
268 }
269
274 if (angleDelta == 0) {
275 return;
276 }
277#endif
278
279 m_accumulatedMouseDelta += angleDelta;
280
283
284 // cursor position on widget
285 QPointF position = event->position();
286 // matching cursor position in document (taking in account scale + translate)
287 QPointF docPosition = widgetToDocument().map(position);
288
289 // loop manage case where angleDelta is greater than one mouse wheel tick
290 // and allows to refresh zoom smoothly in this case
291 // if angleDelta = 360, 360/120 = 3, then scale will be refreshed 3 times
292 // m_accumulatedMouseDelta allow to manage the case where angleDelta in not equal QWheelEvent::DefaultDeltasPerStep
293 while (qAbs(m_accumulatedMouseDelta) >= QWheelEvent::DefaultDeltasPerStep) {
294 if (m_accumulatedMouseDelta > 0) {
295 qreal scaleFactor = 0.5;
296 if (scaleX < 1) scaleFactor = 0.05;
297 // zoom In
298 scaleX += scaleFactor;
299 scaleY += scaleFactor;
300 } else {
301 qreal scaleFactor = 0.5;
302 if (scaleX <= 1) scaleFactor = 0.05;
303 // zoom out
304 scaleX -= scaleFactor;
305 scaleY -= scaleFactor;
306 }
307
308 // update zoom level
309 if (!setScaleImpl(scaleX, scaleY)) {
310 // if can't apply zoom level, reset accumulated delta and exit, no need to try
311 // to continue
313 break;
314 }
315
317
318 // cursor position after scale
319 QPointF offsetPosition = QPointF(position.x() / m_scratchpadScaleX, position.y() / m_scratchpadScaleY);
320
321 // new position after scale is applied
322 //
323 QPoint panPosition = QPointF( docPosition - offsetPosition).toPoint();
324
325 // pan to ensure zoomed point is always under cursor
326 panTo(panPosition.x(), panPosition.y());
327
328 m_accumulatedMouseDelta -= KisAlgebra2D::signPZ(m_accumulatedMouseDelta) * QWheelEvent::DefaultDeltasPerStep;
329 }
330}
331
333{
334 if (!isEnabled()) return;
335
336 if (isModeManuallySet == false) {
337 m_toolMode = modeFromButton(event->button());
338 }
339
340 // see if we are pressing down with a button
341 if (event->button() == Qt::LeftButton ||
342 event->button() == Qt::MiddleButton ||
343 event->button() == Qt::RightButton) {
344 isMouseDown = true;
345 } else {
346 isMouseDown = false;
347 }
348
349 // if mouse is down, we are doing one of three things
350 if (isMouseDown) {
351 if (m_toolMode == PAINTING) {
352 beginStroke(event);
353 event->accept();
354 } else if (m_toolMode == PANNING) {
355 beginPan(event);
356 event->accept();
357 } else if (m_toolMode == SAMPLING) {
358 sample(event);
359 event->accept();
360 }
361 }
362}
363
365{
366 if (!isEnabled()) return;
367 isMouseDown = false;
368
369 if (isModeManuallySet == false) {
370 if (modeFromButton(event->button()) != m_toolMode) return;
371
372 if (m_toolMode == PAINTING) {
373 endStroke(event);
375 event->accept();
376 } else if (m_toolMode == PANNING) {
377 endPan(event);
379 event->accept();
380 } else if (m_toolMode == SAMPLING) {
381 event->accept();
383 }
384
385 } else {
386 if (m_toolMode == PAINTING) {
387 endStroke(event);
388 } else if (m_toolMode == PANNING) {
389 endPan(event);
390 }
391
392 event->accept();
393 }
394}
395
397{
398 if (!isEnabled()) return;
400
401 if (event->point.isNull() == false) {
402 m_helper->cursorMoved(documentToWidget().map(event->point));
403 }
404
405 if (isMouseDown) {
406 if (m_toolMode == PAINTING) {
407 doStroke(event);
408 event->accept();
409 } else if (m_toolMode == PANNING) {
410 doPan(event);
411 event->accept();
412 } else if (m_toolMode == SAMPLING) {
413 sample(event);
414 event->accept();
415 }
416 }
417}
418
420{
421
422 m_helper->initPaint(event,
423 documentToWidget().map(event->point),
424 0,
425 0,
429
430
431}
432
434{
435 m_helper->paintEvent(event);
436}
437
439{
440 Q_UNUSED(event);
441 m_helper->endPaint();
442 Q_EMIT contentChanged();
443}
444
446{
447 setCursor(QCursor(Qt::ClosedHandCursor));
448 m_panDocPoint = event->point;
449}
450
452{
453 QPointF docOffset = event->point - m_panDocPoint;
454
455 m_translateTransform.translate(-docOffset.x(), -docOffset.y());
457 update();
458}
459
461{
462 Q_UNUSED(event);
463
464 // the normal brush editor scratchpad reverts back to paint mode when done
465 if (isModeManuallySet) {
466 setCursor(QCursor(Qt::OpenHandCursor));
467 } else {
468 setCursor(m_cursor);
469 }
471}
472
474{
475 KoColor color;
476 if (KisToolUtils::sampleColor(color, m_paintLayer->projection(), event->point.toPoint())) {
477 Q_EMIT colorSelected(color);
478 }
479}
480
481void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY)
482{
483 // This method is called only when canvas zoom is changed (sigOnScreenResolutionChanged) AND scratchPad is
484 // linked to it
485 //
486 // Otherwise do nothing
487 // If zoom on scratchPad has to be updated, then it has to be updated manually through setScale() method
488
489 // value not changed? do nothing
491
492 // always keep in memory canvas zoom, even if link is not active
495
496 // the scratchpad will use the canvas zoom level...or not
499
500 // memorize current scratchPad scale
503
504 m_scaleTransform = QTransform::fromScale(scaleX, scaleY);
506 update();
507
510 }
511}
512
513bool KisScratchPad::setScale(qreal scaleX, qreal scaleY)
514{
515 const bool retval = setScaleImpl(scaleX, scaleY);
516
517 if (retval) {
520 }
521
522 return retval;
523}
524
525bool KisScratchPad::setScaleImpl(qreal scaleX, qreal scaleY)
526{
527 // developer must ensure zoom level is not linked with canvas zoom level before
528 // calling this method
530
531 scaleX = qBound(0.05, scaleX, 16.0);
532 scaleY = qBound(0.05, scaleY, 16.0);
533
534 // value not changed? do nothing and return false
535 if (scaleX == m_scratchpadScaleX && scaleY == m_scratchpadScaleY) return false;
536
538
539 // memorize current scratchPad scale
542
545 update();
546
547 return true;
548}
549
554
556{
557 return m_scratchpadScaleX;
558}
559
561{
562 return m_scratchpadScaleY;
563}
564
566 // developer must ensure zoom level is not linked with canvas zoom level before
567 // calling this method
569
570 QRectF viewportF = QRectF(imageBounds());
571 QRectF contentsF = QRectF(contentBounds());
572 qreal scale;
573
574 if (contentsF.isEmpty() || viewportF.isEmpty()) return;
575
576 qreal contentRatio = contentsF.width() / contentsF.height();
577 qreal viewportRatio = viewportF.width() / viewportF.height();
578
579 if (viewportRatio > contentRatio) {
580 scale = viewportF.height() / contentsF.height();
581 } else {
582 scale = viewportF.width() / contentsF.width();
583 }
584
585 if (setScaleImpl(scale, scale)) {
587 }
588 panCenter();
589}
590
592 // developer must ensure zoom level is not linked with canvas zoom level before
593 // calling this method
595
596 if (setScaleImpl(1.0, 1.0)) {
598 }
599 panTo(0, 0);
600}
601
602void KisScratchPad::panTo(qint32 x, qint32 y) {
603 m_translateTransform.reset();
604 m_translateTransform.translate(x, y);
606 update();
608}
609
612
613 QPointF viewportF = QPointF(viewportBounds().width(), viewportBounds().height());
614 QRectF contentsF = QRectF(contentBounds());
615
616 QPoint panPosition = QPointF( contentsF.center() - viewportF/2 ).toPoint() ;
617
618 panTo(panPosition.x(), panPosition.y());
619}
620
622{
623 return m_translateTransform.inverted() * m_scaleTransform;
624}
625
627{
628 return m_scaleTransform.inverted() * m_translateTransform;
629}
630
635
637{
638 return rect();
639}
640
642{
643 return m_viewport;
644}
645
647{
648 if (updateViewportImpl()) {
650 }
651}
652
654{
655 const QRect viewport = widgetToDocument().mapRect(rect());
656
657 if (viewport != m_viewport) {
658 m_viewport = viewport;
659 return true;
660 }
661 return false;
662}
663
664
666{
668
669 // -- DIRTY CODE HERE --
670 // Call of nonDefaultPixelArea() should return expected result but no, not in all case
671 //
672 // Doing
673 // - fillLayer()
674 // - contentBounds() --> return expected QRect() bounds
675 //
676 // Doing
677 // - fillDefault()
678 // - do a stroke
679 // - contentBounds() --> return empty QRect()
680 // - do another stroke
681 // - call contentBounds() --> return expected QRect() bounds of 1st stroke, ignoring 2nd stroke
682 // - do another stroke
683 // - call contentBounds() --> return expected QRect() bounds of 2nd stroke, ignoring 3rd stroke...
684 //
685 // Not sure what happen here and what's the best and cleanest solution to apply
686 // Calling calculateExactBounds/nonDefaultPixelArea multiple times fix the problem, except for 1st stroke
687 // for which an empty QRect() is still returned :-/
688 paintDevice->calculateExactBounds(true);
689 paintDevice->nonDefaultPixelArea();
690 paintDevice->calculateExactBounds(true);
691 paintDevice->nonDefaultPixelArea();
692 paintDevice->calculateExactBounds(true);
693
694 return paintDevice->nonDefaultPixelArea();
695}
696
698{
699 Q_EMIT sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect());
700}
701
703{
704 update(rect);
705}
706
708{
709 m_multiSurfaceState = newState;
710
711 if (newState.multiConfig.uiDisplayConfig() != m_displayConfig) {
713 update();
714 }
715}
716
718{
720
721 KisConfig cfg(true);
722
723 QScreen *screen = m_screenMigrationTracker->currentScreenSafe();
724 const int screenId = qApp->screens().indexOf(screen);
725
727 screenId,
730 assignNewSurfaceState(newState);
731}
732
733void KisScratchPad::paintEvent ( QPaintEvent * event ) {
734 if (!m_paintLayer) return;
735
736 QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect()));
737
738 QRect alignedImageRect =
739 imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth,
740 m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect();
741
742 QPointF offset = alignedImageRect.topLeft();
743
744 m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer, KisRenderPassFlag::None);
746
747 QImage image = projection->convertToQImage(m_displayConfig.profile,
748 alignedImageRect.x(),
749 alignedImageRect.y(),
750 alignedImageRect.width(),
751 alignedImageRect.height(),
754
755
756 QPainter gc(this);
757 gc.fillRect(event->rect(), m_checkBrush);
758
759 // if we scale down, it should use Smooth
760 // if we scale up, it should use Fast (nearest Neighbour) to show pixels
761 if (event->rect().width() < image.rect().width()) {
762 gc.setRenderHints(QPainter::SmoothPixmapTransform, true);
763 } else {
764 gc.setRenderHints(QPainter::SmoothPixmapTransform, false); // that will use NN
765 }
766
767 gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset));
768
769 QBrush brush(Qt::lightGray);
770 QPen pen(brush, 1, Qt::DotLine);
771 gc.setPen(pen);
772 if (m_cutoutOverlay.isValid()) {
773 gc.drawRect(m_cutoutOverlay);
774 }
775
776 if (!isEnabled()) {
777 QColor color(Qt::lightGray);
778 color.setAlphaF(0.5);
779 QBrush disabledBrush(color);
780 gc.fillRect(event->rect(), disabledBrush);
781 }
782 gc.end();
783}
784
785void KisScratchPad::resizeEvent( QResizeEvent *event) {
787 QWidget::resizeEvent(event);
788}
789
791{
792 if (m_helper->isRunning()) {
793 m_helper->endPaint();
794 }
795
797 setCursor(m_cursor);
798}
799
801 const QColor &defaultColor)
802{
803 m_resourceProvider = resourceProvider;
804
805 connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)),
806 SLOT(setOnScreenResolution(qreal,qreal)));
807 connect(this, SIGNAL(colorSelected(KoColor)),
808 m_resourceProvider, SLOT(slotSetFGColor(KoColor)));
809
811
812 setFillColor(defaultColor);
813
814 KisPaintDeviceSP paintDevice =
815 new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad");
816
817 m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice);
820
821 fillDefault();
822}
823
825{
826 m_cutoutOverlay = rc;
827}
828
833
835{
836 if (mode.toLower() == "painting") {
838 setCursor(m_cursor);
839 } else if (mode.toLower() == "panning") {
841 setCursor(Qt::OpenHandCursor);
842 } else if (mode.toLower() == "colorsampling") {
844 setCursor(m_colorSamplerCursor);
845 }
846}
847
849{
853
855 // link status updated AND set to True
856 // Then need to update scratchPad zoom to canvas zoom immediately
858 }
859 }
860}
861
866
868{
869 if (!m_paintLayer) return QImage();
871
872
873 QRect rc = widgetToDocument().mapRect(m_cutoutOverlay);
874 QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
875
876 QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(),
877 Qt::IgnoreAspectRatio,
878 Qt::SmoothTransformation);
879
880 return scaledImage;
881}
882
883void KisScratchPad::setPresetImage(const QImage& image)
884{
885 m_presetImage = image;
886}
887
888void KisScratchPad::paintCustomImage(const QImage& loadedImage)
889{
890 // this is 99% copied from the normal paintPresetImage()
891 // we don't want to save over the preset image, so we don't
892 // want to store it in the m_presetImage
893 if (!m_paintLayer) return;
895
896
897 QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
898 QRect imageRect(QPoint(), overlayRect.size());
899
900 QImage scaledImage = loadedImage.scaled(overlayRect.size(),
901 Qt::IgnoreAspectRatio,
902 Qt::SmoothTransformation);
903 KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
904 device->convertFromQImage(scaledImage, 0);
905
906 {
907 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
908 KisPainter painter(paintDevice);
909 painter.beginTransaction();
910 painter.bitBlt(overlayRect.topLeft(), device, imageRect);
911 painter.deleteTransaction();
912 }
913
914
915 update();
916 Q_EMIT contentChanged();
917}
918
920{
921 if (!m_paintLayer) return;
922
923 m_translateTransform.reset(); // image will be loaded at 0,0, so reset panning location
925
926 fillDefault(); // wipes out whatever was there before
927
928 QRect imageSize = image.rect();
930
931 KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
932 device->convertFromQImage(image, 0);
933
934 {
935 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
936 KisPainter painter(paintDevice);
937 painter.beginTransaction();
938 painter.bitBlt(imageSize.topLeft(), device, imageSize);
939 painter.deleteTransaction();
940 }
941
942 update();
943}
944
946{
947 const QRect paintingBounds = m_paintLayer.data()->exactBounds();
948 QImage imageData = m_paintLayer->paintDevice()->convertToQImage(0, paintingBounds.x(), paintingBounds.y(), paintingBounds.width(), paintingBounds.height(),
951 return imageData;
952}
953
955{
956 if (!m_paintLayer) return;
958
959
960 QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
961 QRect imageRect(QPoint(), overlayRect.size());
962
963 QImage scaledImage = m_presetImage.scaled(overlayRect.size(),
964 Qt::IgnoreAspectRatio,
965 Qt::SmoothTransformation);
966 KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
967 device->convertFromQImage(scaledImage, 0);
968
969 {
970 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
971 KisPainter painter(paintDevice);
972 painter.beginTransaction();
973 painter.bitBlt(overlayRect.topLeft(), device, imageRect);
974 painter.deleteTransaction();
975 }
976
977 update();
978 Q_EMIT contentChanged();
979}
980
982{
983 const int screenId = qApp->screens().indexOf(screen);
984
986 assignNewSurfaceState(newState);
987}
988
990{
991 if (!m_paintLayer) return;
993
994 {
995 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
996
997 KisTransaction t(paintDevice);
998 paintDevice->setDefaultPixel(m_defaultColor);
999 paintDevice->clear();
1000 t.end();
1001 }
1002
1003 update();
1004 Q_EMIT contentChanged();
1005}
1006
1008{
1009 if (!m_paintLayer) return;
1010 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1011
1012 QColor transQColor(0,0,0,0);
1013 KoColor transparentColor(transQColor, KoColorSpaceRegistry::instance()->rgb8());
1014 transparentColor.setOpacity(0.0);
1015
1016 {
1017 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1018
1019 KisTransaction t(paintDevice);
1020 paintDevice->setDefaultPixel(transparentColor);
1021 paintDevice->clear();
1022 t.end();
1023 }
1024
1025 update();
1026 Q_EMIT contentChanged();
1027}
1028
1029void KisScratchPad::setFillColor(QColor newColor)
1030{
1032}
1033
1034void KisScratchPad::fillGradient(const QPoint &gradientVectorStart,
1035 const QPoint &gradientVectorEnd,
1038 bool reverseGradient,
1039 bool dither)
1040{
1041 if (!m_paintLayer) return;
1042 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1043
1045 QRect gradientRect = widgetToDocument().mapRect(rect());
1046
1047
1048 {
1049 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1050 KisTransaction t(paintDevice);
1051
1052 paintDevice->clear();
1053
1054 KisGradientPainter painter(paintDevice);
1055 painter.setGradient(gradient);
1056 painter.setGradientShape(gradientShape);
1057 if (gradientVectorStart == gradientVectorEnd && gradientVectorStart == QPoint()) {
1058 // start & end are the same and are at origin: use default rect
1059 painter.paintGradient(gradientRect.topLeft(),
1060 gradientRect.bottomRight(),
1061 gradientRepeat,
1062 0.2,
1063 reverseGradient,
1064 gradientRect.left(), gradientRect.top(),
1065 gradientRect.width(), gradientRect.height(),
1066 dither);
1067 } else {
1068 painter.paintGradient(gradientVectorStart,
1069 gradientVectorEnd,
1070 gradientRepeat,
1071 0.2,
1072 reverseGradient,
1073 gradientRect.left(), gradientRect.top(),
1074 gradientRect.width(), gradientRect.height(),
1075 dither);
1076 }
1077 t.end();
1078 }
1079
1080 update();
1081 Q_EMIT contentChanged();
1082}
1083
1085{
1086 // default legacy method
1087 if (!m_paintLayer) return;
1088 QRect gradientRect = widgetToDocument().mapRect(rect());
1089 fillGradient(gradientRect.topLeft(),
1090 gradientRect.bottomRight(),
1093 false,
1094 false
1095 );
1096}
1097
1098void KisScratchPad::fillPattern(QTransform transform)
1099{
1100 if (!m_paintLayer) return;
1101 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1102
1104
1105 QRect patternRect = widgetToDocument().mapRect(rect());
1106 {
1107 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1108
1109 KisTransaction t(paintDevice);
1110
1111 paintDevice->clear();
1112
1113 KisFillPainter painter(paintDevice);
1114 painter.setPattern(pattern);
1115 painter.setWidth(patternRect.width());
1116 painter.setHeight(patternRect.height());
1117 painter.fillPattern(0, 0, paintDevice, transform);
1118
1119 t.end();
1120 }
1121
1122 update();
1123 Q_EMIT contentChanged();
1124}
1125
1127{
1128 if (!m_paintLayer) return;
1129 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1130
1131 {
1132 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1133 KisTransaction t(paintDevice);
1135 paintDevice->clear();
1136 t.end();
1137 }
1138
1139 update();
1140 Q_EMIT contentChanged();
1141}
1142
1144{
1145 if (!m_paintLayer) return;
1146 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1147
1148 {
1149 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1150 KisTransaction t(paintDevice);
1152 paintDevice->clear();
1153 t.end();
1154 }
1155
1156 update();
1157 Q_EMIT contentChanged();
1158}
1159
1160void KisScratchPad::fillDocument(bool fullContent)
1161{
1162 QRect sourceRect;
1163
1164 if (!m_paintLayer) return;
1165
1166 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1167
1168 if (fullContent) {
1169 sourceRect = QRect(0, 0, m_resourceProvider->currentImage()->width(), m_resourceProvider->currentImage()->height());
1170 } else {
1171 sourceRect = QRect(0, 0, paintDevice->exactBounds().width(), paintDevice->exactBounds().height());
1172 }
1173
1174 {
1175 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1176 KisPainter painter(paintDevice);
1177 painter.beginTransaction();
1178 painter.bitBlt(QPoint(0, 0), m_resourceProvider->currentImage()->projection(), sourceRect);
1179 painter.deleteTransaction();
1180 }
1181
1182 update();
1183 Q_EMIT contentChanged();
1184}
1185
1187{
1188 if (!m_paintLayer) return;
1189 fillDocument(false);
1190}
1191
1192void KisScratchPad::fillLayer(bool fullContent)
1193{
1194 QRect sourceRect;
1195
1196 if (!m_paintLayer) return;
1197
1198 KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
1199
1200 if (fullContent) {
1201 sourceRect = m_resourceProvider->currentNode()->exactBounds();
1202 } else {
1203 sourceRect = QRect(0, 0, paintDevice->exactBounds().width(), paintDevice->exactBounds().height());
1204 }
1205
1206 {
1207 KisUpdateSchedulerLockWithFeedback l(m_updateScheduler);
1208 KisPainter painter(paintDevice);
1209 painter.beginTransaction();
1210 painter.bitBlt(QPoint(0, 0), m_resourceProvider->currentNode()->projection(), sourceRect);
1211 painter.deleteTransaction();
1212 }
1213
1214 update();
1215 Q_EMIT contentChanged();
1216}
1217
1218#include "kis_scratch_pad.moc"
float value(const T *src, size_t ch)
#define KIS_DECLARE_ADAPTED_LOCK(Name, Adapter)
const quint8 OPACITY_OPAQUE_U8
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KoCanvasResourceProvider * resourceManager()
KoAbstractGradientSP currentGradient() const
static QImage createCheckersImage(qint32 checkSize=-1)
static KisConfigNotifier * instance()
void configChanged(void)
CanvasSurfaceMode canvasSurfaceColorSpaceManagementMode(bool defaultValue=false) const
qint32 checkSize(bool defaultValue=false) const
static QCursor load(const QString &cursorName, int hotspotX=-1, int hotspotY=-1)
KoColorConversionTransformation::ConversionFlags conversionFlags
static Options optionsFromKisConfig(const KisConfig &cfg)
const KoColorProfile * profile
KoColorConversionTransformation::Intent intent
void setWidth(int w)
void setHeight(int h)
void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice, QTransform patternTransform=QTransform())
KisPaintDeviceSP projection() const
qint32 width() const
qint32 height() const
KisDisplayConfig uiDisplayConfig() const
State createInitializingConfig(bool isCanvasOpenGL, int screenId, KisProofingConfigurationSP proofingConfig) const
State onConfigChanged(const State &oldState, int screenId, KisConfig::CanvasSurfaceMode surfaceMode, const KisDisplayConfig::Options &options) const
State onScreenChanged(const State &oldState, int screenId) const
State onGuiSurfaceFormatChanged(const State &oldState, const KoColorProfile *uiProfile) const
virtual void clear()
void setDefaultPixel(const KoColor &defPixel)
QRect nonDefaultPixelArea() const
void setDefaultBounds(KisDefaultBoundsBaseSP bounds)
QRect exactBounds() const
const KoColorSpace * colorSpace() const
QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags()) const
KisDefaultBoundsBaseSP defaultBounds() const
void convertFromQImage(const QImage &image, const KoColorProfile *profile, qint32 offsetX=0, qint32 offsetY=0)
QRect calculateExactBounds(bool nonDefaultOnly) const
void deleteTransaction()
void beginTransaction(const KUndo2MagicString &transactionName=KUndo2MagicString(), int timedID=-1)
Begin an undoable paint operation.
void setGradient(const KoAbstractGradientSP gradient)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
void setPattern(const KoPatternSP pattern)
Set the current pattern.
static KisPlatformPluginInterfaceFactory * instance()
void sigRootSurfaceProfileChanged(const KoColorProfile *profile) const
KisScratchPadDefaultBounds(KisScratchPad *scratchPad)
QRect bounds() const override
void * sourceCookie() const override
void setWidgetToDocumentTransform(const QTransform &transform)
KisScratchPadNodeListener(KisScratchPad *scratchPad)
void requestProjectionUpdate(KisNode *node, const QVector< QRect > &rects, KisProjectionUpdateFlags flags) override
KisScratchPadPaintingInformationBuilder(KisScratchPad *scratchPad)
QPointF imageToView(const QPointF &point) override
void fillPattern(QTransform transform)
void setPresetImage(const QImage &image)
void resizeEvent(QResizeEvent *event) override
bool canvasZoomLink()
return True if the scratchpad zoom level stay in sync with canvas
qreal scaleY()
return current scale Y applied on scratchpad (whatever the zoom source is - canvas zoom or set manual...
QImage cutoutOverlay() const
return the contents of the area under the cutoutOverlay rect
void setCanvasZoomLink(bool value)
should the scratchpad zoom level stay in sync with canvas
void paintEvent(QPaintEvent *event) override
void fillLayer(bool fullContent)
Fill the area with what is on your current canvas.
QRect imageBounds() const
KisScratchPadEventFilter * m_eventFilter
void imageUpdated(const QRect &rect)
void endStroke(KoPointerEvent *event)
void pointerMove(KoPointerEvent *event)
void sigUpdateCanvas(const QRect &rect)
void scaleReset()
reset scale value (to 1.0) and reinit position
void scaleToFit()
calculate and apply scale to fit content in viewport
KisCanvasResourceProvider * m_resourceProvider
KisUndoStore * m_undoStore
void loadScratchpadImage(QImage image)
bool setScaleImpl(qreal scaleX, qreal scaleY)
void beginStroke(KoPointerEvent *event)
KisPaintingInformationBuilder * m_infoBuilder
KisUpdateScheduler * m_updateScheduler
QCursor m_colorSamplerCursor
QTransform documentToWidget() const
QTransform m_scaleTransform
KisNodeGraphListener * m_nodeListener
void viewportChanged(const QRect rect)
signal is emitted when scratchpad viewport has been modified (pan, zoom)
void slotUpdateCanvas(const QRect &rect)
KisDisplayConfig m_displayConfig
void colorSelected(const KoColor &color)
KisPaintLayerSP m_paintLayer
Mode modeFromButton(Qt::MouseButton button) const
void doStroke(KoPointerEvent *event)
void sample(KoPointerEvent *event)
bool setScale(qreal scaleX, qreal scaleY)
allow to manually set scratchpad scale when NOT linked to canvas zoom
void pointerPress(KoPointerEvent *event)
void setFillColor(QColor newColor)
void beginPan(KoPointerEvent *event)
void wheelDelta(QWheelEvent *event)
void panTo(qint32 x, qint32 y)
pan scratchpad content to given position
void endPan(KoPointerEvent *event)
void setModeType(QString modeName)
change the mode explicitly to paint, mix, or pan
void pointerRelease(KoPointerEvent *event)
qreal scaleX()
return current scale X applied on scratchpad (whatever the zoom source is - canvas zoom or set manual...
QRect viewportBounds() const
KisPostExecutionUndoAdapter * m_undoAdapter
void setCutoutOverlayRect(const QRect &rc)
set the specified rect as the area taken for
QTransform widgetToDocument() const
KisScratchPad(QWidget *parent=0)
void panCenter()
pan scratchpad content to center content in viewport
friend class KisScratchPadPaintingInformationBuilder
void slotScreenChanged(QScreen *screen)
QImage copyScratchpadImageData()
QScopedPointer< KisToolFreehandHelper > m_helper
void scaleChanged(qreal scale)
signal is emitted when scratchpad scale is changed (from zoom canvas or manually)
QRect contentBounds() const
QTransform m_translateTransform
void setupScratchPad(KisCanvasResourceProvider *resourceProvider, const QColor &defaultColor)
void assignNewSurfaceState(const KisMultiSurfaceStateManager::State &newState)
void contentChanged()
signal is emitted when scratchpad content is changed (stroke or fill)
KisMultiSurfaceStateManager m_multiSurfaceStateManager
friend KisScratchPadEventFilter
~KisScratchPad() override
void doPan(KoPointerEvent *event)
void setModeManually(bool value)
QScopedPointer< KisScreenMigrationTracker > m_screenMigrationTracker
KisMultiSurfaceStateManager::State m_multiSurfaceState
void paintCustomImage(const QImage &loadedImage)
void setOnScreenResolution(qreal scaleX, qreal scaleY)
void sigScreenChanged(QScreen *screen)
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
const KoColorSpace * colorSpace() const
return the current colorSpace
Definition KoColor.h:82
Qt::MouseButton button() const
return button pressed (see QMouseEvent::button());
QPointF point
The point in document coordinates.
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define BORDER_SIZE(scale)
QString button(const QWheelEvent &ev)
bool sampleColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos, KoColor const *const blendColor, int radius, int blend, bool pure)
virtual KisPaintDeviceSP projection() const =0
virtual QRect exactBounds() const
bool paintGradient(const QPointF &gradientVectorStart, const QPointF &gradientVectorEnd, enumGradientRepeat repeat, double antiAliasThreshold, bool reverseGradient, qint32 startx, qint32 starty, qint32 width, qint32 height, bool useDithering=false)
void setGradientShape(enumGradientShape shape)
KisPaintDeviceSP projection() const override
Definition kis_layer.cc:820
KisLayerProjectionPlaneSP projectionPlane
Definition kis_layer.cc:174
virtual void requestProjectionUpdate(KisNode *node, const QVector< QRect > &rects, KisProjectionUpdateFlags flags)
void setGraphListener(KisNodeGraphListener *graphListener)
Definition kis_node.cpp:289
QRect exactBounds() const override
KisPaintDeviceSP paintDevice
static KoColorSpaceRegistry * instance()