示例#1
0
void EditPolygonTool::startMoving()
{
    // Move only the clicked handle, if it was not part of the selection
    if (!mSelectedHandles.contains(mClickedHandle))
        setSelectedHandle(mClickedHandle);

    mMode = Moving;

    MapRenderer *renderer = mapDocument()->renderer();

    // Remember the current object positions
    mOldHandlePositions.clear();
    mOldPolygons.clear();
    mAlignPosition = renderer->screenToPixelCoords((*mSelectedHandles.begin())->pos());

    const auto &selectedHandles = mSelectedHandles;
    for (PointHandle *handle : selectedHandles) {
        const QPointF pos = renderer->screenToPixelCoords(handle->pos());
        mOldHandlePositions.append(handle->pos());
        if (pos.x() < mAlignPosition.x())
            mAlignPosition.setX(pos.x());
        if (pos.y() < mAlignPosition.y())
            mAlignPosition.setY(pos.y());

        MapObject *mapObject = handle->mapObject();
        if (!mOldPolygons.contains(mapObject))
            mOldPolygons.insert(mapObject, mapObject->polygon());
    }
}
示例#2
0
void MapObjectItem::syncWithMapObject()
{
    // Update the whole object when the name or type has changed
    if (mObject->name() != mName || mObject->type() != mType) {
        mName = mObject->name();
        mType = mObject->type();
        update();
        mResizeHandle->update();
    }

    QString toolTip = mName;
    if (!mType.isEmpty())
        toolTip += QLatin1String(" (") + mType + QLatin1String(")");
    setToolTip(toolTip);

    MapRenderer *renderer = mMapDocument->renderer();
    const QPointF pixelPos = renderer->tileToPixelCoords(mObject->position());
    QRectF bounds = renderer->boundingRect(mObject);
    bounds.translate(-pixelPos);

    mSyncing = true;
    setPos(pixelPos);

    if (mBoundingRect != bounds) {
        // Notify the graphics scene about the geometry change in advance
        prepareGeometryChange();
        mBoundingRect = bounds;
        const QPointF bottomRight = mObject->bounds().bottomRight();
        const QPointF handlePos = renderer->tileToPixelCoords(bottomRight);
        mResizeHandle->setPos(handlePos - pixelPos);
    }
    mSyncing = false;
}
示例#3
0
QVariant ResizeHandle::itemChange(GraphicsItemChange change,
                                  const QVariant &value)
{
    if (!mMapObjectItem->mSyncing) {
        MapRenderer *renderer = mMapObjectItem->mapDocument()->renderer();

        if (change == ItemPositionChange) {
            // Calculate the absolute pixel position
            const QPointF itemPos = mMapObjectItem->pos();
            QPointF pixelPos = value.toPointF() + itemPos;

            // Calculate the new coordinates in tiles
            QPointF tileCoords = renderer->pixelToTileCoords(pixelPos);
            const QPointF objectPos = mMapObjectItem->mapObject()->position();
            tileCoords -= objectPos;
            tileCoords.setX(qMax(tileCoords.x(), qreal(0)));
            tileCoords.setY(qMax(tileCoords.y(), qreal(0)));
            if (QApplication::keyboardModifiers() & Qt::ControlModifier)
                tileCoords = tileCoords.toPoint();
            tileCoords += objectPos;

            return renderer->tileToPixelCoords(tileCoords) - itemPos;
        }
        else if (change == ItemPositionHasChanged) {
            // Update the size of the map object
            const QPointF newPos = value.toPointF() + mMapObjectItem->pos();
            QPointF tileCoords = renderer->pixelToTileCoords(newPos);
            tileCoords -= mMapObjectItem->mapObject()->position();
            mMapObjectItem->resize(QSizeF(tileCoords.x(), tileCoords.y()));
        }
    }

    return QGraphicsItem::itemChange(change, value);
}
示例#4
0
void ImageMovementTool::mouseMoved(const QPointF &pos, Qt::KeyboardModifiers modifiers)
{
    AbstractImageTool::mouseMoved(pos, modifiers);

    if (mMousePressed) {
        if (ImageLayer *layer = currentImageLayer()) {
            QPointF diff = pos - mMouseStart;

            bool snapToGrid = Preferences::instance()->snapToGrid();
            bool snapToFineGrid = Preferences::instance()->snapToFineGrid();
            if (modifiers & Qt::ControlModifier) {
                snapToGrid = !snapToGrid;
                snapToFineGrid = false;
            }

            if (snapToGrid || snapToFineGrid) {
                MapRenderer *renderer = mapDocument()->renderer();
                int scale = snapToFineGrid ? Preferences::instance()->gridFine() : 1;
                const QPointF alignScreenPos =
                        renderer->pixelToScreenCoords(mLayerStart);
                const QPointF newAlignPixelPos = alignScreenPos + diff;

                // Snap the position to the grid
                QPointF newTileCoords =
                        (renderer->screenToTileCoords(newAlignPixelPos) * scale).toPoint();
                newTileCoords /= scale;
                diff = renderer->tileToScreenCoords(newTileCoords) - alignScreenPos;
            }

            layer->setPosition(mLayerStart + diff.toPoint());
            mapDocument()->emitImageLayerChanged(layer);
        }

    }
}
示例#5
0
void ImageLayerItem::paint(QPainter *painter,
                           const QStyleOptionGraphicsItem *option,
                           QWidget *)
{
    // TODO: Display a border around the layer when selected
    MapRenderer *renderer = mMapDocument->renderer();
    renderer->drawImageLayer(painter, imageLayer(), option->exposedRect);
}
示例#6
0
/**
 * Creates and removes handle instances as necessary to adapt to a new object
 * selection.
 */
