Add the final TilesetModes UI: Manual/Semi/Auto

This commit is contained in:
David Capello 2019-08-05 08:36:47 -03:00
parent 2baf405b37
commit 92b794d457
25 changed files with 396 additions and 115 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -418,6 +418,10 @@
<part id="outline_vertical" x="192" y="224" w="13" h="15" />
<part id="outline_empty_pixel" x="208" y="224" w="5" h="5" />
<part id="outline_full_pixel" x="214" y="224" w="5" h="5" />
<part id="tiles" x="144" y="208" w="6" h="6" />
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
<part id="tiles_semi" x="156" y="208" w="6" h="6" />
<part id="tiles_auto" x="162" y="208" w="6" h="6" />
</parts>
<styles>
<style id="box" />

View File

@ -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

View File

@ -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();

View File

@ -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<LayerTilemap*>(cel->layer())->tileset() == tileset) {
cel->image()->incrementVersion();
}
}
}
} // namespace cmd
} // namespace app

41
src/app/cmd/remap_tiles.h Normal file
View File

@ -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

View File

@ -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));
}
}

View File

@ -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

View File

@ -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; }

View File

@ -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

View File

@ -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<void>(&ColorBar::onFixWarningClick, this, &m_bgColor, m_bgWarningIcon));
m_redrawTimer.Tick.connect(base::Bind<void>(&ColorBar::onTimerTick, this));
m_buttons.ItemChange.connect(base::Bind<void>(&ColorBar::onPaletteButtonClick, this));
m_tilesButtons.ItemChange.connect(base::Bind<void>(&ColorBar::onTilesButtonClick, this));
m_tilesButton.ItemChange.connect(base::Bind<void>(&ColorBar::onTilesButtonClick, this));
m_tilesetModeButtons.ItemChange.connect(base::Bind<void>(&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<int>::max(),
std::numeric_limits<int>::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<int>::max(),
std::numeric_limits<int>::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<SkinTheme*>(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<SkinTheme*>(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<const doc::LayerTilemap*>(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<const doc::LayerTilemap*>(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<TilesButton>(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<TilesetMode>(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

View File

@ -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<PalettePopup> 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;

View File

@ -766,8 +766,12 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
const int size = m_adapter->size();
for (int i=0; i<size; ++i) {
if (!m_selectedEntries[i])
// TODO why does this fail?
//ASSERT(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);

View File

@ -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 <algorithm>
#include <cmath>
#include <memory>
@ -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<bool> 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<bool>& 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<LayerTilemap*>(cel->layer())->tileset() != tileset)
continue;
Image* tilemapImage = cel->image();
for_each_pixel<TilemapTraits>(
tilemapImage,
[&unusedTiles, &n](const doc::tile_t t) {
const doc::tile_index ti = doc::tile_geti(t);
n = std::max<int>(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 (; ti<remap.size(); ++ti) {
OPS_TRACE(" - ti=%d tj=%d unusedTiles[%d]=%d\n",
ti, tj, ti, (ti < unusedTiles.size() && unusedTiles[ti] ? 1: 0));
if (ti < unusedTiles.size() &&
unusedTiles[ti]) {
cmds->executeAndAdd(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<remap.size(); ++ti) {
OPS_TRACE(" - remap tile[%d] -> %d\n", ti, remap[ti]);
}
#endif
cmds->executeAndAdd(new cmd::RemapTiles(tileset, remap));
}
}
} // namespace app

View File

@ -17,12 +17,14 @@
#include "gfx/region.h"
#include <functional>
#include <vector>
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<bool>& unusedTiles);
} // namespace app
#endif

View File

@ -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(); }

View File

@ -33,6 +33,17 @@ namespace doc {
Image* image() const { return const_cast<Image*>(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; }

View File

@ -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 <algorithm>
namespace doc {
class Image;
@ -152,6 +155,20 @@ namespace doc {
LockImageBits(); // Undefined
};
template<class ImageTraits,
class UnaryFunction>
inline void for_each_pixel(const Image* image, UnaryFunction f) {
const LockImageBits<ImageTraits> bits(image);
std::for_each(bits.begin(), bits.end(), f);
}
template<class ImageTraits,
class UnaryOperation>
inline void transform_image(Image* image, UnaryOperation f) {
LockImageBits<ImageTraits> bits(image);
std::transform(bits.begin(), bits.end(), bits.begin(), f);
}
} // namespace doc
#endif

View File

@ -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<IndexedTraits> bits(image);
LockImageBits<IndexedTraits>::iterator
it = bits.begin(),
end = bits.end();
for (; it != end; ++it)
*it = remap[*it];
switch (image->pixelFormat()) {
case IMAGE_INDEXED:
transform_image<IndexedTraits>(
image, [&remap](color_t c) -> color_t {
return remap[c];
});
break;
case IMAGE_TILEMAP:
transform_image<TilemapTraits>(
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

View File

@ -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<size(); ++i)
inv.map(operator[](i), i);
for (int i=0; i<size(); ++i) {
int j = m_map[i];
if (j == kNoMap)
continue;
inv.map(j, i);
}
return inv;
}
@ -160,6 +164,9 @@ bool Remap::isInvertible(const PalettePicks& usedEntries) const
continue;
int j = m_map[i];
if (j == kNoMap)
continue;
if (picks[j])
return false;
@ -168,4 +175,13 @@ bool Remap::isInvertible(const PalettePicks& usedEntries) const
return true;
}
bool Remap::isIdentity() const
{
for (int i=0; i<size(); ++i) {
if (m_map[i] != i)
return false;
}
return true;
}
} // namespace doc

View File

@ -1,5 +1,6 @@
// Aseprite Document Library
// Copyright (c) 2001-2017 David Capello
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -18,6 +19,8 @@ namespace doc {
class Remap {
public:
static const int kNoMap = -1;
Remap(int entries = 1) : m_map(entries, 0) { }
int size() const {
@ -27,7 +30,10 @@ namespace doc {
// Maps input "fromIndex" value, to "toIndex" output.
void map(int fromIndex, int toIndex) {
ASSERT(fromIndex >= 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<int> m_map;
};

View File

@ -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<LayerTilemap*>(cel->layer())->tileset() == tileset) {
remap_image(cel->image(), remap);
}
}
}
//////////////////////////////////////////////////////////////////////
// Drawing

View File

@ -43,6 +43,7 @@ namespace doc {
class Remap;
class RgbMap;
class SelectedFrames;
class Tileset;
class Tilesets;
typedef std::vector<Palette*> PalettesList;
@ -172,6 +173,8 @@ namespace doc {
void getImages(std::vector<ImageRef>& 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,

View File

@ -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;

View File

@ -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);