Remove "All rights reserved" line from license headers.
[profile/ivi/qtdeclarative.git] / src / quick / items / context2d / qquickcontext2dtexture.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickcontext2dtexture_p.h"
43 #include "qquickcontext2dtile_p.h"
44 #include "qquickcanvasitem_p.h"
45 #include <private/qquickitem_p.h>
46 #include <QtQuick/private/qsgtexture_p.h>
47 #include "qquickcontext2dcommandbuffer_p.h"
48 #include <QOpenGLPaintDevice>
49
50 #include <QOpenGLFramebufferObject>
51 #include <QOpenGLFramebufferObjectFormat>
52 #include <QtCore/QThread>
53
54 #define QT_MINIMUM_FBO_SIZE 64
55
56 static inline int qt_next_power_of_two(int v)
57 {
58     v--;
59     v |= v >> 1;
60     v |= v >> 2;
61     v |= v >> 4;
62     v |= v >> 8;
63     v |= v >> 16;
64     ++v;
65     return v;
66 }
67
68
69 Q_GLOBAL_STATIC(QThread, globalCanvasThreadRenderInstance)
70
71
72 QQuickContext2DTexture::QQuickContext2DTexture()
73     : QSGDynamicTexture()
74     , m_context(0)
75     , m_item(0)
76     , m_canvasSize(QSize(1, 1))
77     , m_tileSize(QSize(1, 1))
78     , m_canvasWindow(QRect(0, 0, 1, 1))
79     , m_dirtyCanvas(false)
80     , m_dirtyTexture(false)
81     , m_threadRendering(false)
82     , m_smooth(false)
83     , m_tiledCanvas(false)
84     , m_doGrabImage(false)
85     , m_painting(false)
86 {
87 }
88
89 QQuickContext2DTexture::~QQuickContext2DTexture()
90 {
91    clearTiles();
92 }
93
94 QSize QQuickContext2DTexture::textureSize() const
95 {
96     return m_canvasWindow.size();
97 }
98
99 void QQuickContext2DTexture::markDirtyTexture()
100 {
101     lock();
102     m_dirtyTexture = true;
103     unlock();
104     emit textureChanged();
105 }
106
107 bool QQuickContext2DTexture::setCanvasSize(const QSize &size)
108 {
109     if (m_canvasSize != size) {
110         m_canvasSize = size;
111         m_dirtyCanvas = true;
112         return true;
113     }
114     return false;
115 }
116
117 bool QQuickContext2DTexture::setTileSize(const QSize &size)
118 {
119     if (m_tileSize != size) {
120         m_tileSize = size;
121         m_dirtyCanvas = true;
122         return true;
123     }
124     return false;
125 }
126
127 void QQuickContext2DTexture::setSmooth(bool smooth)
128 {
129     m_smooth = smooth;
130 }
131
132 void QQuickContext2DTexture::setItem(QQuickCanvasItem* item)
133 {
134     if (!item) {
135         lock();
136         m_item = 0;
137         m_context = 0;
138         unlock();
139         wake();
140     } else if (m_item != item) {
141         lock();
142         m_item = item;
143         m_context = item->context();
144         m_state = m_context->state;
145         unlock();
146         connect(this, SIGNAL(textureChanged()), m_item, SIGNAL(painted()));
147     }
148 }
149
150 bool QQuickContext2DTexture::setCanvasWindow(const QRect& r)
151 {
152     if (m_canvasWindow != r) {
153         m_canvasWindow = r;
154         return true;
155     }
156     return false;
157 }
158
159 bool QQuickContext2DTexture::setDirtyRect(const QRect &r)
160 {
161     bool doDirty = false;
162     if (m_tiledCanvas) {
163         foreach (QQuickContext2DTile* t, m_tiles) {
164             bool dirty = t->rect().intersected(r).isValid();
165             t->markDirty(dirty);
166             if (dirty)
167                 doDirty = true;
168         }
169     } else {
170         doDirty = m_canvasWindow.intersected(r).isValid();
171     }
172     return doDirty;
173 }
174
175 void QQuickContext2DTexture::canvasChanged(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth)
176 {
177     lock();
178
179     QSize ts = tileSize;
180     if (ts.width() > canvasSize.width())
181         ts.setWidth(canvasSize.width());
182
183     if (ts.height() > canvasSize.height())
184         ts.setHeight(canvasSize.height());
185
186     setCanvasSize(canvasSize);
187     setTileSize(ts);
188
189     if (canvasSize == canvasWindow.size()) {
190         m_tiledCanvas = false;
191         m_dirtyCanvas = false;
192     } else {
193         m_tiledCanvas = true;
194     }
195
196     bool doDirty = false;
197     if (dirtyRect.isValid())
198         doDirty = setDirtyRect(dirtyRect);
199
200     bool windowChanged = setCanvasWindow(canvasWindow);
201     if (windowChanged || doDirty) {
202         if (m_threadRendering)
203             QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
204         else if (supportDirectRendering()) {
205            QMetaObject::invokeMethod(this, "paint", Qt::DirectConnection);
206         }
207     }
208
209     setSmooth(smooth);
210     unlock();
211 }
212
213 void QQuickContext2DTexture::paintWithoutTiles()
214 {
215     QQuickContext2DCommandBuffer* ccb = m_context->buffer();
216
217     if (ccb->isEmpty() && m_threadRendering && !m_doGrabImage) {
218         lock();
219         if (m_item)
220             QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, QRectF(0, 0, m_canvasSize.width(), m_canvasSize.height())));
221         wait();
222         unlock();
223     }
224     if (ccb->isEmpty()) {
225         return;
226     }
227
228     QPaintDevice* device = beginPainting();
229     if (!device) {
230         endPainting();
231         return;
232     }
233
234     QPainter p;
235     p.begin(device);
236     if (m_smooth)
237         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
238                                | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
239     else
240         p.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing
241                                  | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, false);
242     p.setCompositionMode(QPainter::CompositionMode_SourceOver);
243     ccb->replay(&p, m_state);
244
245     ccb->clear();
246     markDirtyTexture();
247     endPainting();
248 }
249
250 bool QQuickContext2DTexture::canvasDestroyed()
251 {
252     bool noCanvas = false;
253     lock();
254     noCanvas = m_item == 0;
255     unlock();
256     return noCanvas;
257 }
258
259 void QQuickContext2DTexture::paint()
260 {
261     if (canvasDestroyed())
262         return;
263
264     if (!m_tiledCanvas) {
265         paintWithoutTiles();
266     } else {
267         lock();
268         QRect tiledRegion = createTiles(m_canvasWindow.intersected(QRect(QPoint(0, 0), m_canvasSize)));
269         unlock();
270
271         if (!tiledRegion.isEmpty()) {
272             if (m_threadRendering && !m_doGrabImage) {
273                 QRect dirtyRect;
274
275                 lock();
276                 foreach (QQuickContext2DTile* tile, m_tiles) {
277                     if (tile->dirty()) {
278                         if (dirtyRect.isEmpty())
279                             dirtyRect = tile->rect();
280                         else
281                             dirtyRect |= tile->rect();
282                     }
283                 }
284                 unlock();
285
286                 if (dirtyRect.isValid()) {
287                     lock();
288                     if (m_item)
289                         QMetaObject::invokeMethod(m_item, "_doPainting", Qt::QueuedConnection, Q_ARG(QRectF, dirtyRect));
290                     wait();
291                     unlock();
292                 }
293             }
294
295             if (beginPainting()) {
296                 QQuickContext2D::State oldState = m_state;
297                 QQuickContext2DCommandBuffer* ccb = m_context->buffer();
298                 foreach (QQuickContext2DTile* tile, m_tiles) {
299                     bool dirtyTile = false, dirtyCanvas = false, smooth = false;
300
301                     lock();
302                     dirtyTile = tile->dirty();
303                     smooth = m_smooth;
304                     dirtyCanvas  = m_dirtyCanvas;
305                     unlock();
306
307                     //canvas size or tile size may change during painting tiles
308                     if (dirtyCanvas) {
309                         if (m_threadRendering)
310                             QMetaObject::invokeMethod(this, "paint", Qt::QueuedConnection);
311                         endPainting();
312                         return;
313                     } else if (dirtyTile) {
314                         ccb->replay(tile->createPainter(smooth), oldState);
315                         tile->drawFinished();
316                         lock();
317                         tile->markDirty(false);
318                         unlock();
319                     }
320
321                     compositeTile(tile);
322                 }
323                 ccb->clear();
324                 endPainting();
325                 m_state = oldState;
326                 markDirtyTexture();
327             }
328         }
329     }
330 }
331
332 QRect QQuickContext2DTexture::tiledRect(const QRectF& window, const QSize& tileSize)
333 {
334     if (window.isEmpty())
335         return QRect();
336
337     const int tw = tileSize.width();
338     const int th = tileSize.height();
339     const int h1 = window.left() / tw;
340     const int v1 = window.top() / th;
341
342     const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
343     const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
344
345     return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
346 }
347
348 QRect QQuickContext2DTexture::createTiles(const QRect& window)
349 {
350     QList<QQuickContext2DTile*> oldTiles = m_tiles;
351     m_tiles.clear();
352
353     if (window.isEmpty()) {
354         m_dirtyCanvas = false;
355         return QRect();
356     }
357
358     QRect r = tiledRect(window, m_tileSize);
359
360     const int tw = m_tileSize.width();
361     const int th = m_tileSize.height();
362     const int h1 = window.left() / tw;
363     const int v1 = window.top() / th;
364
365
366     const int htiles = r.width() / tw;
367     const int vtiles = r.height() / th;
368
369     for (int yy = 0; yy < vtiles; ++yy) {
370         for (int xx = 0; xx < htiles; ++xx) {
371             int ht = xx + h1;
372             int vt = yy + v1;
373
374             QQuickContext2DTile* tile = 0;
375
376             QPoint pos(ht * tw, vt * th);
377             QRect rect(pos, m_tileSize);
378
379             for (int i = 0; i < oldTiles.size(); i++) {
380                 if (oldTiles[i]->rect() == rect) {
381                     tile = oldTiles.takeAt(i);
382                     break;
383                 }
384             }
385
386             if (!tile)
387                 tile = createTile();
388
389             tile->setRect(rect);
390             m_tiles.append(tile);
391         }
392     }
393
394     qDeleteAll(oldTiles);
395
396     m_dirtyCanvas = false;
397     return r;
398 }
399
400 void QQuickContext2DTexture::clearTiles()
401 {
402     qDeleteAll(m_tiles);
403     m_tiles.clear();
404 }
405
406 QQuickContext2DFBOTexture::QQuickContext2DFBOTexture()
407     : QQuickContext2DTexture()
408     , m_fbo(0)
409     , m_multisampledFbo(0)
410     , m_paint_device(0)
411 {
412     m_threadRendering = false;
413 }
414
415 QQuickContext2DFBOTexture::~QQuickContext2DFBOTexture()
416 {
417     delete m_fbo;
418     delete m_multisampledFbo;
419     delete m_paint_device;
420 }
421
422 bool QQuickContext2DFBOTexture::setCanvasSize(const QSize &size)
423 {
424     QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
425                   , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
426
427     if (m_canvasSize != s) {
428         m_canvasSize = s;
429         m_dirtyCanvas = true;
430         return true;
431     }
432     return false;
433 }
434
435 bool QQuickContext2DFBOTexture::setTileSize(const QSize &size)
436 {
437     QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.width()))
438                   , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(size.height())));
439     if (m_tileSize != s) {
440         m_tileSize = s;
441         m_dirtyCanvas = true;
442         return true;
443     }
444     return false;
445 }
446
447 bool QQuickContext2DFBOTexture::setCanvasWindow(const QRect& canvasWindow)
448 {
449     QSize s = QSize(qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().width()))
450                   , qMax(QT_MINIMUM_FBO_SIZE, qt_next_power_of_two(canvasWindow.size().height())));
451
452
453     bool doChanged = false;
454     if (m_fboSize != s) {
455         m_fboSize = s;
456         doChanged = true;
457     }
458
459     if (m_canvasWindow != canvasWindow)
460         m_canvasWindow = canvasWindow;
461
462     return doChanged;
463 }
464
465 void QQuickContext2DFBOTexture::bind()
466 {
467     glBindTexture(GL_TEXTURE_2D, textureId());
468     updateBindOptions();
469 }
470
471 QRectF QQuickContext2DFBOTexture::normalizedTextureSubRect() const
472 {
473     return QRectF(0
474                 , 0
475                 , qreal(m_canvasWindow.width()) / m_fboSize.width()
476                 , qreal(m_canvasWindow.height()) / m_fboSize.height());
477 }
478
479
480 int QQuickContext2DFBOTexture::textureId() const
481 {
482     return m_fbo? m_fbo->texture() : 0;
483 }
484
485
486 bool QQuickContext2DFBOTexture::updateTexture()
487 {
488     if (!m_context->buffer()->isEmpty()) {
489         paint();
490     }
491
492     bool textureUpdated = m_dirtyTexture;
493
494     m_dirtyTexture = false;
495
496     if (m_doGrabImage) {
497         grabImage();
498         m_condition.wakeOne();
499         m_doGrabImage = false;
500     }
501     return textureUpdated;
502 }
503
504 QQuickContext2DTile* QQuickContext2DFBOTexture::createTile() const
505 {
506     return new QQuickContext2DFBOTile();
507 }
508
509 void QQuickContext2DFBOTexture::grabImage()
510 {
511     if (m_fbo) {
512         m_grabedImage = m_fbo->toImage();
513     }
514 }
515
516 bool QQuickContext2DFBOTexture::doMultisampling() const
517 {
518     static bool extensionsChecked = false;
519     static bool multisamplingSupported = false;
520
521     if (!extensionsChecked) {
522         QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' ');
523         multisamplingSupported = extensions.contains("GL_EXT_framebuffer_multisample")
524                 && extensions.contains("GL_EXT_framebuffer_blit");
525         extensionsChecked = true;
526     }
527
528     return multisamplingSupported  && m_smooth;
529 }
530
531 QImage QQuickContext2DFBOTexture::toImage(const QRectF& region)
532 {
533 #define QML_CONTEXT2D_WAIT_MAX 5000
534
535     m_doGrabImage = true;
536     if (m_item)
537         m_item->update();
538
539     QImage grabbed;
540     m_mutex.lock();
541     bool ok = m_condition.wait(&m_mutex, QML_CONTEXT2D_WAIT_MAX);
542
543     if (!ok)
544         grabbed = QImage();
545
546     if (region.isValid())
547         grabbed = m_grabedImage.copy(region.toRect());
548     else
549         grabbed = m_grabedImage;
550     m_grabedImage = QImage();
551     return grabbed;
552 }
553
554 void QQuickContext2DFBOTexture::compositeTile(QQuickContext2DTile* tile)
555 {
556     QQuickContext2DFBOTile* t = static_cast<QQuickContext2DFBOTile*>(tile);
557     QRect target = t->rect().intersect(m_canvasWindow);
558     if (target.isValid()) {
559         QRect source = target;
560
561         source.moveTo(source.topLeft() - t->rect().topLeft());
562         target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
563
564         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, target, t->fbo(), source);
565     }
566 }
567 QQuickCanvasItem::RenderTarget QQuickContext2DFBOTexture::renderTarget() const
568 {
569     return QQuickCanvasItem::FramebufferObject;
570 }
571 QPaintDevice* QQuickContext2DFBOTexture::beginPainting()
572 {
573     QQuickContext2DTexture::beginPainting();
574
575     if (m_canvasWindow.size().isEmpty() && !m_threadRendering) {
576         delete m_fbo;
577         delete m_multisampledFbo;
578         m_fbo = 0;
579         m_multisampledFbo = 0;
580         return 0;
581     } else if (!m_fbo || m_fbo->size() != m_fboSize) {
582         delete m_fbo;
583         delete m_multisampledFbo;
584         if (doMultisampling()) {
585             {
586                 QOpenGLFramebufferObjectFormat format;
587                 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
588                 format.setSamples(8);
589                 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, format);
590             }
591             {
592                 QOpenGLFramebufferObjectFormat format;
593                 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
594                 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
595             }
596         } else {
597             QOpenGLFramebufferObjectFormat format;
598             format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
599
600             m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
601         }
602     }
603
604     if (doMultisampling())
605         m_multisampledFbo->bind();
606     else
607         m_fbo->bind();
608
609
610     if (!m_paint_device) {
611         QOpenGLPaintDevice *gl_device = new QOpenGLPaintDevice(m_fbo->size());
612         gl_device->setPaintFlipped(true);
613         m_paint_device = gl_device;
614     }
615
616     return m_paint_device;
617 }
618
619 void QQuickContext2DFBOTexture::endPainting()
620 {
621     QQuickContext2DTexture::endPainting();
622     if (m_multisampledFbo) {
623         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, m_multisampledFbo);
624         m_multisampledFbo->release();
625     } else if (m_fbo)
626         m_fbo->release();
627 }
628 void qt_quit_context2d_render_thread()
629 {
630     QThread* thread = globalCanvasThreadRenderInstance();
631
632     if (thread->isRunning()) {
633         thread->exit(0);
634         thread->wait(1000);
635     }
636 }
637
638 QQuickContext2DImageTexture::QQuickContext2DImageTexture(bool threadRendering)
639     : QQuickContext2DTexture()
640     , m_texture(new QSGPlainTexture())
641 {
642     m_texture->setOwnsTexture(true);
643     m_texture->setHasMipmaps(false);
644
645     m_threadRendering = threadRendering;
646
647     if (m_threadRendering) {
648         QThread* thread = globalCanvasThreadRenderInstance();
649         moveToThread(thread);
650
651         if (!thread->isRunning()) {
652             qAddPostRoutine(qt_quit_context2d_render_thread);
653             thread->start();
654         }
655     }
656 }
657
658 QQuickContext2DImageTexture::~QQuickContext2DImageTexture()
659 {
660     delete m_texture;
661 }
662
663 int QQuickContext2DImageTexture::textureId() const
664 {
665     return m_texture->textureId();
666 }
667
668 void QQuickContext2DImageTexture::lock()
669 {
670     if (m_threadRendering)
671         m_mutex.lock();
672 }
673 void QQuickContext2DImageTexture::unlock()
674 {
675     if (m_threadRendering)
676         m_mutex.unlock();
677 }
678
679 void QQuickContext2DImageTexture::wait()
680 {
681     if (m_threadRendering)
682         m_waitCondition.wait(&m_mutex);
683 }
684
685 void QQuickContext2DImageTexture::wake()
686 {
687     if (m_threadRendering)
688         m_waitCondition.wakeOne();
689 }
690
691 bool QQuickContext2DImageTexture::supportDirectRendering() const
692 {
693     return !m_threadRendering;
694 }
695
696 QQuickCanvasItem::RenderTarget QQuickContext2DImageTexture::renderTarget() const
697 {
698     return QQuickCanvasItem::Image;
699 }
700
701 void QQuickContext2DImageTexture::bind()
702 {
703     m_texture->bind();
704 }
705
706 bool QQuickContext2DImageTexture::updateTexture()
707 {
708     lock();
709     bool textureUpdated = m_dirtyTexture;
710     if (m_dirtyTexture) {
711         m_texture->setImage(m_image);
712         m_dirtyTexture = false;
713     }
714     unlock();
715     return textureUpdated;
716 }
717
718 QQuickContext2DTile* QQuickContext2DImageTexture::createTile() const
719 {
720     return new QQuickContext2DImageTile();
721 }
722
723 void QQuickContext2DImageTexture::grabImage(const QRect& r)
724 {
725     m_doGrabImage = true;
726     paint();
727     m_doGrabImage = false;
728     m_grabedImage = m_image.copy(r);
729 }
730
731 QImage QQuickContext2DImageTexture::toImage(const QRectF& region)
732 {
733     QRect r = region.isValid() ? region.toRect() : QRect(QPoint(0, 0), m_canvasWindow.size());
734     if (threadRendering()) {
735         wake();
736         QMetaObject::invokeMethod(this, "grabImage", Qt::BlockingQueuedConnection, Q_ARG(QRect, r));
737     } else {
738         QMetaObject::invokeMethod(this, "grabImage", Qt::DirectConnection, Q_ARG(QRect, r));
739     }
740     QImage image = m_grabedImage;
741     m_grabedImage = QImage();
742     return image;
743 }
744
745 QPaintDevice* QQuickContext2DImageTexture::beginPainting()
746 {
747      QQuickContext2DTexture::beginPainting();
748
749     if (m_canvasWindow.size().isEmpty())
750         return 0;
751
752     lock();
753     if (m_image.size() != m_canvasWindow.size()) {
754         m_image = QImage(m_canvasWindow.size(), QImage::Format_ARGB32_Premultiplied);
755         m_image.fill(0x00000000);
756     }
757     unlock();
758     return &m_image;
759 }
760
761 void QQuickContext2DImageTexture::compositeTile(QQuickContext2DTile* tile)
762 {
763     Q_ASSERT(!tile->dirty());
764     QQuickContext2DImageTile* t = static_cast<QQuickContext2DImageTile*>(tile);
765     QRect target = t->rect().intersect(m_canvasWindow);
766     if (target.isValid()) {
767         QRect source = target;
768         source.moveTo(source.topLeft() - t->rect().topLeft());
769         target.moveTo(target.topLeft() - m_canvasWindow.topLeft());
770
771         lock();
772         m_painter.begin(&m_image);
773         m_painter.setCompositionMode(QPainter::CompositionMode_Source);
774         m_painter.drawImage(target, t->image(), source);
775         m_painter.end();
776         unlock();
777     }
778 }