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