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