MuseScore/libmscore/image.cpp

589 lines
20 KiB
C++
Raw Normal View History

2012-05-26 14:26:10 +02:00
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2007-2012 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "image.h"
#include "xml.h"
#include "score.h"
#include "undo.h"
#include "mscore.h"
#include "imageStore.h"
2013-05-13 18:49:17 +02:00
namespace Ms {
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// propertyList
//---------------------------------------------------------
static bool defaultAutoScale = false;
static bool defaultLockAspectRatio = true;
static bool defaultSizeIsSpatium = true;
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// Image
//---------------------------------------------------------
Image::Image(Score* s)
: BSymbol(s)
{
2014-05-21 15:49:13 +02:00
imageType = ImageType::NONE;
2015-05-05 18:27:04 +02:00
rasterDoc = 0;
2012-05-26 14:26:10 +02:00
_size = QSizeF(0, 0);
_storeItem = 0;
_dirty = false;
_lockAspectRatio = defaultLockAspectRatio;
_autoScale = defaultAutoScale;
_sizeIsSpatium = defaultSizeIsSpatium;
_linkIsValid = false;
2012-05-26 14:26:10 +02:00
}
Image::Image(const Image& img)
: BSymbol(img)
{
2013-01-11 18:10:18 +01:00
imageType = img.imageType;
2012-05-26 14:26:10 +02:00
buffer = img.buffer;
_size = img._size;
_lockAspectRatio = img._lockAspectRatio;
_autoScale = img._autoScale;
_dirty = img._dirty;
_storeItem = img._storeItem;
_sizeIsSpatium = img._sizeIsSpatium;
2013-01-11 18:10:18 +01:00
if (_storeItem)
_storeItem->reference(this);
_linkPath = img._linkPath;
_linkIsValid = img._linkIsValid;
2015-05-05 18:27:04 +02:00
if (imageType == ImageType::RASTER)
rasterDoc = img.rasterDoc ? new QImage(*img.rasterDoc) : 0;
else if (imageType == ImageType::SVG)
svgDoc = img.svgDoc ? new QSvgRenderer(img.svgDoc) : 0;
2014-07-22 01:00:49 +02:00
setZ(img.z());
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// Image
//---------------------------------------------------------
Image::~Image()
{
if (_storeItem)
_storeItem->dereference(this);
2014-05-21 15:49:13 +02:00
if (imageType == ImageType::SVG)
2013-01-11 18:10:18 +01:00
delete svgDoc;
2014-05-21 15:49:13 +02:00
else if (imageType == ImageType::RASTER)
2013-01-11 18:10:18 +01:00
delete rasterDoc;
}
//---------------------------------------------------------
// setImageType
//---------------------------------------------------------
void Image::setImageType(ImageType t)
{
imageType = t;
2014-05-21 15:49:13 +02:00
if (imageType == ImageType::SVG)
2013-01-11 18:10:18 +01:00
svgDoc = 0;
2014-05-21 15:49:13 +02:00
else if (imageType == ImageType::RASTER)
2013-01-11 18:10:18 +01:00
rasterDoc = 0;
else
qDebug("illegal image type");
}
//---------------------------------------------------------
// imageSize
//---------------------------------------------------------
QSizeF Image::imageSize() const
{
2014-05-21 15:49:13 +02:00
if (imageType == ImageType::RASTER)
2013-01-11 18:10:18 +01:00
return rasterDoc->size();
else
return svgDoc->defaultSize();
}
//---------------------------------------------------------
// scaleFactor
//---------------------------------------------------------
qreal Image::scaleFactor() const
{
2014-05-21 15:49:13 +02:00
if (imageType == ImageType::RASTER)
2015-11-16 14:24:47 +01:00
return ( (_sizeIsSpatium ? spatium() : DPMM) / 0.4 );
2013-01-11 18:10:18 +01:00
else
2015-11-16 14:24:47 +01:00
return (_sizeIsSpatium ? 10.0 : DPMM);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// scale
// return image scale in percent
//---------------------------------------------------------
QSizeF Image::scale() const
{
return scaleForSize(size());
}
//---------------------------------------------------------
// setScale
//---------------------------------------------------------
void Image::setScale(const QSizeF& scale)
{
setSize(sizeForScale(scale));
}
//---------------------------------------------------------
// scaleForSize
//---------------------------------------------------------
QSizeF Image::scaleForSize(const QSizeF& s) const
{
2013-08-31 13:42:45 +02:00
if(!isValid())
return QSizeF();
2012-07-25 23:38:05 +02:00
QSizeF sz = s * scaleFactor();
2012-05-26 14:26:10 +02:00
return QSizeF(
2013-08-31 13:42:45 +02:00
(sz.width() * 100.0)/ imageSize().width(),
(sz.height() * 100.0)/ imageSize().height()
);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// sizeForScale
//---------------------------------------------------------
QSizeF Image::sizeForScale(const QSizeF& scale) const
{
QSizeF s = scale / 100.0;
2015-11-16 14:24:47 +01:00
// qreal sz = _sizeIsSpatium ? spatium() : DPMM;
2012-07-25 23:38:05 +02:00
// QSizeF oSize = imageSize() / sz;
QSizeF oSize = imageSize() / scaleFactor();
2012-05-26 14:26:10 +02:00
return QSizeF(s.width() * oSize.width(), s.height() * oSize.height());
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Image::getProperty(P_ID propertyId) const
{
switch(propertyId) {
2014-05-26 18:18:01 +02:00
case P_ID::AUTOSCALE:
2012-05-26 14:26:10 +02:00
return autoScale();
2014-05-26 18:18:01 +02:00
case P_ID::SIZE:
2012-05-26 14:26:10 +02:00
return size();
2014-05-26 18:18:01 +02:00
case P_ID::SCALE:
2012-05-26 14:26:10 +02:00
return scale();
2014-05-26 18:18:01 +02:00
case P_ID::LOCK_ASPECT_RATIO:
2012-05-26 14:26:10 +02:00
return lockAspectRatio();
2014-05-26 18:18:01 +02:00
case P_ID::SIZE_IS_SPATIUM:
2012-05-26 14:26:10 +02:00
return sizeIsSpatium();
default:
return Element::getProperty(propertyId);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Image::setProperty(P_ID propertyId, const QVariant& v)
{
bool rv = true;
score()->addRefresh(canvasBoundingRect());
switch(propertyId) {
2014-05-26 18:18:01 +02:00
case P_ID::AUTOSCALE:
2012-05-26 14:26:10 +02:00
setAutoScale(v.toBool());
break;
2014-05-26 18:18:01 +02:00
case P_ID::SIZE:
2012-05-26 14:26:10 +02:00
setSize(v.toSizeF());
break;
2014-05-26 18:18:01 +02:00
case P_ID::SCALE:
2012-05-26 14:26:10 +02:00
setScale(v.toSizeF());
break;
2014-05-26 18:18:01 +02:00
case P_ID::LOCK_ASPECT_RATIO:
2012-05-26 14:26:10 +02:00
setLockAspectRatio(v.toBool());
break;
2014-05-26 18:18:01 +02:00
case P_ID::SIZE_IS_SPATIUM:
2012-05-26 14:26:10 +02:00
setSizeIsSpatium(v.toBool());
break;
default:
rv = Element::setProperty(propertyId, v);
break;
}
setGenerated(false);
2016-03-02 13:20:19 +01:00
score()->setLayoutAll();
2012-05-26 14:26:10 +02:00
return rv;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant Image::propertyDefault(P_ID id) const
{
switch(id) {
2014-05-26 18:18:01 +02:00
case P_ID::AUTOSCALE: return defaultAutoScale;
case P_ID::SIZE: break;
case P_ID::LOCK_ASPECT_RATIO: return defaultLockAspectRatio;
case P_ID::SIZE_IS_SPATIUM: return defaultSizeIsSpatium;
default: return Element::propertyDefault(id);
2012-05-26 14:26:10 +02:00
}
return QVariant();
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
2013-01-11 18:10:18 +01:00
void Image::draw(QPainter* painter) const
2012-05-26 14:26:10 +02:00
{
2013-01-11 18:10:18 +01:00
bool emptyImage = false;
2014-05-21 15:49:13 +02:00
if (imageType == ImageType::SVG) {
2013-01-11 18:10:18 +01:00
if (!svgDoc)
emptyImage = true;
else
svgDoc->render(painter, bbox());
}
2014-05-21 15:49:13 +02:00
else if (imageType == ImageType::RASTER) {
2013-12-10 17:16:38 +01:00
if (rasterDoc == nullptr)
emptyImage = true;
2013-01-11 18:10:18 +01:00
else {
2013-12-10 17:16:38 +01:00
painter->save();
QSizeF s;
if (_sizeIsSpatium)
s = _size * spatium();
2013-01-11 18:10:18 +01:00
else
2015-11-16 14:24:47 +01:00
s = _size * DPMM;
2013-12-10 17:16:38 +01:00
if (score()->printing()) {
// use original image size for printing
painter->scale(s.width() / rasterDoc->width(), s.height() / rasterDoc->height());
painter->drawPixmap(QPointF(0, 0), QPixmap::fromImage(*rasterDoc));
}
else {
QTransform t = painter->transform();
QSize ss = QSizeF(s.width() * t.m11(), s.height() * t.m22()).toSize();
t.setMatrix(1.0, t.m12(), t.m13(), t.m21(), 1.0, t.m23(), t.m31(), t.m32(), t.m33());
painter->setWorldTransform(t);
if ((buffer.size() != ss || _dirty) && rasterDoc && !rasterDoc->isNull()) {
buffer = QPixmap::fromImage(rasterDoc->scaled(ss, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
_dirty = false;
}
if (buffer.isNull())
emptyImage = true;
else
painter->drawPixmap(QPointF(0.0, 0.0), buffer);
}
painter->restore();
2013-01-11 18:10:18 +01:00
}
}
if (emptyImage) {
2012-05-26 14:26:10 +02:00
painter->setBrush(Qt::NoBrush);
painter->setPen(Qt::black);
2013-01-11 18:10:18 +01:00
painter->drawRect(bbox());
painter->drawLine(0.0, 0.0, bbox().width(), bbox().height());
painter->drawLine(bbox().width(), 0.0, 0.0, bbox().height());
2012-05-26 14:26:10 +02:00
}
if (selected() && !(score() && score()->printing())) {
painter->setBrush(Qt::NoBrush);
2013-07-04 21:07:38 +02:00
painter->setPen(MScore::selectColor[0]);
2013-01-11 18:10:18 +01:00
painter->drawRect(bbox());
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Image::write(Xml& xml) const
{
// attempt to convert the _linkPath to a path relative to the score
//
2016-03-10 10:41:31 +01:00
// TODO : on Save As, score()->fileInfo() still contains the old path and fname
// if the Save As path is different, image relative path will be wrong!
//
QString relativeFilePath= QString();
2013-01-23 14:14:09 +01:00
if (!_linkPath.isEmpty() && _linkIsValid) {
QFileInfo fi(_linkPath);
2016-03-10 10:41:31 +01:00
// score()->fileInfo()->canonicalPath() would be better
// but we are saving under a temp file name and the 'final' file
// might not exist yet, so canonicalFilePath() may return only "/"
// OTOH, the score 'final' file name is practically always canonical, at this point
2016-03-10 10:41:31 +01:00
QString scorePath = score()->masterScore()->fileInfo()->absolutePath();
QString imgFPath = fi.canonicalFilePath();
// if imgFPath is in (or below) the directory of scorePath
2013-01-23 14:14:09 +01:00
if (imgFPath.startsWith(scorePath, Qt::CaseSensitive)) {
// relative img path is the part exceeding scorePath
imgFPath.remove(0, scorePath.size());
if(imgFPath.startsWith('/'))
imgFPath.remove(0, 1);
relativeFilePath = imgFPath;
}
// try 1 level up
else {
// reduce scorePath by one path level
fi.setFile(scorePath);
scorePath = fi.path();
// if imgFPath is in (or below) the directory up the score directory
2013-01-23 14:14:09 +01:00
if (imgFPath.startsWith(scorePath, Qt::CaseSensitive)) {
// relative img path is the part exceeding new scorePath plus "../"
imgFPath.remove(0, scorePath.size());
2013-01-23 14:14:09 +01:00
if (!imgFPath.startsWith('/'))
imgFPath.prepend('/');
imgFPath.prepend("..");
relativeFilePath = imgFPath;
}
}
}
// if no match, use full _linkPath
2013-01-23 14:14:09 +01:00
if (relativeFilePath.isEmpty())
relativeFilePath = _linkPath;
2012-05-26 14:26:10 +02:00
xml.stag("Image");
2013-01-23 14:14:09 +01:00
BSymbol::writeProperties(xml);
// keep old "path" tag, for backward compatibility and because it is used elsewhere
// (for instance by Box:read(), Measure:read(), Note:read(), ...)
xml.tag("path", _storeItem ? _storeItem->hashName() : relativeFilePath);
xml.tag("linkPath", relativeFilePath);
2012-05-26 14:26:10 +02:00
2014-05-26 18:18:01 +02:00
writeProperty(xml, P_ID::AUTOSCALE);
writeProperty(xml, P_ID::SIZE);
writeProperty(xml, P_ID::LOCK_ASPECT_RATIO);
writeProperty(xml, P_ID::SIZE_IS_SPATIUM);
2012-05-26 14:26:10 +02:00
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
2013-01-11 18:10:18 +01:00
void Image::read(XmlReader& e)
2012-05-26 14:26:10 +02:00
{
if (score()->mscVersion() <= 114)
2012-05-26 14:26:10 +02:00
_sizeIsSpatium = false;
2013-01-11 18:10:18 +01:00
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
2012-05-26 14:26:10 +02:00
if (tag == "autoScale")
2014-05-26 18:18:01 +02:00
setProperty(P_ID::AUTOSCALE, Ms::getProperty(P_ID::AUTOSCALE, e));
2012-05-26 14:26:10 +02:00
else if (tag == "size")
2014-05-26 18:18:01 +02:00
setProperty(P_ID::SIZE, Ms::getProperty(P_ID::SIZE, e));
2012-05-26 14:26:10 +02:00
else if (tag == "lockAspectRatio")
2014-05-26 18:18:01 +02:00
setProperty(P_ID::LOCK_ASPECT_RATIO, Ms::getProperty(P_ID::LOCK_ASPECT_RATIO, e));
2012-05-26 14:26:10 +02:00
else if (tag == "sizeIsSpatium")
2014-05-26 18:18:01 +02:00
setProperty(P_ID::SIZE_IS_SPATIUM, Ms::getProperty(P_ID::SIZE_IS_SPATIUM, e));
else if (tag == "path")
2013-01-11 18:10:18 +01:00
_storePath = e.readElementText();
else if (tag == "linkPath")
_linkPath = e.readElementText();
else if (tag == "subtype") // obsolete
e.skipCurrentElement();
2013-01-23 14:14:09 +01:00
else if (!BSymbol::readProperties(e))
2013-01-11 18:10:18 +01:00
e.unknown();
2012-05-26 14:26:10 +02:00
}
// once all paths are read, load img or retrieve it from store
// loading from file is tried first to update the stored image, if necessary
2013-01-11 18:10:18 +01:00
qDebug("linkPath <%s>", qPrintable(_linkPath));
qDebug("storePath <%s>", qPrintable(_storePath));
QString path;
bool loaded = false;
// if a store path is given, attempt to get the image from the store
if (!_storePath.isEmpty()) {
_storeItem = imageStore.getImage(_storePath);
2013-01-11 18:10:18 +01:00
if (_storeItem) {
_storeItem->reference(this);
loaded = true;
2013-01-11 18:10:18 +01:00
}
// if no image in store, attempt to load from path (for backward compatibility)
else
loaded = load(_storePath);
2013-01-11 18:10:18 +01:00
path = _storePath;
}
// if no succes from store path, attempt loading from link path (for .mscx files)
if (!loaded) {
_linkIsValid = load(_linkPath);
2013-01-11 18:10:18 +01:00
path = _linkPath;
}
2013-01-11 18:10:18 +01:00
if (path.endsWith(".svg"))
2014-05-21 15:49:13 +02:00
setImageType(ImageType::SVG);
2013-01-11 18:10:18 +01:00
else
2014-05-21 15:49:13 +02:00
setImageType(ImageType::RASTER);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// load
// load image from file and put into ImageStore
// return true on success
//---------------------------------------------------------
bool Image::load(const QString& ss)
{
2013-01-11 18:10:18 +01:00
qDebug("Image::load <%s>", qPrintable(ss));
QString path(ss);
// if file path is relative, prepend score path
QFileInfo fi(path);
2013-01-11 18:10:18 +01:00
if (fi.isRelative()) {
2016-03-10 10:41:31 +01:00
path.prepend(score()->masterScore()->fileInfo()->absolutePath() + "/");
fi.setFile(path);
2013-01-11 18:10:18 +01:00
}
_linkIsValid = false; // assume link fname is invalid
QFile f(path);
2013-01-11 18:10:18 +01:00
if (!f.open(QIODevice::ReadOnly)) {
qDebug("Image::load<%s> failed", qPrintable(path));
2012-05-26 14:26:10 +02:00
return false;
2013-01-11 18:10:18 +01:00
}
2012-05-26 14:26:10 +02:00
QByteArray ba = f.readAll();
f.close();
_linkIsValid = true;
_linkPath = fi.canonicalFilePath();
_storeItem = imageStore.add(_linkPath, ba);
2012-11-01 00:40:48 +01:00
_storeItem->reference(this);
2013-07-04 09:45:21 +02:00
if (path.endsWith(".svg"))
2014-05-21 15:49:13 +02:00
setImageType(ImageType::SVG);
2013-07-04 09:45:21 +02:00
else
2014-05-21 15:49:13 +02:00
setImageType(ImageType::RASTER);
2012-05-26 14:26:10 +02:00
return true;
}
2014-10-16 18:12:58 +02:00
//---------------------------------------------------------
// loadFromData
// load image from data and put into ImageStore
// return true on success
//---------------------------------------------------------
bool Image::loadFromData(const QString& ss, const QByteArray& ba)
{
qDebug("Image::loadFromData <%s>", qPrintable(ss));
_linkIsValid = false;
_linkPath = "";
_storeItem = imageStore.add(ss, ba);
_storeItem->reference(this);
if (ss.endsWith(".svg"))
setImageType(ImageType::SVG);
else
setImageType(ImageType::RASTER);
return true;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// editDrag
//---------------------------------------------------------
void Image::editDrag(const EditData& ed)
{
qreal ratio = _size.width() / _size.height();
qreal dx = ed.delta.x();
qreal dy = ed.delta.y();
if (_sizeIsSpatium) {
qreal _spatium = spatium();
dx /= _spatium;
dy /= _spatium;
}
else {
2015-11-16 14:24:47 +01:00
dx /= DPMM;
dy /= DPMM;
2012-05-26 14:26:10 +02:00
}
if (ed.curGrip == Grip::START) {
2012-05-26 14:26:10 +02:00
_size.setWidth(_size.width() + dx);
if (_lockAspectRatio)
_size.setHeight(_size.width() / ratio);
}
else {
_size.setHeight(_size.height() + dy);
if (_lockAspectRatio)
_size.setWidth(_size.height() * ratio);
}
layout();
}
//---------------------------------------------------------
// updateGrips
//---------------------------------------------------------
void Image::updateGrips(Grip* defaultGrip, QVector<QRectF>& grip) const
2012-05-26 14:26:10 +02:00
{
*defaultGrip = Grip(1);
2012-05-26 14:26:10 +02:00
QRectF r(pageBoundingRect());
grip[0].translate(QPointF(r.x() + r.width(), r.y() + r.height() * .5));
grip[1].translate(QPointF(r.x() + r.width() * .5, r.y() + r.height()));
}
//---------------------------------------------------------
// layout
//---------------------------------------------------------
2013-01-11 18:10:18 +01:00
void Image::layout()
2012-05-26 14:26:10 +02:00
{
2014-11-01 14:03:03 +01:00
setPos(0.0, 0.0);
2014-05-21 15:49:13 +02:00
if (imageType == ImageType::SVG && !svgDoc) {
2012-05-26 14:26:10 +02:00
if (_storeItem) {
2013-01-11 18:10:18 +01:00
svgDoc = new QSvgRenderer(_storeItem->buffer());
if (svgDoc->isValid()) {
2012-05-26 14:26:10 +02:00
if (_size.isNull()) {
2013-01-11 18:10:18 +01:00
_size = svgDoc->defaultSize();
2012-05-26 14:26:10 +02:00
if (_sizeIsSpatium)
_size /= 10.0; // by convention
}
}
}
}
2014-05-21 15:49:13 +02:00
else if (imageType == ImageType::RASTER && !rasterDoc) {
2012-05-26 14:26:10 +02:00
if (_storeItem) {
2013-01-11 18:10:18 +01:00
rasterDoc = new QImage;
rasterDoc->loadFromData(_storeItem->buffer());
if (!rasterDoc->isNull()) {
2012-05-26 14:26:10 +02:00
if (_size.isNull()) {
2013-01-11 18:10:18 +01:00
_size = rasterDoc->size() * 0.4;
2012-05-26 14:26:10 +02:00
if (_sizeIsSpatium)
_size /= spatium();
else
2015-11-16 14:24:47 +01:00
_size /= DPMM;
2012-05-26 14:26:10 +02:00
}
_dirty = true;
}
}
}
2015-11-16 14:24:47 +01:00
qreal f = _sizeIsSpatium ? spatium() : DPMM;
2012-05-26 14:26:10 +02:00
// if autoscale && inside a box, scale to box relevant size
if (autoScale() && parent() && ((parent()->type() == Element::Type::HBOX || parent()->type() == Element::Type::VBOX))) {
2012-05-26 14:26:10 +02:00
if (_lockAspectRatio) {
QSizeF size(imageSize());
qreal ratio = size.width() / size.height();
qreal w = parent()->width();
qreal h = parent()->height();
if ((w / h) < ratio) {
_size.setWidth(w / f);
_size.setHeight((w / ratio) / f);
}
else {
_size.setHeight(h / f);
_size.setWidth(h * ratio / f);
}
}
else
_size = parent()->bbox().size() / f;
}
// in any case, adjust position relative to parent
adjustReadPos();
2013-01-02 09:29:17 +01:00
bbox().setRect(0.0, 0.0, _size.width() * f, _size.height() * f);
2012-05-26 14:26:10 +02:00
}
2013-05-13 18:49:17 +02:00
}