diff --git a/data/extensions/aseprite-theme/sheet.png b/data/extensions/aseprite-theme/sheet.png
index bc08c417e..4e3f1630d 100644
Binary files a/data/extensions/aseprite-theme/sheet.png and b/data/extensions/aseprite-theme/sheet.png differ
diff --git a/data/extensions/aseprite-theme/theme.xml b/data/extensions/aseprite-theme/theme.xml
index f27eef9c5..c3dabae98 100644
--- a/data/extensions/aseprite-theme/theme.xml
+++ b/data/extensions/aseprite-theme/theme.xml
@@ -418,6 +418,10 @@
+
+
+
+
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index e94a21ad5..b98ecb540 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -447,6 +447,7 @@ add_library(app-lib
cmd/move_layer.cpp
cmd/patch_cel.cpp
cmd/remap_colors.cpp
+ cmd/remap_tiles.cpp
cmd/remove_cel.cpp
cmd/remove_frame.cpp
cmd/remove_layer.cpp
diff --git a/src/app/cmd/add_tile.cpp b/src/app/cmd/add_tile.cpp
index 22ba04ad7..1556d637e 100644
--- a/src/app/cmd/add_tile.cpp
+++ b/src/app/cmd/add_tile.cpp
@@ -25,7 +25,7 @@ AddTile::AddTile(doc::Tileset* tileset,
: WithTileset(tileset)
, WithImage(image.get())
, m_size(0)
- , m_tileIndex(-1)
+ , m_tileIndex(doc::tile_i_notile)
, m_imageRef(image)
{
}
@@ -82,7 +82,10 @@ void AddTile::onRedo()
void AddTile::addTile(doc::Tileset* tileset, const doc::ImageRef& image)
{
- m_tileIndex = tileset->add(image);
+ if (m_tileIndex == doc::tile_i_notile)
+ m_tileIndex = tileset->add(image);
+ else
+ tileset->insert(m_tileIndex, image);
tileset->sprite()->incrementVersion();
tileset->incrementVersion();
diff --git a/src/app/cmd/remap_tiles.cpp b/src/app/cmd/remap_tiles.cpp
new file mode 100644
index 000000000..974942e4f
--- /dev/null
+++ b/src/app/cmd/remap_tiles.cpp
@@ -0,0 +1,62 @@
+// Aseprite
+// Copyright (C) 2019 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/remap_tiles.h"
+
+#include "doc/cel.h"
+#include "doc/cels_range.h"
+#include "doc/image.h"
+#include "doc/layer.h"
+#include "doc/layer_tilemap.h"
+#include "doc/remap.h"
+#include "doc/sprite.h"
+#include "doc/tileset.h"
+
+namespace app {
+namespace cmd {
+
+using namespace doc;
+
+RemapTiles::RemapTiles(Tileset* tileset,
+ const Remap& remap)
+ : WithTileset(tileset)
+ , m_remap(remap)
+{
+}
+
+void RemapTiles::onExecute()
+{
+ Tileset* tileset = this->tileset();
+ Sprite* spr = tileset->sprite();
+ spr->remapTilemaps(tileset, m_remap);
+ incrementVersions(tileset);
+}
+
+void RemapTiles::onUndo()
+{
+ Tileset* tileset = this->tileset();
+ Sprite* spr = tileset->sprite();
+ spr->remapTilemaps(tileset, m_remap.invert());
+ incrementVersions(tileset);
+}
+
+void RemapTiles::incrementVersions(Tileset* tileset)
+{
+ Sprite* spr = tileset->sprite();
+ for (const Cel* cel : spr->uniqueCels()) {
+ if (cel->layer()->isTilemap() &&
+ static_cast(cel->layer())->tileset() == tileset) {
+ cel->image()->incrementVersion();
+ }
+ }
+}
+
+} // namespace cmd
+} // namespace app
diff --git a/src/app/cmd/remap_tiles.h b/src/app/cmd/remap_tiles.h
new file mode 100644
index 000000000..4cf81108a
--- /dev/null
+++ b/src/app/cmd/remap_tiles.h
@@ -0,0 +1,41 @@
+// Aseprite
+// Copyright (C) 2019 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifndef APP_CMD_REMAP_TILES_H_INCLUDED
+#define APP_CMD_REMAP_TILES_H_INCLUDED
+#pragma once
+
+#include "app/cmd.h"
+#include "app/cmd/with_tileset.h"
+#include "doc/remap.h"
+
+namespace app {
+namespace cmd {
+ using namespace doc;
+
+ class RemapTiles : public Cmd
+ , public WithTileset {
+ public:
+ RemapTiles(Tileset* tileset,
+ const Remap& remap);
+
+ protected:
+ void onExecute() override;
+ void onUndo() override;
+ size_t onMemSize() const override {
+ return sizeof(*this) + m_remap.getMemSize();
+ }
+
+ private:
+ void incrementVersions(Tileset* tileset);
+
+ Remap m_remap;
+ };
+
+} // namespace cmd
+} // namespace app
+
+#endif
diff --git a/src/app/cmd/trim_cel.cpp b/src/app/cmd/trim_cel.cpp
index 8fe15e5b0..1382f644b 100644
--- a/src/app/cmd/trim_cel.cpp
+++ b/src/app/cmd/trim_cel.cpp
@@ -1,4 +1,5 @@
// Aseprite
+// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
@@ -29,7 +30,7 @@ TrimCel::TrimCel(Cel* cel)
cel->image()->maskColor(),
cel->layer(), newBounds)) {
newBounds.offset(cel->position());
- if (cel->bounds() != newBounds) {
+ if (cel->imageBounds() != newBounds) {
add(new cmd::CropCel(cel, newBounds));
}
}
diff --git a/src/app/script/engine.cpp b/src/app/script/engine.cpp
index c54c47480..413697378 100644
--- a/src/app/script/engine.cpp
+++ b/src/app/script/engine.cpp
@@ -349,9 +349,9 @@ Engine::Engine()
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "TilesetMode");
- setfield_integer(L, "LOCKED", TilesetMode::Locked);
- setfield_integer(L, "MODIFY_EXISTENT", TilesetMode::ModifyExistent);
- setfield_integer(L, "GENERATE_ALL_TILES", TilesetMode::GenerateAllTiles);
+ setfield_integer(L, "MANUAL", TilesetMode::Manual);
+ setfield_integer(L, "SEMI", TilesetMode::Semi);
+ setfield_integer(L, "AUTO", TilesetMode::Auto);
lua_pop(L, 1);
// Register classes/prototypes
diff --git a/src/app/site.h b/src/app/site.h
index 9b9ebf9e0..5047f92db 100644
--- a/src/app/site.h
+++ b/src/app/site.h
@@ -52,7 +52,7 @@ namespace app {
, m_sprite(nullptr)
, m_layer(nullptr)
, m_frame(0)
- , m_tilesetMode(TilesetMode::Locked) { }
+ , m_tilesetMode(TilesetMode::Manual) { }
const Focus focus() const { return m_focus; }
bool inEditor() const { return m_focus == InEditor; }
diff --git a/src/app/tileset_mode.h b/src/app/tileset_mode.h
index 54c73882d..2d0e8867a 100644
--- a/src/app/tileset_mode.h
+++ b/src/app/tileset_mode.h
@@ -13,10 +13,9 @@ namespace app {
// These modes are available edition modes for the tileset when an
// tilemap is edited.
enum class TilesetMode {
- Locked, // Cannot edit the tileset (don't modify or add new tiles)
- ModifyExistent, // Modify existent tiles (don't create new ones)
- //GenerateNewTiles, // Auto-generate new tiles, edit existent ones
- GenerateAllTiles, // Auto-generate new and modified tiles
+ Manual, // Modify existent tiles (don't create new ones)
+ Semi, // Auto-generate new tiles if needed, edit existent ones
+ Auto, // Auto-generate new and modified tiles
};
} // namespace app
diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp
index fd9e288a2..1acd03ce3 100644
--- a/src/app/ui/color_bar.cpp
+++ b/src/app/ui/color_bar.cpp
@@ -88,12 +88,6 @@ enum class PalButton {
MAX
};
-enum class TilesButton {
- MODE,
- AUTO,
- MAX
-};
-
using namespace app::skin;
using namespace ui;
@@ -147,7 +141,8 @@ ColorBar* ColorBar::m_instance = NULL;
ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
: Box(align)
, m_buttons(int(PalButton::MAX))
- , m_tilesButtons(int(TilesButton::MAX))
+ , m_tilesButton(1)
+ , m_tilesetModeButtons(3)
, m_splitter(Splitter::ByPercentage, VERTICAL)
, m_paletteView(true, PaletteView::FgBgColors, this, 16)
, m_tilesView(true, PaletteView::FgBgTiles, this, 16)
@@ -170,7 +165,7 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
, m_lastButtons(kButtonLeft)
, m_editMode(false)
, m_tilesMode(false)
- , m_autoTilesMode(true)
+ , m_tilesetMode(TilesetMode::Semi)
, m_redrawTimer(250, this)
, m_redrawAll(false)
, m_implantChange(false)
@@ -185,10 +180,12 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
m_buttons.addItem(theme->parts.palPresets());
m_buttons.addItem(theme->parts.palOptions());
- m_tilesButtons.addItem("Tiles");
- m_tilesButtons.addItem("Auto");
- m_tilesButtons.setVisible(false);
- m_tilesButtons.getItem((int)TilesButton::AUTO)->setHotColor(theme->colors.editPalFace());
+ m_tilesButton.addItem(theme->parts.tiles());
+
+ m_tilesetModeButtons.addItem(theme->parts.tilesManual());
+ m_tilesetModeButtons.addItem(theme->parts.tilesSemi());
+ m_tilesetModeButtons.addItem(theme->parts.tilesAuto());
+ setTilesetMode(m_tilesetMode);
m_paletteView.setColumns(8);
m_tilesView.setColumns(8);
@@ -213,8 +210,11 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
setColorSelector(
Preferences::instance().colorBar.selector());
+ m_tilesHBox.addChild(&m_tilesButton);
+ m_tilesHBox.addChild(&m_tilesetModeButtons);
+
addChild(&m_buttons);
- addChild(&m_tilesButtons);
+ addChild(&m_tilesHBox);
addChild(&m_splitter);
HBox* fgBox = new HBox;
@@ -239,7 +239,8 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
m_bgWarningIcon->Click.connect(base::Bind(&ColorBar::onFixWarningClick, this, &m_bgColor, m_bgWarningIcon));
m_redrawTimer.Tick.connect(base::Bind(&ColorBar::onTimerTick, this));
m_buttons.ItemChange.connect(base::Bind(&ColorBar::onPaletteButtonClick, this));
- m_tilesButtons.ItemChange.connect(base::Bind(&ColorBar::onTilesButtonClick, this));
+ m_tilesButton.ItemChange.connect(base::Bind(&ColorBar::onTilesButtonClick, this));
+ m_tilesetModeButtons.ItemChange.connect(base::Bind(&ColorBar::onTilesetModeButtonClick, this));
tooltipManager->addTooltipFor(&m_fgColor, "Foreground color", LEFT);
tooltipManager->addTooltipFor(&m_bgColor, "Background color", LEFT);
@@ -257,11 +258,14 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
m_bgColor.resetSizeHint();
m_fgColor.setSizeHint(0, m_fgColor.sizeHint().h);
m_bgColor.setSizeHint(0, m_bgColor.sizeHint().h);
- m_buttons.setMinSize(gfx::Size(0, theme->dimensions.colorBarButtonsHeight()));
- m_buttons.setMaxSize(gfx::Size(std::numeric_limits::max(),
- std::numeric_limits::max())); // TODO add resetMaxSize
- m_buttons.setMaxSize(gfx::Size(m_buttons.sizeHint().w,
- theme->dimensions.colorBarButtonsHeight()));
+
+ for (auto w : { &m_buttons, &m_tilesButton, &m_tilesetModeButtons }) {
+ w->setMinSize(gfx::Size(0, theme->dimensions.colorBarButtonsHeight()));
+ w->setMaxSize(gfx::Size(std::numeric_limits::max(),
+ std::numeric_limits::max())); // TODO add resetMaxSize
+ w->setMaxSize(gfx::Size(m_buttons.sizeHint().w,
+ theme->dimensions.colorBarButtonsHeight()));
+ }
// Change color-bar background color (not ColorBar::setBgColor)
this->Widget::setBgColor(theme->colors.tabActiveFace());
@@ -444,8 +448,7 @@ void ColorBar::setEditMode(bool state)
m_editMode = state;
item->setIcon(state ? theme->parts.timelineOpenPadlockActive():
theme->parts.timelineClosedPadlockNormal());
- item->setHotColor(state ? theme->colors.editPalFace():
- gfx::ColorNone);
+ item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
// Deselect color entries when we cancel editing
if (!state)
@@ -456,7 +459,7 @@ bool ColorBar::inTilesMode() const
{
return
(m_tilesMode &&
- m_tilesButtons.isVisible() &&
+ m_tilesHBox.isVisible() &&
m_lastDocument &&
m_lastDocument->sprite());
}
@@ -467,10 +470,11 @@ void ColorBar::setTilesMode(bool state)
const bool isTilemap = (site.layer() && site.layer()->isTilemap());
SkinTheme* theme = static_cast(this->theme());
- ButtonSet::Item* item = m_tilesButtons.getItem((int)TilesButton::MODE);
+ ButtonSet::Item* item = m_tilesButton.getItem(0);
m_tilesMode = state;
item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
+ item->setMono(true);
if (state && isTilemap) {
manager()->freeWidget(&m_paletteView);
@@ -486,30 +490,25 @@ void ColorBar::setTilesMode(bool state)
layout();
}
-bool ColorBar::inAutoTilesMode() const
-{
- return
- (m_autoTilesMode &&
- m_tilesButtons.isVisible() &&
- m_lastDocument &&
- m_lastDocument->sprite());
-}
-
-void ColorBar::setAutoTilesMode(bool state)
-{
- SkinTheme* theme = static_cast(this->theme());
- ButtonSet::Item* item = m_tilesButtons.getItem((int)TilesButton::AUTO);
-
- m_autoTilesMode = state;
- item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
-}
-
TilesetMode ColorBar::tilesetMode() const
{
- if (inAutoTilesMode())
- return TilesetMode::GenerateAllTiles;
+ if (m_tilesHBox.isVisible() &&
+ m_lastDocument &&
+ m_lastDocument->sprite()) {
+ return m_tilesetMode;
+ }
else
- return TilesetMode::ModifyExistent;
+ return TilesetMode::Manual;
+}
+
+void ColorBar::setTilesetMode(const TilesetMode mode)
+{
+ m_tilesetMode = mode;
+
+ for (int i=0; i<3; ++i) {
+ ButtonSet::Item* item = m_tilesetModeButtons.getItem(i);
+ item->setSelected(int(mode) == i);
+ }
}
void ColorBar::onActiveSiteChange(const Site& site)
@@ -527,20 +526,20 @@ void ColorBar::onActiveSiteChange(const Site& site)
}
bool isTilemap = false;
- if (site.layer()) {
+ if (site.layer())
isTilemap = site.layer()->isTilemap();
- if (m_tilesButtons.isVisible() != isTilemap) {
- m_tilesButtons.setVisible(isTilemap);
- layout();
- }
- if (isTilemap) {
- doc::ObjectId newTilesetId =
- static_cast(site.layer())->tileset()->id();
- if (m_lastTilesetId != newTilesetId) {
- m_lastTilesetId = newTilesetId;
- m_scrollableTilesView.updateView();
- }
+ if (m_tilesHBox.isVisible() != isTilemap) {
+ m_tilesHBox.setVisible(isTilemap);
+ layout();
+ }
+
+ if (isTilemap) {
+ doc::ObjectId newTilesetId =
+ static_cast(site.layer())->tileset()->id();
+ if (m_lastTilesetId != newTilesetId) {
+ m_lastTilesetId = newTilesetId;
+ m_scrollableTilesView.updateView();
}
}
if (!isTilemap) {
@@ -563,7 +562,6 @@ void ColorBar::onTilesetChanged(DocEvent& ev)
if (!ui::is_ui_thread())
return;
- m_tilesButtons.deselectItems();
if (m_scrollableTilesView.isVisible())
m_scrollableTilesView.updateView();
@@ -639,20 +637,15 @@ void ColorBar::onPaletteButtonClick()
void ColorBar::onTilesButtonClick()
{
- int item = m_tilesButtons.selectedItem();
- m_tilesButtons.deselectItems();
+ m_tilesButton.deselectItems();
+ setTilesMode(!inTilesMode());
+}
- switch (static_cast(item)) {
-
- case TilesButton::MODE:
- setTilesMode(!inTilesMode());
- break;
-
- case TilesButton::AUTO:
- setAutoTilesMode(!inAutoTilesMode());
- break;
-
- }
+void ColorBar::onTilesetModeButtonClick()
+{
+ int item = m_tilesetModeButtons.selectedItem();
+ m_tilesetModeButtons.deselectItems();
+ setTilesetMode(static_cast(item));
}
void ColorBar::onRemapButtonClick()
@@ -1398,9 +1391,13 @@ void ColorBar::setupTooltips(TooltipManager* tooltipManager)
tooltipManager->addTooltipFor(&m_remapButton, "Matches old indexes with new indexes", BOTTOM);
tooltipManager->addTooltipFor(
- m_tilesButtons.getItem((int)TilesButton::MODE), "Show Tileset", BOTTOM);
+ m_tilesButton.getItem(0), "Show/Hide Tileset", BOTTOM);
tooltipManager->addTooltipFor(
- m_tilesButtons.getItem((int)TilesButton::AUTO), "Generate new tiles automatically", BOTTOM);
+ m_tilesetModeButtons.getItem((int)TilesetMode::Manual), "Manual-mode: Modify existent tiles,\ndon't create new tiles automatically", BOTTOM);
+ tooltipManager->addTooltipFor(
+ m_tilesetModeButtons.getItem((int)TilesetMode::Semi), "Semi-mode: Modify and reuse existent tiles,\ncreate/delete tiles if needed/possible", BOTTOM);
+ tooltipManager->addTooltipFor(
+ m_tilesetModeButtons.getItem((int)TilesetMode::Auto), "Auto-mode: Don't modify existent tiles,\ngenerate new tiles automatically only", BOTTOM);
}
// static
diff --git a/src/app/ui/color_bar.h b/src/app/ui/color_bar.h
index 5b0f71dc9..a547f79d0 100644
--- a/src/app/ui/color_bar.h
+++ b/src/app/ui/color_bar.h
@@ -83,10 +83,8 @@ namespace app {
bool inTilesMode() const;
void setTilesMode(bool state);
- bool inAutoTilesMode() const;
- void setAutoTilesMode(bool state);
-
TilesetMode tilesetMode() const;
+ void setTilesetMode(const TilesetMode mode);
ColorButton* fgColorButton() { return &m_fgColor; }
ColorButton* bgColorButton() { return &m_bgColor; }
@@ -120,6 +118,7 @@ namespace app {
void onAfterExecuteCommand(CommandExecutionEvent& ev);
void onPaletteButtonClick();
void onTilesButtonClick();
+ void onTilesetModeButtonClick();
void onRemapButtonClick();
void onPaletteIndexChange(PaletteIndexChangeEvent& ev);
void onFgColorChangeFromPreferences();
@@ -170,7 +169,9 @@ namespace app {
class WarningIcon;
ButtonSet m_buttons;
- ButtonSet m_tilesButtons;
+ ui::HBox m_tilesHBox;
+ ButtonSet m_tilesButton;
+ ButtonSet m_tilesetModeButtons;
std::unique_ptr m_palettePopup;
ui::Splitter m_splitter;
ui::VBox m_palettePlaceholder;
@@ -217,7 +218,7 @@ namespace app {
// True if we should be putting/setting tiles.
bool m_tilesMode;
- bool m_autoTilesMode;
+ TilesetMode m_tilesetMode;
// Timer to redraw editors after a palette change.
ui::Timer m_redrawTimer;
diff --git a/src/app/ui/palette_view.cpp b/src/app/ui/palette_view.cpp
index e0e53824b..a907e102c 100644
--- a/src/app/ui/palette_view.cpp
+++ b/src/app/ui/palette_view.cpp
@@ -766,8 +766,12 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
const int size = m_adapter->size();
for (int i=0; i= 0 && i < m_selectedEntries.size());
+ if (i >= 0 && i < m_selectedEntries.size() &&
+ !m_selectedEntries[i]) {
continue;
+ }
const int k = (dragging ? m_hot.color+j: i);
diff --git a/src/app/util/cel_ops.cpp b/src/app/util/cel_ops.cpp
index 6f9b5bed1..21314711e 100644
--- a/src/app/util/cel_ops.cpp
+++ b/src/app/util/cel_ops.cpp
@@ -15,6 +15,8 @@
#include "app/cmd/clear_cel.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/copy_region.h"
+#include "app/cmd/remap_tiles.h"
+#include "app/cmd/remove_tile.h"
#include "app/cmd/replace_image.h"
#include "app/cmd/set_cel_position.h"
#include "app/cmd_sequence.h"
@@ -40,6 +42,7 @@
#include "render/quantization.h"
#include "render/render.h"
+#include
#include
#include
@@ -403,7 +406,8 @@ void modify_tilemap_cel_region(
newTilemapBounds.x, newTilemapBounds.y, newTilemapBounds.w, newTilemapBounds.h);
// Autogenerate tiles
- if (tilesetMode == TilesetMode::GenerateAllTiles) {
+ if (tilesetMode == TilesetMode::Auto ||
+ tilesetMode == TilesetMode::Semi) {
doc::TilesetHashTable hashImages; // TODO the hashImages should be inside the Tileset
{
// Add existent tiles in the hash table
@@ -434,6 +438,8 @@ void modify_tilemap_cel_region(
regionToPatch -= gfx::Region(grid.tileToCanvas(oldTilemapBounds));
regionToPatch |= region;
+ std::vector modifiedTileIndexes(tileset->size(), false);
+
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(regionToPatch)) {
const int u = tilePt.x-newTilemapBounds.x;
const int v = tilePt.y-newTilemapBounds.y;
@@ -445,6 +451,9 @@ void modify_tilemap_cel_region(
const doc::tile_index ti = doc::tile_geti(t);
const doc::ImageRef existenTileImage = tileset->get(ti);
+ if (tilesetMode == TilesetMode::Semi)
+ modifiedTileIndexes[ti] = true;
+
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
ImageRef tileImage(getTileImage(existenTileImage, tileInCanvasRc));
if (grid.hasMask())
@@ -455,6 +464,9 @@ void modify_tilemap_cel_region(
auto it = hashImages.find(tileImage);
if (it != hashImages.end()) {
tileIndex = it->second; // TODO
+
+ if (tilesetMode == TilesetMode::Semi)
+ modifiedTileIndexes[tileIndex] = false;
}
else {
auto addTile = new cmd::AddTile(tileset, tileImage);
@@ -464,6 +476,8 @@ void modify_tilemap_cel_region(
hashImages[tileImage] = tileIndex;
}
+ OPS_TRACE(" - tile %d -> %d\n", ti, tileIndex);
+
const doc::tile_t tile = doc::tile(tileIndex, 0);
if (t != tile) {
newTilemap->putPixel(u, v, tile);
@@ -471,8 +485,6 @@ void modify_tilemap_cel_region(
}
}
- doc->notifyTilesetChanged(tileset);
-
if (newTilemap->width() != cel->image()->width() ||
newTilemap->height() != cel->image()->height()) {
gfx::Point newPos = grid.tileToCanvas(newTilemapBounds.origin());
@@ -491,9 +503,17 @@ void modify_tilemap_cel_region(
tilePtsRgn,
gfx::Point(0, 0)));
}
+
+ // Remove unused tiles
+ if (tilesetMode == TilesetMode::Semi) {
+ // TODO reuse tiles that will be removed in the algorithm above
+ remove_unused_tiles_from_tileset(cmds, tileset, modifiedTileIndexes);
+ }
+
+ doc->notifyTilesetChanged(tileset);
}
// Modify active set of tiles manually / don't auto-generate new tiles
- else if (tilesetMode == TilesetMode::ModifyExistent) {
+ else if (tilesetMode == TilesetMode::Manual) {
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(region)) {
// Ignore modifications outside the tilemap
if (!cel->image()->bounds().contains(tilePt.x, tilePt.y))
@@ -567,4 +587,59 @@ void clear_mask_from_cel(CmdSequence* cmds,
}
}
+void remove_unused_tiles_from_tileset(
+ CmdSequence* cmds,
+ doc::Tileset* tileset,
+ std::vector& unusedTiles)
+{
+ OPS_TRACE("remove_unused_tiles_from_tileset\n");
+
+ int n = tileset->size();
+
+ for (Cel* cel : tileset->sprite()->cels()) {
+ if (!cel->layer()->isTilemap() ||
+ static_cast(cel->layer())->tileset() != tileset)
+ continue;
+
+ Image* tilemapImage = cel->image();
+ for_each_pixel(
+ tilemapImage,
+ [&unusedTiles, &n](const doc::tile_t t) {
+ const doc::tile_index ti = doc::tile_geti(t);
+ n = std::max(n, ti+1);
+ if (ti >= 0 &&
+ ti < int(unusedTiles.size()) &&
+ unusedTiles[ti]) {
+ unusedTiles[ti] = false;
+ }
+ });
+ }
+
+ doc::Remap remap(n);
+ doc::tile_index ti, tj;
+ ti = tj = 0;
+ for (; tiexecuteAndAdd(new cmd::RemoveTile(tileset, tj));
+ // Map to nothing, so the map can be invertible
+ remap.map(ti, doc::Remap::kNoMap);
+ }
+ else {
+ remap.map(ti, tj++);
+ }
+ }
+
+ if (!remap.isIdentity()) {
+#ifdef _DEBUG
+ for (ti=0; ti %d\n", ti, remap[ti]);
+ }
+#endif
+ cmds->executeAndAdd(new cmd::RemapTiles(tileset, remap));
+ }
+}
+
} // namespace app
diff --git a/src/app/util/cel_ops.h b/src/app/util/cel_ops.h
index e4568c1fe..d6d6e0993 100644
--- a/src/app/util/cel_ops.h
+++ b/src/app/util/cel_ops.h
@@ -17,12 +17,14 @@
#include "gfx/region.h"
#include
+#include
namespace doc {
class Cel;
class Layer;
class LayerTilemap;
class Sprite;
+ class Tileset;
}
namespace app {
@@ -68,6 +70,13 @@ namespace app {
doc::Cel* cel,
const TilesetMode tilesetMode);
+ // unusedTiles is a set of possibles tiles to check if they are
+ // really unused by all tilemaps.
+ void remove_unused_tiles_from_tileset(
+ CmdSequence* cmds,
+ doc::Tileset* tileset,
+ std::vector& unusedTiles);
+
} // namespace app
#endif
diff --git a/src/doc/cel.h b/src/doc/cel.h
index 7fddbcc0d..6cb8b3f17 100644
--- a/src/doc/cel.h
+++ b/src/doc/cel.h
@@ -39,6 +39,8 @@ namespace doc {
const gfx::RectF& boundsF() const { return m_data->boundsF(); }
int opacity() const { return m_data->opacity(); }
+ gfx::Rect imageBounds() const { return m_data->imageBounds(); }
+
LayerImage* layer() const { return m_layer; }
Image* image() const { return m_data->image(); }
ImageRef imageRef() const { return m_data->imageRef(); }
diff --git a/src/doc/cel_data.h b/src/doc/cel_data.h
index 8e535e7b4..1cb37f143 100644
--- a/src/doc/cel_data.h
+++ b/src/doc/cel_data.h
@@ -33,6 +33,17 @@ namespace doc {
Image* image() const { return const_cast(m_image.get()); };
ImageRef imageRef() const { return m_image; }
+ // Returns a rectangle with the bounds of the image (width/height
+ // of the image) in the position of the cel (useful to compare
+ // active tilemap bounds when we have to change the tilemap cel
+ // bounds).
+ gfx::Rect imageBounds() const {
+ return gfx::Rect(m_bounds.x,
+ m_bounds.y,
+ m_image->width(),
+ m_image->height());
+ }
+
void setImage(const ImageRef& image, Layer* layer);
void setPosition(const gfx::Point& pos);
void setOpacity(int opacity) { m_opacity = opacity; }
diff --git a/src/doc/image_bits.h b/src/doc/image_bits.h
index 7ef714fc5..85c72801f 100644
--- a/src/doc/image_bits.h
+++ b/src/doc/image_bits.h
@@ -1,5 +1,6 @@
// Aseprite Document Library
-// Copyright (c) 2001-2014 David Capello
+// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -8,6 +9,8 @@
#define DOC_IMAGE_BITS_H_INCLUDED
#pragma once
+#include
+
namespace doc {
class Image;
@@ -152,6 +155,20 @@ namespace doc {
LockImageBits(); // Undefined
};
+ template
+ inline void for_each_pixel(const Image* image, UnaryFunction f) {
+ const LockImageBits bits(image);
+ std::for_each(bits.begin(), bits.end(), f);
+ }
+
+ template
+ inline void transform_image(Image* image, UnaryOperation f) {
+ LockImageBits bits(image);
+ std::transform(bits.begin(), bits.end(), bits.begin(), f);
+ }
+
} // namespace doc
#endif
diff --git a/src/doc/primitives.cpp b/src/doc/primitives.cpp
index cf9ce05e2..372a35f8c 100644
--- a/src/doc/primitives.cpp
+++ b/src/doc/primitives.cpp
@@ -413,17 +413,24 @@ bool is_same_image(const Image* i1, const Image* i2)
void remap_image(Image* image, const Remap& remap)
{
- ASSERT(image->pixelFormat() == IMAGE_INDEXED);
- if (image->pixelFormat() != IMAGE_INDEXED)
- return;
+ ASSERT(image->pixelFormat() == IMAGE_INDEXED ||
+ image->pixelFormat() == IMAGE_TILEMAP);
- LockImageBits bits(image);
- LockImageBits::iterator
- it = bits.begin(),
- end = bits.end();
-
- for (; it != end; ++it)
- *it = remap[*it];
+ switch (image->pixelFormat()) {
+ case IMAGE_INDEXED:
+ transform_image(
+ image, [&remap](color_t c) -> color_t {
+ return remap[c];
+ });
+ break;
+ case IMAGE_TILEMAP:
+ transform_image(
+ image, [&remap](color_t c) -> color_t {
+ ASSERT(remap[c] != Remap::kNoMap);
+ return remap[c];
+ });
+ break;
+ }
}
// TODO test this hash routine and find a better alternative
diff --git a/src/doc/remap.cpp b/src/doc/remap.cpp
index cbacc15d5..0857454f7 100644
--- a/src/doc/remap.cpp
+++ b/src/doc/remap.cpp
@@ -1,6 +1,6 @@
// Aseprite Document Library
-// Copyright (c) 2020 Igara Studio S.A.
-// Copyright (c) 2001-2016 David Capello
+// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -135,8 +135,12 @@ void Remap::merge(const Remap& other)
Remap Remap::invert() const
{
Remap inv(size());
- for (int i=0; i= 0 && fromIndex < size());
- ASSERT(toIndex >= 0 && toIndex < size());
+ // toIndex = kNoMap means (there is no remap for this value, useful
+ // to ignore this entry when we invert the map)
+ ASSERT(toIndex == kNoMap ||
+ toIndex >= 0 && toIndex < size());
m_map[fromIndex] = toIndex;
}
@@ -57,6 +63,10 @@ namespace doc {
// undo data, without saving all images' pixels.
bool isInvertible(const PalettePicks& usedEntries) const;
+ // Returns true if the remap does nothing (each map entry is
+ // matched to itself).
+ bool isIdentity() const;
+
private:
std::vector m_map;
};
diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp
index c67d5823d..01f3a5f67 100644
--- a/src/doc/sprite.cpp
+++ b/src/doc/sprite.cpp
@@ -525,6 +525,17 @@ void Sprite::remapImages(const Remap& remap)
remap_image(image.get(), remap);
}
+void Sprite::remapTilemaps(const Tileset* tileset,
+ const Remap& remap)
+{
+ for (Cel* cel : uniqueCels()) {
+ if (cel->layer()->isTilemap() &&
+ static_cast(cel->layer())->tileset() == tileset) {
+ remap_image(cel->image(), remap);
+ }
+ }
+}
+
//////////////////////////////////////////////////////////////////////
// Drawing
diff --git a/src/doc/sprite.h b/src/doc/sprite.h
index 97ea7bea8..3c6db2b19 100644
--- a/src/doc/sprite.h
+++ b/src/doc/sprite.h
@@ -43,6 +43,7 @@ namespace doc {
class Remap;
class RgbMap;
class SelectedFrames;
+ class Tileset;
class Tilesets;
typedef std::vector PalettesList;
@@ -172,6 +173,8 @@ namespace doc {
void getImages(std::vector& images) const;
void remapImages(const Remap& remap);
+ void remapTilemaps(const Tileset* tileset,
+ const Remap& remap);
void pickCels(const double x,
const double y,
const frame_t frame,
diff --git a/src/doc/tile.h b/src/doc/tile.h
index 0a128cf98..f44b64d89 100644
--- a/src/doc/tile.h
+++ b/src/doc/tile.h
@@ -20,6 +20,7 @@ namespace doc {
const uint32_t tile_i_shift = 0; // Tile index
const uint32_t tile_f_shift = 28; // Flags (flip, rotation)
+ const uint32_t tile_i_notile = 0xffffffff;
const uint32_t tile_i_mask = 0x1fffffff;
const uint32_t tile_f_mask = 0xe0000000; // 3 flags
const uint32_t tile_f_flipx = 0x20000000;
diff --git a/src/doc/tileset.h b/src/doc/tileset.h
index 713ed2c84..57c1267ec 100644
--- a/src/doc/tileset.h
+++ b/src/doc/tileset.h
@@ -43,16 +43,16 @@ namespace doc {
tile_index size() const { return tile_index(m_tiles.size()); }
void resize(const tile_index ntiles);
- ImageRef get(const tile_index index) const {
- if (index < size())
- return m_tiles[index];
+ ImageRef get(const tile_index ti) const {
+ if (ti < size())
+ return m_tiles[ti];
else
return ImageRef(nullptr);
}
- void set(const tile_index index,
+ void set(const tile_index ti,
const ImageRef& image) {
- m_tiles[index] = image;
+ m_tiles[ti] = image;
}
tile_index add(const ImageRef& image) {
@@ -60,6 +60,12 @@ namespace doc {
return tile_t(m_tiles.size()-1);
}
+ void insert(const tile_index ti,
+ const ImageRef& image) {
+ ASSERT(ti <= size());
+ m_tiles.insert(m_tiles.begin()+ti, image);
+ }
+
void erase(const tile_index ti) {
ASSERT(ti >= 0 && ti < size());
m_tiles.erase(m_tiles.begin()+ti);