//=============================================================================
//  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"
 
namespace Ms {
 
//---------------------------------------------------------
//   propertyList
//---------------------------------------------------------
 
static bool defaultAutoScale        = false;
static bool defaultLockAspectRatio  = true;
static bool defaultSizeIsSpatium    = true;
 
//---------------------------------------------------------
//   Image
//---------------------------------------------------------
 
Image::Image(Score* s)
   : BSymbol(s)
      {
      imageType        = ImageType::NONE;
      rasterDoc        = 0;
      _size            = QSizeF(0, 0);
      _storeItem       = 0;
      _dirty           = false;
      _lockAspectRatio = defaultLockAspectRatio;
      _autoScale       = defaultAutoScale;
      _sizeIsSpatium   = defaultSizeIsSpatium;
      _linkIsValid     = false;
      }
 
Image::Image(const Image& img)
   : BSymbol(img)
      {
      imageType        = img.imageType;
      buffer           = img.buffer;
      _size            = img._size;
      _lockAspectRatio = img._lockAspectRatio;
      _autoScale       = img._autoScale;
      _dirty           = img._dirty;
      _storeItem       = img._storeItem;
      _sizeIsSpatium   = img._sizeIsSpatium;
      if (_storeItem)
            _storeItem->reference(this);
      _linkPath        = img._linkPath;
      _linkIsValid     = img._linkIsValid;
      if (imageType == ImageType::RASTER)
            rasterDoc = img.rasterDoc ? new QImage(*img.rasterDoc) : 0;
      else if (imageType == ImageType::SVG)
            svgDoc = img.svgDoc ? new QSvgRenderer(_storeItem->buffer()) : 0;
      setZ(img.z());
      }
 
//---------------------------------------------------------
//   Image
//---------------------------------------------------------
 
Image::~Image()
      {
      if (_storeItem)
            _storeItem->dereference(this);
      if (imageType == ImageType::SVG)
            delete svgDoc;
      else if (imageType == ImageType::RASTER)
            delete rasterDoc;
      }
 
//---------------------------------------------------------
//   setImageType
//---------------------------------------------------------
 
void Image::setImageType(ImageType t)
      {
      imageType = t;
      if (imageType == ImageType::SVG)
            svgDoc = 0;
      else if (imageType == ImageType::RASTER)
            rasterDoc = 0;
      else
            qDebug("illegal image type");
      }
 
//---------------------------------------------------------
//   imageSize
//---------------------------------------------------------
 
QSizeF Image::imageSize() const
      {
      if (!isValid())
            return QSizeF();
      return imageType == ImageType::RASTER ? rasterDoc->size() : svgDoc->defaultSize();
      }
 
//---------------------------------------------------------
//   draw
//---------------------------------------------------------
 
void Image::draw(QPainter* painter) const
      {
      bool emptyImage = false;
      if (imageType == ImageType::SVG) {
            if (!svgDoc)
                  emptyImage = true;
            else
                  svgDoc->render(painter, bbox());
            }
      else if (imageType == ImageType::RASTER) {
            if (rasterDoc == nullptr)
                  emptyImage = true;
            else {
                  painter->save();
                  QSizeF s;
                  if (_sizeIsSpatium)
                        s = _size * spatium();
                  else
                        s = _size * DPMM;
                  if (score()->printing() && !MScore::svgPrinting) {
                        // use original image size for printing, but not for svg for reasonable file size.
                        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();
                  }
            }
      if (emptyImage) {
            painter->setBrush(Qt::NoBrush);
            painter->setPen(Qt::black);
            painter->drawRect(bbox());
            painter->drawLine(0.0, 0.0, bbox().width(), bbox().height());
            painter->drawLine(bbox().width(), 0.0, 0.0, bbox().height());
            }
      if (selected() && !(score() && score()->printing())) {
            painter->setBrush(Qt::NoBrush);
            painter->setPen(MScore::selectColor[0]);
            painter->drawRect(bbox());
            }
      }
 
//---------------------------------------------------------
//   write
//---------------------------------------------------------
 
void Image::write(XmlWriter& xml) const
      {
      // attempt to convert the _linkPath to a path relative to the score
      //
      // 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();
      if (!_linkPath.isEmpty() && _linkIsValid) {
            QFileInfo fi(_linkPath);
            // 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
            QString scorePath = score()->masterScore()->fileInfo()->absolutePath();
            QString imgFPath  = fi.canonicalFilePath();
            // if imgFPath is in (or below) the directory of scorePath
            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
                  if (imgFPath.startsWith(scorePath, Qt::CaseSensitive)) {
                        // relative img path is the part exceeding new scorePath plus "../"
                        imgFPath.remove(0, scorePath.size());
                        if (!imgFPath.startsWith('/'))
                              imgFPath.prepend('/');
                        imgFPath.prepend("..");
                        relativeFilePath = imgFPath;
                        }
                  }
            }
      // if no match, use full _linkPath
      if (relativeFilePath.isEmpty())
            relativeFilePath = _linkPath;
 
      xml.stag("Image");
      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);
 
