/*
Drawpile - a collaborative drawing program.
Copyright (C) 2013-2019 Calle Laakkonen
Drawpile is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Drawpile is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Drawpile. If not, see .
*/
#include "loader.h"
#include "net/client.h"
#include "ora/orareader.h"
#include "canvas/aclfilter.h"
#include "core/layerstack.h"
#include "core/layer.h"
#include "core/tilevector.h"
#include "../libshared/net/layer.h"
#include "../libshared/net/annotation.h"
#include "../libshared/net/meta.h"
#include "../libshared/net/meta2.h"
#include "../libshared/net/image.h"
#include
#include
#include
#include
#include
namespace canvas {
using protocol::MessageList;
using protocol::MessagePtr;
SessionLoader::~SessionLoader()
{
}
MessageList BlankCanvasLoader::loadInitCommands()
{
return MessageList()
<< MessagePtr(new protocol::CanvasResize(1, 0, _size.width(), _size.height(), 0))
<< MessagePtr(new protocol::CanvasBackground(1, _color.rgba()))
<< MessagePtr(new protocol::LayerCreate(1, 0x0102, 0, 0, 0, QStringLiteral("Layer 1")))
;
}
QPixmap ImageCanvasLoader::loadThumbnail(const QSize &maxSize) const
{
QImage thumbnail;
if(m_filename.endsWith(".ora", Qt::CaseInsensitive))
thumbnail = openraster::loadOpenRasterThumbnail(m_filename);
else
thumbnail.load(m_filename);
if(thumbnail.isNull())
return QPixmap();
if(thumbnail.width() > maxSize.width() || thumbnail.height() > maxSize.height()) {
thumbnail = thumbnail.scaled(maxSize, Qt::KeepAspectRatio);
}
return QPixmap::fromImage(thumbnail);
}
MessageList ImageCanvasLoader::loadInitCommands()
{
if(m_filename.endsWith(".ora", Qt::CaseInsensitive)) {
// Load OpenRaster image
// TODO identify by filetype magic?
openraster::OraResult ora = openraster::loadOpenRaster(m_filename);
if(!ora.error.isEmpty()) {
m_error = ora.error;
return MessageList();
}
if(ora.warnings != openraster::OraResult::NO_WARNINGS) {
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");
if((ora.warnings & openraster::OraResult::ORA_EXTENDED))
text += "\n- " + QGuiApplication::tr("Application specific extensions are used");
if((ora.warnings & openraster::OraResult::ORA_NESTED))
text += "\n- " + QGuiApplication::tr("Nested layers are not fully supported");
if((ora.warnings & openraster::OraResult::UNSUPPORTED_BACKGROUND_TILE))
text += "\n- " + QGuiApplication::tr("Unsupported background tile size");
m_warning = text;
}
m_dpi = QPair(ora.dpiX, ora.dpiY);
return ora.commands;
} else {
// Load an image using Qt's image loader.
// If the image is animated, each frame is loaded as a layer
MessageList msgs;
QImageReader ir(m_filename);
int layerId = 1;
while(true) {
QImage image = ir.read();
if(image.isNull()) {
if(layerId>1)
break;
m_error = ir.errorString();
return MessageList();
}
if(layerId==1) {
m_dpi = QPair(int(image.dotsPerMeterX() * 0.0254), int(image.dotsPerMeterY() * 0.0254));
msgs << MessagePtr(new protocol::CanvasResize(1, 0, image.size().width(), image.size().height(), 0));
}
const auto tileset = paintcore::LayerTileSet::fromImage(
image.convertToFormat(QImage::Format_ARGB32_Premultiplied)
);
msgs << protocol::MessagePtr(new protocol::LayerCreate(
1,
layerId,
0,
tileset.background.rgba(),
0,
QStringLiteral("Layer %1").arg(layerId)
));
msgs << protocol::MessagePtr(new protocol::LayerAttributes(
1,
layerId,
0,
0,
255,
paintcore::BlendMode::MODE_NORMAL
));
tileset.toPutTiles(1, layerId, 0, msgs);
++layerId;
if(!ir.supportsAnimation()) {
// Don't try to read any more frames if this format
// does not support animation.
break;
}
}
return msgs;
}
}
MessageList QImageCanvasLoader::loadInitCommands()
{
MessageList msgs;
msgs << MessagePtr(new protocol::CanvasResize(1, 0, m_image.size().width(), m_image.size().height(), 0));
const auto tileset = paintcore::LayerTileSet::fromImage(
m_image.convertToFormat(QImage::Format_ARGB32_Premultiplied)
);
msgs << protocol::MessagePtr(new protocol::LayerCreate(
1,
1,
0,
tileset.background.rgba(),
0,
QStringLiteral("Layer 1")
));
msgs << protocol::MessagePtr(new protocol::LayerAttributes(
1,
1,
0,
0,
255,
paintcore::BlendMode::MODE_NORMAL
));
tileset.toPutTiles(1, 1, 0, msgs);
return msgs;
}
MessageList SnapshotLoader::loadInitCommands()
{
MessageList msgs;
// Most important bit first: canvas initialization
const QSize imgsize = m_layers->size();
msgs.append(MessagePtr(new protocol::CanvasResize(m_contextId, 0, imgsize.width(), imgsize.height(), 0)));
const QColor solidBgColor = m_layers->background().solidColor();
if(solidBgColor.isValid())
msgs.append(MessagePtr(new protocol::CanvasBackground(m_contextId, solidBgColor.rgba())));
else
msgs.append(MessagePtr(new protocol::CanvasBackground(
m_contextId,
qCompress(reinterpret_cast(m_layers->background().constData()), paintcore::Tile::BYTES)
)));
// Preset default layer
if(m_defaultLayer>0)
msgs.append(MessagePtr(new protocol::DefaultLayer(m_contextId, uint16_t(m_defaultLayer))));
// Add pinned message (if any)
if(!m_pinnedMessage.isEmpty()) {
msgs.append(protocol::Chat::pin(m_contextId, m_pinnedMessage));
}
// Create layers
for(int i=0;ilayerCount();++i) {
const paintcore::Layer *layer = m_layers->getLayerByIndex(i);
const auto tileset = paintcore::LayerTileSet::fromLayer(*layer);
msgs << protocol::MessagePtr(new protocol::LayerCreate(
m_contextId,
layer->id(),
0,
tileset.background.rgba(),
0,
layer->title()
));
msgs << protocol::MessagePtr(new protocol::LayerAttributes(
m_contextId,
layer->id(),
0,
(layer->isCensored() ? protocol::LayerAttributes::FLAG_CENSOR : 0) |
(layer->isFixed() ? protocol::LayerAttributes::FLAG_FIXED : 0),
layer->opacity(),
layer->blendmode()
));
tileset.toPutTiles(m_contextId, layer->id(), 0, msgs);
// Put active sublayers (if any)
for(const paintcore::Layer *sublayer : layer->sublayers()) {
if(sublayer->id() > 0 && sublayer->id() < 256 && !sublayer->isHidden()) {
const auto subtileset = paintcore::LayerTileSet::fromLayer(*sublayer);
msgs << protocol::MessagePtr(new protocol::LayerAttributes(
m_contextId,
layer->id(),
sublayer->id(),
0,
sublayer->opacity(),
sublayer->blendmode()
));
subtileset.toPutTiles(m_contextId, layer->id(), sublayer->id(), msgs);
}
}
// Set layer ACLs (if found)
if(m_aclfilter) {
const canvas::AclFilter::LayerAcl acl = m_aclfilter->layerAcl(layer->id());
if(acl.locked || acl.tier != canvas::Tier::Guest || !acl.exclusive.isEmpty())
msgs << MessagePtr(new protocol::LayerACL(m_contextId, layer->id(), acl.locked, int(acl.tier), acl.exclusive));
}
}
// Create annotations
for(const paintcore::Annotation &a : m_layers->annotations()->getAnnotations()) {
const QRect g = a.rect;
msgs.append(MessagePtr(new protocol::AnnotationCreate(m_contextId, a.id, g.x(), g.y(), g.width(), g.height())));
msgs.append((MessagePtr(new protocol::AnnotationEdit(m_contextId, a.id, a.background.rgba(), a.flags(), 0, a.text))));
}
// Session and user ACLs
if(m_aclfilter) {
uint8_t features[canvas::FeatureCount];
for(int i=0;ifeatureTier(Feature(i)));
msgs.append(MessagePtr(new protocol::FeatureAccessLevels(m_contextId, features)));
msgs.append(MessagePtr(new protocol::UserACL(m_contextId, m_aclfilter->lockedUsers())));
}
return msgs;
}
QPair SnapshotLoader::dotsPerInch() const
{
return m_layers->dotsPerInch();
}
}