1 /*
2  * stampbrush.cpp
3  * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright 2010 Stefan Beller <stefanbeller@googlemail.com>
5  *
6  * This file is part of Tiled.
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  *
18  * You should have received a copy of the GNU General Public License along with
19  * this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "stampbrush.h"
23 
24 #include "addremovelayer.h"
25 #include "addremovetileset.h"
26 #include "brushitem.h"
27 #include "geometry.h"
28 #include "map.h"
29 #include "mapdocument.h"
30 #include "mapscene.h"
31 #include "painttilelayer.h"
32 #include "staggeredrenderer.h"
33 #include "stampactions.h"
34 #include "tile.h"
35 #include "tilestamp.h"
36 #include "wangset.h"
37 #include "wangfiller.h"
38 
39 #include <QAction>
40 #include <QToolBar>
41 #include <QVector>
42 
43 #include <memory>
44 
45 using namespace Tiled;
46 
StampBrush(QObject * parent)47 StampBrush::StampBrush(QObject *parent)
48     : AbstractTileTool("StampTool",
49                        tr("Stamp Brush"),
50                        QIcon(QLatin1String(
51                                ":images/22/stock-tool-clone.png")),
52                        QKeySequence(Qt::Key_B),
53                        nullptr,
54                        parent)
55     , mBrushBehavior(Free)
56     , mIsRandom(false)
57     , mIsWangFill(false)
58     , mWangSet(nullptr)
59     , mRandomCacheValid(true)
60     , mStampActions(new StampActions(this))
61 {
62     connect(mStampActions->random(), &QAction::toggled, this, &StampBrush::randomChanged);
63     connect(mStampActions->wangFill(), &QAction::toggled, this, &StampBrush::wangFillChanged);
64 
65     connect(mStampActions->flipHorizontal(), &QAction::triggered,
66             [this] { emit stampChanged(mStamp.flipped(FlipHorizontally)); });
67     connect(mStampActions->flipVertical(), &QAction::triggered,
68             [this] { emit stampChanged(mStamp.flipped(FlipVertically)); });
69     connect(mStampActions->rotateLeft(), &QAction::triggered,
70             [this] { emit stampChanged(mStamp.rotated(RotateLeft)); });
71     connect(mStampActions->rotateRight(), &QAction::triggered,
72             [this] { emit stampChanged(mStamp.rotated(RotateRight)); });
73 }
74 
~StampBrush()75 StampBrush::~StampBrush()
76 {
77 }
78 
deactivate(MapScene * scene)79 void StampBrush::deactivate(MapScene *scene)
80 {
81     mCaptureStampHelper.reset();
82     AbstractTileTool::deactivate(scene);
83 }
84 
tilePositionChanged(QPoint pos)85 void StampBrush::tilePositionChanged(QPoint pos)
86 {
87     if (mBrushBehavior == Paint) {
88         // Draw a line from the previous point to avoid gaps, skipping the
89         // first point, since it was painted when the mouse was pressed, or the
90         // last time the mouse was moved.
91         QVector<QPoint> points = pointsOnLine(mPrevTilePosition, pos);
92         QHash<TileLayer*, QRegion> paintedRegions;
93 
94         for (int i = 1; i < points.size(); ++i) {
95             drawPreviewLayer(QVector<QPoint>() << points.at(i));
96 
97             // Only update the brush item for the last drawn piece
98             if (i == points.size() - 1)
99                 brushItem()->setMap(mPreviewMap);
100 
101             doPaint(Mergeable, &paintedRegions);
102         }
103 
104         QHashIterator<TileLayer*, QRegion> ri(paintedRegions);
105         while (ri.hasNext()) {
106             ri.next();
107             emit mapDocument()->regionEdited(ri.value(), ri.key());
108         }
109     } else {
110         updatePreview();
111     }
112     mPrevTilePosition = pos;
113 }
114 
mousePressed(QGraphicsSceneMouseEvent * event)115 void StampBrush::mousePressed(QGraphicsSceneMouseEvent *event)
116 {
117     if (brushItem()->isVisible()) {
118         if (event->button() == Qt::LeftButton) {
119             switch (mBrushBehavior) {
120             case Line:
121                 mStampReference = tilePosition();
122                 mBrushBehavior = LineStartSet;
123                 break;
124             case Circle:
125                 mStampReference = tilePosition();
126                 mBrushBehavior = CircleMidSet;
127                 break;
128             case LineStartSet:
129                 doPaint();
130                 mStampReference = tilePosition();
131                 break;
132             case CircleMidSet:
133                 doPaint();
134                 break;
135             case Paint:
136                 beginPaint();
137                 break;
138             case Free:
139                 beginPaint();
140                 mBrushBehavior = Paint;
141                 break;
142             case Capture:
143                 break;
144             }
145             return;
146         } else if (event->button() == Qt::RightButton && event->modifiers() == Qt::NoModifier) {
147             beginCapture();
148             return;
149         }
150     }
151 
152     AbstractTileTool::mousePressed(event);
153 }
154 
mouseReleased(QGraphicsSceneMouseEvent * event)155 void StampBrush::mouseReleased(QGraphicsSceneMouseEvent *event)
156 {
157     switch (mBrushBehavior) {
158     case LineStartSet:
159         if (event->button() == Qt::LeftButton) {
160             if (mStampReference != tilePosition()) {
161                 doPaint();
162                 mBrushBehavior = Line;
163             }
164         }
165         break;
166     case CircleMidSet:
167         if (event->button() == Qt::LeftButton) {
168             if (mStampReference != tilePosition()) {
169                 doPaint();
170                 updateBrushBehavior();
171             }
172         }
173         break;
174     case Capture:
175         if (event->button() == Qt::RightButton) {
176             endCapture();
177             mBrushBehavior = Free;
178         }
179         break;
180     case Paint:
181         if (event->button() == Qt::LeftButton) {
182             mBrushBehavior = Free;
183 
184             // allow going over different variations by repeatedly clicking
185             updatePreview();
186         }
187         break;
188     default:
189         // do nothing?
190         break;
191     }
192 }
193 
modifiersChanged(Qt::KeyboardModifiers modifiers)194 void StampBrush::modifiersChanged(Qt::KeyboardModifiers modifiers)
195 {
196     mModifiers = modifiers;
197 
198     if (!mStamp.isEmpty() || mIsWangFill)
199         updateBrushBehavior();
200 }
201 
updateBrushBehavior()202 void StampBrush::updateBrushBehavior()
203 {
204     BrushBehavior brushBehavior = mBrushBehavior;
205 
206     if (mModifiers & Qt::ShiftModifier) {
207         if (mModifiers & Qt::ControlModifier) {
208             if (brushBehavior == LineStartSet) {
209                 brushBehavior = CircleMidSet;
210             } else if (brushBehavior != CircleMidSet) {
211                 brushBehavior = Circle;
212             }
213         } else {
214             if (brushBehavior == CircleMidSet) {
215                 brushBehavior = LineStartSet;
216             } else if (brushBehavior != LineStartSet) {
217                 brushBehavior = Line;
218             }
219         }
220     } else if (brushBehavior != Paint && brushBehavior != Capture) {
221         brushBehavior = Free;
222     }
223 
224     if (mBrushBehavior != brushBehavior) {
225         mBrushBehavior = brushBehavior;
226         updatePreview();
227     }
228 }
229 
languageChanged()230 void StampBrush::languageChanged()
231 {
232     setName(tr("Stamp Brush"));
233 
234     mStampActions->languageChanged();
235 }
236 
mapDocumentChanged(MapDocument * oldDocument,MapDocument * newDocument)237 void StampBrush::mapDocumentChanged(MapDocument *oldDocument,
238                                     MapDocument *newDocument)
239 {
240     AbstractTileTool::mapDocumentChanged(oldDocument, newDocument);
241 
242     if (oldDocument) {
243         disconnect(oldDocument, &MapDocument::tileProbabilityChanged,
244                    this, &StampBrush::invalidateRandomCache);
245     }
246 
247     if (newDocument) {
248         invalidateRandomCache();
249         updatePreview();
250         connect(newDocument, &MapDocument::tileProbabilityChanged,
251                 this, &StampBrush::invalidateRandomCache);
252     }
253 }
254 
findTileLayerByName(const Map & map,const QString & name)255 static TileLayer *findTileLayerByName(const Map &map, const QString &name)
256 {
257     return static_cast<TileLayer*>(map.findLayer(name, Layer::TileLayerType));
258 }
259 
targetLayers() const260 QList<Layer *> StampBrush::targetLayers() const
261 {
262     if (mIsRandom || mIsWangFill || mStamp.isEmpty())
263         return AbstractTileTool::targetLayers();
264 
265     return targetLayersForStamp(mStamp);
266 }
267 
268 /**
269  * Updates the list used random stamps.
270  * This is done by taking all non-null tiles from the original stamp mStamp.
271  */
updateRandomList()272 void StampBrush::updateRandomList()
273 {
274     mRandomCellPicker.clear();
275 
276     if (!mIsRandom)
277         return;
278 
279     mMissingTilesets.clear();
280 
281     for (const TileStampVariation &variation : mStamp.variations()) {
282         mapDocument()->unifyTilesets(variation.map, mMissingTilesets);
283 
284         for (auto layer : variation.map->tileLayers())
285             for (const Cell &cell : *static_cast<TileLayer*>(layer))
286                 if (const Tile *tile = cell.tile())
287                     mRandomCellPicker.add(cell, tile->probability());
288     }
289 }
290 
setStamp(const TileStamp & stamp)291 void StampBrush::setStamp(const TileStamp &stamp)
292 {
293     if (mStamp == stamp)
294         return;
295 
296     mStamp = stamp;
297 
298     invalidateRandomCache();
299     updatePreview();
300 }
301 
populateToolBar(QToolBar * toolBar)302 void StampBrush::populateToolBar(QToolBar *toolBar)
303 {
304     mStampActions->populateToolBar(toolBar, mIsRandom, mIsWangFill);
305 }
306 
setWangSet(WangSet * wangSet)307 void StampBrush::setWangSet(WangSet *wangSet)
308 {
309     mWangSet = wangSet;
310 
311     mMissingTilesets.clear();
312 
313     if (!wangSet)
314         return;
315 
316     const SharedTileset &tileset = wangSet->tileset()->sharedPointer();
317 
318     if (!mapDocument() || !mapDocument()->map()->tilesets().contains(tileset))
319        mMissingTilesets.append(tileset);
320 }
321 
beginPaint()322 void StampBrush::beginPaint()
323 {
324     if (mBrushBehavior != Free)
325         return;
326 
327     mBrushBehavior = Paint;
328     doPaint();
329 }
330 
beginCapture()331 void StampBrush::beginCapture()
332 {
333     if (mBrushBehavior != Free)
334         return;
335 
336     mBrushBehavior = Capture;
337     mCaptureStampHelper.beginCapture(tilePosition());
338 
339     setStamp(TileStamp());
340 }
341 
endCapture()342 void StampBrush::endCapture()
343 {
344     if (mBrushBehavior != Capture)
345         return;
346 
347     mBrushBehavior = Free;
348 
349     TileStamp stamp = mCaptureStampHelper.endCapture(*mapDocument(), tilePosition());
350     if (!stamp.isEmpty())
351         emit stampChanged(TileStamp(stamp));
352     else
353         updatePreview();
354 }
355 
356 /**
357  * Merges the tile layer of its brush item into the current map.
358  *
359  * \a flags can be set to Mergeable, which applies to the undo command.
360  *
361  * \a paintedRegions is an optional argument that can be used to accumilate
362  * the regions touched for each tile layer.
363  */
doPaint(int flags,QHash<TileLayer *,QRegion> * paintedRegions)364 void StampBrush::doPaint(int flags, QHash<TileLayer*, QRegion> *paintedRegions)
365 {
366     // local reference to avoid issues when member gets cleared
367     SharedMap preview = mPreviewMap;
368     if (!preview)
369         return;
370 
371     mapDocument()->paintTileLayers(preview.data(),
372                                    (flags & Mergeable) == Mergeable,
373                                    &mMissingTilesets,
374                                    paintedRegions);
375 }
376 
377 struct PaintOperation
378 {
379     QPoint pos;
380     Map *stamp;
381 };
382 
shiftRows(TileLayer * tileLayer,Map::StaggerIndex staggerIndex)383 static void shiftRows(TileLayer *tileLayer, Map::StaggerIndex staggerIndex)
384 {
385     tileLayer->resize(QSize(tileLayer->width() + 1, tileLayer->height()), QPoint());
386 
387     for (int y = (staggerIndex + 1) & 1; y < tileLayer->height(); y += 2) {
388         for (int x = tileLayer->width() - 2; x >= 0; --x)
389             tileLayer->setCell(x + 1, y, tileLayer->cellAt(x, y));
390         tileLayer->setCell(0, y, Cell());
391     }
392 }
393 
shiftColumns(TileLayer * tileLayer,Map::StaggerIndex staggerIndex)394 static void shiftColumns(TileLayer *tileLayer, Map::StaggerIndex staggerIndex)
395 {
396     tileLayer->resize(QSize(tileLayer->width(), tileLayer->height() + 1), QPoint());
397 
398     for (int x = (staggerIndex + 1) & 1; x < tileLayer->width(); x += 2) {
399         for (int y = tileLayer->height() - 2; y >= 0; --y)
400             tileLayer->setCell(x, y + 1, tileLayer->cellAt(x, y));
401         tileLayer->setCell(x, 0, Cell());
402     }
403 }
404 
drawPreviewLayer(const QVector<QPoint> & points)405 void StampBrush::drawPreviewLayer(const QVector<QPoint> &points)
406 {
407     mPreviewMap.clear();
408 
409     if (mStamp.isEmpty() && !mIsWangFill)
410         return;
411 
412     if (mIsRandom) {
413         if (!mRandomCacheValid) {
414             updateRandomList();
415             mRandomCacheValid = true;
416         }
417 
418         if (mRandomCellPicker.isEmpty())
419             return;
420 
421         QRect bounds;
422         for (const QPoint &p : points)
423             bounds |= QRect(p, p);
424 
425         Map::Parameters mapParameters = mapDocument()->map()->parameters();
426         mapParameters.width = bounds.width();
427         mapParameters.height = bounds.height();
428         mapParameters.infinite = false;
429         SharedMap preview = SharedMap::create(mapParameters);
430 
431         std::unique_ptr<TileLayer> previewLayer {
432             new TileLayer(QString(), bounds.topLeft(), bounds.size())
433         };
434 
435         for (const QPoint &p : points) {
436             const Cell &cell = mRandomCellPicker.pick();
437             previewLayer->setCell(p.x() - bounds.left(),
438                                   p.y() - bounds.top(),
439                                   cell);
440         }
441 
442         preview->addLayer(std::move(previewLayer));
443         preview->addTilesets(preview->usedTilesets());
444         mPreviewMap = preview;
445     } else if (mIsWangFill) {
446         if (!mWangSet)
447             return;
448 
449         const TileLayer *tileLayer = currentTileLayer();
450         if (!tileLayer)
451             return;
452 
453         QRegion paintedRegion;
454         for (const QPoint &p : points)
455             paintedRegion += QRect(p, p);
456 
457         const QRect bounds = paintedRegion.boundingRect();
458 
459         Map::Parameters mapParameters = mapDocument()->map()->parameters();
460         mapParameters.width = bounds.width();
461         mapParameters.height = bounds.height();
462         mapParameters.infinite = false;
463         SharedMap preview = SharedMap::create(mapParameters);
464 
465         std::unique_ptr<TileLayer> previewLayer {
466             new TileLayer(QString(), bounds.topLeft(), bounds.size())
467         };
468 
469         WangFiller wangFiller(*mWangSet, mapDocument()->renderer());
470 
471         wangFiller.fillRegion(*previewLayer, *tileLayer, paintedRegion);
472 
473         preview->addLayer(std::move(previewLayer));
474         preview->addTileset(mWangSet->tileset()->sharedPointer());
475         mPreviewMap = preview;
476     } else {
477         QRegion paintedRegion;
478         QVector<PaintOperation> operations;
479         QHash<const Map *, QRegion> regionCache;
480         QHash<const Map *, Map *> shiftedCopies;
481 
482         mMissingTilesets.clear();
483 
484         for (const QPoint &p : points) {
485             Map *map = mStamp.randomVariation().map;
486             mapDocument()->unifyTilesets(map, mMissingTilesets);
487 
488             Map::StaggerAxis mapStaggerAxis = mapDocument()->map()->staggerAxis();
489 
490             // if staggered map, makes sure stamp stays the same
491             if (mapDocument()->map()->isStaggered()
492                     && ((mapStaggerAxis == Map::StaggerY) ? map->height() > 1 : map->width() > 1)) {
493 
494                 Map::StaggerIndex mapStaggerIndex = mapDocument()->map()->staggerIndex();
495                 Map::StaggerIndex stampStaggerIndex = map->staggerIndex();
496 
497                 if (mapStaggerAxis == Map::StaggerY) {
498                     bool topIsOdd = (p.y() - map->height() / 2) & 1;
499 
500                     if ((stampStaggerIndex == mapStaggerIndex) == topIsOdd) {
501                         Map *shiftedMap = shiftedCopies.value(map);
502                         if (!shiftedMap) {
503                             shiftedMap = map->clone().release();
504                             shiftedCopies.insert(map, shiftedMap);
505 
506                             LayerIterator it(shiftedMap, Layer::TileLayerType);
507                             while (auto tileLayer = static_cast<TileLayer*>(it.next()))
508                                 shiftRows(tileLayer, stampStaggerIndex);
509                         }
510                         map = shiftedMap;
511                     }
512                 } else {
513                     bool leftIsOdd = (p.x() - map->width() / 2) & 1;
514 
515                     if ((stampStaggerIndex == mapStaggerIndex) == leftIsOdd) {
516                         Map *shiftedMap = shiftedCopies.value(map);
517                         if (!shiftedMap) {
518                             shiftedMap = map->clone().release();
519                             shiftedCopies.insert(map, shiftedMap);
520 
521                             LayerIterator it(shiftedMap, Layer::TileLayerType);
522                             while (auto tileLayer = static_cast<TileLayer*>(it.next()))
523                                 shiftColumns(tileLayer, stampStaggerIndex);
524                         }
525                         map = shiftedMap;
526                     }
527                 }
528             }
529 
530             QRegion stampRegion;
531             if (regionCache.contains(map)) {
532                 stampRegion = regionCache.value(map);
533             } else {
534                 stampRegion = map->tileRegion();
535                 regionCache.insert(map, stampRegion);
536             }
537 
538             QPoint centered(p.x() - map->width() / 2,
539                             p.y() - map->height() / 2);
540 
541             const QRegion region = stampRegion.translated(centered.x(),
542                                                           centered.y());
543             if (!paintedRegion.intersects(region)) {
544                 paintedRegion += region;
545 
546                 PaintOperation op = { centered, map };
547                 operations.append(op);
548             }
549         }
550 
551         const QRect bounds = paintedRegion.boundingRect();
552 
553         Map::Parameters mapParameters = mapDocument()->map()->parameters();
554         mapParameters.width = bounds.width();
555         mapParameters.height = bounds.height();
556         mapParameters.infinite = false;
557         SharedMap preview = SharedMap::create(mapParameters);
558 
559         for (const PaintOperation &op : operations) {
560             LayerIterator layerIterator(op.stamp, Layer::TileLayerType);
561             while (auto tileLayer = static_cast<TileLayer*>(layerIterator.next())) {
562                 TileLayer *target = findTileLayerByName(*preview, tileLayer->name());
563                 if (!target) {
564                     target = new TileLayer(tileLayer->name(), bounds.topLeft(), bounds.size());
565                     preview->addLayer(target);
566                 }
567                 target->merge(op.pos - bounds.topLeft() + tileLayer->position(), tileLayer);
568             }
569         }
570 
571         qDeleteAll(shiftedCopies);
572 
573         preview->addTilesets(preview->usedTilesets());
574         mPreviewMap = preview;
575     }
576 }
577 
578 /**
579  * Updates the position of the brush item based on the mouse position.
580  */
updatePreview()581 void StampBrush::updatePreview()
582 {
583     updatePreview(tilePosition());
584 }
585 
updatePreview(QPoint tilePos)586 void StampBrush::updatePreview(QPoint tilePos)
587 {
588     if (!mapDocument())
589         return;
590 
591     QRegion tileRegion;
592 
593     if (mBrushBehavior == Capture) {
594         mPreviewMap.clear();
595         tileRegion = mCaptureStampHelper.capturedArea(tilePos);
596     } else {
597         switch (mBrushBehavior) {
598         case LineStartSet:
599             drawPreviewLayer(pointsOnLine(mStampReference, tilePos));
600             break;
601         case CircleMidSet:
602             drawPreviewLayer(pointsOnEllipse(mStampReference, tilePos));
603             break;
604         case Capture:
605             // already handled above
606             break;
607         case Circle:
608             // while finding the mid point, there is no need to show
609             // the (maybe bigger than 1x1) stamp
610             mPreviewMap.clear();
611             break;
612         case Line:
613         case Free:
614         case Paint:
615             drawPreviewLayer(QVector<QPoint>() << tilePos);
616             break;
617         }
618 
619         if (mPreviewMap)
620             tileRegion = mPreviewMap->tileRegion();
621 
622         if (tileRegion.isEmpty())
623             tileRegion = QRect(tilePos, tilePos);
624     }
625 
626     brushItem()->setMap(mPreviewMap, tileRegion);
627 }
628 
setRandom(bool value)629 void StampBrush::setRandom(bool value)
630 {
631     if (mIsRandom == value)
632         return;
633 
634     mIsRandom = value;
635 
636     if (mIsRandom) {
637         mIsWangFill = false;
638         mStampActions->wangFill()->setChecked(false);
639     }
640 
641     invalidateRandomCache();
642     updatePreview();
643 }
644 
setWangFill(bool value)645 void StampBrush::setWangFill(bool value)
646 {
647     if (mIsWangFill == value)
648         return;
649 
650     mIsWangFill = value;
651 
652     if (mIsWangFill) {
653         mIsRandom = false;
654         mStampActions->random()->setChecked(false);
655     }
656 
657     updatePreview();
658 }
659 
invalidateRandomCache()660 void StampBrush::invalidateRandomCache()
661 {
662     mRandomCacheValid = false;
663 }
664 
665 #include "moc_stampbrush.cpp"
666