Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "image.h"
24 :
25 : #include "io/file.h"
26 : #include "io/fileinfo.h"
27 :
28 : #include "draw/iimageprovider.h" // IWYU pragma: keep
29 : #include "draw/types/pixmap.h"
30 : #include "draw/svgrenderer.h"
31 :
32 : #include "../editing/editdata.h"
33 : #include "../editing/elementeditdata.h"
34 :
35 : #include "imageStore.h"
36 : #include "masterscore.h"
37 : #include "mscore.h"
38 : #include "score.h"
39 :
40 : #include "log.h"
41 :
42 : using namespace mu;
43 : using namespace muse::io;
44 : using namespace muse::draw;
45 : using namespace mu::engraving;
46 :
47 : namespace mu::engraving {
48 : //---------------------------------------------------------
49 : // propertyList
50 : //---------------------------------------------------------
51 :
52 : static bool defaultAutoScale = false;
53 : static bool defaultLockAspectRatio = true;
54 : static bool defaultSizeIsSpatium = true;
55 :
56 : //---------------------------------------------------------
57 : // Image
58 : //---------------------------------------------------------
59 :
60 24 : Image::Image(DummyParentOr<EngravingItem> parent)
61 24 : : BSymbol(ElementType::IMAGE, parent, ElementFlag::MOVABLE)
62 : {
63 24 : m_imageType = ImageType::NONE;
64 24 : m_size = SizeF(0.0, 0.0);
65 24 : m_storeItem = 0;
66 24 : m_dirty = false;
67 24 : m_lockAspectRatio = defaultLockAspectRatio;
68 24 : m_autoScale = defaultAutoScale;
69 24 : m_sizeIsSpatium = defaultSizeIsSpatium;
70 24 : }
71 :
72 6 : Image::Image(const Image& img)
73 6 : : BSymbol(img)
74 : {
75 6 : m_imageType = img.m_imageType;
76 6 : m_buffer = img.m_buffer;
77 6 : m_size = img.m_size;
78 6 : m_lockAspectRatio = img.m_lockAspectRatio;
79 6 : m_autoScale = img.m_autoScale;
80 6 : m_dirty = img.m_dirty;
81 6 : m_storeItem = img.m_storeItem;
82 6 : m_sizeIsSpatium = img.m_sizeIsSpatium;
83 6 : if (m_storeItem) {
84 3 : m_storeItem->reference(this);
85 : }
86 6 : if (m_imageType == ImageType::RASTER) {
87 3 : m_rasterDoc = img.m_rasterDoc ? std::make_shared<Pixmap>(*img.m_rasterDoc) : nullptr;
88 3 : } else if (m_imageType == ImageType::SVG) {
89 0 : m_svgDoc = img.m_svgDoc ? new SvgRenderer(m_storeItem->buffer()) : 0;
90 : }
91 6 : setZ(img.z());
92 6 : }
93 :
94 : //---------------------------------------------------------
95 : // Image
96 : //---------------------------------------------------------
97 :
98 60 : Image::~Image()
99 : {
100 30 : if (m_storeItem) {
101 24 : m_storeItem->dereference(this);
102 : }
103 30 : if (m_imageType == ImageType::SVG) {
104 0 : delete m_svgDoc;
105 : }
106 60 : }
107 :
108 : //---------------------------------------------------------
109 : // setImageType
110 : //---------------------------------------------------------
111 :
112 21 : void Image::setImageType(ImageType t)
113 : {
114 21 : m_imageType = t;
115 21 : if (m_imageType == ImageType::SVG) {
116 0 : m_svgDoc = 0;
117 21 : } else if (m_imageType == ImageType::RASTER) {
118 21 : m_rasterDoc.reset();
119 : } else {
120 0 : LOGD("illegal image type");
121 : }
122 21 : }
123 :
124 : //---------------------------------------------------------
125 : // imageSize
126 : //---------------------------------------------------------
127 :
128 34 : muse::SizeF Image::imageSize() const
129 : {
130 34 : if (!isValid()) {
131 8 : return muse::SizeF();
132 : }
133 :
134 26 : if (m_imageType == ImageType::RASTER) {
135 26 : muse::Size rasterSize = m_rasterDoc->size();
136 26 : return muse::SizeF(rasterSize.width(), rasterSize.height());
137 : }
138 :
139 0 : return m_svgDoc->defaultSize();
140 : }
141 :
142 : //---------------------------------------------------------
143 : // isImageFramed
144 : //---------------------------------------------------------
145 :
146 0 : bool Image::isImageFramed() const
147 : {
148 0 : if (!ownershipParent()) {
149 0 : return false;
150 : }
151 :
152 0 : return ownershipParent()->isBox();
153 : }
154 :
155 : //---------------------------------------------------------
156 : // imageAspectRatio
157 : //---------------------------------------------------------
158 :
159 0 : double Image::imageAspectRatio() const
160 : {
161 0 : return m_size.width() / m_size.height();
162 : }
163 :
164 : //---------------------------------------------------------
165 : // setImageHeight
166 : //---------------------------------------------------------
167 :
168 0 : void Image::updateImageHeight(const double& height)
169 : {
170 0 : double aspectRatio = imageAspectRatio();
171 :
172 0 : m_size.setHeight(height);
173 :
174 0 : if (m_lockAspectRatio) {
175 0 : m_size.setWidth(height * aspectRatio);
176 : }
177 0 : }
178 :
179 : //---------------------------------------------------------
180 : // setImageWidth
181 : //---------------------------------------------------------
182 :
183 0 : void Image::updateImageWidth(const double& width)
184 : {
185 0 : double aspectRatio = imageAspectRatio();
186 :
187 0 : m_size.setWidth(width);
188 :
189 0 : if (m_lockAspectRatio) {
190 0 : m_size.setHeight(width / aspectRatio);
191 : }
192 0 : }
193 :
194 : //---------------------------------------------------------
195 : // imageHeight
196 : //---------------------------------------------------------
197 :
198 0 : double Image::imageHeight() const
199 : {
200 0 : return m_size.height();
201 : }
202 :
203 : //---------------------------------------------------------
204 : // imageWidth
205 : //---------------------------------------------------------
206 :
207 0 : double Image::imageWidth() const
208 : {
209 0 : return m_size.width();
210 : }
211 :
212 21 : bool Image::loadFromStore(const std::string& storePath)
213 : {
214 21 : m_storePath = storePath;
215 21 : if (storePath.empty()) {
216 0 : return false;
217 : }
218 :
219 21 : m_storeItem = imageStore.getImage(storePath);
220 21 : if (!m_storeItem) {
221 2 : return false;
222 : }
223 19 : m_storeItem->reference(this);
224 :
225 19 : std::string suffix = muse::io::suffix(storePath);
226 19 : if (suffix == "svg" || suffix == "svgz") {
227 0 : setImageType(ImageType::SVG);
228 : } else {
229 19 : setImageType(ImageType::RASTER);
230 : }
231 :
232 19 : return true;
233 19 : }
234 :
235 : //---------------------------------------------------------
236 : // loadFromFile
237 : // load image from file and put into ImageStore
238 : // return true on success
239 : //---------------------------------------------------------
240 :
241 5 : bool Image::loadFromFile(muse::io::path_t path)
242 : {
243 5 : if (path.empty()) {
244 0 : return false;
245 : }
246 :
247 5 : if (FileInfo(path).isRelative()) {
248 : // if file path is relative, prepend score path
249 5 : path = masterScore()->fileInfo()->absoluteDirPath() + '/' + path;
250 : }
251 :
252 5 : File f(path);
253 5 : if (!f.open(IODevice::ReadOnly)) {
254 3 : LOGD() << "failed load file: " << path;
255 3 : return false;
256 : }
257 2 : muse::ByteArray ba = f.readAll();
258 2 : f.close();
259 :
260 2 : m_storeItem = imageStore.add(path.toStdString(), ba);
261 2 : m_storeItem->reference(this);
262 2 : if (path.hasSuffix("svg") || path.hasSuffix("svgz")) {
263 0 : setImageType(ImageType::SVG);
264 : } else {
265 2 : setImageType(ImageType::RASTER);
266 : }
267 2 : return true;
268 5 : }
269 :
270 : //---------------------------------------------------------
271 : // loadFromData
272 : // load image from data and put into ImageStore
273 : // return true on success
274 : //---------------------------------------------------------
275 :
276 0 : bool Image::loadFromData(const std::string& name, const muse::ByteArray& ba)
277 : {
278 0 : m_storeItem = imageStore.add(name, ba);
279 0 : m_storeItem->reference(this);
280 :
281 0 : muse::io::path_t ioPath(name);
282 0 : if (ioPath.hasSuffix("svg") || ioPath.hasSuffix("svgz")) {
283 0 : setImageType(ImageType::SVG);
284 : } else {
285 0 : setImageType(ImageType::RASTER);
286 : }
287 0 : return true;
288 0 : }
289 :
290 : //---------------------------------------------------------
291 : // startDrag
292 : //---------------------------------------------------------
293 :
294 0 : void Image::startDragGrip(EditData& data)
295 : {
296 0 : BSymbol::startDragGrip(data);
297 0 : ElementEditDataPtr eed = data.getData(this);
298 :
299 0 : eed->pushProperty(Pid::SIZE);
300 0 : }
301 :
302 : //---------------------------------------------------------
303 : // dragGrip
304 : //---------------------------------------------------------
305 :
306 0 : void Image::dragGrip(EditData& ed)
307 : {
308 0 : if (ed.curGrip == Grip::MIDDLE) {
309 0 : setOffset(offset() + ed.evtDelta);
310 0 : triggerLayout();
311 0 : return;
312 : }
313 :
314 0 : double ratio = m_size.width() / m_size.height();
315 0 : double dx = ed.delta.x();
316 0 : double dy = ed.delta.y();
317 0 : if (m_sizeIsSpatium) {
318 0 : double _spatium = spatium();
319 0 : dx /= _spatium;
320 0 : dy /= _spatium;
321 : } else {
322 0 : dx /= DPMM;
323 0 : dy /= DPMM;
324 : }
325 0 : if (ed.curGrip == Grip::START) {
326 0 : m_size.setWidth(m_size.width() + dx);
327 0 : if (m_lockAspectRatio) {
328 0 : m_size.setHeight(m_size.width() / ratio);
329 : }
330 0 : } else if (ed.curGrip == Grip::END) {
331 0 : m_size.setHeight(m_size.height() + dy);
332 0 : if (m_lockAspectRatio) {
333 0 : m_size.setWidth(m_size.height() * ratio);
334 : }
335 : }
336 :
337 0 : triggerLayout();
338 : }
339 :
340 : //---------------------------------------------------------
341 : // gripsPositions
342 : //---------------------------------------------------------
343 :
344 0 : std::vector<PointF> Image::gripsPositions(const EditData&) const
345 : {
346 0 : RectF r(pageBoundingRect());
347 : return {
348 0 : PointF(r.x() + r.width(), r.y() + r.height() * .5),
349 0 : PointF(r.x() + r.width() * .5, r.y() + r.height())
350 0 : };
351 : }
352 :
353 : //---------------------------------------------------------
354 : // pixel2Size
355 : //---------------------------------------------------------
356 :
357 34 : SizeF Image::pixel2size(const SizeF& s) const
358 : {
359 34 : return s / (m_sizeIsSpatium ? spatium() : DPMM);
360 : }
361 :
362 : //---------------------------------------------------------
363 : // size2pixel
364 : //---------------------------------------------------------
365 :
366 60 : SizeF Image::size2pixel(const SizeF& s) const
367 : {
368 60 : return s * (m_sizeIsSpatium ? spatium() : DPMM);
369 : }
370 :
371 67 : void Image::init()
372 : {
373 67 : if (m_imageType == ImageType::SVG && !m_svgDoc) {
374 0 : if (m_storeItem) {
375 0 : m_svgDoc = new SvgRenderer(m_storeItem->buffer());
376 : }
377 67 : } else if (m_imageType == ImageType::RASTER && !m_rasterDoc) {
378 21 : if (m_storeItem) {
379 21 : m_rasterDoc = imageProvider()->createPixmap(m_storeItem->buffer());
380 21 : if (!m_rasterDoc->isNull()) {
381 21 : m_dirty = true;
382 : }
383 : }
384 : }
385 67 : if (m_size.isNull()) {
386 11 : m_size = pixel2size(imageSize());
387 : }
388 67 : }
389 :
390 : //---------------------------------------------------------
391 : // getProperty
392 : //---------------------------------------------------------
393 :
394 299 : PropertyValue Image::getProperty(Pid propertyId) const
395 : {
396 299 : switch (propertyId) {
397 23 : case Pid::AUTOSCALE:
398 23 : return autoScale();
399 23 : case Pid::SIZE:
400 23 : return PropertyValue::fromValue(size());
401 0 : case Pid::IMAGE_HEIGHT:
402 0 : return imageHeight();
403 0 : case Pid::IMAGE_WIDTH:
404 0 : return imageWidth();
405 0 : case Pid::IMAGE_FRAMED:
406 0 : return isImageFramed();
407 23 : case Pid::LOCK_ASPECT_RATIO:
408 23 : return lockAspectRatio();
409 23 : case Pid::SIZE_IS_SPATIUM:
410 23 : return sizeIsSpatium();
411 207 : default:
412 207 : return EngravingItem::getProperty(propertyId);
413 : }
414 : }
415 :
416 : //---------------------------------------------------------
417 : // setProperty
418 : //---------------------------------------------------------
419 :
420 14 : bool Image::setProperty(Pid propertyId, const PropertyValue& v)
421 : {
422 14 : bool rv = true;
423 14 : score()->addRefresh(canvasBoundingRect());
424 14 : switch (propertyId) {
425 0 : case Pid::AUTOSCALE:
426 0 : setAutoScale(v.toBool());
427 0 : break;
428 14 : case Pid::SIZE:
429 14 : setSize(v.value<SizeF>());
430 14 : break;
431 0 : case Pid::IMAGE_HEIGHT:
432 0 : updateImageHeight(v.toDouble());
433 0 : break;
434 0 : case Pid::IMAGE_WIDTH:
435 0 : updateImageWidth(v.toDouble());
436 0 : break;
437 0 : case Pid::IMAGE_FRAMED:
438 0 : break;
439 0 : case Pid::LOCK_ASPECT_RATIO:
440 0 : setLockAspectRatio(v.toBool());
441 0 : break;
442 0 : case Pid::SIZE_IS_SPATIUM: {
443 0 : SizeF s = size2pixel(m_size);
444 0 : setSizeIsSpatium(v.toBool());
445 0 : m_size = pixel2size(s);
446 : }
447 0 : break;
448 0 : default:
449 0 : rv = EngravingItem::setProperty(propertyId, v);
450 0 : break;
451 : }
452 14 : setGenerated(false);
453 14 : m_dirty = true;
454 14 : triggerLayout();
455 14 : return rv;
456 : }
457 :
458 : //---------------------------------------------------------
459 : // propertyDefault
460 : //---------------------------------------------------------
461 :
462 299 : PropertyValue Image::propertyDefault(Pid id) const
463 : {
464 299 : switch (id) {
465 23 : case Pid::AUTOSCALE:
466 23 : return defaultAutoScale;
467 23 : case Pid::SIZE:
468 23 : return PropertyValue::fromValue(pixel2size(imageSize()));
469 0 : case Pid::IMAGE_HEIGHT:
470 0 : return pixel2size(imageSize()).height();
471 0 : case Pid::IMAGE_WIDTH:
472 0 : return pixel2size(imageSize()).width();
473 0 : case Pid::IMAGE_FRAMED:
474 0 : return isImageFramed();
475 23 : case Pid::LOCK_ASPECT_RATIO:
476 23 : return defaultLockAspectRatio;
477 23 : case Pid::SIZE_IS_SPATIUM:
478 23 : return defaultSizeIsSpatium;
479 207 : default:
480 207 : return EngravingItem::propertyDefault(id);
481 : }
482 : }
483 : }
|