void EditPolygonTool::updateHandles()
{
    const QSet<MapObjectItem*> &selection = mapScene()->selectedObjectItems();

    // First destroy the handles for objects that are no longer selected
    QMutableMapIterator<MapObjectItem*, QList<PointHandle*> > i(mHandles);
    while (i.hasNext()) {
        i.next();
        if (!selection.contains(i.key())) {
            const auto &handles = i.value();
            for (PointHandle *handle : handles) {
                if (handle->isSelected())
                    mSelectedHandles.remove(handle);
                delete handle;
            }

            i.remove();
        }
    }

    MapRenderer *renderer = mapDocument()->renderer();

    for (MapObjectItem *item : selection) {
        const MapObject *object = item->mapObject();
        if (!object->cell().isEmpty())
            continue;

        QPolygonF polygon = object->polygon();
        polygon.translate(object->position());

        QList<PointHandle*> pointHandles = mHandles.value(item);

        // Create missing handles
        while (pointHandles.size() < polygon.size()) {
            PointHandle *handle = new PointHandle(item, pointHandles.size());
            pointHandles.append(handle);
            mapScene()->addItem(handle);
        }

        // Remove superfluous handles
        while (pointHandles.size() > polygon.size()) {
            PointHandle *handle = pointHandles.takeLast();
            if (handle->isSelected())
                mSelectedHandles.remove(handle);
            delete handle;
        }

        // Update the position of all handles
        for (int i = 0; i < pointHandles.size(); ++i) {
            const QPointF &point = polygon.at(i);
            const QPointF handlePos = renderer->pixelToScreenCoords(point);
            const QPointF internalHandlePos = handlePos - item->pos();
            pointHandles.at(i)->setPos(item->mapToScene(internalHandlePos));
        }

        mHandles.insert(item, pointHandles);
    }
}
示例#7
0
void TileSelectionItem::paint(QPainter *painter,
                              const QStyleOptionGraphicsItem *option,
                              QWidget *)
{
    const QRegion &selection = mMapDocument->selectedArea();
    QColor highlight = QApplication::palette().highlight().color();
    highlight.setAlpha(128);

    MapRenderer *renderer = mMapDocument->renderer();
    renderer->drawTileSelection(painter, selection, highlight,
                                option->exposedRect);
}
示例#8
0
void MapScene::setMapDocument(MapDocument *mapDocument)
{
    if (mMapDocument) {
        mMapDocument->disconnect(this);

        if (!mSelectedObjectItems.isEmpty()) {
            mSelectedObjectItems.clear();
            emit selectedObjectItemsChanged();
        }
    }

    mMapDocument = mapDocument;

    if (mMapDocument) {
        MapRenderer *renderer = mMapDocument->renderer();
        renderer->setObjectLineWidth(mObjectLineWidth);
        renderer->setFlag(ShowTileObjectOutlines, mShowTileObjectOutlines);

        connect(mMapDocument, SIGNAL(mapChanged()),
                this, SLOT(mapChanged()));
        connect(mMapDocument, &MapDocument::regionChanged,
                this, &MapScene::repaintRegion);
        connect(mMapDocument, SIGNAL(tileLayerDrawMarginsChanged(TileLayer*)),
                this, SLOT(tileLayerDrawMarginsChanged(TileLayer*)));
        connect(mMapDocument, SIGNAL(layerAdded(int)),
                this, SLOT(layerAdded(int)));
        connect(mMapDocument, SIGNAL(layerRemoved(int)),
                this, SLOT(layerRemoved(int)));
        connect(mMapDocument, SIGNAL(layerChanged(int)),
                this, SLOT(layerChanged(int)));
        connect(mMapDocument, SIGNAL(objectGroupChanged(ObjectGroup*)),
                this, SLOT(objectGroupChanged(ObjectGroup*)));
        connect(mMapDocument, SIGNAL(imageLayerChanged(ImageLayer*)),
                this, SLOT(imageLayerChanged(ImageLayer*)));
        connect(mMapDocument, SIGNAL(currentLayerIndexChanged(int)),
                this, SLOT(currentLayerIndexChanged()));
        connect(mMapDocument, SIGNAL(tilesetTileOffsetChanged(Tileset*)),
                this, SLOT(tilesetTileOffsetChanged(Tileset*)));
        connect(mMapDocument, SIGNAL(objectsInserted(ObjectGroup*,int,int)),
                this, SLOT(objectsInserted(ObjectGroup*,int,int)));
        connect(mMapDocument, SIGNAL(objectsRemoved(QList<MapObject*>)),
                this, SLOT(objectsRemoved(QList<MapObject*>)));
        connect(mMapDocument, SIGNAL(objectsChanged(QList<MapObject*>)),
                this, SLOT(objectsChanged(QList<MapObject*>)));
        connect(mMapDocument, SIGNAL(objectsIndexChanged(ObjectGroup*,int,int)),
                this, SLOT(objectsIndexChanged(ObjectGroup*,int,int)));
        connect(mMapDocument, SIGNAL(selectedObjectsChanged()),
                this, SLOT(updateSelectedObjectItems()));
    }

    refreshScene();
}
示例#9
0
void MapObjectItem::syncWithMapObject()
{
    // Update the whole object when the name or polygon has changed
    if (mObject->name() != mName || mObject->polygon() != mPolygon) {
        mName = mObject->name();
        mPolygon = mObject->polygon();
        update();
    }

    const QColor color = objectColor(mObject);
    if (mColor != color) {
        mColor = color;
        update();
        mResizeHandle->update();
    }

    QString toolTip = mName;
    const QString &type = mObject->type();
    if (!type.isEmpty())
        toolTip += QLatin1String(" (") + type + QLatin1String(")");
    setToolTip(toolTip);

    MapRenderer *renderer = mMapDocument->renderer();
    const QPointF pixelPos = renderer->tileToPixelCoords(mObject->position());
    QRectF bounds = renderer->boundingRect(mObject);

    bounds.translate(-pixelPos);

    setPos(pixelPos);
    setZValue(pixelPos.y());
    setRotation(mObject->rotation());
    setTransformOriginPoint(objectCenter());

    mSyncing = true;

    if (mBoundingRect != bounds) {
        // Notify the graphics scene about the geometry change in advance
        prepareGeometryChange();
        mBoundingRect = bounds;
        const QPointF bottomRight = mObject->bounds().bottomRight();
        const QPointF handlePos = renderer->tileToPixelCoords(bottomRight);
        mResizeHandle->setPos(handlePos - pixelPos);
    }

    mSyncing = false;

    setVisible(mObject->isVisible());
}
示例#10
0
static void extendMapRect(QRect &mapBoundingRect, const MapRenderer &renderer)
{
    // Start with the basic map size
    QRectF rect(mapBoundingRect);

    // Take into account large tiles extending beyond their cell
    for (const Layer *layer : renderer.map()->tileLayers()) {
        const TileLayer *tileLayer = static_cast<const TileLayer*>(layer);
        const QPointF offset = tileLayer->totalOffset();

        for (int y = 0; y < tileLayer->height(); ++y) {
            for (int x = 0; x < tileLayer->width(); ++x) {
                const Cell &cell = tileLayer->cellAt(x, y);

                if (!cell.isEmpty()) {
                    QRectF r = cellRect(renderer, cell, QPointF(x, y));
                    r.translate(offset);
                    rect |= r;
                }
            }
        }
    }

    mapBoundingRect = rect.toAlignedRect();
}
示例#11
0
void MapObjectItem::syncWithMapObject()
{
    const QColor color = mObject->effectiveColor();

    // Update the whole object when the name, polygon or color has changed
    if (mPolygon != mObject->polygon() || mColor != color) {
        mPolygon = mObject->polygon();
        mColor = color;
        update();
    }

    QString toolTip = mObject->name();
    const QString &type = mObject->type();
    if (!type.isEmpty())
        toolTip += QLatin1String(" (") + type + QLatin1String(")");
    setToolTip(toolTip);

    MapRenderer *renderer = mMapDocument->renderer();
    const QPointF pixelPos = renderer->pixelToScreenCoords(mObject->position());
    QRectF bounds = renderer->boundingRect(mObject);

    bounds.translate(-pixelPos);

    if (renderer->flags().testFlag(ShowTileCollisionShapes))
        expandBoundsToCoverTileCollisionObjects(bounds);

    setPos(pixelPos);
    setRotation(mObject->rotation());

    if (ObjectGroup *objectGroup = mObject->objectGroup()) {
        if (objectGroup->drawOrder() == ObjectGroup::TopDownOrder)
            setZValue(pixelPos.y());

        if (mIsHoveredIndicator) {
            auto totalOffset = objectGroup->totalOffset();
            setTransform(QTransform::fromTranslate(totalOffset.x(), totalOffset.y()));
        }
    }

    if (mBoundingRect != bounds) {
        // Notify the graphics scene about the geometry change in advance
        prepareGeometryChange();
        mBoundingRect = bounds;
    }

    setVisible(mObject->isVisible());
}
示例#12
0
void EditPolygonTool::updateMovingItems(const QPointF &pos,
                                        Qt::KeyboardModifiers modifiers)
{
    MapRenderer *renderer = mapDocument()->renderer();
    QPointF diff = pos - mStart;

    SnapHelper snapHelper(renderer, modifiers);

    if (snapHelper.snaps()) {
        const QPointF alignScreenPos = renderer->pixelToScreenCoords(mAlignPosition);
        const QPointF newAlignScreenPos = alignScreenPos + diff;

        QPointF newAlignPixelPos = renderer->screenToPixelCoords(newAlignScreenPos);
        snapHelper.snap(newAlignPixelPos);

        diff = renderer->pixelToScreenCoords(newAlignPixelPos) - alignScreenPos;
    }

    const auto &selectedHandles = mSelectedHandles;

    int i = 0;
    for (PointHandle *handle : selectedHandles) {
        // update handle position
        const QPointF newScreenPos = mOldHandlePositions.at(i) + diff;
        handle->setPos(newScreenPos);

        // calculate new pixel position of polygon node
        const MapObjectItem *item = handle->mapObjectItem();
        const QPointF newInternalPos = item->mapFromScene(newScreenPos);
        const QPointF newScenePos = item->pos() + newInternalPos;
        const QPointF newPixelPos = renderer->screenToPixelCoords(newScenePos);

        // update the polygon
        MapObject *mapObject = item->mapObject();
        QPolygonF polygon = mapObject->polygon();
        polygon[handle->pointIndex()] = newPixelPos - mapObject->position();
        mapDocument()->mapObjectModel()->setObjectPolygon(mapObject, polygon);

        ++i;
    }
}
示例#13
0
QVariant MapObjectItem::itemChange(GraphicsItemChange change,
                                   const QVariant &value)
{
    if (!mSyncing) {
        MapRenderer *renderer = mMapDocument->renderer();

        if (change == ItemPositionChange
            && (QApplication::keyboardModifiers() & Qt::ControlModifier))
        {
            const QPointF pixelDiff = value.toPointF() - mOldItemPos;
            const QPointF newPixelPos =
                    renderer->tileToPixelCoords(mOldObjectPos) + pixelDiff;
            // Snap the position to the grid
            const QPointF newTileCoords =
                    renderer->pixelToTileCoords(newPixelPos).toPoint();

            return renderer->tileToPixelCoords(newTileCoords);
        }
        else if (change == ItemPositionHasChanged) {
            // Update the position of the map object
            const QPointF pixelDiff = value.toPointF() - mOldItemPos;
            const QPointF newPixelPos =
                    renderer->tileToPixelCoords(mOldObjectPos) + pixelDiff;
            mObject->setPosition(renderer->pixelToTileCoords(newPixelPos));
        }
    }

    return QGraphicsItem::itemChange(change, value);
}
示例#14
0
static QRectF cellRect(const MapRenderer &renderer,
                       const Cell &cell,
                       const QPointF &tileCoords)
{
    const Tile *tile = cell.tile();
    if (!tile)
        return QRectF();

    QPointF pixelCoords = renderer.tileToScreenCoords(tileCoords);
    QPointF offset = tile->offset();
    QSize size = tile->size();

    if (cell.flippedAntiDiagonally())
        std::swap(size.rwidth(), size.rheight());

    // This is a correction needed because tileToScreenCoords does not return
    // the bottom-left origin of the tile image, but rather the top-left
    // corner of the cell.
    pixelCoords.ry() += renderer.map()->tileHeight() - size.height();

    return QRectF(pixelCoords, size).translated(offset);
}
void SaveAsImageDialog::accept() {
    const QString fileName = ui->name->text();
    if (fileName.isEmpty())
        return;

    if (QFile::exists(fileName)) {
        const QMessageBox::StandardButton button =
                QMessageBox::warning(this,
                                     tr("Save as Image"),
                                     tr("%1 already exists.\n"
                                        "Do you want to replace it?")
                                     .arg(QFileInfo(fileName).fileName()),
                                     QMessageBox::Yes | QMessageBox::No,
                                     QMessageBox::No);

        if (button != QMessageBox::Yes)
            return;
    }

    const bool visibleLayersOnly = ui->visibleLayers->isChecked();
    const bool useCurrentScale = ui->currentZoomLevel->isChecked();
    const bool drawTileGrid = ui->showGrid->isChecked();

    MapRenderer *renderer = mMapEditor->renderer();

    //remember the current render flags
    const Aurora::RenderFlags renderFlags = renderer->flags();

    renderer->setFlag(ShowTileObjectOutlines, false);

    QSize mapSize = renderer->mapSize();
    if (useCurrentScale)
        mapSize *= mCurrentScale;

    QImage image(mapSize, QImage::Format_ARGB32);
    image.fill(Qt::transparent);
    QPainter painter(&image);

    if (useCurrentScale && mCurrentScale != qreal(1)) {
        painter.setRenderHints(QPainter::SmoothPixmapTransform |
                               QPainter::HighQualityAntialiasing);
        painter.setTransform(QTransform::fromScale(mCurrentScale,
                                                   mCurrentScale));
    }

    foreach (const Layer *layer, mMapEditor->map()->layers()) {
        if (visibleLayersOnly && !layer->isVisible())
            continue;

        painter.setOpacity(layer->opacity());

        const Tilelayer *tilelayer = dynamic_cast<const Tilelayer*>(layer);

        if (tilelayer) {
            renderer->drawTilelayer(&painter, tilelayer);
        }
    }

    if (drawTileGrid) {
        renderer->drawGrid(&painter, QRectF(QPointF(), renderer->mapSize()));
    }

    //restore the previous render flags
    renderer->setFlags(renderFlags);

    image.save(fileName);
    mPath = QFileInfo(fileName).path();

    //store settings for next time
    QSettings *s = Preferences::instance()->settings();
    s->setValue(QLatin1String(VISIBLE_ONLY_KEY), visibleLayersOnly);
    s->setValue(QLatin1String(CURRENT_SCALE_KEY), useCurrentScale);
    s->setValue(QLatin1String(DRAW_GRID_KEY), drawTileGrid);

    QDialog::accept();
}
示例#16
0
void SaveAsImageDialog::accept()
{
    const QString fileName = mUi->fileNameEdit->text();
    if (fileName.isEmpty())
        return;

    if (QFile::exists(fileName)) {
        const QMessageBox::StandardButton button =
                QMessageBox::warning(this,
                                     tr("Save as Image"),
                                     tr("%1 already exists.\n"
                                        "Do you want to replace it?")
                                     .arg(QFileInfo(fileName).fileName()),
                                     QMessageBox::Yes | QMessageBox::No,
                                     QMessageBox::No);

        if (button != QMessageBox::Yes)
            return;
    }

    const bool visibleLayersOnly = mUi->visibleLayersOnly->isChecked();
    const bool useCurrentScale = mUi->currentZoomLevel->isChecked();
    const bool drawTileGrid = mUi->drawTileGrid->isChecked();

    MapRenderer *renderer = mMapDocument->renderer();
    QSize mapSize = renderer->mapSize();

    if (useCurrentScale)
        mapSize *= mCurrentScale;

    QImage image(mapSize, QImage::Format_ARGB32);
    image.fill(Qt::transparent);
    QPainter painter(&image);

    if (useCurrentScale && mCurrentScale != qreal(1)) {
        painter.setRenderHints(QPainter::SmoothPixmapTransform |
                               QPainter::HighQualityAntialiasing);
        painter.setTransform(QTransform::fromScale(mCurrentScale,
                                                   mCurrentScale));
    }

    foreach (const Layer *layer, mMapDocument->map()->layers()) {
        if (visibleLayersOnly && !layer->isVisible())
            continue;

        painter.setOpacity(layer->opacity());

        const TileLayer *tileLayer = dynamic_cast<const TileLayer*>(layer);
        const ObjectGroup *objGroup = dynamic_cast<const ObjectGroup*>(layer);

        if (tileLayer) {
            renderer->drawTileLayer(&painter, tileLayer);
        } else if (objGroup) {
            QColor color = objGroup->color();
            if (!color.isValid())
                color = Qt::gray;

            // TODO: Support colors for different object types
            foreach (const MapObject *object, objGroup->objects())
                renderer->drawMapObject(&painter, object, color);
        }
    }

    if (drawTileGrid) {
        QVector<GridStyle> gridStyles = QVector<GridStyle>() << GridStyle(1, QColor(Qt::black), Qt::CustomDashLine);

        renderer->drawGrid(&painter, QRectF(QPointF(), renderer->mapSize()), gridStyles);
    }

    image.save(fileName);
    mPath = QFileInfo(fileName).path();

    // Store settings for next time
    QSettings *s = Preferences::instance()->settings();
    s->setValue(QLatin1String(VISIBLE_ONLY_KEY), visibleLayersOnly);
    s->setValue(QLatin1String(CURRENT_SCALE_KEY), useCurrentScale);
    s->setValue(QLatin1String(DRAW_GRID_KEY), drawTileGrid);

    QDialog::accept();
}
示例#17
0
void ExportAsImageDialog::accept()
{
    const QString fileName = mUi->fileNameEdit->text();
    if (fileName.isEmpty())
        return;

    if (QFile::exists(fileName)) {
        const QMessageBox::StandardButton button =
                QMessageBox::warning(this,
                                     tr("Export as Image"),
                                     tr("%1 already exists.\n"
                                        "Do you want to replace it?")
                                     .arg(QFileInfo(fileName).fileName()),
                                     QMessageBox::Yes | QMessageBox::No,
                                     QMessageBox::No);

        if (button != QMessageBox::Yes)
            return;
    }

    const bool visibleLayersOnly = mUi->visibleLayersOnly->isChecked();
    const bool useCurrentScale = mUi->currentZoomLevel->isChecked();
    const bool drawTileGrid = mUi->drawTileGrid->isChecked();
    const bool includeBackgroundColor = mUi->includeBackgroundColor->isChecked();

    MapRenderer *renderer = mMapDocument->renderer();

    // Remember the current render flags
    const Tiled::RenderFlags renderFlags = renderer->flags();

    renderer->setFlag(ShowTileObjectOutlines, false);

    QSize mapSize = renderer->mapSize();

    QMargins margins = mMapDocument->map()->computeLayerOffsetMargins();
    mapSize.setWidth(mapSize.width() + margins.left() + margins.right());
    mapSize.setHeight(mapSize.height() + margins.top() + margins.bottom());

    if (useCurrentScale)
        mapSize *= mCurrentScale;

    QImage image;

    try {
        image = QImage(mapSize, QImage::Format_ARGB32_Premultiplied);

        if (includeBackgroundColor) {
            if (mMapDocument->map()->backgroundColor().isValid())
                image.fill(mMapDocument->map()->backgroundColor());
            else
                image.fill(Qt::gray);
        } else {
            image.fill(Qt::transparent);
        }
    } catch (const std::bad_alloc &) {
        QMessageBox::critical(this,
                              tr("Out of Memory"),
                              tr("Could not allocate sufficient memory for the image. "
                                 "Try reducing the zoom level or using a 64-bit version of Tiled."));
        return;
    }

    if (image.isNull()) {
        const size_t gigabyte = 1073741824;
        const size_t memory = size_t(mapSize.width()) * size_t(mapSize.height()) * 4;
        const double gigabytes = (double) memory / gigabyte;

        QMessageBox::critical(this,
                              tr("Image too Big"),
                              tr("The resulting image would be %1 x %2 pixels and take %3 GB of memory. "
                                 "Tiled is unable to create such an image. Try reducing the zoom level.")
                              .arg(mapSize.width())
                              .arg(mapSize.height())
                              .arg(gigabytes, 0, 'f', 2));
        return;
    }

    QPainter painter(&image);

    if (useCurrentScale) {
        if (smoothTransform(mCurrentScale))
            painter.setRenderHints(QPainter::SmoothPixmapTransform);

        painter.setTransform(QTransform::fromScale(mCurrentScale,
                                                   mCurrentScale));
        renderer->setPainterScale(mCurrentScale);
    } else {
        renderer->setPainterScale(1);
    }

    painter.translate(margins.left(), margins.top());

    foreach (const Layer *layer, mMapDocument->map()->layers()) {
        if (visibleLayersOnly && !layer->isVisible())
            continue;

        painter.setOpacity(layer->opacity());
        painter.translate(layer->offset());

        const TileLayer *tileLayer = dynamic_cast<const TileLayer*>(layer);
        const ObjectGroup *objGroup = dynamic_cast<const ObjectGroup*>(layer);
        const ImageLayer *imageLayer = dynamic_cast<const ImageLayer*>(layer);

        if (tileLayer) {
            renderer->drawTileLayer(&painter, tileLayer);
        } else if (objGroup) {
            QList<MapObject*> objects = objGroup->objects();

            if (objGroup->drawOrder() == ObjectGroup::TopDownOrder)
                qStableSort(objects.begin(), objects.end(), objectLessThan);

            foreach (const MapObject *object, objects) {
                if (object->isVisible()) {
                    if (object->rotation() != qreal(0)) {
                        QPointF origin = renderer->pixelToScreenCoords(object->position());
                        painter.save();
                        painter.translate(origin);
                        painter.rotate(object->rotation());
                        painter.translate(-origin);
                    }

                    const QColor color = MapObjectItem::objectColor(object);
                    renderer->drawMapObject(&painter, object, color);

                    if (object->rotation() != qreal(0))
                        painter.restore();
                }
            }
        } else if (imageLayer) {
            renderer->drawImageLayer(&painter, imageLayer);
        }

        painter.translate(-layer->offset());
    }