Skip to content

Commit

Permalink
Add support to match flipped tiles automatically in Auto/Stack modes
Browse files Browse the repository at this point in the history
By default Aseprite will not try to match flipped versions of the
tiles (as it requires more CPU), but when we create a tileset we can
specify which flips can be matched automatically (new
Tileset::matchFlags() property).

These flags are just for the Auto mode, if we manually insert a
flipped tile, that is always supported, even when the matchFlags() are
not specified.
  • Loading branch information
dacap committed Nov 9, 2023
1 parent 25f61ff commit 302d998
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 20 deletions.
5 changes: 5 additions & 0 deletions data/strings/en.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,11 @@ Visible aid to see the first tile with content from the tileset
as index 1 (by default, one-based index) or other value.
E.g. you can use 0 here for zero-based indexing.
END
allow_flipped_tiles = Allow Flipped Tiles:
allow_flipped_tiles_tooltip = <<<END
Aseprite can reuse tiles matching automatically with their flipped
versions (in X, Y, or Diagonal axes) in Auto/Stack modes.
END
[tileset_selector_window]
title = Tileset
Expand Down
9 changes: 9 additions & 0 deletions data/widgets/tileset_selector.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,14 @@
<expr id="base_index" text="1" tooltip="@.base_tooltip" />
<boxfiller cell_hspan="2" />
</grid>

<hbox>
<label text="@.allow_flipped_tiles" />
<buttonset id="flipped_tiles" columns="3" multiple="true">
<item id="xflip" text="X" minwidth="20" tooltip="@.allow_flipped_tiles_tooltip" tooltip_dir="bottom" />
<item id="yflip" text="Y" minwidth="20" tooltip="@.allow_flipped_tiles_tooltip" tooltip_dir="bottom" />
<item id="dflip" text="D" minwidth="20" tooltip="@.allow_flipped_tiles_tooltip" tooltip_dir="bottom" />
</buttonset>
</hbox>
</vbox>
</gui>
5 changes: 5 additions & 0 deletions docs/ase-file-specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,11 @@ The data of this chunk is as follows:
(this is the new format). In rare cases this bit is off,
and the empty tile will be equal to 0xffffffff (used in
internal versions of Aseprite)
8 - Aseprite will try to match modified tiles with their X
flipped version automatically in Auto mode when using
this tileset.
16 - Same for Y flips
32 - Same for D(iagonal) flips
DWORD Number of tiles
WORD Tile Width
WORD Tile Height
Expand Down
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ add_library(app-lib
cmd/set_tile_data_properties.cpp
cmd/set_tile_data_property.cpp
cmd/set_tileset_base_index.cpp
cmd/set_tileset_match_flags.cpp
cmd/set_tileset_name.cpp
cmd/set_total_frames.cpp
cmd/set_transparent_color.cpp
Expand Down
45 changes: 45 additions & 0 deletions src/app/cmd/set_tileset_match_flags.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Aseprite
// Copyright (C) 2023 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/set_tileset_match_flags.h"

#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/tileset.h"

namespace app {
namespace cmd {

SetTilesetMatchFlags::SetTilesetMatchFlags(Tileset* tileset,
const tile_flags matchFlags)
: WithTileset(tileset)
, m_oldMatchFlags(tileset->matchFlags())
, m_newMatchFlags(matchFlags)
{
}

void SetTilesetMatchFlags::onExecute()
{
auto ts = tileset();
ts->setMatchFlags(m_newMatchFlags);
ts->incrementVersion();
ts->sprite()->incrementVersion();
}

void SetTilesetMatchFlags::onUndo()
{
auto ts = tileset();
ts->setMatchFlags(m_oldMatchFlags);
ts->incrementVersion();
ts->sprite()->incrementVersion();
}

} // namespace cmd
} // namespace app
40 changes: 40 additions & 0 deletions src/app/cmd/set_tileset_match_flags.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifndef APP_CMD_SET_TILESET_MATCH_FLAGS_H_INCLUDED
#define APP_CMD_SET_TILESET_MATCH_FLAGS_H_INCLUDED
#pragma once

#include "app/cmd.h"
#include "app/cmd/with_tileset.h"
#include "doc/tile.h"

namespace app {
namespace cmd {
using namespace doc;

class SetTilesetMatchFlags : public Cmd
, public WithTileset {
public:
SetTilesetMatchFlags(Tileset* tileset,
const tile_flags matchFlags);

protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this);
}

private:
tile_flags m_oldMatchFlags;
tile_flags m_newMatchFlags;
};

} // namespace cmd
} // namespace app