      writeProperty(xml, P_ID::AUTOSCALE);
      writeProperty(xml, P_ID::SIZE);
      writeProperty(xml, P_ID::LOCK_ASPECT_RATIO);
      writeProperty(xml, P_ID::SIZE_IS_SPATIUM);
 
      xml.etag();
      }
 
//---------------------------------------------------------
//   read
//---------------------------------------------------------
 
void Image::read(XmlReader& e)
      {
      if (score()->mscVersion() <= 114)
            _sizeIsSpatium = false;
 
      while (e.readNextStartElement()) {
            const QStringRef& tag(e.name());
            if (tag == "autoScale")
                  setProperty(P_ID::AUTOSCALE, Ms::getProperty(P_ID::AUTOSCALE, e));
            else if (tag == "size")
                  setProperty(P_ID::SIZE, Ms::getProperty(P_ID::SIZE, e));
            else if (tag == "lockAspectRatio")
                  setProperty(P_ID::LOCK_ASPECT_RATIO, Ms::getProperty(P_ID::LOCK_ASPECT_RATIO, e));
            else if (tag == "sizeIsSpatium")
                  setProperty(P_ID::SIZE_IS_SPATIUM, Ms::getProperty(P_ID::SIZE_IS_SPATIUM, e));
            else if (tag == "path")
                  _storePath = e.readElementText();
            else if (tag == "linkPath")
                  _linkPath = e.readElementText();
            else if (tag == "subtype")    // obsolete
                  e.skipCurrentElement();
            else if (!BSymbol::readProperties(e))
                  e.unknown();
            }
 
      // 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
 
      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);
            if (_storeItem) {
                  _storeItem->reference(this);
                  loaded = true;
                  }
            // if no image in store, attempt to load from path (for backward compatibility)
            else
                  loaded = load(_storePath);
            path = _storePath;
            }
      // if no succes from store path, attempt loading from link path (for .mscx files)
      if (!loaded) {
            _linkIsValid = load(_linkPath);
            path = _linkPath;
            }
 
      if (path.endsWith(".svg"))
            setImageType(ImageType::SVG);
      else
            setImageType(ImageType::RASTER);
      }
 
//---------------------------------------------------------
//   load
//    load image from file and put into ImageStore
//    return true on success
//---------------------------------------------------------
 
bool Image::load(const QString& ss)
      {
      qDebug("Image::load <%s>", qPrintable(ss));
      QString path(ss);
      // if file path is relative, prepend score path
      QFileInfo fi(path);
      if (fi.isRelative()) {
            path.prepend(score()->masterScore()->fileInfo()->absolutePath() + "/");
            fi.setFile(path);
            }
 
      _linkIsValid = false;                     // assume link fname is invalid
      QFile f(path);
      if (!f.open(QIODevice::ReadOnly)) {
            qDebug("Image::load<%s> failed", qPrintable(path));
            return false;
            }
      QByteArray ba = f.readAll();
      f.close();
 
      _linkIsValid = true;
      _linkPath = fi.canonicalFilePath();
      _storeItem = imageStore.add(_linkPath, ba);
      _storeItem->reference(this);
      if (path.endsWith(".svg"))
            setImageType(ImageType::SVG);
      else
            setImageType(ImageType::RASTER);
      return true;
      }
 
//---------------------------------------------------------
//   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;
      }
 
//---------------------------------------------------------
//   ImageEditData
//---------------------------------------------------------
 
class ImageEditData : public ElementEditData {
   public:
      QSizeF size;
      };
 
//---------------------------------------------------------
//   startDrag
//---------------------------------------------------------
 
void Image::startDrag(EditData& data)
      {
      ImageEditData* ed = new ImageEditData();
      ed->e    = this;
      ed->size = _size;
      data.addData(ed);
      }
 
//---------------------------------------------------------
//   editDrag
//---------------------------------------------------------
 
