1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2013-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 "loader.h"
21 #include "net/client.h"
22 #include "ora/orareader.h"
23 #include "canvas/aclfilter.h"
24 
25 #include "core/layerstack.h"
26 #include "core/layer.h"
27 #include "core/tilevector.h"
28 
29 #include "../libshared/net/layer.h"
30 #include "../libshared/net/annotation.h"
31 #include "../libshared/net/meta.h"
32 #include "../libshared/net/meta2.h"
33 #include "../libshared/net/image.h"
34 
35 #include <QDebug>
36 #include <QGuiApplication>
37 #include <QImage>
38 #include <QPixmap>
39 #include <QImageReader>
40 
41 namespace canvas {
42 
43 using protocol::MessageList;
44 using protocol::MessagePtr;
45 
~SessionLoader()46 SessionLoader::~SessionLoader()
47 {
48 }
49 
loadInitCommands()50 MessageList BlankCanvasLoader::loadInitCommands()
51 {
52 	return MessageList()
53 		<< MessagePtr(new protocol::CanvasResize(1, 0, _size.width(), _size.height(), 0))
54 		<< MessagePtr(new protocol::CanvasBackground(1, _color.rgba()))
55 		<< MessagePtr(new protocol::LayerCreate(1, 0x0102, 0, 0, 0, QStringLiteral("Layer 1")))
56 		;
57 }
58 
loadThumbnail(const QSize & maxSize) const59 QPixmap ImageCanvasLoader::loadThumbnail(const QSize &maxSize) const
60 {
61 	QImage thumbnail;
62 
63 	if(m_filename.endsWith(".ora", Qt::CaseInsensitive))
64 		thumbnail = openraster::loadOpenRasterThumbnail(m_filename);
65 	else
66 		thumbnail.load(m_filename);
67 
68 	if(thumbnail.isNull())
69 		return QPixmap();
70 
71 	if(thumbnail.width() > maxSize.width() || thumbnail.height() > maxSize.height()) {
72 		thumbnail = thumbnail.scaled(maxSize, Qt::KeepAspectRatio);
73 	}
74 
75 	return QPixmap::fromImage(thumbnail);
76 }
77 
loadInitCommands()78 MessageList ImageCanvasLoader::loadInitCommands()
79 {
80 	if(m_filename.endsWith(".ora", Qt::CaseInsensitive)) {
81 		// Load OpenRaster image
82 		// TODO identify by filetype magic?
83 		openraster::OraResult ora = openraster::loadOpenRaster(m_filename);
84 
85 		if(!ora.error.isEmpty()) {
86 			m_error = ora.error;
87 			return MessageList();
88 		}
89 
90 		if(ora.warnings != openraster::OraResult::NO_WARNINGS) {
91 			QString text = QGuiApplication::tr("Drawpile does not support all the features used in this OpenRaster file. Saving this file may result in data loss.\n");
92 			if((ora.warnings & openraster::OraResult::ORA_EXTENDED))
93 				text += "\n- " + QGuiApplication::tr("Application specific extensions are used");
94 			if((ora.warnings & openraster::OraResult::ORA_NESTED))
95 				text += "\n- " + QGuiApplication::tr("Nested layers are not fully supported");
96 			if((ora.warnings & openraster::OraResult::UNSUPPORTED_BACKGROUND_TILE))
97 				text += "\n- " + QGuiApplication::tr("Unsupported background tile size");
98 
99 			m_warning = text;
100 		}
101 		m_dpi = QPair<int,int>(ora.dpiX, ora.dpiY);
102 		return ora.commands;
103 
104 	} else {
105 		// Load an image using Qt's image loader.
106 		// If the image is animated, each frame is loaded as a layer
107 		MessageList msgs;
108 		QImageReader ir(m_filename);
109 		int layerId = 1;
110 
111 		while(true) {
112 			QImage image = ir.read();
113 
114 			if(image.isNull()) {
115 				if(layerId>1)
116 					break;
117 				m_error = ir.errorString();
118 				return MessageList();
119 			}
120 
121 			if(layerId==1) {
122 				m_dpi = QPair<int,int>(int(image.dotsPerMeterX() * 0.0254), int(image.dotsPerMeterY() * 0.0254));
123 				msgs << MessagePtr(new protocol::CanvasResize(1, 0, image.size().width(), image.size().height(), 0));
124 			}
125 
126 			const auto tileset = paintcore::LayerTileSet::fromImage(
127 				image.convertToFormat(QImage::Format_ARGB32_Premultiplied)
128 				);
129 
130 			msgs << protocol::MessagePtr(new protocol::LayerCreate(
131 				1,
132 				layerId,
133 				0,
134 				tileset.background.rgba(),
135 				0,
136 				QStringLiteral("Layer %1").arg(layerId)
137 			));
138 
139 			msgs << protocol::MessagePtr(new protocol::LayerAttributes(
140 				1,
141 				layerId,
142 				0,
143 				0,
144 				255,
145 				paintcore::BlendMode::MODE_NORMAL
146 			));
147 
148 			tileset.toPutTiles(1, layerId, 0, msgs);
149 
150 			++layerId;
151 
152 			if(!ir.supportsAnimation()) {
153 				// Don't try to read any more frames if this format
154 				// does not support animation.
155 				break;
156 			}
157 		}
158 
159 		return msgs;
160 	}
161 }
162 
loadInitCommands()163 MessageList QImageCanvasLoader::loadInitCommands()
164 {
165 	MessageList msgs;
166 
167 	msgs << MessagePtr(new protocol::CanvasResize(1, 0, m_image.size().width(), m_image.size().height(), 0));
168 
169 	const auto tileset = paintcore::LayerTileSet::fromImage(
170 		m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied)
171 		);
172 
173 	msgs << protocol::MessagePtr(new protocol::LayerCreate(
174 		1,
175 		1,
176 		0,
177 		tileset.background.rgba(),
178 		0,
179 		QStringLiteral("Layer 1")
180 	));
181 
182 	msgs << protocol::MessagePtr(new protocol::LayerAttributes(
183 		1,
184 		1,
185 		0,
186 		0,
187 		255,
188 		paintcore::BlendMode::MODE_NORMAL
189 	));
190 
191 	tileset.toPutTiles(1, 1, 0, msgs);
192 
193 	return msgs;
194 }
195 
loadInitCommands()196 MessageList SnapshotLoader::loadInitCommands()
197 {
198 	MessageList msgs;
199 
200 	// Most important bit first: canvas initialization
201 	const QSize imgsize = m_layers->size();
202 	msgs.append(MessagePtr(new protocol::CanvasResize(m_contextId, 0, imgsize.width(), imgsize.height(), 0)));
203 
204 	const QColor solidBgColor = m_layers->background().solidColor();
205 	if(solidBgColor.isValid())
206 		msgs.append(MessagePtr(new protocol::CanvasBackground(m_contextId, solidBgColor.rgba())));
207 	else
208 		msgs.append(MessagePtr(new protocol::CanvasBackground(
209 			m_contextId,
210 			qCompress(reinterpret_cast<const uchar*>(m_layers->background().constData()), paintcore::Tile::BYTES)
211 			)));
212 
213 	// Preset default layer
214 	if(m_defaultLayer>0)
215 		msgs.append(MessagePtr(new protocol::DefaultLayer(m_contextId, uint16_t(m_defaultLayer))));
216 
217 	// Add pinned message (if any)
218 	if(!m_pinnedMessage.isEmpty()) {
219 		msgs.append(protocol::Chat::pin(m_contextId, m_pinnedMessage));
220 	}
221 
222 	// Create layers
223 	for(int i=0;i<m_layers->layerCount();++i) {
224 		const paintcore::Layer *layer = m_layers->getLayerByIndex(i);
225 
226 		const auto tileset = paintcore::LayerTileSet::fromLayer(*layer);
227 
228 		msgs << protocol::MessagePtr(new protocol::LayerCreate(
229 			m_contextId,
230 			layer->id(),
231 			0,
232 			tileset.background.rgba(),
233 			0,
234 			layer->title()
235 		));
236 
237 		msgs << protocol::MessagePtr(new protocol::LayerAttributes(
238 			m_contextId,
239 			layer->id(),
240 			0,
241 			(layer->isCensored() ? protocol::LayerAttributes::FLAG_CENSOR : 0) |
242 			(layer->isFixed() ? protocol::LayerAttributes::FLAG_FIXED : 0),
243 			layer->opacity(),
244 			layer->blendmode()
245 		));
246 
247 		tileset.toPutTiles(m_contextId, layer->id(), 0, msgs);
248 
249 		// Put active sublayers (if any)
250 		for(const paintcore::Layer *sublayer : layer->sublayers()) {
251 			if(sublayer->id() > 0 && sublayer->id() < 256 && !sublayer->isHidden()) {
252 				const auto subtileset = paintcore::LayerTileSet::fromLayer(*sublayer);
253 				msgs << protocol::MessagePtr(new protocol::LayerAttributes(
254 					m_contextId,
255 					layer->id(),
256 					sublayer->id(),
257 					0,
258 					sublayer->opacity(),
259 					sublayer->blendmode()
260 					));
261 
262 				subtileset.toPutTiles(m_contextId, layer->id(), sublayer->id(), msgs);
263 			}
264 		}
265 
266 		// Set layer ACLs (if found)
267 		if(m_aclfilter) {
268 			const canvas::AclFilter::LayerAcl acl = m_aclfilter->layerAcl(layer->id());
269 			if(acl.locked || acl.tier != canvas::Tier::Guest || !acl.exclusive.isEmpty())
270 				msgs << MessagePtr(new protocol::LayerACL(m_contextId, layer->id(), acl.locked, int(acl.tier), acl.exclusive));
271 		}
272 	}
273 
274 	// Create annotations
275 	for(const paintcore::Annotation &a : m_layers->annotations()->getAnnotations()) {
276 		const QRect g = a.rect;
277 		msgs.append(MessagePtr(new protocol::AnnotationCreate(m_contextId, a.id, g.x(), g.y(), g.width(), g.height())));
278 		msgs.append((MessagePtr(new protocol::AnnotationEdit(m_contextId, a.id, a.background.rgba(), a.flags(), 0, a.text))));
279 	}
280 
281 	// Session and user ACLs
282 	if(m_aclfilter) {
283 		uint8_t features[canvas::FeatureCount];
284 		for(int i=0;i<canvas::FeatureCount;++i)
285 			features[i] = uint8_t(m_aclfilter->featureTier(Feature(i)));
286 
287 		msgs.append(MessagePtr(new protocol::FeatureAccessLevels(m_contextId, features)));
288 		msgs.append(MessagePtr(new protocol::UserACL(m_contextId, m_aclfilter->lockedUsers())));
289 	}
290 
291 	return msgs;
292 }
293 
dotsPerInch() const294 QPair<int,int> SnapshotLoader::dotsPerInch() const
295 {
296 	return m_layers->dotsPerInch();
297 }
298 
299 }
300 
301