1 /*
2 Drawpile - a collaborative drawing program.
3
4 Copyright (C) 2008-2019 Calle Laakkonen
5
6 Drawpile is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Drawpile is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Drawpile. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "layer.h"
21 #include "layerstack.h"
22 #include "layerstackobserver.h"
23 #include "tile.h"
24 #include "rasterop.h"
25 #include "concurrent.h"
26
27 #include <QPainter>
28 #include <QMimeData>
29 #include <QDataStream>
30
31 namespace paintcore {
32
33 static const Tile CENSORED_TILE = Tile::ZebraBlock(QColor("#232629"), QColor("#eff0f1"));
34 static const Tile ZEBRA_TILE = Tile::ZebraBlock(Qt::red, Qt::black, 2);
35
36 static const int MAX_SIZE = 32767;
37
LayerStack(QObject * parent)38 LayerStack::LayerStack(QObject *parent)
39 : QObject(parent), m_width(0), m_height(0), m_xtiles(0), m_ytiles(0), m_dpix(0), m_dpiy(0),
40 m_viewmode(NORMAL), m_viewlayeridx(0), m_highlightId(0),
41 m_onionskinsBelow(4), m_onionskinsAbove(4), m_openEditors(0), m_onionskinTint(true), m_censorLayers(false)
42 {
43 m_annotations = new AnnotationModel(this);
44 }
45
LayerStack(const LayerStack * orig,QObject * parent)46 LayerStack::LayerStack(const LayerStack *orig, QObject *parent)
47 : QObject(parent),
48 m_width(orig->m_width),
49 m_height(orig->m_height),
50 m_xtiles(orig->m_xtiles),
51 m_ytiles(orig->m_ytiles),
52 m_dpix(orig->m_dpix),
53 m_dpiy(orig->m_dpiy),
54 m_viewmode(orig->m_viewmode),
55 m_viewlayeridx(orig->m_viewlayeridx),
56 m_highlightId(orig->m_highlightId),
57 m_onionskinsBelow(orig->m_onionskinsBelow),
58 m_openEditors(0),
59 m_onionskinTint(orig->m_onionskinTint),
60 m_censorLayers(orig->m_censorLayers)
61 {
62 m_annotations = orig->m_annotations->clone(this);
63 m_backgroundTile = orig->m_backgroundTile;
64 for(const Layer *l : orig->m_layers)
65 m_layers << new Layer(*l);
66 }
67
~LayerStack()68 LayerStack::~LayerStack()
69 {
70 for(LayerStackObserver *observer : m_observers)
71 observer->detachFromLayerStack();
72
73 for(Layer *l : m_layers)
74 delete l;
75 }
76
findChangeBounds(int contextId)77 QPair<int,QRect> LayerStack::findChangeBounds(int contextId)
78 {
79 for(const Layer *l : m_layers) {
80 const QRect r = l->changeBounds(contextId);
81 if(!r.isNull())
82 return QPair<int,QRect>(l->id(), r);
83 }
84 return QPair<int,QRect>(0, QRect());
85 }
86
getLayerByIndex(int index) const87 const Layer *LayerStack::getLayerByIndex(int index) const
88 {
89 return m_layers.at(index);
90 }
91
getLayer(int id) const92 const Layer *LayerStack::getLayer(int id) const
93 {
94 // Since the expected number of layers is always fairly low,
95 // we can get away with a simple linear search. (Note that IDs
96 // may appear in random order due to layers being moved around.)
97 for(const Layer *l : m_layers)
98 if(l->id() == id)
99 return l;
100 return nullptr;
101 }
102
103 /**
104 * @param id layer id
105 * @return layer index. Returns a negative index if layer is not found
106 */
indexOf(int id) const107 int LayerStack::indexOf(int id) const
108 {
109 for(int i=0;i<m_layers.size();++i)
110 if(m_layers.at(i)->id() == id)
111 return i;
112 return -1;
113 }
114
getFlatTile(int x,int y) const115 Tile LayerStack::getFlatTile(int x, int y) const
116 {
117 Tile t = m_backgroundTile;
118 flattenTile(t.data(), x, y);
119 return t;
120 }
121
layerAt(int x,int y) const122 const Layer *LayerStack::layerAt(int x, int y) const
123 {
124 for(int i=m_layers.size()-1;i>=0;--i) {
125 const Layer * l = m_layers.at(i);
126 if(l->isVisible()) {
127 if(l->pixelAt(x,y) > 0)
128 return l;
129
130 // Check sublayers too
131 for(const Layer *sl : l->sublayers()) {
132 if(sl->isVisible() && sl->pixelAt(x, y) > 0)
133 return l;
134 }
135 }
136 }
137 return nullptr;
138 }
139
colorAt(int x,int y,int dia) const140 QColor LayerStack::colorAt(int x, int y, int dia) const
141 {
142 if(m_layers.isEmpty())
143 return QColor();
144
145 if(x<0 || y<0 || x>=m_width || y>=m_height)
146 return QColor();
147
148 if(dia<=1) {
149 // TODO some more efficient way of doing this
150 Tile tile = getFlatTile(x/Tile::SIZE, y/Tile::SIZE);
151 quint32 c = tile.data()[(y-Tile::roundDown(y)) * Tile::SIZE + (x-Tile::roundDown(x))];
152 return QColor(c);
153
154 } else {
155 const int r = dia/2+1;
156 const int x1 = qBound(0, (x-r) / Tile::SIZE, m_xtiles-1);
157 const int x2 = qBound(0, (x+r) / Tile::SIZE, m_xtiles-1);
158 const int y1 = qBound(0, (y-r) / Tile::SIZE, m_ytiles-1);
159 const int y2 = qBound(0, (y+r) / Tile::SIZE, m_ytiles-1);
160
161 Layer flat(0, QString(), Qt::transparent, size());
162 EditableLayer ef(&flat, nullptr, 0);
163
164 ef.putTile(0, 0, 9999*9999, m_backgroundTile);
165
166 for(int tx=x1;tx<=x2;++tx) {
167 for(int ty=y1;ty<=y2;++ty) {
168 ef.putTile(tx, ty, 0, getFlatTile(tx, ty));
169 }
170 }
171
172 return flat.colorAt(x, y, dia);
173 }
174 }
175
tileLastEditedBy(int tx,int ty) const176 int LayerStack::tileLastEditedBy(int tx, int ty) const
177 {
178 if(tx < 0 || ty < 0 || tx >= m_xtiles || ty >= m_ytiles)
179 return 0;
180
181 for(int i=m_layers.size()-1;i>=0;--i) {
182 if(isVisible(i)) {
183 const Tile &t = m_layers.at(i)->tile(tx, ty);
184 if(!t.isNull())
185 return t.lastEditedBy();
186 }
187 }
188
189 return 0;
190 }
191
toFlatImage(bool includeAnnotations,bool includeBackground,bool includeSublayers) const192 QImage LayerStack::toFlatImage(bool includeAnnotations, bool includeBackground, bool includeSublayers) const
193 {
194 if(m_layers.isEmpty())
195 return QImage();
196
197 Layer flat(0, QString(), Qt::transparent, size());
198 EditableLayer ef(&flat, nullptr, 0);
199
200 if(includeBackground)
201 ef.putTile(0, 0, 9999*9999, m_backgroundTile);
202
203 for(int i=0;i<m_layers.size();++i) {
204 if(m_layers.at(i)->isVisible() && (includeBackground || !m_layers.at(i)->isFixed())) {
205 const Layer *l = m_layers.at(i);
206 if(includeSublayers && l->hasSublayers()) {
207 Layer ll = Layer(*l);
208 EditableLayer el(&ll, nullptr, 0);
209 el.mergeAllSublayers();
210 ef.merge(&ll);
211
212 } else {
213 ef.merge(l);
214 }
215 }
216 }
217
218 QImage image = ef->toImage();
219
220 if(includeAnnotations) {
221 QPainter painter(&image);
222 for(const Annotation &a : m_annotations->getAnnotations())
223 a.paint(&painter);
224 }
225
226 if(m_dpix > 0 && m_dpiy > 0) {
227 image.setDotsPerMeterX(int(m_dpix / 0.0254));
228 image.setDotsPerMeterY(int(m_dpiy / 0.0254));
229 }
230 return image;
231 }
232
flatLayerImage(int layerIdx) const233 QImage LayerStack::flatLayerImage(int layerIdx) const
234 {
235 Q_ASSERT(layerIdx>=0 && layerIdx < m_layers.size());
236
237 Layer flat(0, QString(), Qt::transparent, size());
238 EditableLayer ef(&flat, nullptr, 0);
239
240 ef.putTile(0, 0, 9999*9999, m_backgroundTile);
241
242 for(int i=0;i<m_layers.size();++i) {
243 if(i == layerIdx || m_layers.at(i)->isFixed())
244 ef.merge(m_layers.at(i));
245 }
246
247 QImage image = ef->toImage();
248 if(m_dpix > 0 && m_dpiy > 0) {
249 image.setDotsPerMeterX(int(m_dpix / 0.0254));
250 image.setDotsPerMeterY(int(m_dpiy / 0.0254));
251 }
252
253 return image;
254 }
255
256 // Flatten a single tile
flattenTile(quint32 * data,int xindex,int yindex) const257 void LayerStack::flattenTile(quint32 *data, int xindex, int yindex) const
258 {
259 // Composite visible layers
260 int layeridx = 0;
261 for(const Layer *l : m_layers) {
262 if(isVisible(layeridx)) {
263 const Tile &tile = l->tile(xindex, yindex);
264 const quint32 tint = layerTint(layeridx);
265
266 if(m_censorLayers && l->isCensored()) {
267 // This layer must be censored
268 if(!tile.isNull())
269 compositePixels(l->blendmode(), data, CENSORED_TILE.constData(),
270 Tile::LENGTH, layerOpacity(layeridx));
271
272 } else if(l->sublayers().count() || tint!=0 || m_highlightId > 0) {
273 // Sublayers (or tint) present, composite them first
274 quint32 ldata[Tile::SIZE*Tile::SIZE];
275 tile.copyTo(ldata);
276
277 for(const Layer *sl : l->sublayers()) {
278 if(sl->isVisible()) {
279 const Tile &subtile = sl->tile(xindex, yindex);
280 if(!subtile.isNull()) {
281 compositePixels(sl->blendmode(), ldata, subtile.constData(),
282 Tile::LENGTH, sl->opacity());
283 }
284 }
285 }
286
287 if(m_highlightId > 0 && m_highlightId == tile.lastEditedBy()) {
288 // MODE_RECOLOR looks really nice here, but can be misleading.
289 // Use per-pixel highlighting if/when per-pixel tagging is implemented.
290 compositePixels(BlendMode::MODE_NORMAL, ldata, ZEBRA_TILE.constData(),
291 Tile::LENGTH, 128);
292 }
293
294 if(tint)
295 tintPixels(ldata, sizeof ldata / sizeof *ldata, tint);
296
297
298 // Composite merged tile
299 compositePixels(l->blendmode(), data, ldata,
300 Tile::SIZE*Tile::SIZE, layerOpacity(layeridx));
301
302 } else if(!tile.isNull()) {
303 // No sublayers or tint, just this tile as it is
304 compositePixels(l->blendmode(), data, tile.constData(),
305 Tile::LENGTH, layerOpacity(layeridx));
306 }
307 }
308
309 ++layeridx;
310 }
311 }
312
beginWriteSequence()313 void LayerStack::beginWriteSequence()
314 {
315 ++m_openEditors;
316 }
317
endWriteSequence()318 void LayerStack::endWriteSequence()
319 {
320 --m_openEditors;
321 Q_ASSERT(m_openEditors>=0);
322 if(m_openEditors == 0) {
323 for(auto observer : m_observers)
324 observer->canvasWriteSequenceDone();
325 }
326 }
327
layerOpacity(int idx) const328 int LayerStack::layerOpacity(int idx) const
329 {
330 Q_ASSERT(idx>=0 && idx < m_layers.size());
331 int o = m_layers.at(idx)->opacity();
332
333 if(viewMode()==ONIONSKIN && !m_layers.at(idx)->isFixed()) {
334
335 const int d = m_viewlayeridx - idx;
336 qreal rd;
337 if(d<0 && d>=-m_onionskinsAbove)
338 rd = -d/qreal(m_onionskinsAbove+1);
339 else if(d>=0 && d <=m_onionskinsBelow)
340 rd = d/qreal(m_onionskinsBelow+1);
341 else
342 return 0;
343
344 return int(o * ((1-rd) * (1-rd)));
345 }
346
347 return o;
348 }
349
layerTint(int idx) const350 quint32 LayerStack::layerTint(int idx) const
351 {
352 if(m_onionskinTint && viewMode() == ONIONSKIN && !m_layers.at(idx)->isFixed()) {
353 if(idx < m_viewlayeridx)
354 return 0x80ff3333;
355 else if(idx > m_viewlayeridx)
356 return 0x803333ff;
357 }
358
359 return 0;
360 }
361
isVisible(int idx) const362 bool LayerStack::isVisible(int idx) const
363 {
364 Q_ASSERT(idx>=0 && idx < m_layers.size());
365 if(!m_layers.at(idx)->isVisible())
366 return false;
367
368 switch(viewMode()) {
369 case NORMAL: break;
370 case SOLO: return idx == m_viewlayeridx || m_layers.at(idx)->isFixed();
371 case ONIONSKIN: return layerOpacity(idx) > 0;
372 }
373
374 return true;
375 }
376
makeSavepoint()377 Savepoint LayerStack::makeSavepoint()
378 {
379 Savepoint sp;
380 for(Layer *l : m_layers) {
381 l->optimize();
382 sp.layers.append(new Layer(*l));
383 }
384
385 sp.annotations = m_annotations->getAnnotations();
386 sp.background = m_backgroundTile;
387
388 sp.size = size();
389
390 return sp;
391 }
392
Savepoint(const Savepoint & other)393 Savepoint::Savepoint(const Savepoint &other)
394 {
395 for(Layer *l : other.layers)
396 layers << new Layer(*l);
397 annotations = other.annotations;
398 background = other.background;
399 size = other.size;
400 }
401
operator =(const Savepoint & other)402 Savepoint &Savepoint::operator=(const Savepoint &other)
403 {
404 if(&other != this) {
405 for(Layer *l : layers)
406 delete l;
407 layers.clear();
408 for(Layer *l : other.layers)
409 layers << new Layer(*l);
410 annotations = other.annotations;
411 background = other.background;
412 size = other.size;
413 }
414 return *this;
415 }
416
~Savepoint()417 Savepoint::~Savepoint()
418 {
419 for(Layer *l : layers)
420 delete l;
421 }
422
restoreSavepoint(const Savepoint & savepoint)423 void EditableLayerStack::restoreSavepoint(const Savepoint &savepoint)
424 {
425 const QSize oldsize(d->m_width, d->m_height);
426 if(d->width() != savepoint.size.width() || d->height() != savepoint.size.height()) {
427 // Restore canvas size if it was different in the savepoint
428 d->m_width = savepoint.size.width();
429 d->m_height = savepoint.size.height();
430 d->m_xtiles = Tile::roundTiles(d->m_width);
431 d->m_ytiles = Tile::roundTiles(d->m_height);
432 for(auto observer : d->m_observers)
433 observer->canvasResized(0, 0, oldsize);
434 emit d->resized(0, 0, oldsize);
435
436 } else {
437 // Mark changed tiles as changed. Usually savepoints are quite close together
438 // so most tiles will remain unchanged
439 if(savepoint.layers.size() != d->m_layers.size()) {
440 // Layers added or deleted, just refresh everything
441 // (force refresh even if layer stack is empty)
442 for(auto observer : d->m_observers)
443 observer->markDirty();
444
445 } else {
446 // Layer count has not changed, compare layer contents
447 for(int l=0;l<savepoint.layers.size();++l) {
448 const Layer *l0 = d->m_layers.at(l);
449 const Layer *l1 = savepoint.layers.at(l);
450 if(l0->effectiveOpacity() != l1->effectiveOpacity()) {
451 // Layer opacity has changed, refresh everything
452 for(auto observer : d->m_observers)
453 observer->markDirty();
454 break;
455 }
456
457 // Gather list of sublayer IDs to compare
458 QVarLengthArray<int, 10> sublayers;
459 for(const Layer *sl : l0->sublayers())
460 if(!sl->isHidden() && !sublayers.contains(sl->id()))
461 sublayers << sl->id();
462
463 for(const Layer *sl : l1->sublayers())
464 if(!sl->isHidden() && !sublayers.contains(sl->id()))
465 sublayers << sl->id();
466
467 // Compare sublayers
468 const int tilecount = d->m_xtiles * d->m_ytiles;
469
470 for(int sublayerId : sublayers) {
471 const Layer *sl0 = l0->getVisibleSublayer(sublayerId);
472 const Layer *sl1 = l1->getVisibleSublayer(sublayerId);
473 const Layer *delta = nullptr;
474
475 if(sl0) {
476 if(sl1) {
477 // Visible in both, compare content
478 for(int i=0;i<tilecount;++i) {
479 // Note: An identity comparison works here, because the tiles
480 // utilize copy-on-write semantics. Unchanged tiles will share
481 // data pointers between savepoints.
482 if(sl0->tile(i) != sl1->tile(i)) {
483 for(auto observer : d->m_observers)
484 observer->markDirty(i);
485 }
486 }
487 } else {
488 // Not visible in sl1
489 delta = sl0;
490 }
491 } else {
492 // Not visible in sl0, therefore must be visible in sl1
493 delta = sl1;
494 }
495
496 if(delta) {
497 // Visible in one but not both: mark opaque areas as dirty
498 for(int i=0;i<tilecount;++i) {
499 if(!delta->tile(i).isNull())
500 for(auto observer : d->m_observers)
501 observer->markDirty(i);
502 }
503 }
504 }
505
506 // Compare the main layer
507 for(int i=0;i<d->m_xtiles*d->m_ytiles;++i) {
508 // Note: An identity comparison works here, because the tiles
509 // utilize copy-on-write semantics. Unchanged tiles will share
510 // data pointers between savepoints.
511 if(l0->tile(i) != l1->tile(i)) {
512 for(auto observer : d->m_observers)
513 observer->markDirty(i);
514 }
515 }
516 }
517 }
518 }
519
520 // Restore layers
521 while(!d->m_layers.isEmpty())
522 delete d->m_layers.takeLast();
523 for(const Layer *l : savepoint.layers)
524 d->m_layers.append(new Layer(*l));
525
526 // Restore background
527 setBackground(savepoint.background);
528
529 // Restore annotations
530 d->m_annotations->setAnnotations(savepoint.annotations);
531 }
532
resize(int top,int right,int bottom,int left)533 void EditableLayerStack::resize(int top, int right, int bottom, int left)
534 {
535 const QSize oldsize(d->m_width, d->m_height);
536
537 const int newtop = -top;
538 const int newleft = -left;
539 const int newright = d->m_width + right;
540 const int newbottom = d->m_height + bottom;
541 if(newtop >= newbottom || newleft >= newright) {
542 qWarning("Invalid resize: borders reversed");
543 return;
544 }
545
546 const int newwidth = newright - newleft;
547 const int newheight = newbottom - newtop;
548
549 if(newwidth < 1 || newheight < 1 || newwidth > MAX_SIZE || newheight > MAX_SIZE) {
550 qWarning("Invalid resize: size would be %d x %d", newwidth, newheight);
551 return;
552 }
553
554 d->m_width = newwidth;
555 d->m_height = newheight;
556
557 d->m_xtiles = Tile::roundTiles(d->m_width);
558 d->m_ytiles = Tile::roundTiles(d->m_height);
559
560 for(Layer *l : d->m_layers)
561 EditableLayer(l, d, contextId).resize(top, right, bottom, left);
562
563 if(left || top) {
564 // Update annotation positions
565 QPoint offset(left, top);
566 for(const Annotation &a : d->m_annotations->getAnnotations()) {
567 d->m_annotations->reshapeAnnotation(a.id, a.rect.translated(offset));
568 }
569 }
570
571 for(auto observer : d->m_observers)
572 observer->canvasResized(left, top, oldsize);
573
574 emit d->resized(left, top, oldsize);
575 }
576
setBackground(const Tile & tile)577 void EditableLayerStack::setBackground(const Tile &tile)
578 {
579 if(tile.equals(d->m_backgroundTile))
580 return;
581
582 d->m_backgroundTile = tile;
583
584 for(auto observer : d->m_observers)
585 observer->canvasBackgroundChanged(tile);
586 }
587
588 /**
589 * @param id ID of the new layer
590 * @param source source layer ID (used when copy or insert is true)
591 * @param color background color (used when copy is false)
592 * @param insert if true, the new layer is inserted above source (source 0 inserts at the bottom of the stack)
593 * @param copy if true, the layer content is copied from the source
594 * @param name layer title
595 * @return newly created layer or null in case of error
596 */
createLayer(int id,int source,const QColor & color,bool insert,bool copy,const QString & name)597 EditableLayer EditableLayerStack::createLayer(int id, int source, const QColor &color, bool insert, bool copy, const QString &name)
598 {
599 if(d->getLayer(id)) {
600 qWarning("Layer #%d already exists!", id);
601 return EditableLayer();
602 }
603
604 if(d->m_width<=0 || d->m_height<=0) {
605 // We tolerate this, but in normal operation the canvas size should be
606 // set before creating any layers.
607 qWarning("Layer created before canvas size was set!");
608 }
609
610 // Find source layer if specified
611 int sourceIdx=-1;
612 if(source>0) {
613 for(int i=0;i<d->m_layers.size();++i) {
614 if(d->m_layers.at(i)->id() == source) {
615 sourceIdx = i;
616 break;
617 }
618 }
619
620 if(sourceIdx<0) {
621 qWarning("Source layer %d not found!", source);
622 return EditableLayer();
623 }
624 }
625
626 // Create or copy new layer
627 Layer *nl;
628 if(copy) {
629 if(sourceIdx<0) {
630 qWarning("No layer copy source specified!");
631 return EditableLayer();
632 }
633
634 nl = new Layer(*d->m_layers.at(sourceIdx));
635 EditableLayer enl(nl, nullptr, 0);
636 enl.setTitle(name);
637 enl.setId(id);
638
639 } else {
640 nl = new Layer(id, name, color, d->size());
641 }
642
643 // Insert the new layer in the appropriate spot
644 int pos;
645 if(insert)
646 pos = sourceIdx+1;
647 else
648 pos = d->m_layers.size();
649
650 d->m_layers.insert(pos, nl);
651
652 // Dirty regions must be marked after the layer is in the stack
653 EditableLayer editable(nl, d, 0);
654
655 if(copy) {
656 editable.markOpaqueDirty();
657
658 } else if(color.alpha()>0) {
659 for(auto observer : d->m_observers)
660 observer->markDirty();
661 }
662
663 return editable;
664 }
665
666 /**
667 * @param id layer ID
668 * @return true if layer was found and deleted
669 */
deleteLayer(int id)670 bool EditableLayerStack::deleteLayer(int id)
671 {
672 for(int i=0;i<d->m_layers.size();++i) {
673 if(d->m_layers.at(i)->id() == id) {
674 EditableLayer(d->m_layers.at(i), d, contextId).markOpaqueDirty();
675 delete d->m_layers.takeAt(i);
676
677 return true;
678 }
679 }
680 return false;
681 }
682
683 /**
684 * @param neworder list of layer IDs in the new order
685 * @pre neworder must be a permutation of the current layer order
686 */
reorderLayers(const QList<uint16_t> & neworder)687 void EditableLayerStack::reorderLayers(const QList<uint16_t> &neworder)
688 {
689 Q_ASSERT(neworder.size() == d->m_layers.size());
690 QList<Layer*> newstack;
691 newstack.reserve(d->m_layers.size());
692 for(const int id : neworder) {
693 Layer *l = nullptr;
694 for(int i=0;i<d->m_layers.size();++i) {
695 if(d->m_layers.at(i)->id() == id) {
696 l=d->m_layers.takeAt(i);
697 break;
698 }
699 }
700 Q_ASSERT(l);
701 newstack.append(l);
702 }
703 d->m_layers = newstack;
704 for(auto observer : d->m_observers)
705 observer->markDirty();
706 }
707
708 /**
709 * @param id id of the layer that will be merged
710 */
mergeLayerDown(int id)711 void EditableLayerStack::mergeLayerDown(int id) {
712 const Layer *top;
713 Layer *btm=nullptr;
714 for(int i=0;i<d->m_layers.size();++i) {
715 if(d->m_layers[i]->id() == id) {
716 top = d->m_layers[i];
717 if(i>0)
718 btm = d->m_layers[i-1];
719 break;
720 }
721 }
722 if(btm)
723 EditableLayer(btm, d, contextId).merge(top);
724 else
725 qWarning("Tried to merge bottom-most layer");
726 }
727
getEditableLayerByIndex(int index)728 EditableLayer EditableLayerStack::getEditableLayerByIndex(int index)
729 {
730 return EditableLayer(d->m_layers[index], d, contextId);
731 }
732
getEditableLayer(int id)733 EditableLayer EditableLayerStack::getEditableLayer(int id)
734 {
735 for(Layer *l : d->m_layers)
736 if(l->id() == id)
737 return EditableLayer(l, d, contextId);
738 return EditableLayer();
739 }
740
reset()741 void EditableLayerStack::reset()
742 {
743 const QSize oldsize(d->m_width, d->m_height);
744 d->m_width = 0;
745 d->m_height = 0;
746 d->m_xtiles = 0;
747 d->m_ytiles = 0;
748 for(Layer *l : d->m_layers)
749 delete l;
750 d->m_layers.clear();
751 d->m_annotations->clear();
752
753 d->m_backgroundTile = Tile();
754
755 for(auto *observer : d->m_observers) {
756 observer->canvasResized(0, 0, oldsize);
757 observer->canvasBackgroundChanged(Tile());
758 }
759
760 emit d->resized(0, 0, oldsize);
761 }
762
removePreviews()763 void EditableLayerStack::removePreviews()
764 {
765 for(Layer *l : d->m_layers) {
766 EditableLayer(l, d, contextId).removePreviews();
767 }
768 }
769
mergeSublayers(int id)770 void EditableLayerStack::mergeSublayers(int id)
771 {
772 for(Layer *l : d->m_layers) {
773 EditableLayer(l, d, contextId).mergeSublayer(id);
774 }
775 }
776
mergeAllSublayers()777 void EditableLayerStack::mergeAllSublayers()
778 {
779 for(Layer *l : d->m_layers) {
780 EditableLayer(l, d, contextId).mergeAllSublayers();
781 }
782 }
783
setViewMode(LayerStack::ViewMode mode)784 void EditableLayerStack::setViewMode(LayerStack::ViewMode mode)
785 {
786 if(mode != d->m_viewmode) {
787 d->m_viewmode = mode;
788 for(auto observer : d->m_observers)
789 observer->markDirty();
790 }
791 }
792
setViewLayer(int id)793 void EditableLayerStack::setViewLayer(int id)
794 {
795 for(int i=0;i<d->m_layers.size();++i) {
796 if(d->m_layers.at(i)->id() == id) {
797 d->m_viewlayeridx = i;
798 if(d->m_viewmode != LayerStack::NORMAL) {
799 for(auto observer : d->m_observers)
800 observer->markDirty();
801 }
802 break;
803 }
804 }
805 }
806
setInspectorHighlight(int contextId)807 void EditableLayerStack::setInspectorHighlight(int contextId)
808 {
809 if(d->m_highlightId != contextId) {
810 d->m_highlightId = contextId;
811 for(auto observer : d->m_observers)
812 observer->markDirty();
813 }
814 }
815
setOnionskinMode(int below,int above,bool tint)816 void EditableLayerStack::setOnionskinMode(int below, int above, bool tint)
817 {
818 d->m_onionskinsBelow = below;
819 d->m_onionskinsAbove = above;
820 d->m_onionskinTint = tint;
821
822 if(d->m_viewmode == LayerStack::ONIONSKIN) {
823 for(auto observer : d->m_observers)
824 observer->markDirty();
825 }
826 }
827
setCensorship(bool censor)828 void EditableLayerStack::setCensorship(bool censor)
829 {
830 if(d->m_censorLayers != censor) {
831 d->m_censorLayers = censor;
832 // We could check if this really needs to be called, but this
833 // flag is changed very infrequently
834 for(auto observer : d->m_observers)
835 observer->markDirty();
836 }
837 }
838
839 }
840
841