Add ability to export map stitch images.

This commit is contained in:
Marcus Huderle 2020-04-18 13:07:41 -05:00 committed by huderlem
parent 800d584eb5
commit bb6dbedabf
11 changed files with 275 additions and 57 deletions

View file

@ -12,6 +12,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
### Added ### Added
- Support for [pokefirered](https://github.com/pret/pokefirered). Kanto fans rejoice! At long last porymap supports the FRLG decompilation project. - Support for [pokefirered](https://github.com/pret/pokefirered). Kanto fans rejoice! At long last porymap supports the FRLG decompilation project.
- Add ability to export map stitches with `File -> Export Map Stitch Image...`.
### Changed ### Changed
- Porymap now saves map and encounter json data in an order consistent with the upstream repos. This will provide more comprehensible diffs, among other things. - Porymap now saves map and encounter json data in an order consistent with the upstream repos. This will provide more comprehensible diffs, among other things.
@ -20,6 +21,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix bug where pressing TAB key did not navigate through widgets in the wild encounter tables. - Fix bug where pressing TAB key did not navigate through widgets in the wild encounter tables.
- Fix bug that allowed selecting an invalid metatile in the metatile selector. - Fix bug that allowed selecting an invalid metatile in the metatile selector.
## [3.0.1] - 2020-03-04 ## [3.0.1] - 2020-03-04
### Fixed ### Fixed
- Fix bug on Mac where tileset images were corrupted when saving. - Fix bug on Mac where tileset images were corrupted when saving.

View file

@ -2890,6 +2890,7 @@
<addaction name="action_Save_Project"/> <addaction name="action_Save_Project"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Export_Map_Image"/> <addaction name="action_Export_Map_Image"/>
<addaction name="actionExport_Stitched_Map_Image"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Exit"/> <addaction name="action_Exit"/>
</widget> </widget>
@ -3172,6 +3173,11 @@
<string>Themes...</string> <string>Themes...</string>
</property> </property>
</action> </action>
<action name="actionExport_Stitched_Map_Image">
<property name="text">
<string>Export Map Stitch Image...</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View file

@ -131,6 +131,8 @@ public:
void objectsView_onMouseMove(QMouseEvent *event); void objectsView_onMouseMove(QMouseEvent *event);
void objectsView_onMouseRelease(QMouseEvent *event); void objectsView_onMouseRelease(QMouseEvent *event);
int getBorderDrawDistance(int dimension);
private: private:
void setConnectionItemsVisible(bool); void setConnectionItemsVisible(bool);
void setBorderItemsVisible(bool, qreal = 1); void setBorderItemsVisible(bool, qreal = 1);
@ -147,7 +149,6 @@ private:
void updateMirroredConnectionMap(MapConnection*, QString); void updateMirroredConnectionMap(MapConnection*, QString);
void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false); void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false);
void updateEncounterFields(EncounterFields newFields); void updateEncounterFields(EncounterFields newFields);
int getBorderDrawDistance(int dimension);
Event* createNewObjectEvent(); Event* createNewObjectEvent();
Event* createNewWarpEvent(); Event* createNewWarpEvent();
Event* createNewHealLocationEvent(); Event* createNewHealLocationEvent();

View file

@ -62,6 +62,10 @@
#include <memory> #include <memory>
#include <initializer_list> #include <initializer_list>
#include <QtGlobal>
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 1)
#include "qstringhash.h"
#endif
#include "orderedmap.h" #include "orderedmap.h"
#ifdef _MSC_VER #ifdef _MSC_VER

View file

