diff --git a/NEWS.md b/NEWS.md index 7e16f8d5020..a65f88d3536 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ * JSON format: Fixed tile order when loading a tileset using the old format * tmxrasterizer: Added --hide-object and --show-object arguments (by Lars Luz, #3819) +* tmxrasterizer: Added --frames and --frame-duration arguments to export animated maps as multiple images * Windows: Fixed the support for WebP images (updated to Qt 6.5.3) ### Tiled 1.10.2 (4 August 2023) diff --git a/src/tmxrasterizer/main.cpp b/src/tmxrasterizer/main.cpp index c649e094f61..717bdd60bd4 100644 --- a/src/tmxrasterizer/main.cpp +++ b/src/tmxrasterizer/main.cpp @@ -85,15 +85,21 @@ int main(int argc, char *argv[]) QCoreApplication::translate("main", "Don't render object layers.") }, { QStringLiteral("hide-image-layers"), QCoreApplication::translate("main", "Don't render image layers.") }, - { QStringLiteral("advance-animations"), - QCoreApplication::translate("main", "If used, tile animations are advanced by the specified duration."), - QCoreApplication::translate("main", "duration") }, { QStringLiteral("hide-object"), QCoreApplication::translate("main", "Specifies an object to omit from the output image. Can be repeated to hide multiple objects. If multiple objects share the specified name they all will be hidden."), QCoreApplication::translate("main", "name") }, { QStringLiteral("show-object"), QCoreApplication::translate("main", "If used only specified objects are shown. Can be repeated to show multiple specified objects only. If multiple objects share the specified name they all will be shown."), - QCoreApplication::translate("main", "name") } + QCoreApplication::translate("main", "name") }, + { QStringLiteral("advance-animations"), + QCoreApplication::translate("main", "If used, tile animations are advanced by the specified duration in milliseconds."), + QCoreApplication::translate("main", "duration") }, + { QStringLiteral("frames"), + QCoreApplication::translate("main", "Number of frames to export. This will add a frame number suffix to the image names. Animations are advanced by for each frame."), + QCoreApplication::translate("main", "number") }, + { QStringLiteral("frame-duration"), + QCoreApplication::translate("main", "Duration of each frame in milliseconds, defaults to 100."), + QCoreApplication::translate("main", "number") }, }); parser.addPositionalArgument(QStringLiteral("map|world"), QCoreApplication::translate("main", "Map or world file to render.")); parser.addPositionalArgument(QStringLiteral("image"), QCoreApplication::translate("main", "Image file to output.")); @@ -157,5 +163,23 @@ int main(int argc, char *argv[]) } } + if (parser.isSet(QLatin1String("frames"))) { + bool ok; + w.setFrameCount(parser.value(QLatin1String("frames")).toInt(&ok)); + if (!ok || w.frameCount() < 0) { + qWarning().noquote() << QCoreApplication::translate("main", "Invalid number of frames specified: \"%1\"").arg(parser.value(QLatin1String("frames"))); + exit(1); + } + } + + if (parser.isSet(QLatin1String("frame-duration"))) { + bool ok; + w.setFrameDuration(parser.value(QLatin1String("frame-duration")).toInt(&ok)); + if (!ok || w.frameDuration() < 0) { + qWarning().noquote() << QCoreApplication::translate("main", "Invalid frame-duration specified: \"%1\"").arg(parser.value(QLatin1String("frame-duration"))); + exit(1); + } + } + return w.render(fileToOpen, fileToSave); } diff --git a/src/tmxrasterizer/tmxrasterizer.cpp b/src/tmxrasterizer/tmxrasterizer.cpp index c8380d6cb1f..0ed40705176 100644 --- a/src/tmxrasterizer/tmxrasterizer.cpp +++ b/src/tmxrasterizer/tmxrasterizer.cpp @@ -37,6 +37,7 @@ #include "worldmanager.h" #include +#include #include #include @@ -131,12 +132,36 @@ bool TmxRasterizer::shouldDrawObject(const MapObject *object) const int TmxRasterizer::render(const QString &fileName, - const QString &imageFileName) + QString imageFileName) { - if (fileName.endsWith(QLatin1String(".world"), Qt::CaseInsensitive)) - return renderWorld(fileName, imageFileName); - else - return renderMap(fileName, imageFileName); + const QFileInfo imageFileInfo(imageFileName); + + const QString imagePath = imageFileInfo.path(); + const QString imageBaseName = imageFileInfo.completeBaseName(); + const QString imageSuffix = imageFileInfo.suffix(); + + const int frameCount = qMax(1, mFrameCount); + + for (int frame = 0; frame < frameCount; ++frame) { + if (mFrameCount > 0) { + imageFileName = QString(QLatin1String("%1/%2%3.%4")) + .arg(imagePath, imageBaseName, QString::number(frame), imageSuffix); + } + + int ret; + + if (fileName.endsWith(QLatin1String(".world"), Qt::CaseInsensitive)) + ret = renderWorld(fileName, imageFileName); + else + ret = renderMap(fileName, imageFileName); + + if (ret) + return ret; + + mAdvanceAnimations += mFrameDuration; + } + + return 0; } int TmxRasterizer::renderMap(const QString &mapFileName, diff --git a/src/tmxrasterizer/tmxrasterizer.h b/src/tmxrasterizer/tmxrasterizer.h index 1870a65f9b3..43abcdf7e95 100644 --- a/src/tmxrasterizer/tmxrasterizer.h +++ b/src/tmxrasterizer/tmxrasterizer.h @@ -49,6 +49,8 @@ class TmxRasterizer int tileSize() const { return mTileSize; } int size() const { return mSize; } int advanceAnimations() const { return mAdvanceAnimations; } + int frameCount() const { return mFrameCount; } + int frameDuration() const { return mFrameDuration; } bool useAntiAliasing() const { return mUseAntiAliasing; } bool smoothImages() const { return mSmoothImages; } bool ignoreVisibility() const { return mIgnoreVisibility; } @@ -57,6 +59,8 @@ class TmxRasterizer void setTileSize(int tileSize) { mTileSize = tileSize; } void setSize(int size) { mSize = size; } void setAdvanceAnimations(int duration) { mAdvanceAnimations = duration; } + void setFrameCount(int frameCount) { mFrameCount = frameCount; } + void setFrameDuration(int frameDuration) { mFrameDuration = frameDuration; } void setAntiAliasing(bool useAntiAliasing) { mUseAntiAliasing = useAntiAliasing; } void setSmoothImages(bool smoothImages) { mSmoothImages = smoothImages; } void setIgnoreVisibility(bool IgnoreVisibility) { mIgnoreVisibility = IgnoreVisibility; } @@ -69,13 +73,15 @@ class TmxRasterizer void setLayerTypeVisible(Layer::TypeFlag layerType, bool visible); - int render(const QString &fileName, const QString &imageFileName); + int render(const QString &fileName, QString imageFileName); private: qreal mScale = 1.0; int mTileSize = 0; int mSize = 0; int mAdvanceAnimations = 0; + int mFrameCount = 0; + int mFrameDuration = 100; bool mUseAntiAliasing = false; bool mSmoothImages = true; bool mIgnoreVisibility = false;