void Image::editDrag(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 {
            dx /= DPMM;
            dy /= DPMM;
            }
      if (ed.curGrip == Grip::START) {
            _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();
      }
 
//---------------------------------------------------------
//   endEditDrag
//---------------------------------------------------------
 
void Image::endEditDrag(EditData& ed)
      {
      ImageEditData* ied = static_cast<ImageEditData*>(ed.getData(this));
      if (_size != ied->size)
            score()->undoPropertyChanged(this, P_ID::SIZE, ied->size);
      }
 
//---------------------------------------------------------
//   updateGrips
//---------------------------------------------------------
 
void Image::updateGrips(EditData& ed) const
      {
      QRectF r(pageBoundingRect());
      ed.grip[0].translate(QPointF(r.x() + r.width(), r.y() + r.height() * .5));
      ed.grip[1].translate(QPointF(r.x() + r.width() * .5, r.y() + r.height()));
      }
 
//---------------------------------------------------------
//   pixel2Size
//---------------------------------------------------------
 
QSizeF Image::pixel2size(const QSizeF& s) const
      {
      return s / (_sizeIsSpatium ? spatium() : DPMM);
      }
 
//---------------------------------------------------------
//   size2pixel
//---------------------------------------------------------
 
QSizeF Image::size2pixel(const QSizeF& s) const
      {
      return s * (_sizeIsSpatium ? spatium() : DPMM);
      }
 
//---------------------------------------------------------
//   layout
//---------------------------------------------------------
 
void Image::layout()
      {
      setPos(0.0, 0.0);
      if (imageType == ImageType::SVG && !svgDoc) {
            if (_storeItem)
                  svgDoc = new QSvgRenderer(_storeItem->buffer());
            }
      else if (imageType == ImageType::RASTER && !rasterDoc) {
            if (_storeItem) {
                  rasterDoc = new QImage;
                  rasterDoc->loadFromData(_storeItem->buffer());
                  if (!rasterDoc->isNull())
                        _dirty = true;
                  }
            }
      if (_size.isNull())
            _size = pixel2size(imageSize());
 
      // if autoscale && inside a box, scale to box relevant size
      if (autoScale() && parent() && ((parent()->isHBox() || parent()->isVBox()))) {
            if (_lockAspectRatio) {
                  qreal f = _sizeIsSpatium ? spatium() : DPMM;
                  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 = pixel2size(parent()->bbox().size());
            }
 
      // in any case, adjust position relative to parent
      adjustReadPos();
      setbbox(QRectF(QPointF(), size2pixel(_size)));
      }
 
//---------------------------------------------------------
//   getProperty
//---------------------------------------------------------
 
QVariant Image::getProperty(P_ID propertyId) const
      {
      switch(propertyId) {
            case P_ID::AUTOSCALE:
                  return autoScale();
            case P_ID::SIZE:
                  return size();
            case P_ID::LOCK_ASPECT_RATIO:
                  return lockAspectRatio();
            case P_ID::SIZE_IS_SPATIUM:
                  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) {
            case P_ID::AUTOSCALE:
                  setAutoScale(v.toBool());
                  break;
            case P_ID::SIZE:
                  setSize(v.toSizeF());
                  break;
            case P_ID::LOCK_ASPECT_RATIO:
                  setLockAspectRatio(v.toBool());
                  break;
            case P_ID::SIZE_IS_SPATIUM:
                  {
                  QSizeF s = size2pixel(_size);
                  setSizeIsSpatium(v.toBool());
                  _size = pixel2size(s);
                  }
                  break;
            default:
                  rv = Element::setProperty(propertyId, v);
                  break;
            }
      setGenerated(false);
      _dirty = true;
      triggerLayout();
      return rv;
      }
 
//---------------------------------------------------------
//   propertyDefault
//---------------------------------------------------------
 
QVariant Image::propertyDefault(P_ID id) const
      {
      switch(id) {
            case P_ID::AUTOSCALE:
                  return defaultAutoScale;
            case P_ID::SIZE:
                  return pixel2size(imageSize());
            case P_ID::LOCK_ASPECT_RATIO:
                  return defaultLockAspectRatio;
            case P_ID::SIZE_IS_SPATIUM:
                  return defaultSizeIsSpatium;
            default:
                  return Element::propertyDefault(id);
            }
      return QVariant();
      }
 
//---------------------------------------------------------
//   startEdit
//---------------------------------------------------------
 
void Image::startEdit(EditData& ed)
      {
      ed.grips   = 2;
      ed.curGrip = Grip(1);
      }
 
}
 

V595 The 'score()' pointer was utilized before it was verified against nullptr. Check lines: 133, 162.