@ -1640,7 +1640,7 @@ private:
* the erased element and all the ones after the erased element (including end()). * the erased element and all the ones after the erased element (including end()).
* Otherwise all the iterators are invalidated if an erase occurs. * Otherwise all the iterators are invalidated if an erase occurs.
*/ */
template<class Key, template<class Key,
class T, class T,
class Hash = std::hash<Key>, class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>, class KeyEqual = std::equal_to<Key>,
@ -2031,7 +2031,7 @@ public:
T& operator[](const Key& key) { return m_ht[key]; } T& operator[](const Key& key) { return m_ht[key]; }
T& operator[](Key&& key) { return m_ht[std::move(key)]; } T& operator[](Key&& key) { return m_ht[std::move(key)]; }
@ -2042,7 +2042,7 @@ public:
* Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
* as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
*/ */
size_type count(const Key& key, std::size_t precalculated_hash) const { size_type count(const Key& key, std::size_t precalculated_hash) const {
return m_ht.count(key, precalculated_hash); return m_ht.count(key, precalculated_hash);
} }
@ -2079,7 +2079,7 @@ public:
/** /**
* @copydoc find(const Key& key, std::size_t precalculated_hash) * @copydoc find(const Key& key, std::size_t precalculated_hash)
*/ */
const_iterator find(const Key& key, std::size_t precalculated_hash) const { const_iterator find(const Key& key, std::size_t precalculated_hash) const {
return m_ht.find(key, precalculated_hash); return m_ht.find(key, precalculated_hash);
} }
@ -2124,7 +2124,7 @@ public:
* Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same
* as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.
*/ */
std::pair<iterator, iterator> equal_range(const Key& key, std::size_t precalculated_hash) { std::pair<iterator, iterator> equal_range(const Key& key, std::size_t precalculated_hash) {
return m_ht.equal_range(key, precalculated_hash); return m_ht.equal_range(key, precalculated_hash);
} }
@ -2133,7 +2133,7 @@ public:
/** /**
* @copydoc equal_range(const Key& key, std::size_t precalculated_hash) * @copydoc equal_range(const Key& key, std::size_t precalculated_hash)
*/ */
std::pair<const_iterator, const_iterator> equal_range(const Key& key, std::size_t precalculated_hash) const { std::pair<const_iterator, const_iterator> equal_range(const Key& key, std::size_t precalculated_hash) const {
return m_ht.equal_range(key, precalculated_hash); return m_ht.equal_range(key, precalculated_hash);
} }

19
include/lib/qstringhash.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef QSTRINGHASH_H
#define QSTRINGHASH_H
#include <QHash>
#include <QString>
#include <functional>
// This is a custom hash function for QString so it can be used as
// a key in a std::hash structure. Qt 5.14 added this function, so
// this file should only be included in earlier versions.
namespace std {
template<> struct hash<QString> {
std::size_t operator()(const QString& s) const noexcept {
return static_cast<size_t>(qHash(s));
}
};
}
#endif // QSTRINGHASH_H

View file

@ -109,6 +109,7 @@ private slots:
void currentMetatilesSelectionChanged(); void currentMetatilesSelectionChanged();
void on_action_Export_Map_Image_triggered(); void on_action_Export_Map_Image_triggered();
void on_actionExport_Stitched_Map_Image_triggered();
void on_comboBox_ConnectionDirection_currentIndexChanged(const QString &arg1); void on_comboBox_ConnectionDirection_currentIndexChanged(const QString &arg1);
void on_spinBox_ConnectionOffset_valueChanged(int offset); void on_spinBox_ConnectionOffset_valueChanged(int offset);
@ -219,6 +220,7 @@ private:
void closeSupplementaryWindows(); void closeSupplementaryWindows();
bool isProjectOpen(); bool isProjectOpen();
void showExportMapImageWindow(bool stitchMode);
}; };
enum MapListUserRoles { enum MapListUserRoles {

View file

@ -15,7 +15,7 @@ class MapImageExporter : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit MapImageExporter(QWidget *parent, Editor *editor); explicit MapImageExporter(QWidget *parent, Editor *editor, bool stitchMode);
~MapImageExporter(); ~MapImageExporter();
private: private:
@ -39,9 +39,12 @@ private:
bool showGrid = false; bool showGrid = false;
bool showBorder = false; bool showBorder = false;
bool showCollision = false; bool showCollision = false;
bool stitchMode = false;
void updatePreview(); void updatePreview();
void saveImage(); void saveImage();
QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder);
QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder);
private slots: private slots:
void on_checkBox_Objects_stateChanged(int state); void on_checkBox_Objects_stateChanged(int state);

