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