Changeset View
Changeset View
Standalone View
Standalone View
plugins/dockers/overview/overviewwidget.cc
Show All 16 Lines | |||||
17 | */ | 17 | */ | ||
18 | 18 | | |||
19 | 19 | | |||
20 | #include "overviewwidget.h" | 20 | #include "overviewwidget.h" | ||
21 | 21 | | |||
22 | #include <QMouseEvent> | 22 | #include <QMouseEvent> | ||
23 | #include <QPainter> | 23 | #include <QPainter> | ||
24 | #include <QCursor> | 24 | #include <QCursor> | ||
25 | #include <QMutex> | ||||
25 | 26 | | |||
26 | #include <KoCanvasController.h> | 27 | #include <KoCanvasController.h> | ||
27 | #include <KoZoomController.h> | 28 | #include <KoZoomController.h> | ||
28 | 29 | | |||
29 | #include <kis_canvas2.h> | 30 | #include <kis_canvas2.h> | ||
30 | #include <KisViewManager.h> | 31 | #include <KisViewManager.h> | ||
31 | #include <kis_image.h> | 32 | #include <kis_image.h> | ||
32 | #include <kis_paint_device.h> | 33 | #include <kis_paint_device.h> | ||
33 | #include <kis_signal_compressor.h> | 34 | #include <kis_signal_compressor.h> | ||
34 | #include <kis_config.h> | 35 | #include <kis_config.h> | ||
36 | #include "kis_idle_watcher.h" | ||||
37 | #include "krita_utils.h" | ||||
38 | #include "kis_painter.h" | ||||
39 | #include <KoUpdater.h> | ||||
40 | #include "kis_transform_worker.h" | ||||
41 | #include "kis_filter_strategy.h" | ||||
42 | #include <KoColorSpaceRegistry.h> | ||||
43 | | ||||
44 | const qreal oversample = 2.; | ||||
45 | const int thumbnailTileDim = 128; | ||||
46 | | ||||
47 | struct OverviewThumbnailStrokeStrategy::Private { | ||||
48 | | ||||
49 | class InitData : public KisStrokeJobData | ||||
50 | { | ||||
51 | public: | ||||
52 | InitData(KisPaintDeviceSP _device) | ||||
53 | : KisStrokeJobData(SEQUENTIAL), | ||||
54 | device(_device) | ||||
55 | {} | ||||
56 | | ||||
57 | KisPaintDeviceSP device; | ||||
58 | }; | ||||
59 | | ||||
60 | class ProcessData : public KisStrokeJobData | ||||
61 | { | ||||
62 | public: | ||||
63 | ProcessData(KisPaintDeviceSP _dev, KisPaintDeviceSP _thumbDev, const QSize& _thumbnailSize, const QRect &_rect) | ||||
64 | : KisStrokeJobData(CONCURRENT), | ||||
65 | dev(_dev), thumbDev(_thumbDev), thumbnailSize(_thumbnailSize), tileRect(_rect) | ||||
66 | {} | ||||
67 | | ||||
68 | KisPaintDeviceSP dev; | ||||
69 | KisPaintDeviceSP thumbDev; | ||||
70 | QSize thumbnailSize; | ||||
71 | QRect tileRect; | ||||
72 | }; | ||||
73 | class FinishProcessing : public KisStrokeJobData | ||||
74 | { | ||||
75 | public: | ||||
76 | FinishProcessing(KisPaintDeviceSP _thumbDev) | ||||
77 | : KisStrokeJobData(SEQUENTIAL), | ||||
78 | thumbDev(_thumbDev) | ||||
79 | {} | ||||
80 | KisPaintDeviceSP thumbDev; | ||||
81 | }; | ||||
82 | }; | ||||
35 | 83 | | |||
36 | OverviewWidget::OverviewWidget(QWidget * parent) | 84 | OverviewWidget::OverviewWidget(QWidget * parent) | ||
37 | : QWidget(parent) | 85 | : QWidget(parent) | ||
38 | , m_compressor(new KisSignalCompressor(500, KisSignalCompressor::POSTPONE, this)) | | |||
39 | , m_canvas(0) | 86 | , m_canvas(0) | ||
40 | , m_dragging(false) | 87 | , m_dragging(false) | ||
88 | , m_imageIdleWatcher(250) | ||||
41 | { | 89 | { | ||
42 | setMouseTracking(true); | 90 | setMouseTracking(true); | ||
43 | connect(m_compressor, SIGNAL(timeout()), SLOT(startUpdateCanvasProjection())); | | |||
44 | KisConfig cfg; | 91 | KisConfig cfg; | ||
45 | QRgb c = cfg.readEntry("OverviewWidgetColor", 0xFF454C); | 92 | QRgb c = cfg.readEntry("OverviewWidgetColor", 0xFF454C); | ||
46 | m_outlineColor = QColor(c); | 93 | m_outlineColor = QColor(c); | ||
47 | } | 94 | } | ||
48 | 95 | | |||
49 | OverviewWidget::~OverviewWidget() | 96 | OverviewWidget::~OverviewWidget() | ||
50 | { | 97 | { | ||
51 | } | 98 | } | ||
52 | 99 | | |||
53 | void OverviewWidget::setCanvas(KoCanvasBase * canvas) | 100 | void OverviewWidget::setCanvas(KoCanvasBase * canvas) | ||
54 | { | 101 | { | ||
55 | if (m_canvas) { | 102 | if (m_canvas) { | ||
56 | m_canvas->image()->disconnect(this); | 103 | m_canvas->image()->disconnect(this); | ||
57 | } | 104 | } | ||
58 | 105 | | |||
59 | m_canvas = dynamic_cast<KisCanvas2*>(canvas); | 106 | m_canvas = dynamic_cast<KisCanvas2*>(canvas); | ||
60 | 107 | | |||
61 | if (m_canvas) { | 108 | if (m_canvas) { | ||
62 | connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)), m_compressor, SLOT(start()), Qt::UniqueConnection); | 109 | m_imageIdleWatcher.setTrackedImage(m_canvas->image()); | ||
63 | connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)), m_compressor, SLOT(start()), Qt::UniqueConnection); | 110 | | ||
111 | connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &OverviewWidget::generateThumbnail); | ||||
112 | | ||||
113 | connect(m_canvas->image(), SIGNAL(sigImageUpdated(QRect)),SLOT(startUpdateCanvasProjection())); | ||||
114 | connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)),SLOT(startUpdateCanvasProjection())); | ||||
115 | | ||||
64 | connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(update()), Qt::UniqueConnection); | 116 | connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(update()), Qt::UniqueConnection); | ||
65 | m_compressor->start(); | 117 | generateThumbnail(); | ||
66 | } | 118 | } | ||
67 | } | 119 | } | ||
68 | 120 | | |||
69 | QSize OverviewWidget::calculatePreviewSize() | 121 | QSize OverviewWidget::calculatePreviewSize() | ||
70 | { | 122 | { | ||
71 | QSize imageSize(m_canvas->image()->bounds().size()); | 123 | QSize imageSize(m_canvas->image()->bounds().size()); | ||
72 | imageSize.scale(size(), Qt::KeepAspectRatio); | 124 | imageSize.scale(size(), Qt::KeepAspectRatio); | ||
73 | return imageSize; | 125 | return imageSize; | ||
74 | } | 126 | } | ||
75 | 127 | | |||
76 | QPointF OverviewWidget::previewOrigin() | 128 | QPointF OverviewWidget::previewOrigin() | ||
77 | { | 129 | { | ||
78 | return QPointF((width() - m_pixmap.width())/2.0f, (height() - m_pixmap.height())/2.0f); | 130 | return QPointF((width() - m_pixmap.width()) / 2.0f, (height() - m_pixmap.height()) / 2.0f); | ||
79 | } | 131 | } | ||
80 | 132 | | |||
81 | QPolygonF OverviewWidget::previewPolygon() | 133 | QPolygonF OverviewWidget::previewPolygon() | ||
82 | { | 134 | { | ||
83 | if (m_canvas) { | 135 | if (m_canvas) { | ||
84 | const KisCoordinatesConverter* converter = m_canvas->coordinatesConverter(); | 136 | const KisCoordinatesConverter* converter = m_canvas->coordinatesConverter(); | ||
85 | QPolygonF canvasPoly = QPolygonF(QRectF(m_canvas->canvasWidget()->rect())); | 137 | QPolygonF canvasPoly = QPolygonF(QRectF(m_canvas->canvasWidget()->rect())); | ||
86 | QPolygonF imagePoly = converter->widgetToImage<QPolygonF>(canvasPoly); | 138 | QPolygonF imagePoly = converter->widgetToImage<QPolygonF>(canvasPoly); | ||
87 | 139 | | |||
88 | QTransform imageToPreview = imageToPreviewTransform(); | 140 | QTransform imageToPreview = imageToPreviewTransform(); | ||
89 | 141 | | |||
90 | return imageToPreview.map(imagePoly); | 142 | return imageToPreview.map(imagePoly); | ||
91 | } | 143 | } | ||
92 | return QPolygonF(); | 144 | return QPolygonF(); | ||
93 | } | 145 | } | ||
94 | 146 | | |||
95 | QTransform OverviewWidget::imageToPreviewTransform() | 147 | QTransform OverviewWidget::imageToPreviewTransform() | ||
96 | { | 148 | { | ||
97 | QTransform imageToPreview; | 149 | QTransform imageToPreview; | ||
98 | imageToPreview.scale(calculatePreviewSize().width()/(float)m_canvas->image()->width(), | 150 | imageToPreview.scale(calculatePreviewSize().width() / (float)m_canvas->image()->width(), | ||
99 | calculatePreviewSize().height()/(float)m_canvas->image()->height()); | 151 | calculatePreviewSize().height() / (float)m_canvas->image()->height()); | ||
100 | return imageToPreview; | 152 | return imageToPreview; | ||
101 | } | 153 | } | ||
102 | 154 | | |||
103 | void OverviewWidget::startUpdateCanvasProjection() | 155 | void OverviewWidget::startUpdateCanvasProjection() | ||
104 | { | 156 | { | ||
105 | if (!m_canvas) return; | 157 | m_imageIdleWatcher.startCountdown(); | ||
106 | | ||||
107 | KisImageSP image = m_canvas->image(); | | |||
108 | QSize previewSize = calculatePreviewSize(); | | |||
109 | | ||||
110 | if (isVisible() && previewSize.isValid()) { | | |||
111 | QImage img = | | |||
112 | image->projection()-> | | |||
113 | createThumbnail(previewSize.width(), previewSize.height(), image->bounds()); | | |||
114 | | ||||
115 | m_pixmap = QPixmap::fromImage(img); | | |||
116 | } | | |||
117 | update(); | | |||
118 | } | 158 | } | ||
119 | 159 | | |||
120 | void OverviewWidget::showEvent(QShowEvent *event) | 160 | void OverviewWidget::showEvent(QShowEvent *event) | ||
121 | { | 161 | { | ||
122 | Q_UNUSED(event); | 162 | Q_UNUSED(event); | ||
123 | m_compressor->start(); | 163 | m_imageIdleWatcher.startCountdown(); | ||
124 | } | 164 | } | ||
125 | 165 | | |||
126 | void OverviewWidget::resizeEvent(QResizeEvent *event) | 166 | void OverviewWidget::resizeEvent(QResizeEvent *event) | ||
127 | { | 167 | { | ||
128 | Q_UNUSED(event); | 168 | Q_UNUSED(event); | ||
129 | if (m_canvas) { | 169 | if (m_canvas) { | ||
130 | if (!m_pixmap.isNull()) { | 170 | if (!m_pixmap.isNull()) { | ||
131 | QSize newSize = calculatePreviewSize(); | 171 | QSize newSize = calculatePreviewSize(); | ||
132 | m_pixmap = m_pixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); | 172 | m_pixmap = m_pixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); | ||
133 | } | 173 | } | ||
134 | m_compressor->start(); | 174 | m_imageIdleWatcher.startCountdown(); | ||
135 | } | 175 | } | ||
136 | } | 176 | } | ||
137 | 177 | | |||
138 | void OverviewWidget::mousePressEvent(QMouseEvent* event) | 178 | void OverviewWidget::mousePressEvent(QMouseEvent* event) | ||
139 | { | 179 | { | ||
140 | if (m_canvas) { | 180 | if (m_canvas) { | ||
141 | QPointF previewPos = event->pos() - previewOrigin(); | 181 | QPointF previewPos = event->pos() - previewOrigin(); | ||
142 | 182 | | |||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Line(s) | 222 | { | |||
184 | 224 | | |||
185 | if (delta > 0) { | 225 | if (delta > 0) { | ||
186 | m_canvas->viewManager()->zoomController()->zoomAction()->zoomIn(); | 226 | m_canvas->viewManager()->zoomController()->zoomAction()->zoomIn(); | ||
187 | } else { | 227 | } else { | ||
188 | m_canvas->viewManager()->zoomController()->zoomAction()->zoomOut(); | 228 | m_canvas->viewManager()->zoomController()->zoomAction()->zoomOut(); | ||
189 | } | 229 | } | ||
190 | } | 230 | } | ||
191 | 231 | | |||
232 | void OverviewWidget::generateThumbnail() | ||||
233 | { | ||||
234 | if (isVisible()) { | ||||
235 | QMutexLocker locker(&mutex); | ||||
236 | if (m_canvas) { | ||||
237 | QSize previewSize = calculatePreviewSize(); | ||||
238 | if(previewSize.isValid()){ | ||||
239 | KisImageSP image = m_canvas->image(); | ||||
240 | | ||||
241 | if (!strokeId.isNull()) { | ||||
242 | image->cancelStroke(strokeId); | ||||
243 | image->waitForDone(); | ||||
244 | } | ||||
245 | | ||||
246 | OverviewThumbnailStrokeStrategy* stroke = new OverviewThumbnailStrokeStrategy(image); | ||||
247 | connect(stroke, SIGNAL(thumbnailUpdated(QImage)), this, SLOT(updateThumbnail(QImage))); | ||||
248 | | ||||
249 | strokeId = image->startStroke(stroke); | ||||
250 | KisPaintDeviceSP dev = image->projection(); | ||||
251 | KisPaintDeviceSP thumbDev = new KisPaintDevice(dev->colorSpace()); | ||||
252 | | ||||
253 | //creating a special stroke that computes thumbnail image in small chunks that can be quickly interrupted | ||||
254 | //if user starts painting | ||||
255 | QList<KisStrokeJobData*> jobs = OverviewThumbnailStrokeStrategy::createJobsData(dev, image->bounds(), thumbDev, previewSize); | ||||
256 | | ||||
257 | Q_FOREACH (KisStrokeJobData *jd, jobs) { | ||||
258 | image->addJob(strokeId, jd); | ||||
259 | } | ||||
260 | image->endStroke(strokeId); | ||||
261 | } | ||||
262 | } | ||||
263 | } | ||||
264 | } | ||||
265 | | ||||
266 | void OverviewWidget::updateThumbnail(QImage pixmap) | ||||
267 | { | ||||
268 | m_pixmap = QPixmap::fromImage(pixmap); | ||||
269 | update(); | ||||
270 | } | ||||
271 | | ||||
192 | 272 | | |||
193 | void OverviewWidget::paintEvent(QPaintEvent* event) | 273 | void OverviewWidget::paintEvent(QPaintEvent* event) | ||
194 | { | 274 | { | ||
195 | QWidget::paintEvent(event); | 275 | QWidget::paintEvent(event); | ||
196 | 276 | | |||
197 | if (m_canvas) { | 277 | if (m_canvas) { | ||
198 | QPainter p(this); | 278 | QPainter p(this); | ||
199 | p.translate(previewOrigin()); | 279 | p.translate(previewOrigin()); | ||
Show All 12 Lines | |||||
212 | p.drawPolygon(outline.intersected(previewPolygon())); | 292 | p.drawPolygon(outline.intersected(previewPolygon())); | ||
213 | 293 | | |||
214 | pen.setStyle(Qt::SolidLine); | 294 | pen.setStyle(Qt::SolidLine); | ||
215 | p.setPen(pen); | 295 | p.setPen(pen); | ||
216 | p.drawPolygon(previewPolygon()); | 296 | p.drawPolygon(previewPolygon()); | ||
217 | } | 297 | } | ||
218 | } | 298 | } | ||
219 | 299 | | |||
300 | OverviewThumbnailStrokeStrategy::OverviewThumbnailStrokeStrategy(KisImageWSP image) | ||||
301 | : KisSimpleStrokeStrategy("OverviewThumbnail"), m_image(image) | ||||
302 | { | ||||
303 | enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); | ||||
304 | enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); | ||||
305 | //enableJob(KisSimpleStrokeStrategy::JOB_FINISH); | ||||
306 | enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); | ||||
307 | | ||||
308 | setRequestsOtherStrokesToEnd(false); | ||||
309 | setClearsRedoOnStart(false); | ||||
310 | setCanForgetAboutMe(true); | ||||
311 | } | ||||
312 | | ||||
313 | QList<KisStrokeJobData *> OverviewThumbnailStrokeStrategy::createJobsData(KisPaintDeviceSP dev, const QRect& imageRect, KisPaintDeviceSP thumbDev, const QSize& thumbnailSize) | ||||
314 | { | ||||
315 | QSize thumbnailOversampledSize = oversample * thumbnailSize; | ||||
316 | | ||||
317 | if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { | ||||
318 | thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); | ||||
319 | } | ||||
320 | | ||||
321 | QVector<QRect> tileRects = KritaUtils::splitRectIntoPatches(QRect(QPoint(0, 0), thumbnailOversampledSize), QSize(thumbnailTileDim, thumbnailTileDim)); | ||||
322 | QList<KisStrokeJobData*> jobsData; | ||||
323 | | ||||
324 | Q_FOREACH (const QRect &tileRectangle, tileRects) { | ||||
325 | jobsData << new OverviewThumbnailStrokeStrategy::Private::ProcessData(dev, thumbDev, thumbnailOversampledSize, tileRectangle); | ||||
326 | } | ||||
327 | jobsData << new OverviewThumbnailStrokeStrategy::Private::FinishProcessing(thumbDev); | ||||
328 | | ||||
329 | return jobsData; | ||||
330 | } | ||||
331 | | ||||
332 | OverviewThumbnailStrokeStrategy::~OverviewThumbnailStrokeStrategy() | ||||
333 | { | ||||
334 | } | ||||
335 | | ||||
336 | | ||||
337 | void OverviewThumbnailStrokeStrategy::initStrokeCallback() | ||||
338 | { | ||||
339 | } | ||||
340 | | ||||
341 | void OverviewThumbnailStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) | ||||
342 | { | ||||
343 | Private::ProcessData *d_pd = dynamic_cast<Private::ProcessData*>(data); | ||||
344 | if (d_pd) { | ||||
345 | //we aren't going to use oversample capability of createThumbnailDevice because it recomputes exact bounds for each small patch, which is | ||||
346 | //slow. We'll handle scaling separately. | ||||
347 | KisPaintDeviceSP thumbnailTile = d_pd->dev->createThumbnailDeviceOversampled(d_pd->thumbnailSize.width(), d_pd->thumbnailSize.height(), 1, m_image->bounds(), d_pd->tileRect); | ||||
348 | { | ||||
349 | QMutexLocker locker(&m_thumbnailMergeMutex); | ||||
350 | KisPainter gc(d_pd->thumbDev); | ||||
351 | gc.bitBlt(QPoint(d_pd->tileRect.x(), d_pd->tileRect.y()), thumbnailTile, d_pd->tileRect); | ||||
352 | } | ||||
353 | return; | ||||
354 | } | ||||
355 | | ||||
356 | | ||||
357 | Private::FinishProcessing *d_fp = dynamic_cast<Private::FinishProcessing*>(data); | ||||
358 | if (d_fp) { | ||||
359 | QImage overviewImage; | ||||
360 | | ||||
361 | KoDummyUpdater updater; | ||||
362 | KisTransformWorker worker(d_fp->thumbDev, 1 / oversample, 1 / oversample, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, | ||||
363 | &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); | ||||
364 | worker.run(); | ||||
365 | | ||||
366 | overviewImage = d_fp->thumbDev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile()); | ||||
367 | emit thumbnailUpdated(overviewImage); | ||||
368 | return; | ||||
369 | } | ||||
370 | } | ||||
371 | | ||||
372 | void OverviewThumbnailStrokeStrategy::finishStrokeCallback() | ||||
373 | { | ||||
374 | } | ||||
375 | | ||||
376 | void OverviewThumbnailStrokeStrategy::cancelStrokeCallback() | ||||
377 | { | ||||
378 | } |