diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 139efc4c6..1811e2fac 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -463,6 +463,7 @@ add_library(app-lib cmd/remove_tile.cpp cmd/remove_tileset.cpp cmd/replace_image.cpp + cmd/replace_tileset.cpp cmd/reselect_mask.cpp cmd/set_cel_bounds.cpp cmd/set_cel_data.cpp diff --git a/src/app/cmd/replace_tileset.cpp b/src/app/cmd/replace_tileset.cpp new file mode 100644 index 000000000..a1b5dd6ba --- /dev/null +++ b/src/app/cmd/replace_tileset.cpp @@ -0,0 +1,74 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/cmd/replace_tileset.h" + +#include "doc/layer.h" +#include "doc/layer_tilemap.h" +#include "doc/sprite.h" +#include "doc/sprite.h" +#include "doc/tileset.h" +#include "doc/tileset_io.h" +#include "doc/tilesets.h" + +namespace app { +namespace cmd { + +using namespace doc; + +ReplaceTileset::ReplaceTileset(doc::Sprite* sprite, + const doc::tileset_index tsi, + doc::Tileset* newTileset) + : WithSprite(sprite) + , m_tsi(tsi) + , m_newTileset(newTileset) +{ +} + +void ReplaceTileset::onExecute() +{ + Sprite* spr = sprite(); + doc::Tileset* actualTileset = spr->tilesets()->get(m_tsi); + doc::Tileset* restoreTileset; + if (m_newTileset) { + restoreTileset = m_newTileset; + m_newTileset = nullptr; + } + else { + restoreTileset = doc::read_tileset(m_stream, spr, true); + } + + m_stream.str(std::string()); + m_stream.clear(); + doc::write_tileset(m_stream, actualTileset); + m_size = size_t(m_stream.tellp()); + + replaceTileset(restoreTileset); + delete actualTileset; +} + +void ReplaceTileset::replaceTileset(Tileset* newTileset) +{ + Sprite* spr = sprite(); + + for (Layer* lay : spr->allLayers()) { + if (lay->isTilemap() && + static_cast(lay)->tilesetIndex() == m_tsi) { + lay->incrementVersion(); + } + } + + spr->replaceTileset(m_tsi, newTileset); + spr->tilesets()->incrementVersion(); + spr->incrementVersion(); +} + +} // namespace cmd +} // namespace app diff --git a/src/app/cmd/replace_tileset.h b/src/app/cmd/replace_tileset.h new file mode 100644 index 000000000..1bcae1163 --- /dev/null +++ b/src/app/cmd/replace_tileset.h @@ -0,0 +1,47 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_CMD_REPLACE_TILESET_H_INCLUDED +#define APP_CMD_REPLACE_TILESET_H_INCLUDED +#pragma once + +#include "app/cmd.h" +#include "app/cmd/with_sprite.h" +#include "doc/tileset.h" + +#include + +namespace app { +namespace cmd { + + class ReplaceTileset : public Cmd + , public WithSprite { + public: + ReplaceTileset(doc::Sprite* sprite, + const doc::tileset_index tsi, + doc::Tileset* newTileset); + + protected: + void onExecute() override; + void onUndo() override { onExecute(); } + void onRedo() override { onExecute(); } + size_t onMemSize() const override { + return sizeof(*this) + m_size; + } + + private: + void replaceTileset(doc::Tileset* newTileset); + + doc::tileset_index m_tsi; + doc::Tileset* m_newTileset; + std::stringstream m_stream; + size_t m_size = 0; + }; + +} // namespace cmd +} // namespace app + +#endif diff --git a/src/app/commands/cmd_sprite_size.cpp b/src/app/commands/cmd_sprite_size.cpp index 98dd7aa6a..2ffa17273 100644 --- a/src/app/commands/cmd_sprite_size.cpp +++ b/src/app/commands/cmd_sprite_size.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -9,6 +9,7 @@ #include "config.h" #endif +#include "app/cmd/replace_tileset.h" #include "app/cmd/set_cel_bounds.h" #include "app/cmd/set_slice_key.h" #include "app/commands/command.h" @@ -27,10 +28,12 @@ #include "doc/cels_range.h" #include "doc/image.h" #include "doc/layer.h" +#include "doc/layer_tilemap.h" #include "doc/mask.h" #include "doc/primitives.h" #include "doc/slice.h" #include "doc/sprite.h" +#include "doc/tilesets.h" #include "ui/ui.h" #include "sprite_size.xml.h" @@ -89,28 +92,91 @@ protected: // [working thread] void onJob() override { DocApi api = writer().document()->getApi(tx()); + Tilesets* tilesets = sprite()->tilesets(); - int cels_count = 0; + int img_count = 0; + if (tilesets) { + for (Tileset* tileset : *tilesets) { + img_count += tileset->size(); + } + } for (Cel* cel : sprite()->uniqueCels()) { // TODO add size() member function to CelsRange (void)cel; - ++cels_count; + ++img_count; } + int progress = 0; const gfx::SizeF scale( double(m_new_width) / double(sprite()->width()), double(m_new_height) / double(sprite()->height())); + // Resize tilesets + if (tilesets) { + for (tileset_index tsi=0; tsisize(); ++tsi) { + Tileset* tileset = tilesets->get(tsi); + ASSERT(tileset); + + gfx::Size newGridSize = tileset->grid().tileSize(); + newGridSize.w *= scale.w; + newGridSize.h *= scale.h; + if (newGridSize.w < 1) newGridSize.w = 1; + if (newGridSize.h < 1) newGridSize.h = 1; + doc::Grid newGrid(newGridSize); + + auto newTileset = new doc::Tileset(sprite(), newGrid, tileset->size()); + doc::tile_index idx = 0; + for (doc::ImageRef tileImg : *tileset) { + if (idx != 0) { + doc::ImageRef newTileImg( + resize_image( + tileImg.get(), + scale, + m_resize_method, + sprite()->palette(0), + sprite()->rgbMap(0))); // TODO first frame? + + newTileset->set(idx, newTileImg); + } + + jobProgress((float)progress / img_count); + ++progress; + ++idx; + } + tx()(new cmd::ReplaceTileset(sprite(), tsi, newTileset)); + + // Cancel all the operation? + if (isCanceled()) + return; // Tx destructor will undo all operations + } + } + // For each cel... - int progress = 0; for (Cel* cel : sprite()->uniqueCels()) { - resize_cel_image( - tx(), cel, scale, - m_resize_method, - cel->layer()->isReference() ? + // We need to adjust only the origin/position of tilemap cels + // (because tiles are resized automatically when we resize the + // tileset). + if (cel->layer()->isTilemap()) { + Tileset* tileset = static_cast(cel->layer())->tileset(); + gfx::Size canvasSize = + tileset->grid().tilemapSizeToCanvas( + gfx::Size(cel->image()->width(), + cel->image()->height())); + gfx::Rect newBounds(cel->x()*scale.w, + cel->y()*scale.h, + canvasSize.w, + canvasSize.h); + tx()(new cmd::SetCelBoundsF(cel, newBounds)); + } + else { + resize_cel_image( + tx(), cel, scale, + m_resize_method, + cel->layer()->isReference() ? -cel->boundsF().origin(): gfx::PointF(-cel->bounds().origin())); + } - jobProgress((float)progress / cels_count); + jobProgress((float)progress / img_count); ++progress; // Cancel all the operation? diff --git a/src/app/util/resize_image.cpp b/src/app/util/resize_image.cpp index 3c78f714c..73026a4f3 100644 --- a/src/app/util/resize_image.cpp +++ b/src/app/util/resize_image.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (c) 2019-2020 Igara Studio S.A. +// Copyright (c) 2019-2021 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -26,7 +26,7 @@ namespace app { doc::Image* resize_image( - doc::Image* image, + const doc::Image* image, const gfx::SizeF& scale, const doc::algorithm::ResizeMethod method, const Palette* pal, diff --git a/src/app/util/resize_image.h b/src/app/util/resize_image.h index 35a75eda0..13063eb7e 100644 --- a/src/app/util/resize_image.h +++ b/src/app/util/resize_image.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (c) 2019 Igara Studio S.A. +// Copyright (c) 2019-2021 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -24,7 +24,7 @@ namespace app { class Tx; doc::Image* resize_image( - doc::Image* image, + const doc::Image* image, const gfx::SizeF& scale, const doc::algorithm::ResizeMethod method, const doc::Palette* pal, diff --git a/src/doc/layer_tilemap.cpp b/src/doc/layer_tilemap.cpp index 6de503108..736644388 100644 --- a/src/doc/layer_tilemap.cpp +++ b/src/doc/layer_tilemap.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2019-2020 Igara Studio S.A. +// Copyright (c) 2019-2021 Igara Studio S.A. // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -38,6 +38,10 @@ Grid LayerTilemap::grid() const void LayerTilemap::setTilesetIndex(tileset_index tsi) { + // "m_tilesetIndex" could be already equal to "tsi", but this + // function is used by Sprite::replaceTilemap() to update the + // m_tileset pointer even in that case. + m_tilesetIndex = tsi; m_tileset = sprite()->tilesets()->get(tsi); } diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index 64426d2fc..62cc9c7ac 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -541,6 +541,22 @@ void Sprite::replaceImage(ObjectId curImageId, const ImageRef& newImage) } } +void Sprite::replaceTileset(tileset_index tsi, Tileset* newTileset) +{ + ASSERT(hasTilesets()); + + tilesets()->set(tsi, newTileset); + + for (Layer* layer : allLayers()) { + if (layer->isTilemap() && + static_cast(layer)->tilesetIndex() == tsi) { + // Set same index just to update the internal tileset pointer in + // LayerTilemap + static_cast(layer)->setTilesetIndex(tsi); + } + } +} + // TODO replace it with a images iterator void Sprite::getImages(std::vector& images) const { diff --git a/src/doc/sprite.h b/src/doc/sprite.h index 2006efa4b..7a3c52e45 100644 --- a/src/doc/sprite.h +++ b/src/doc/sprite.h @@ -24,6 +24,7 @@ #include "doc/rgbmap_algorithm.h" #include "doc/slices.h" #include "doc/tags.h" +#include "doc/tile.h" #include "doc/with_user_data.h" #include "gfx/rect.h" @@ -183,6 +184,7 @@ namespace doc { // Images void replaceImage(ObjectId curImageId, const ImageRef& newImage); + void replaceTileset(tileset_index tsi, Tileset* newTileset); // Returns all sprite images (cel + tiles) that aren't tilemaps void getImages(std::vector& images) const;