Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-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 : #include "drawdatacomp.h"
23 :
24 : #include <list>
25 :
26 : #include "global/realfn.h"
27 : #include "global/containers.h"
28 :
29 : #include "log.h"
30 :
31 : using namespace muse::draw;
32 :
33 : namespace muse::draw::comp {
34 : static const int DEFAULT_PREC(3);
35 :
36 : struct Polygon;
37 : static bool isEqual(const Polygon& p1, const Polygon& p2, DrawDataComp::Tolerance tolerance);
38 :
39 : struct Path {
40 : const DrawData::Item* obj = nullptr;
41 : const DrawData::Data* data = nullptr;
42 : const DrawPath* path = nullptr;
43 : };
44 :
45 : struct Polygon {
46 : const DrawData::Item* obj = nullptr;
47 : const DrawData::Data* data = nullptr;
48 : const DrawPolygon* polygon = nullptr;
49 : bool operator==(const Polygon& o) const { return isEqual(*this, o, DrawDataComp::Tolerance()); }
50 : };
51 :
52 : struct Text {
53 : const DrawData::Item* obj = nullptr;
54 : const DrawData::Data* data = nullptr;
55 : const DrawText* text = nullptr;
56 : };
57 :
58 : struct Pixmap {
59 : const DrawData::Item* obj = nullptr;
60 : const DrawData::Data* data = nullptr;
61 : const DrawPixmap* pixmap = nullptr;
62 : };
63 :
64 : struct Data {
65 : std::list<Path> paths;
66 : std::list<Polygon> polygons;
67 : std::list<Text> texts;
68 : std::list<Pixmap> pixmaps;
69 : };
70 :
71 : template<class T>
72 : static bool isEqual(const std::vector<T>& v1, const std::vector<T>& v2, DrawDataComp::Tolerance tolerance);
73 :
74 760 : static bool isEqual(const double& v1, const double& v2, double tolerance)
75 : {
76 760 : if (tolerance > 0) {
77 0 : double delta = RealFloor(v1, DEFAULT_PREC) - RealFloor(v2, DEFAULT_PREC);
78 0 : return std::fabs(delta) < tolerance;
79 : }
80 760 : return RealIsEqual(RealFloor(v1, DEFAULT_PREC), RealFloor(v2, DEFAULT_PREC));
81 : }
82 :
83 288 : static bool isEqual(const PointF& p1, const PointF& p2, double tolerance)
84 : {
85 288 : if (!isEqual(p1.x(), p2.x(), tolerance)) {
86 56 : return false;
87 : }
88 :
89 232 : if (!isEqual(p1.y(), p2.y(), tolerance)) {
90 168 : return false;
91 : }
92 :
93 64 : return true;
94 : }
95 :
96 54 : static bool isEqual(const RectF& r1, const RectF& r2, double tolerance)
97 : {
98 54 : if (!isEqual(r1.x(), r2.x(), tolerance)) {
99 0 : return false;
100 : }
101 :
102 54 : if (!isEqual(r1.y(), r2.y(), tolerance)) {
103 8 : return false;
104 : }
105 :
106 46 : if (!isEqual(r1.width(), r2.width(), tolerance)) {
107 0 : return false;
108 : }
109 :
110 46 : if (!isEqual(r1.height(), r2.height(), tolerance)) {
111 0 : return false;
112 : }
113 :
114 46 : return true;
115 : }
116 :
117 0 : static bool isEqual(const Size& sz1, const Size& sz2)
118 : {
119 0 : if (sz1.width() != sz2.width()) {
120 0 : return false;
121 : }
122 :
123 0 : if (sz1.height() != sz2.height()) {
124 0 : return false;
125 : }
126 :
127 0 : return true;
128 : }
129 :
130 4 : static bool isEqual(const Pen& p1, const Pen& p2)
131 : {
132 4 : if (p1.style() != p2.style()) {
133 0 : return false;
134 : }
135 :
136 4 : if (!RealIsEqual(p1.widthF(), p2.widthF())) {
137 0 : return false;
138 : }
139 :
140 4 : if (p1.color() != p2.color()) {
141 0 : return false;
142 : }
143 :
144 4 : return true;
145 : }
146 :
147 4 : static bool isEqual(const Brush& b1, const Brush& b2)
148 : {
149 4 : if (b1.style() != b2.style()) {
150 0 : return false;
151 : }
152 :
153 4 : if (b1.color() != b2.color()) {
154 0 : return false;
155 : }
156 :
157 4 : return true;
158 : }
159 :
160 : #if 0 // currently unused, disabled due to an MSVC compiler warning C4505
161 : static bool isEqual(const Font& f1, const Font& f2)
162 : {
163 : if (f1.family() != f2.family()) {
164 : return false;
165 : }
166 :
167 : if (!RealIsEqual(f1.pointSizeF(), f2.pointSizeF())) {
168 : return false;
169 : }
170 :
171 : if (f1.weight() != f2.weight()) {
172 : return false;
173 : }
174 :
175 : if (f1.italic() != f2.italic()) {
176 : return false;
177 : }
178 :
179 : return true;
180 : }
181 :
182 : static bool isEqual(const Transform& t1, const Transform& t2, double tolerance)
183 : {
184 : if (!isEqual(t1.m11(), t2.m11(), tolerance)) {
185 : return false;
186 : }
187 :
188 : if (!isEqual(t1.m12(), t2.m12(), tolerance)) {
189 : return false;
190 : }
191 :
192 : if (!isEqual(t1.m13(), t2.m13(), tolerance)) {
193 : return false;
194 : }
195 :
196 : if (!isEqual(t1.m21(), t2.m21(), tolerance)) {
197 : return false;
198 : }
199 :
200 : if (!isEqual(t1.m22(), t2.m22(), tolerance)) {
201 : return false;
202 : }
203 :
204 : if (!isEqual(t1.m23(), t2.m23(), tolerance)) {
205 : return false;
206 : }
207 :
208 : if (!isEqual(t1.m31(), t2.m31(), tolerance)) {
209 : return false;
210 : }
211 :
212 : if (!isEqual(t1.m32(), t2.m32(), tolerance)) {
213 : return false;
214 : }
215 :
216 : if (!isEqual(t1.m33(), t2.m33(), tolerance)) {
217 : return false;
218 : }
219 :
220 : return true;
221 : }
222 :
223 : static bool isEqual(const DrawData::State& s1, const DrawData::State& s2, DrawDataComp::Tolerance tolerance)
224 : {
225 : if (s1.isAntialiasing != s2.isAntialiasing) {
226 : return false;
227 : }
228 :
229 : if (s1.compositionMode != s2.compositionMode) {
230 : return false;
231 : }
232 :
233 : if (!isEqual(s1.pen, s2.pen)) {
234 : return false;
235 : }
236 :
237 : if (!isEqual(s1.brush, s2.brush)) {
238 : return false;
239 : }
240 :
241 : if (!isEqual(s1.font, s2.font)) {
242 : return false;
243 : }
244 :
245 : if (!isEqual(s1.transform, s2.transform, tolerance.base)) {
246 : return false;
247 : }
248 :
249 : return true;
250 : }
251 :
252 : #endif
253 :
254 4 : static bool isEqual(const PainterPath& v1, const PainterPath& v2, double tolerance)
255 : {
256 4 : if (v1.elementCount() != v2.elementCount()) {
257 0 : return false;
258 : }
259 :
260 24 : for (size_t i = 0; i < v1.elementCount(); ++i) {
261 20 : PainterPath::Element e1 = v1.elementAt(i);
262 20 : PainterPath::Element e2 = v2.elementAt(i);
263 :
264 20 : if (e1.type != e2.type) {
265 0 : return false;
266 : }
267 :
268 20 : if (!isEqual(e1.x, e2.x, tolerance)) {
269 0 : return false;
270 : }
271 :
272 20 : if (!isEqual(e1.y, e2.y, tolerance)) {
273 0 : return false;
274 : }
275 : }
276 :
277 4 : return true;
278 : }
279 :
280 4 : static bool isEqual(const DrawPath& v1, const DrawPath& v2, DrawDataComp::Tolerance tolerance)
281 : {
282 4 : if (v1.mode != v2.mode) {
283 0 : return false;
284 : }
285 :
286 4 : if (!isEqual(v1.path, v2.path, tolerance.base)) {
287 0 : return false;
288 : }
289 :
290 4 : if (!isEqual(v1.pen, v2.pen)) {
291 0 : return false;
292 : }
293 :
294 4 : if (!isEqual(v1.brush, v2.brush)) {
295 0 : return false;
296 : }
297 :
298 4 : return true;
299 : }
300 :
301 234 : static bool isEqual(const PolygonF& v1, const PolygonF& v2, double tolerance)
302 : {
303 234 : if (v1.size() != v2.size()) {
304 0 : return false;
305 : }
306 :
307 298 : for (size_t i = 0; i < v1.size(); ++i) {
308 288 : const PointF& p1 = v1.at(i);
309 288 : const PointF& p2 = v2.at(i);
310 :
311 288 : if (!isEqual(p1, p2, tolerance)) {
312 224 : return false;
313 : }
314 : }
315 :
316 10 : return true;
317 : }
318 :
319 234 : static bool isEqual(const DrawPolygon& v1, const DrawPolygon& v2, DrawDataComp::Tolerance tolerance)
320 : {
321 234 : if (v1.mode != v2.mode) {
322 0 : return false;
323 : }
324 :
325 234 : if (!isEqual(v1.polygon, v2.polygon, tolerance.base)) {
326 224 : return false;
327 : }
328 :
329 10 : return true;
330 : }
331 :
332 54 : static bool isEqual(const DrawText& v1, const DrawText& v2, DrawDataComp::Tolerance tolerance)
333 : {
334 54 : if (v1.mode != v2.mode) {
335 0 : return false;
336 : }
337 :
338 54 : if (!isEqual(v1.rect, v2.rect, tolerance.base)) {
339 8 : return false;
340 : }
341 :
342 46 : if (v1.alignment != v2.alignment) {
343 0 : return false;
344 : }
345 :
346 46 : if (v1.textFlags != v2.textFlags) {
347 0 : return false;
348 : }
349 :
350 46 : if (v1.text != v2.text) {
351 34 : return false;
352 : }
353 :
354 12 : return true;
355 : }
356 :
357 0 : static bool isEqual(const DrawPixmap& v1, const DrawPixmap& v2, DrawDataComp::Tolerance tolerance)
358 : {
359 0 : if (v1.mode != v2.mode) {
360 0 : return false;
361 : }
362 :
363 0 : if (!isEqual(v1.rect, v2.rect, tolerance.base)) {
364 0 : return false;
365 : }
366 :
367 0 : if (!isEqual(v1.pm.size(), v2.pm.size())) {
368 0 : return false;
369 : }
370 :
371 0 : if (!isEqual(v1.offset, v2.offset, tolerance.base)) {
372 0 : return false;
373 : }
374 :
375 0 : return true;
376 : }
377 :
378 : #if 0 // currently unused, disabled due to an MSVC compiler warning C4505
379 : static bool isEqual(const DrawData::Data& d1, const DrawData::Data& d2, DrawDataComp::Tolerance tolerance)
380 : {
381 : if (!isEqual(d1.state, d2.state, tolerance)) {
382 : return false;
383 : }
384 :
385 : if (!isEqual(d1.paths, d2.paths, tolerance)) {
386 : return false;
387 : }
388 :
389 : if (!isEqual(d1.polygons, d2.polygons, tolerance)) {
390 : return false;
391 : }
392 :
393 : if (!isEqual(d1.texts, d2.texts, tolerance)) {
394 : return false;
395 : }
396 :
397 : if (!isEqual(d1.pixmaps, d2.pixmaps, tolerance)) {
398 : return false;
399 : }
400 :
401 : return true;
402 : }
403 :
404 : #endif
405 :
406 : template<class T>
407 : static bool isEqual(const std::vector<T>& v1, const std::vector<T>& v2, DrawDataComp::Tolerance tolerance)
408 : {
409 : if (v1.size() != v2.size()) {
410 : return false;
411 : }
412 :
413 : for (size_t i = 0; i < v1.size(); ++i) {
414 : const T& vi1 = v1[i];
415 : const T& vi2 = v2[i];
416 :
417 : if (isEqual(vi1, vi2, tolerance)) {
418 : continue;
419 : } else {
420 : bool found = false;
421 : for (size_t j = 0; j < v2.size(); ++j) {
422 : const T& vj2 = v2[j];
423 :
424 : if (isEqual(vi1, vj2, tolerance)) {
425 : found = true;
426 : break;
427 : }
428 : }
429 :
430 : if (!found) {
431 : return false;
432 : }
433 : }
434 : }
435 : return true;
436 : }
437 :
438 : #if 0 // currently unused, disabled due to an MSVC compiler warning C4505
439 : static bool isEqual(const DrawData::Object& o1, const DrawData::Object& o2, DrawDataComp::Tolerance tolerance)
440 : {
441 : if (o1.name != o2.name) {
442 : return false;
443 : }
444 :
445 : if (!isEqual(o1.pagePos, o2.pagePos, tolerance.base)) {
446 : return false;
447 : }
448 :
449 : if (!isEqual(o1.datas, o2.datas, tolerance)) {
450 : return false;
451 : }
452 :
453 : return true;
454 : }
455 :
456 : #endif
457 :
458 4 : static bool isEqual(const Path& p1, const Path& p2, DrawDataComp::Tolerance tolerance)
459 : {
460 4 : if (p1.obj->name != p2.obj->name) {
461 0 : return false;
462 : }
463 :
464 4 : if (p1.data->state != p2.data->state) {
465 0 : return false;
466 : }
467 :
468 4 : return isEqual(*p1.path, *p2.path, tolerance);
469 : }
470 :
471 3378 : static bool isEqual(const Polygon& p1, const Polygon& p2, DrawDataComp::Tolerance tolerance)
472 : {
473 3378 : if (p1.obj->name != p2.obj->name) {
474 0 : return false;
475 : }
476 :
477 3378 : if (p1.data->state != p2.data->state) {
478 3144 : return false;
479 : }
480 :
481 234 : return isEqual(*p1.polygon, *p2.polygon, tolerance);
482 : }
483 :
484 3802 : static bool isEqual(const Text& p1, const Text& p2, DrawDataComp::Tolerance tolerance)
485 : {
486 3802 : if (p1.obj->name != p2.obj->name) {
487 0 : return false;
488 : }
489 :
490 3802 : if (p1.data->state != p2.data->state) {
491 3748 : return false;
492 : }
493 :
494 54 : return isEqual(*p1.text, *p2.text, tolerance);
495 : }
496 :
497 0 : static bool isEqual(const comp::Pixmap& p1, const comp::Pixmap& p2, DrawDataComp::Tolerance tolerance)
498 : {
499 0 : if (p1.obj->name != p2.obj->name) {
500 0 : return false;
501 : }
502 :
503 0 : if (p1.data->state != p2.data->state) {
504 0 : return false;
505 : }
506 :
507 0 : return isEqual(*p1.pixmap, *p2.pixmap, tolerance);
508 : }
509 :
510 : template<class T>
511 : static bool contains(const std::vector<T>& v1, const T& val, DrawDataComp::Tolerance tolerance)
512 : {
513 : for (size_t i = 0; i < v1.size(); ++i) {
514 : const T& t = v1.at(i);
515 : if (isEqual(t, val, tolerance)) {
516 : return true;
517 : }
518 : }
519 : return false;
520 : }
521 :
522 : template<class T>
523 : static void difference(std::vector<T>& diff, const std::vector<T>& v1, const std::vector<T>& v2, DrawDataComp::Tolerance tolerance)
524 : {
525 : for (size_t i = 0; i < v1.size(); ++i) {
526 : const T& t = v1.at(i);
527 : if (!contains(v2, t, tolerance)) {
528 : diff.push_back(t);
529 : }
530 : }
531 : }
532 :
533 : template<class T>
534 186 : static bool contains(const std::list<T>& v1, const T& val, DrawDataComp::Tolerance tolerance)
535 : {
536 7344 : for (const T& t : v1) {
537 7184 : if (isEqual(t, val, tolerance)) {
538 26 : return true;
539 : }
540 : }
541 160 : return false;
542 : }
543 :
544 : template<class T>
545 16 : static void difference(std::list<T>& diff, const std::list<T>& v1, const std::list<T>& v2, DrawDataComp::Tolerance tolerance)
546 : {
547 202 : for (const T& t : v1) {
548 186 : if (!contains(v2, t, tolerance)) {
549 160 : diff.push_back(t);
550 : }
551 : }
552 16 : }
553 :
554 4 : static void difference(Data& diff, const Data& d1, const Data& d2, DrawDataComp::Tolerance tolerance)
555 : {
556 4 : difference(diff.paths, d1.paths, d2.paths, tolerance);
557 4 : difference(diff.polygons, d1.polygons, d2.polygons, tolerance);
558 4 : difference(diff.texts, d1.texts, d2.texts, tolerance);
559 4 : difference(diff.pixmaps, d1.pixmaps, d2.pixmaps, tolerance);
560 4 : }
561 :
562 6 : static void toCompData(Data& cd, const DrawData::Item& item)
563 : {
564 158 : for (const DrawData::Data& d : item.datas) {
565 156 : for (const DrawPath& p : d.paths) {
566 4 : cd.paths.push_back(comp::Path { &item, &d, &p });
567 : }
568 242 : for (const DrawPolygon& p : d.polygons) {
569 90 : cd.polygons.push_back(comp::Polygon { &item, &d, &p });
570 : }
571 244 : for (const DrawText& p : d.texts) {
572 92 : cd.texts.push_back(comp::Text { &item, &d, &p });
573 : }
574 152 : for (const DrawPixmap& p : d.pixmaps) {
575 0 : cd.pixmaps.push_back(comp::Pixmap { &item, &d, &p });
576 : }
577 : }
578 :
579 8 : for (const DrawData::Item& ch : item.chilren) {
580 2 : toCompData(cd, ch);
581 : }
582 6 : }
583 :
584 4 : static Data toCompData(const DrawDataPtr& dd)
585 : {
586 4 : Data cd;
587 4 : toCompData(cd, dd->item);
588 4 : return cd;
589 0 : }
590 :
591 4 : static void fillDrawData(DrawDataPtr& dd, const comp::Data& cd)
592 : {
593 : //! NOTE Not effective, but efficiency is not required yet
594 :
595 : // collect objects (save order)
596 4 : std::vector<const DrawData::Item*> objs;
597 4 : for (const comp::Path& p : cd.paths) {
598 0 : if (!muse::contains(objs, p.obj)) {
599 0 : objs.push_back(p.obj);
600 : }
601 : }
602 84 : for (const comp::Polygon& p : cd.polygons) {
603 80 : if (!muse::contains(objs, p.obj)) {
604 4 : objs.push_back(p.obj);
605 : }
606 : }
607 84 : for (const comp::Text& p : cd.texts) {
608 80 : if (!muse::contains(objs, p.obj)) {
609 0 : objs.push_back(p.obj);
610 : }
611 : }
612 4 : for (const comp::Pixmap& p : cd.pixmaps) {
613 0 : if (!muse::contains(objs, p.obj)) {
614 0 : objs.push_back(p.obj);
615 : }
616 : }
617 :
618 : // collect data
619 8 : for (const DrawData::Item* o : objs) {
620 4 : DrawData::Item dobj;
621 4 : dobj.name = o->name;
622 :
623 160 : auto findOrCreateDData = [](DrawData::Item& dobj, int state) {
624 160 : DrawData::Data* ddata = nullptr;
625 :
626 : // find by state
627 4374 : for (DrawData::Data& od : dobj.datas) {
628 4214 : if (od.state == state) {
629 32 : ddata = &od;
630 : }
631 : }
632 :
633 : // add new, if not find
634 160 : if (!ddata) {
635 128 : dobj.datas.push_back(DrawData::Data());
636 128 : ddata = &dobj.datas.back();
637 128 : ddata->state = state;
638 : }
639 :
640 160 : return ddata;
641 : };
642 :
643 4 : for (const comp::Path& p : cd.paths) {
644 0 : DrawData::Data* ddata = findOrCreateDData(dobj, p.data->state);
645 0 : ddata->paths.push_back(*p.path);
646 : }
647 :
648 84 : for (const comp::Polygon& p : cd.polygons) {
649 80 : DrawData::Data* ddata = findOrCreateDData(dobj, p.data->state);
650 80 : ddata->polygons.push_back(*p.polygon);
651 : }
652 :
653 84 : for (const comp::Text& p : cd.texts) {
654 80 : DrawData::Data* ddata = findOrCreateDData(dobj, p.data->state);
655 80 : ddata->texts.push_back(*p.text);
656 : }
657 :
658 4 : for (const comp::Pixmap& p : cd.pixmaps) {
659 0 : DrawData::Data* ddata = findOrCreateDData(dobj, p.data->state);
660 0 : ddata->pixmaps.push_back(*p.pixmap);
661 : }
662 :
663 4 : dd->item.chilren.push_back(dobj);
664 4 : }
665 4 : }
666 : } // muse::draw::comp
667 :
668 2 : Diff DrawDataComp::compare(const DrawDataPtr& data, const DrawDataPtr& origin, Tolerance tolerance)
669 : {
670 2 : Diff diff;
671 2 : IF_ASSERT_FAILED(data) {
672 0 : return diff;
673 : }
674 :
675 2 : IF_ASSERT_FAILED(origin) {
676 0 : return diff;
677 : }
678 :
679 2 : comp::Data cdata = comp::toCompData(data);
680 2 : comp::Data corigin = comp::toCompData(origin);
681 :
682 2 : comp::Data added;
683 2 : comp::difference(added, corigin, cdata, tolerance);
684 :
685 2 : comp::Data removed;
686 2 : comp::difference(removed, cdata, corigin, tolerance);
687 :
688 2 : diff.dataAdded = std::make_shared<DrawData>();
689 2 : diff.dataAdded->name = origin->name;
690 2 : diff.dataAdded->viewport = origin->viewport;
691 2 : diff.dataAdded->states = origin->states;
692 2 : fillDrawData(diff.dataAdded, added);
693 :
694 2 : diff.dataRemoved = std::make_shared<DrawData>();
695 2 : diff.dataRemoved->name = origin->name;
696 2 : diff.dataRemoved->viewport = origin->viewport;
697 2 : diff.dataRemoved->states = origin->states;
698 2 : fillDrawData(diff.dataRemoved, removed);
699 :
700 2 : return diff;
701 2 : }
|