1 /*
2 Drawpile - a collaborative drawing program.
3
4 Copyright (C) 2019 Calle Laakkonen
5
6 Drawpile is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Drawpile is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Drawpile. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "layerstackobserver.h"
21 #include "layerstack.h"
22 #include "concurrent.h"
23
24 #include <QPainter>
25
26 namespace paintcore {
27
LayerStackObserver()28 LayerStackObserver::LayerStackObserver()
29 : m_layerstack(nullptr)
30 {
31 }
32
~LayerStackObserver()33 LayerStackObserver::~LayerStackObserver()
34 {
35 detachFromLayerStack();
36 }
37
attachToLayerStack(LayerStack * layerstack)38 void LayerStackObserver::attachToLayerStack(LayerStack *layerstack)
39 {
40 Q_ASSERT(layerstack);
41
42 if(m_layerstack)
43 m_layerstack->m_observers.removeAll(this);
44
45 m_layerstack = layerstack;
46 m_layerstack->m_observers.append(this);
47
48 canvasResized(0, 0, QSize());
49 canvasBackgroundChanged(layerstack->background());
50 }
51
detachFromLayerStack()52 void LayerStackObserver::detachFromLayerStack()
53 {
54 if(m_layerstack) {
55 m_layerstack->m_observers.removeAll(this);
56 m_layerstack = nullptr;
57 }
58 }
59
markDirty(const QRect & area)60 void LayerStackObserver::markDirty(const QRect &area)
61 {
62 Q_ASSERT(m_layerstack);
63
64 if(m_layerstack->m_layers.isEmpty() || m_layerstack->m_width<=0 || m_layerstack->m_height<=0)
65 return;
66
67 const int XT = m_layerstack->m_xtiles;
68 const int YT = m_layerstack->m_ytiles;
69
70 Q_ASSERT(XT * YT == m_dirtytiles.size());
71
72 const int tx0 = qBound(0, area.left() / Tile::SIZE, XT-1);
73 const int tx1 = qBound(tx0, area.right() / Tile::SIZE, XT-1) + 1;
74 int ty0 = qBound(0, area.top() / Tile::SIZE, YT-1);
75 const int ty1 = qBound(ty0, area.bottom() / Tile::SIZE, YT-1);
76
77 for(;ty0<=ty1;++ty0) {
78 m_dirtytiles.fill(true, ty0*XT + tx0, ty0*XT + tx1);
79 }
80 m_dirtyrect |= area;
81 }
82
markDirty()83 void LayerStackObserver::markDirty()
84 {
85 m_dirtytiles.fill(true);
86 m_dirtyrect = QRect(QPoint(), m_layerstack->size());
87 }
88
markDirty(int x,int y)89 void LayerStackObserver::markDirty(int x, int y)
90 {
91 Q_ASSERT(x>=0 && x < m_layerstack->m_xtiles);
92 Q_ASSERT(y>=0 && y < m_layerstack->m_ytiles);
93 Q_ASSERT(m_layerstack->m_xtiles * m_layerstack->m_ytiles == m_dirtytiles.size());
94
95 m_dirtytiles.setBit(y*m_layerstack->m_xtiles + x);
96
97 m_dirtyrect |= QRect(x*Tile::SIZE, y*Tile::SIZE, Tile::SIZE, Tile::SIZE);
98 }
99
markDirty(int index)100 void LayerStackObserver::markDirty(int index)
101 {
102 Q_ASSERT(m_layerstack->m_xtiles * m_layerstack->m_ytiles == m_dirtytiles.size());
103 Q_ASSERT(index>=0 && index < m_dirtytiles.size());
104
105 m_dirtytiles.setBit(index);
106
107 const int y = index / m_layerstack->m_xtiles;
108 const int x = index % m_layerstack->m_xtiles;
109
110 m_dirtyrect |= QRect(x*Tile::SIZE, y*Tile::SIZE, Tile::SIZE, Tile::SIZE);
111 }
112
canvasWriteSequenceDone()113 void LayerStackObserver::canvasWriteSequenceDone()
114 {
115 areaChanged(m_dirtyrect);
116 m_dirtyrect = QRect();
117 }
118
canvasResized(int xoffset,int yoffset,const QSize & oldsize)119 void LayerStackObserver::canvasResized(int xoffset, int yoffset, const QSize &oldsize)
120 {
121 Q_ASSERT(m_layerstack);
122 m_dirtytiles = QBitArray(m_layerstack->m_xtiles * m_layerstack->m_ytiles, true);
123 m_dirtyrect = QRect(QPoint(), m_layerstack->size());
124 resized(xoffset, yoffset, oldsize);
125 }
126
canvasBackgroundChanged(const Tile & tile)127 void LayerStackObserver::canvasBackgroundChanged(const Tile &tile)
128 {
129 // Check if background tile has any transparent pixels
130 bool isTransparent = tile.isNull();
131 if(!tile.isNull()) {
132 const quint32 *ptr = tile.constData();
133 for(int i=0;i<Tile::LENGTH;++i,++ptr) {
134 if(qAlpha(*ptr) < 255) {
135 isTransparent = true;
136 break;
137 }
138 }
139 }
140
141 // If background tile is (at least partially) transparent, composite it with
142 // the checkerboard pattern. (TODO: draw background in the view widget)
143 if(isTransparent) {
144 Tile::fillChecker(m_paintBackgroundTile.data(), QColor(128,128,128), Qt::white);
145 m_paintBackgroundTile.merge(tile, 255, BlendMode::MODE_NORMAL);
146
147 } else {
148 m_paintBackgroundTile = tile;
149 }
150
151 markDirty();
152 }
153
154 namespace {
155 struct UpdateTile {
UpdateTilepaintcore::__anone4beebe60111::UpdateTile156 UpdateTile(int x_, int y_) : x(x_), y(y_) {}
157
158 int x, y;
159 quint32 data[Tile::LENGTH];
160 };
161 }
162
paintChangedTiles(const QRect & rect,QPaintDevice * target)163 void LayerStackObserver::paintChangedTiles(const QRect &rect, QPaintDevice *target)
164 {
165 Q_ASSERT(m_layerstack);
166 if(m_layerstack->width() <=0 || m_layerstack->height() <= 0)
167 return;
168
169 // Affected tile range
170 const int tx0 = qBound(0, rect.left() / Tile::SIZE, m_layerstack->m_xtiles-1);
171 const int tx1 = qBound(tx0, rect.right() / Tile::SIZE, m_layerstack->m_xtiles-1);
172 const int ty0 = qBound(0, rect.top() / Tile::SIZE, m_layerstack->m_ytiles-1);
173 const int ty1 = qBound(ty0, rect.bottom() / Tile::SIZE, m_layerstack->m_ytiles-1);
174
175 // Gather list of tiles in need of updating
176 QList<UpdateTile*> updates;
177
178 for(int ty=ty0;ty<=ty1;++ty) {
179 const int y = ty*m_layerstack->m_xtiles;
180 for(int tx=tx0;tx<=tx1;++tx) {
181 const int i = y+tx;
182 if(m_dirtytiles.testBit(i)) {
183 updates.append(new UpdateTile(tx, ty));
184 m_dirtytiles.clearBit(i);
185 }
186 }
187 }
188
189 if(!updates.isEmpty()) {
190 // Flatten tiles
191 concurrentForEach<UpdateTile*>(updates, [this](UpdateTile *t) {
192 m_paintBackgroundTile.copyTo(t->data);
193 m_layerstack->flattenTile(t->data, t->x, t->y);
194 });
195
196 // Paint flattened tiles
197 QPainter painter(target);
198 painter.setCompositionMode(QPainter::CompositionMode_Source);
199 while(!updates.isEmpty()) {
200 UpdateTile *ut = updates.takeLast();
201 painter.drawImage(
202 ut->x*Tile::SIZE,
203 ut->y*Tile::SIZE,
204 QImage(reinterpret_cast<const uchar*>(ut->data),
205 Tile::SIZE, Tile::SIZE,
206 QImage::Format_ARGB32_Premultiplied
207 )
208 );
209 delete ut;
210 }
211 }
212 }
213
214 }
215