1 /*
2  * minimaprenderer.cpp
3  * Copyright 2017, Yuriy Natarov <natarur@gmail.com>
4  * Copyright 2012, Christoph Schnackenberg <bluechs@gmx.de>
5  * Copyright 2012-2020, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
6  *
7  * This file is part of libtiled.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are met:
11  *
12  *    1. Redistributions of source code must retain the above copyright notice,
13  *       this list of conditions and the following disclaimer.
14  *
15  *    2. Redistributions in binary form must reproduce the above copyright
16  *       notice, this list of conditions and the following disclaimer in the
17  *       documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
22  * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "minimaprenderer.h"
32 
33 #include "imagelayer.h"
34 #include "map.h"
35 #include "mapobject.h"
36 #include "maprenderer.h"
37 #include "objectgroup.h"
38 #include "tilelayer.h"
39 
40 #include <QPainter>
41 
42 #include "qtcompat_p.h"
43 
44 using namespace Tiled;
45 
MiniMapRenderer(const Map * map)46 MiniMapRenderer::MiniMapRenderer(const Map *map)
47     : mMap(map)
48     , mRenderer(MapRenderer::create(map))
49 {
50     mRenderer->setFlag(ShowTileObjectOutlines, false);
51 }
52 
~MiniMapRenderer()53 MiniMapRenderer::~MiniMapRenderer()
54 {
55 }
56 
mapSize() const57 QSize MiniMapRenderer::mapSize() const
58 {
59     QRect mapBoundingRect = mRenderer->mapBoundingRect();
60     QSize mapSize = mapBoundingRect.size();
61 
62     QMargins margins = mMap->computeLayerOffsetMargins();
63     mapSize.setWidth(mapSize.width() + margins.left() + margins.right());
64     mapSize.setHeight(mapSize.height() + margins.top() + margins.bottom());
65 
66     return mapSize;
67 }
68 
render(QSize size,RenderFlags renderFlags) const69 QImage MiniMapRenderer::render(QSize size, RenderFlags renderFlags) const
70 {
71     QImage image(size, QImage::Format_ARGB32_Premultiplied);
72     renderToImage(image, renderFlags);
73     return image;
74 }
75 
objectLessThan(const MapObject * a,const MapObject * b)76 static bool objectLessThan(const MapObject *a, const MapObject *b)
77 {
78     return a->y() < b->y();
79 }
80 
cellRect(const MapRenderer & renderer,const Cell & cell,const QPointF & tileCoords)81 static QRectF cellRect(const MapRenderer &renderer,
82                        const Cell &cell,
83                        const QPointF &tileCoords)
84 {
85     const Tile *tile = cell.tile();
86     if (!tile)
87         return QRectF();
88 
89     QPointF pixelCoords = renderer.tileToScreenCoords(tileCoords);
90     QPointF offset = tile->offset();
91     QSize size = tile->size();
92 
93     if (cell.flippedAntiDiagonally())
94         std::swap(size.rwidth(), size.rheight());
95 
96     // This is a correction needed because tileToScreenCoords does not return
97     // the bottom-left origin of the tile image, but rather the top-left
98     // corner of the cell.
99     pixelCoords.ry() += renderer.map()->tileHeight() - size.height();
100 
101     return QRectF(pixelCoords, size).translated(offset);
102 }
103 
extendMapRect(QRect & mapBoundingRect,const MapRenderer & renderer)104 static void extendMapRect(QRect &mapBoundingRect, const MapRenderer &renderer)
105 {
106     // Start with the basic map size
107     QRectF rect(mapBoundingRect);
108 
109     // Take into account large tiles extending beyond their cell
110     for (const Layer *layer : renderer.map()->tileLayers()) {
111         const TileLayer *tileLayer = static_cast<const TileLayer*>(layer);
112         const QPointF offset = tileLayer->totalOffset();
113 
114         for (int y = 0; y < tileLayer->height(); ++y) {
115             for (int x = 0; x < tileLayer->width(); ++x) {
116                 const Cell &cell = tileLayer->cellAt(x, y);
117 
118                 if (!cell.isEmpty()) {
119                     QRectF r = cellRect(renderer, cell, QPointF(x, y));
120                     r.translate(offset);
121                     rect |= r;
122                 }
123             }
124         }
125     }
126 
127     mapBoundingRect = rect.toAlignedRect();
128 }
129 
renderToImage(QImage & image,RenderFlags renderFlags) const130 void MiniMapRenderer::renderToImage(QImage &image, RenderFlags renderFlags) const
131 {
132     if (!mMap)
133         return;
134     if (image.isNull())
135         return;
136 
137     const bool drawObjects = renderFlags.testFlag(RenderFlag::DrawMapObjects);
138     const bool drawTileLayers = renderFlags.testFlag(RenderFlag::DrawTileLayers);
139     const bool drawImageLayers = renderFlags.testFlag(RenderFlag::DrawImageLayers);
140     const bool drawTileGrid = renderFlags.testFlag(RenderFlag::DrawGrid);
141     const bool visibleLayersOnly = renderFlags.testFlag(RenderFlag::IgnoreInvisibleLayer);
142 
143     QRect mapBoundingRect = mRenderer->mapBoundingRect();
144 
145     if (renderFlags.testFlag(IncludeOverhangingTiles))
146         extendMapRect(mapBoundingRect, *mRenderer);
147 
148     QSize mapSize = mapBoundingRect.size();
149     QMargins margins = mMap->computeLayerOffsetMargins();
150     mapSize.setWidth(mapSize.width() + margins.left() + margins.right());
151     mapSize.setHeight(mapSize.height() + margins.top() + margins.bottom());
152 
153     // Determine the largest possible scale
154     const qreal scale = qMin(static_cast<qreal>(image.width()) / mapSize.width(),
155                              static_cast<qreal>(image.height()) / mapSize.height());
156 
157     if (renderFlags.testFlag(DrawBackground) && mMap->backgroundColor().isValid())
158         image.fill(mMap->backgroundColor());
159     else
160         image.fill(Qt::transparent);
161 
162     QPainter painter(&image);
163     painter.setRenderHints(QPainter::SmoothPixmapTransform, renderFlags.testFlag(SmoothPixmapTransform));
164 
165     // Center the map in the requested size
166     const QSize scaledMapSize = mapSize * scale;
167     const QPointF centerOffset((image.width() - scaledMapSize.width()) / 2,
168                                (image.height() - scaledMapSize.height()) / 2);
169 
170     painter.translate(centerOffset);
171     painter.scale(scale, scale);
172     painter.translate(margins.left(), margins.top());
173     painter.translate(-mapBoundingRect.topLeft());
174 
175     mRenderer->setPainterScale(scale);
176 
177     LayerIterator iterator(mMap);
178     while (const Layer *layer = iterator.next()) {
179         if (visibleLayersOnly && layer->isHidden())
180             continue;
181 
182         const auto offset = layer->totalOffset();
183 
184         painter.setOpacity(layer->effectiveOpacity());
185         painter.translate(offset);
186 
187         switch (layer->layerType()) {
188         case Layer::TileLayerType: {
189             if (drawTileLayers) {
190                 const TileLayer *tileLayer = static_cast<const TileLayer*>(layer);
191                 mRenderer->drawTileLayer(&painter, tileLayer);
192             }
193             break;
194         }
195 
196         case Layer::ObjectGroupType: {
197             if (drawObjects) {
198                 const ObjectGroup *objectGroup = static_cast<const ObjectGroup*>(layer);
199                 QList<MapObject*> objects = objectGroup->objects();
200 
201                 if (objectGroup->drawOrder() == ObjectGroup::TopDownOrder)
202                     std::stable_sort(objects.begin(), objects.end(), objectLessThan);
203 
204                 for (const MapObject *object : qAsConst(objects)) {
205                     if (object->isVisible()) {
206                         if (object->rotation() != qreal(0)) {
207                             QPointF origin = mRenderer->pixelToScreenCoords(object->position());
208                             painter.save();
209                             painter.translate(origin);
210                             painter.rotate(object->rotation());
211                             painter.translate(-origin);
212                         }
213 
214                         const QColor color = object->effectiveColor();
215                         mRenderer->drawMapObject(&painter, object, color);
216 
217                         if (object->rotation() != qreal(0))
218                             painter.restore();
219                     }
220                 }
221             }
222             break;
223         }
224         case Layer::ImageLayerType: {
225             if (drawImageLayers) {
226                 const ImageLayer *imageLayer = static_cast<const ImageLayer*>(layer);
227                 mRenderer->drawImageLayer(&painter, imageLayer);
228             }
229             break;
230         }
231 
232         case Layer::GroupLayerType:
233             // Recursion handled by LayerIterator
234             break;
235         }
236 
237         painter.translate(-offset);
238     }
239 
240     if (drawTileGrid)
241         mRenderer->drawGrid(&painter, mapBoundingRect, mGridColor);
242 
243     if (drawObjects && mRenderObjectLabelCallback) {
244         for (const Layer *layer : mMap->objectGroups()) {
245             if (visibleLayersOnly && layer->isHidden())
246                 continue;
247 
248             const ObjectGroup *objectGroup = static_cast<const ObjectGroup*>(layer);
249 
250             for (const MapObject *object : objectGroup->objects())
251                 if (object->isVisible())
252                     mRenderObjectLabelCallback(painter, object, *mRenderer);
253         }
254     }
255 }
256