#endif
7 changes: 6 additions & 1 deletion src/app/commands/cmd_layer_properties.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
Expand All @@ -15,6 +15,7 @@
#include "app/cmd/set_layer_opacity.h"
#include "app/cmd/set_layer_tileset.h"
#include "app/cmd/set_tileset_base_index.h"
#include "app/cmd/set_tileset_match_flags.h"
#include "app/cmd/set_tileset_name.h"
#include "app/cmd/set_user_data.h"
#include "app/commands/command.h"
Expand Down Expand Up @@ -362,6 +363,7 @@ class LayerPropertiesWindow : public app::gen::LayerProperties,
tilesetInfo.grid = tileset->grid();
tilesetInfo.name = tileset->name();
tilesetInfo.baseIndex = tileset->baseIndex();
tilesetInfo.matchFlags = tileset->matchFlags();
tilesetInfo.tsi = tilemap->tilesetIndex();

try {
Expand All @@ -376,6 +378,7 @@ class LayerPropertiesWindow : public app::gen::LayerProperties,

if (tileset->name() != tilesetInfo.name ||
tileset->baseIndex() != tilesetInfo.baseIndex ||
tileset->matchFlags() != tilesetInfo.matchFlags ||
tilesetInfo.tsi != tilemap->tilesetIndex()) {
ContextWriter writer(UIContext::instance());
Tx tx(writer.context(), "Set Tileset Properties");
Expand All @@ -388,6 +391,8 @@ class LayerPropertiesWindow : public app::gen::LayerProperties,
tx(new cmd::SetTilesetName(tileset, tilesetInfo.name));
if (tileset->baseIndex() != tilesetInfo.baseIndex)
tx(new cmd::SetTilesetBaseIndex(tileset, tilesetInfo.baseIndex));
if (tileset->matchFlags() != tilesetInfo.matchFlags)
tx(new cmd::SetTilesetMatchFlags(tileset, tilesetInfo.matchFlags));
// TODO catch the tileset base index modification from the editor
App::instance()->mainWindow()->invalidate();
tx.commit();
Expand Down
4 changes: 3 additions & 1 deletion src/app/commands/cmd_new_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ void NewLayerCommand::onExecute(Context* context)
context->activeSite().grid():
doc::Grid(params().gridBounds()));
tilesetInfo.baseIndex = 1;
tilesetInfo.matchFlags = 0; // TODO default flags?

#ifdef ENABLE_UI
// If params specify to ask the user about the name...
Expand Down Expand Up @@ -236,8 +237,8 @@ void NewLayerCommand::onExecute(Context* context)

name = window.name()->text();
if (tilesetSelector) {
pref.tileset.baseIndex(tilesetSelector->getInfo().baseIndex);
tilesetInfo = tilesetSelector->getInfo();
pref.tileset.baseIndex(tilesetInfo.baseIndex);
}
}
#endif
Expand Down Expand Up @@ -286,6 +287,7 @@ void NewLayerCommand::onExecute(Context* context)
if (tilesetInfo.newTileset) {
auto tileset = new Tileset(sprite, tilesetInfo.grid, 1);
tileset->setBaseIndex(tilesetInfo.baseIndex);
tileset->setMatchFlags(tilesetInfo.matchFlags);
tileset->setName(tilesetInfo.name);

auto addTileset = new cmd::AddTileset(sprite, tileset);
Expand Down
5 changes: 5 additions & 0 deletions src/app/file/ase_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,11 @@ static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
else
flags |= ASE_TILESET_FLAG_EMBEDDED;

doc::tile_flags tf = tileset->matchFlags();
if (tf & doc::tile_f_xflip) flags |= ASE_TILESET_FLAG_MATCH_XFLIP;
if (tf & doc::tile_f_yflip) flags |= ASE_TILESET_FLAG_MATCH_YFLIP;
if (tf & doc::tile_f_dflip) flags |= ASE_TILESET_FLAG_MATCH_DFLIP;

fputl(si, f); // Tileset ID
fputl(flags, f); // Tileset Flags
fputl(tileset->size(), f);
Expand Down
46 changes: 34 additions & 12 deletions src/app/ui/tileset_selector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ TilesetSelector::TilesetSelector(const doc::Sprite* sprite,
{
initTheme();

name()->setText(m_info.name);
gridWidth()->setTextf("%d", m_info.grid.tileSize().w);
gridHeight()->setTextf("%d", m_info.grid.tileSize().h);
baseIndex()->setTextf("%d", m_info.baseIndex);
fillControls(m_info.name,
m_info.grid.tileSize(),
m_info.baseIndex,
m_info.matchFlags);

if (!m_info.allowNewTileset) {
tilesets()->deleteAllItems();
Expand Down Expand Up @@ -63,22 +63,35 @@ TilesetSelector::TilesetSelector(const doc::Sprite* sprite,
updateControlsState(sprite->tilesets());
}

void TilesetSelector::fillControls(const std::string& nameValue,
const gfx::Size& gridSize,
const int baseIndexValue,
const doc::tile_flags matchFlags)
{
name()->setText(nameValue);
gridWidth()->setTextf("%d", gridSize.w);
gridHeight()->setTextf("%d", gridSize.h);
baseIndex()->setTextf("%d", baseIndexValue);
xflip()->setSelected((matchFlags & doc::tile_f_xflip) ? true: false);
yflip()->setSelected((matchFlags & doc::tile_f_yflip) ? true: false);
dflip()->setSelected((matchFlags & doc::tile_f_dflip) ? true: false);
}

void TilesetSelector::updateControlsState(const doc::Tilesets* spriteTilesets)
{
if (m_info.enabled) {
int index = getSelectedItemIndex();
bool isNewTileset = (index == 0);
const int index = getSelectedItemIndex();
const bool isNewTileset = (index == 0);
if (isNewTileset) {
name()->setText("");
baseIndex()->setTextf("%d", 1);
}
else {
doc::Tileset* ts = spriteTilesets->get(index-1);
doc::Grid grid = ts->grid();
name()->setText(ts->name());
gridWidth()->setTextf("%d", grid.tileSize().w);
gridHeight()->setTextf("%d", grid.tileSize().h);
baseIndex()->setTextf("%d", ts->baseIndex());
const doc::Tileset* ts = spriteTilesets->get(index-1);
fillControls(ts->name(),
ts->grid().tileSize(),
ts->baseIndex(),
ts->matchFlags());
}

name()->setEnabled(isNewTileset || !m_info.allowNewTileset);
Expand All @@ -90,6 +103,10 @@ void TilesetSelector::updateControlsState(const doc::Tilesets* spriteTilesets)
tilesets()->setEnabled(false);
gridWidth()->setEnabled(false);
gridHeight()->setEnabled(false);
baseIndex()->setEnabled(false);
xflip()->setEnabled(false);
yflip()->setEnabled(false);
dflip()->setEnabled(false);
}
}

Expand All @@ -110,6 +127,11 @@ TilesetSelector::Info TilesetSelector::getInfo()
}
info.name = name()->text();
info.baseIndex = baseIndex()->textInt();
info.matchFlags =
(xflip()->isSelected() ? doc::tile_f_xflip: 0) |
(yflip()->isSelected() ? doc::tile_f_yflip: 0) |
(dflip()->isSelected() ? doc::tile_f_dflip: 0);

return info;
}

Expand Down
7 changes: 6 additions & 1 deletion src/app/ui/tileset_selector.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Aseprite
// Copyright (c) 2019-2020 Igara Studio S.A.
// Copyright (c) 2019-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
Expand Down Expand Up @@ -37,6 +37,7 @@ namespace app {
std::string name;
doc::Grid grid;
int baseIndex = 1;
doc::tile_flags matchFlags = 0;
doc::tileset_index tsi = -1;
};

Expand All @@ -47,6 +48,10 @@ namespace app {
Info getInfo();

private:
void fillControls(const std::string& name,
const gfx::Size& gridSize,
const int baseIndex,
const doc::tile_flags matchFlags);
void updateControlsState(const doc::Tilesets* spriteTilesets);

// Returns the selected item index as if the combobox always has the "New Tileset"
Expand Down
Loading

0 comments on commit 302d998

Please sign in to comment.