View file

@ -134,7 +134,8 @@ HEADERS += include/core/block.h \
include/project.h \ include/project.h \
include/settings.h \ include/settings.h \
include/log.h \ include/log.h \
include/ui/newtilesetdialog.h include/ui/newtilesetdialog.h \
include/lib/qstringhash.h
FORMS += forms/mainwindow.ui \ FORMS += forms/mainwindow.ui \
forms/eventpropertiesframe.ui \ forms/eventpropertiesframe.ui \

View file

@ -28,6 +28,7 @@
#include <QSysInfo> #include <QSysInfo>
#include <QDesktopServices> #include <QDesktopServices>
#include <QMatrix> #include <QMatrix>
#include <QSet>
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), QMainWindow(parent),
@ -2080,13 +2081,21 @@ void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryT
this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); this->editor->updateSecondaryTileset(secondaryTilesetLabel, true);
} }
void MainWindow::on_action_Export_Map_Image_triggered() void MainWindow::on_action_Export_Map_Image_triggered() {
{ showExportMapImageWindow(false);
if (!this->mapImageExporter) { }
this->mapImageExporter = new MapImageExporter(this, this->editor);
connect(this->mapImageExporter, &QObject::destroyed, [=](QObject *) { this->mapImageExporter = nullptr; }); void MainWindow::on_actionExport_Stitched_Map_Image_triggered() {
this->mapImageExporter->setAttribute(Qt::WA_DeleteOnClose); showExportMapImageWindow(true);
} }
void MainWindow::showExportMapImageWindow(bool stitchMode) {
if (this->mapImageExporter)
delete this->mapImageExporter;
this->mapImageExporter = new MapImageExporter(this, this->editor, stitchMode);
connect(this->mapImageExporter, &QObject::destroyed, [=](QObject *) { this->mapImageExporter = nullptr; });
this->mapImageExporter->setAttribute(Qt::WA_DeleteOnClose);
if (!this->mapImageExporter->isVisible()) { if (!this->mapImageExporter->isVisible()) {
this->mapImageExporter->show(); this->mapImageExporter->show();

View file

@ -7,13 +7,19 @@
#include <QPainter> #include <QPainter>
#include <QPoint> #include <QPoint>
MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_) : #define STITCH_MODE_BORDER_DISTANCE 2
MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, bool stitchMode) :
QDialog(parent_), QDialog(parent_),
ui(new Ui::MapImageExporter) ui(new Ui::MapImageExporter)
{ {
ui->setupUi(this); ui->setupUi(this);
this->map = editor_->map; this->map = editor_->map;
this->editor = editor_; this->editor = editor_;
this->stitchMode = stitchMode;
this->setWindowTitle(this->stitchMode ? "Export Map Stitch Image" : "Export Map Image");
this->ui->groupBox_Connections->setVisible(!this->stitchMode);
this->ui->comboBox_MapSelection->addItems(*editor->project->mapNames); this->ui->comboBox_MapSelection->addItems(*editor->project->mapNames);
this->ui->comboBox_MapSelection->setCurrentText(map->name); this->ui->comboBox_MapSelection->setCurrentText(map->name);
@ -28,32 +34,192 @@ MapImageExporter::~MapImageExporter() {
} }
void MapImageExporter::saveImage() { void MapImageExporter::saveImage() {
QString defaultFilepath = QString("%1/%2.png").arg(editor->project->root).arg(map->name); QString title = this->stitchMode ? "Export Map Stitch Image" : "Export Map Image";
QString filepath = QFileDialog::getSaveFileName(this, "Export Map Image", defaultFilepath, QString defaultFilename = this->stitchMode ? QString("Stitch_From_%1").arg(map->name) : map->name;
QString defaultFilepath = QString("%1/%2.png").arg(editor->project->root).arg(defaultFilename);
QString filepath = QFileDialog::getSaveFileName(this, title, defaultFilepath,
"Image Files (*.png *.jpg *.bmp)"); "Image Files (*.png *.jpg *.bmp)");
if (!filepath.isEmpty()) { if (!filepath.isEmpty()) {
this->preview.save(filepath); if (this->stitchMode) {
QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this);
progress.setAutoClose(true);
progress.setWindowModality(Qt::WindowModal);
progress.setModal(true);
QPixmap pixmap = this->getStitchedImage(&progress, this->showBorder);
if (progress.wasCanceled()) {
progress.close();
return;
}
pixmap.save(filepath);
progress.close();
} else {
this->preview.save(filepath);
}
this->close(); this->close();
} }
} }
struct StitchedMap {
int x;
int y;
Map* map;
};
QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool includeBorder) {
// Do a bread-first search to gather a collection of
// all reachable maps with their relative offsets.
QSet<QString> visited;
QList<StitchedMap> stitchedMaps;
QList<StitchedMap> unvisited;
unvisited.append(StitchedMap{0, 0, this->editor->map});
progress->setLabelText("Gathering stitched maps...");
while (!unvisited.isEmpty()) {
if (progress->wasCanceled()) {
return QPixmap();
}
progress->setMaximum(visited.size() + unvisited.size());
progress->setValue(visited.size());
StitchedMap cur = unvisited.takeFirst();
if (visited.contains(cur.map->name))
continue;
visited.insert(cur.map->name);
stitchedMaps.append(cur);
for (MapConnection *connection : cur.map->connections) {
if (connection->direction == "dive" || connection->direction == "emerge")
continue;
int x = cur.x;
int y = cur.y;
int offset = connection->offset.toInt(nullptr, 0);
Map *connectionMap = this->editor->project->loadMap(connection->map_name);
if (connection->direction == "up") {
x += offset;
y -= connectionMap->getHeight();
} else if (connection->direction == "down") {
x += offset;
y += cur.map->getHeight();
} else if (connection->direction == "left") {
x -= connectionMap->getWidth();
y += offset;
} else if (connection->direction == "right") {
x += cur.map->getWidth();
y += offset;
}
unvisited.append(StitchedMap{x, y, connectionMap});
}
}
// Determine the overall dimensions of the stitched maps.
int maxX = INT_MIN;
int minX = INT_MAX;
int maxY = INT_MIN;
int minY = INT_MAX;
for (StitchedMap map : stitchedMaps) {
int left = map.x;
int right = map.x + map.map->getWidth();
int top = map.y;
int bottom = map.y + map.map->getHeight();
if (left < minX)
minX = left;
if (right > maxX)
maxX = right;
if (top < minY)
minY = top;
if (bottom > maxY)
maxY = bottom;
}
if (includeBorder) {
minX -= STITCH_MODE_BORDER_DISTANCE;
maxX += STITCH_MODE_BORDER_DISTANCE;
minY -= STITCH_MODE_BORDER_DISTANCE;
maxY += STITCH_MODE_BORDER_DISTANCE;
}
// Draw the maps on the full canvas, while taking
// their respective offsets into account.
progress->setLabelText("Drawing stitched maps...");
progress->setValue(0);
progress->setMaximum(stitchedMaps.size());
int numDrawn = 0;
QPixmap stitchedPixmap((maxX - minX) * 16, (maxY - minY) * 16);
QPainter painter(&stitchedPixmap);
for (StitchedMap map : stitchedMaps) {
if (progress->wasCanceled()) {
return QPixmap();
}
progress->setValue(numDrawn);
numDrawn++;
int pixelX = (map.x - minX) * 16;
int pixelY = (map.y - minY) * 16;
if (includeBorder) {
pixelX -= STITCH_MODE_BORDER_DISTANCE * 16;
pixelY -= STITCH_MODE_BORDER_DISTANCE * 16;
}
QPixmap pixmap = this->getFormattedMapPixmap(map.map, false);
painter.drawPixmap(pixelX, pixelY, pixmap);
}
// When including the borders, we simply draw all the maps again
// without their borders, since the first pass results in maps
// being occluded by other map borders.
if (includeBorder) {
progress->setLabelText("Drawing stitched maps without borders...");
progress->setValue(0);
progress->setMaximum(stitchedMaps.size());
numDrawn = 0;
for (StitchedMap map : stitchedMaps) {
if (progress->wasCanceled()) {
return QPixmap();
}
progress->setValue(numDrawn);
numDrawn++;
int pixelX = (map.x - minX) * 16;
int pixelY = (map.y - minY) * 16;
QPixmap pixmapWithoutBorders = this->getFormattedMapPixmap(map.map, true);
painter.drawPixmap(pixelX, pixelY, pixmapWithoutBorders);
}
}
return stitchedPixmap;
}
void MapImageExporter::updatePreview() { void MapImageExporter::updatePreview() {
if (scene) { if (scene) {
delete scene; delete scene;
scene = nullptr; scene = nullptr;
} }
preview = getFormattedMapPixmap(this->map, false);
scene = new QGraphicsScene; scene = new QGraphicsScene;
scene->addPixmap(preview);
this->scene->setSceneRect(this->scene->itemsBoundingRect());
this->ui->graphicsView_Preview->setScene(scene);
this->ui->graphicsView_Preview->setFixedSize(scene->itemsBoundingRect().width() + 2,
scene->itemsBoundingRect().height() + 2);
}
QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
QPixmap pixmap;
// draw background layer / base image // draw background layer / base image
map->render(true);
if (showCollision) { if (showCollision) {
preview = map->collision_pixmap; map->renderCollision(editor->collisionOpacity, true);
pixmap = map->collision_pixmap;
} else { } else {
preview = map->pixmap; pixmap = map->pixmap;
} }
// draw events // draw events
QPainter eventPainter(&preview); QPainter eventPainter(&pixmap);
QList<Event*> events = map->getAllEvents(); QList<Event*> events = map->getAllEvents();
editor->project->loadEventPixmaps(events);
for (Event *event : events) { for (Event *event : events) {
QString group = event->get("event_group_type"); QString group = event->get("event_group_type");
if ((showObjects && group == "object_event_group") if ((showObjects && group == "object_event_group")
@ -69,31 +235,40 @@ void MapImageExporter::updatePreview() {
// note: this will break when allowing map to be selected from drop down maybe // note: this will break when allowing map to be selected from drop down maybe
int borderHeight = 0, borderWidth = 0; int borderHeight = 0, borderWidth = 0;
bool forceDrawBorder = showUpConnections || showDownConnections || showLeftConnections || showRightConnections; bool forceDrawBorder = showUpConnections || showDownConnections || showLeftConnections || showRightConnections;
if (showBorder || forceDrawBorder) { if (!ignoreBorder && (showBorder || forceDrawBorder)) {
borderHeight = BORDER_DISTANCE * 16, borderWidth = BORDER_DISTANCE * 16; int borderDistance = this->stitchMode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE;
QPixmap newPreview = QPixmap(map->pixmap.width() + borderWidth * 2, map->pixmap.height() + borderHeight * 2); map->renderBorder();
QPainter borderPainter(&newPreview); int borderHorzDist = editor->getBorderDrawDistance(map->getBorderWidth());
for (auto borderItem : editor->borderItems) { int borderVertDist = editor->getBorderDrawDistance(map->getBorderHeight());
borderPainter.drawImage(QPoint(borderItem->x() + borderWidth, borderItem->y() + borderHeight), borderWidth = borderDistance * 16;
borderItem->pixmap().toImage()); borderHeight = borderDistance * 16;
QPixmap newPixmap = QPixmap(map->pixmap.width() + borderWidth * 2, map->pixmap.height() + borderHeight * 2);
QPainter borderPainter(&newPixmap);
for (int y = borderDistance - borderVertDist; y < map->getHeight() + borderVertDist * 2; y += map->getBorderHeight()) {
for (int x = borderDistance - borderHorzDist; x < map->getWidth() + borderHorzDist * 2; x += map->getBorderWidth()) {
borderPainter.drawPixmap(x * 16, y * 16, map->layout->border_pixmap);
}
} }
borderPainter.drawImage(QPoint(borderWidth, borderHeight), preview.toImage());
borderPainter.drawImage(borderWidth, borderHeight, pixmap.toImage());
borderPainter.end(); borderPainter.end();
preview = newPreview; pixmap = newPixmap;
} }
// if showing connections, draw on outside of image if (!this->stitchMode) {
QPainter connectionPainter(&preview); // if showing connections, draw on outside of image
for (auto connectionItem : editor->connection_edit_items) { QPainter connectionPainter(&pixmap);
QString direction = connectionItem->connection->direction; for (auto connectionItem : editor->connection_edit_items) {
if ((showUpConnections && direction == "up") QString direction = connectionItem->connection->direction;
|| (showDownConnections && direction == "down") if ((showUpConnections && direction == "up")
|| (showLeftConnections && direction == "left") || (showDownConnections && direction == "down")
|| (showRightConnections && direction == "right")) || (showLeftConnections && direction == "left")
connectionPainter.drawImage(connectionItem->initialX + borderWidth, connectionItem->initialY + borderHeight, || (showRightConnections && direction == "right"))
connectionItem->basePixmap.toImage()); connectionPainter.drawImage(connectionItem->initialX + borderWidth, connectionItem->initialY + borderHeight,
connectionItem->basePixmap.toImage());
}
connectionPainter.end();
} }
connectionPainter.end();
// draw grid directly onto the pixmap // draw grid directly onto the pixmap
// since the last grid lines are outside of the pixmap, add a pixel to the bottom and right // since the last grid lines are outside of the pixmap, add a pixel to the bottom and right
@ -102,24 +277,20 @@ void MapImageExporter::updatePreview() {
if (borderHeight) addY = 0; if (borderHeight) addY = 0;
if (borderWidth) addX = 0; if (borderWidth) addX = 0;
QPixmap newPreview = QPixmap(preview.width() + addX, preview.height() + addY); QPixmap newPixmap= QPixmap(pixmap.width() + addX, pixmap.height() + addY);
QPainter gridPainter(&newPreview); QPainter gridPainter(&newPixmap);
gridPainter.drawImage(QPoint(0, 0), preview.toImage()); gridPainter.drawImage(QPoint(0, 0), pixmap.toImage());
for (auto lineItem : editor->gridLines) { for (int x = 0; x < newPixmap.width(); x += 16) {
QPointF addPos(borderWidth, borderHeight); gridPainter.drawLine(x, 0, x, newPixmap.height());
gridPainter.drawLine(lineItem->line().p1() + addPos, lineItem->line().p2() + addPos); }
for (int y = 0; y < newPixmap.height(); y += 16) {
gridPainter.drawLine(0, y, newPixmap.width(), y);
} }
gridPainter.end(); gridPainter.end();
preview = newPreview; pixmap = newPixmap;
} }
scene->addPixmap(preview); return pixmap;
this->scene->setSceneRect(this->scene->itemsBoundingRect());
this->ui->graphicsView_Preview->setScene(scene);
this->ui->graphicsView_Preview->setFixedSize(scene->itemsBoundingRect().width() + 2,
scene->itemsBoundingRect().height() + 2);
} }
void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) { void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) {