diff --git a/data/pref.xml b/data/pref.xml
index 002a92a71..855a55f89 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -221,8 +221,8 @@
-
-
+
+
diff --git a/data/strings/en.ini b/data/strings/en.ini
index 9d1785e85..0f8e7d60b 100644
--- a/data/strings/en.ini
+++ b/data/strings/en.ini
@@ -1023,6 +1023,10 @@ default_new_layer_name = New Layer
new_tileset = New Tileset
grid_width = Grid Width:
grid_height = Grid Height:
+first_visible_index = First Visible Index:
+first_visible_index_tooltip = <<
-
+
-
+
-
+
+
+
+
+
diff --git a/docs/ase-file-specs.md b/docs/ase-file-specs.md
index 41584b794..7f2587602 100644
--- a/docs/ase-file-specs.md
+++ b/docs/ase-file-specs.md
@@ -206,9 +206,9 @@ This chunk determine where to put a cel in the specified layer/frame.
WORD Height in pixels
BYTE[] "Raw Cel" data compressed with ZLIB method (see NOTE.3)
+ For cel type = 3 (Compressed Tilemap)
- WORD Width in pixels
- WORD Height in pixels
- WORD Bits per tile (8, 16, or 32)
+ WORD Width in number of tiles
+ WORD Height in number of tiles
+ WORD Bits per tile (at the moment it's always 32-bit per tile)
DWORD Bitmask for tile ID (e.g. 0x1fffffff for 32-bit tiles)
DWORD Bitmask for X flip
DWORD Bitmask for Y flip
@@ -367,10 +367,17 @@ the Tags chunk.
DWORD Tileset flags
1 - Include link to external file
2 - Include tiles inside this file
+ 4 - Tilemaps using this tileset use tile ID=0 as empty tile
+ (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)
DWORD Number of tiles
WORD Tile Width
WORD Tile Height
- BYTE[16] Reserved
+ SHORT Number to show in the screen from the tile with index 1 and
+ so on (by default this is field is 1, so the data that is
+ displayed is equivalent to the data in memory)
+ BYTE[14] Reserved
STRING Name of the tileset
+ If flag 1 is set
DWORD ID of the external file. This ID is one entry
diff --git a/src/app/cmd/add_tile.cpp b/src/app/cmd/add_tile.cpp
index 79d16429d..8f6980616 100644
--- a/src/app/cmd/add_tile.cpp
+++ b/src/app/cmd/add_tile.cpp
@@ -19,14 +19,12 @@
namespace app {
namespace cmd {
-using namespace doc;
-
AddTile::AddTile(doc::Tileset* tileset,
const doc::ImageRef& image)
: WithTileset(tileset)
, WithImage(image.get())
, m_size(0)
- , m_tileIndex(doc::tile_i_notile)
+ , m_tileIndex(doc::notile)
, m_imageRef(image)
{
}
@@ -46,7 +44,7 @@ void AddTile::onExecute()
doc::Tileset* tileset = this->tileset();
ASSERT(tileset);
- if (m_tileIndex != doc::tile_i_notile) {
+ if (m_tileIndex != doc::notile) {
ASSERT(!m_imageRef);
tileset->sprite()->incrementVersion();
tileset->incrementVersion();
@@ -99,7 +97,7 @@ void AddTile::onFireNotifications()
void AddTile::addTile(doc::Tileset* tileset, const doc::ImageRef& image)
{
- if (m_tileIndex == doc::tile_i_notile)
+ if (m_tileIndex == doc::notile)
m_tileIndex = tileset->add(image);
else
tileset->insert(m_tileIndex, image);
diff --git a/src/app/cmd/clear_mask.cpp b/src/app/cmd/clear_mask.cpp
index 4ac7ffbbd..118999b24 100644
--- a/src/app/cmd/clear_mask.cpp
+++ b/src/app/cmd/clear_mask.cpp
@@ -54,7 +54,7 @@ ClearMask::ClearMask(Cel* cel)
imageBounds = gfx::Rect(grid.canvasToTile(cel->position()),
cel->image()->size());
maskBounds = grid.canvasToTile(mask->bounds());
- m_bgcolor = doc::tile_i_notile; // TODO configurable empty tile
+ m_bgcolor = doc::notile; // TODO configurable empty tile
}
else {
imageBounds = cel->bounds();
diff --git a/src/app/cmd/copy_region.cpp b/src/app/cmd/copy_region.cpp
index 001ae2454..bffc3bdce 100644
--- a/src/app/cmd/copy_region.cpp
+++ b/src/app/cmd/copy_region.cpp
@@ -86,7 +86,7 @@ void CopyRegion::swap()
void CopyTileRegion::rehash()
{
- ASSERT(m_tileIndex != tile_i_notile);
+ ASSERT(m_tileIndex != notile);
ASSERT(m_tilesetId != NullId);
if (m_tilesetId != NullId) {
auto tileset = get(m_tilesetId);
@@ -95,7 +95,7 @@ void CopyTileRegion::rehash()
tileset->incrementVersion();
tileset->notifyTileContentChange(m_tileIndex);
- // Notify thath the tileset changed
+ // Notify that the tileset changed
static_cast(tileset->sprite()->document())
->notifyTilesetChanged(tileset);
}
diff --git a/src/app/color.cpp b/src/app/color.cpp
index 7bd197d6f..9643cb946 100644
--- a/src/app/color.cpp
+++ b/src/app/color.cpp
@@ -84,7 +84,7 @@ Color Color::fromGray(int g, int a)
// static
Color Color::fromIndex(int index)
{
- ASSERT(index >= 0 || index == tile_i_notile);
+ ASSERT(index >= 0 || index == doc::notile);
Color color(Color::IndexType);
color.m_value.index = index;
diff --git a/src/app/color_picker.cpp b/src/app/color_picker.cpp
index a5ce62e47..303a3b0d6 100644
--- a/src/app/color_picker.cpp
+++ b/src/app/color_picker.cpp
@@ -96,7 +96,7 @@ bool get_cel_pixel(const Cel* cel,
}
ColorPicker::ColorPicker()
- : m_tile(doc::tile_i_notile)
+ : m_tile(doc::notile)
, m_alpha(0)
, m_layer(nullptr)
{
diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp
index ff1eaaca9..fb06c7648 100644
--- a/src/app/commands/cmd_new_layer.cpp
+++ b/src/app/commands/cmd_new_layer.cpp
@@ -195,6 +195,7 @@ void NewLayerCommand::onExecute(Context* context)
TilesetSelector::Info tilesetInfo;
tilesetInfo.newTileset = true;
tilesetInfo.grid = context->activeSite().grid();
+ tilesetInfo.firstVisibleIndex = 1;
#ifdef ENABLE_UI
// If params specify to ask the user about the name...
@@ -266,7 +267,9 @@ void NewLayerCommand::onExecute(Context* context)
case Type::TilemapLayer: {
tileset_index tsi;
if (tilesetInfo.newTileset) {
- auto tileset = new Tileset(sprite, tilesetInfo.grid, 0);
+ auto tileset = new Tileset(sprite, tilesetInfo.grid, 1);
+ tileset->setFirstVisibleIndex(tilesetInfo.firstVisibleIndex);
+
auto addTileset = new cmd::AddTileset(sprite, tileset);
tx(addTileset);
diff --git a/src/app/crash/read_document.cpp b/src/app/crash/read_document.cpp
index 5434935ca..a1d4ed827 100644
--- a/src/app/crash/read_document.cpp
+++ b/src/app/crash/read_document.cpp
@@ -43,6 +43,7 @@
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
#include "doc/user_data_io.h"
+#include "doc/util.h"
#include "fixmath/fixmath.h"
#include
@@ -498,7 +499,11 @@ private:
}
Tileset* readTileset(std::ifstream& s) {
- return read_tileset(s, m_sprite, false);
+ bool isOldVersion = false;
+ Tileset* tileset = read_tileset(s, m_sprite, false, &isOldVersion);
+ if (tileset && isOldVersion)
+ m_updateOldTilemapWithTileset.insert(tileset->id());
+ return tileset;
}
Tag* readTag(std::ifstream& s) {
@@ -531,6 +536,22 @@ private:
}
}
}
+
+ // Fix tilemaps using old tilesets
+ if (!m_updateOldTilemapWithTileset.empty()) {
+ for (Tileset* tileset : *spr->tilesets()) {
+ if (m_updateOldTilemapWithTileset.find(tileset->id()) == m_updateOldTilemapWithTileset.end())
+ continue;
+
+ for (Cel* cel : spr->uniqueCels()) {
+ if (cel->image()->pixelFormat() == IMAGE_TILEMAP &&
+ static_cast(cel->layer())->tileset() == tileset) {
+ doc::fix_old_tilemap(cel->image(), tileset,
+ tile_i_mask, tile_f_mask);
+ }
+ }
+ }
+ }
}
bool canceled() const {
@@ -550,6 +571,9 @@ private:
std::vector > m_celsToLoad;
std::map m_images;
std::map m_celdatas;
+ // Each ObjectId is a tileset ID that didn't contain the empty tile
+ // as the first tile (this was an old format used in internal betas)
+ std::set m_updateOldTilemapWithTileset;
base::task_token* m_taskToken;
};
diff --git a/src/app/doc_api.cpp b/src/app/doc_api.cpp
index 1565a688e..8918bef3c 100644
--- a/src/app/doc_api.cpp
+++ b/src/app/doc_api.cpp
@@ -271,7 +271,7 @@ bool DocApi::cropCel(LayerImage* layer,
paintPos.x, paintPos.y,
newCelBounds.w, newCelBounds.h,
image->pixelFormat() == IMAGE_TILEMAP ?
- tile_i_notile : m_document->bgColor(layer)));
+ notile : m_document->bgColor(layer)));
// Try to shrink the image ignoring transparent borders
gfx::Rect frameBounds;
@@ -285,7 +285,7 @@ bool DocApi::cropCel(LayerImage* layer,
frameBounds.x, frameBounds.y,
frameBounds.w, frameBounds.h,
image->pixelFormat() == IMAGE_TILEMAP ?
- tile_i_notile : m_document->bgColor(layer)));
+ notile : m_document->bgColor(layer)));
newCelPos += frameBounds.origin();
}
diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp
index 87096d15b..cf3a2a89e 100644
--- a/src/app/file/ase_format.cpp
+++ b/src/app/file/ase_format.cpp
@@ -1270,18 +1270,21 @@ static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
const tileset_index si)
{
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TILESET);
- int flags = 0;
+
+ // We always save with the tile zero as the empty tile now
+ int flags = ASE_TILESET_FLAG_ZERO_IS_NOTILE;
if (!tileset->externalFilename().empty())
flags |= ASE_TILESET_FLAG_EXTERNAL_FILE;
else
flags |= ASE_TILESET_FLAG_EMBEDDED;
- fputl(si, f); // Tileset ID
- fputl(flags, f); // Tileset Flags (2=include tiles inside file)
+ fputl(si, f); // Tileset ID
+ fputl(flags, f); // Tileset Flags
fputl(tileset->size(), f);
fputw(tileset->grid().tileSize().w, f);
fputw(tileset->grid().tileSize().h, f);
- ase_file_write_padding(f, 16);
+ fputw(short(tileset->firstVisibleIndex()), f);
+ ase_file_write_padding(f, 14);
ase_file_write_string(f, tileset->name()); // tileset name
// Flag 1 = external tileset
diff --git a/src/app/modules/gfx.cpp b/src/app/modules/gfx.cpp
index 685e978e3..6c46df86f 100644
--- a/src/app/modules/gfx.cpp
+++ b/src/app/modules/gfx.cpp
@@ -197,7 +197,7 @@ void draw_tile(ui::Graphics* g,
else
draw_checked_grid(g, rc, gfx::Size(rc.w/4, rc.h/2));
- if (tile == doc::tile_i_notile)
+ if (tile == doc::notile)
return;
doc::Tileset* ts = site.tileset();
diff --git a/src/app/script/tileset_class.cpp b/src/app/script/tileset_class.cpp
index 2015ee465..8e2d5360d 100644
--- a/src/app/script/tileset_class.cpp
+++ b/src/app/script/tileset_class.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -69,6 +69,21 @@ int Tileset_get_grid(lua_State* L)
return 1;
}
+int Tileset_get_firstVisibleIndex(lua_State* L)
+{
+ auto tileset = get_docobj(L, 1);
+ lua_pushinteger(L, tileset->firstVisibleIndex());
+ return 1;
+}
+
+int Tileset_set_firstVisibleIndex(lua_State* L)
+{
+ auto tileset = get_docobj(L, 1);
+ int i = lua_tointeger(L, 2);
+ tileset->setFirstVisibleIndex(i);
+ return 0;
+}
+
const luaL_Reg Tileset_methods[] = {
{ "__eq", Tileset_eq },
{ "__len", Tileset_len },
@@ -81,6 +96,7 @@ const luaL_Reg Tileset_methods[] = {
const Property Tileset_properties[] = {
{ "name", Tileset_get_name, Tileset_set_name },
{ "grid", Tileset_get_grid, nullptr },
+ { "firstVisibleIndex", Tileset_get_firstVisibleIndex, Tileset_set_firstVisibleIndex },
{ nullptr, nullptr, nullptr }
};
diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h
index 61ccd3b1e..ad6c63efc 100644
--- a/src/app/tools/inks.h
+++ b/src/app/tools/inks.h
@@ -336,7 +336,7 @@ public:
setProc(get_ink_proc(loop));
}
else if (loop->getDstImage()->pixelFormat() == IMAGE_TILEMAP) {
- color_t clearColor = doc::tile_i_notile;
+ color_t clearColor = doc::notile;
loop->setPrimaryColor(clearColor);
loop->setSecondaryColor(clearColor);
setProc(new CopyInkProcessing(loop));
diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp
index e4f3b30ec..244018ce2 100644
--- a/src/app/ui/color_bar.cpp
+++ b/src/app/ui/color_bar.cpp
@@ -821,7 +821,7 @@ void ColorBar::onRemapTilesButtonClick()
if (n > 0) {
for (const ImageRef& tilemap : tilemaps) {
for (const doc::tile_t t : LockImageBits(tilemap.get()))
- if (t != doc::tile_i_notile)
+ if (t != doc::notile)
usedTiles[doc::tile_geti(t)] = true;
}
}
@@ -832,17 +832,19 @@ void ColorBar::onRemapTilesButtonClick()
for (tile_index ti=0; tiget(ti);
- tile_index destTi = (img ? tileset->findTileIndex(img):
- doc::tile_i_notile);
+ tile_index destTi;
if (img) {
+ tileset->findTileIndex(img, destTi);
+
COLOR_BAR_TRACE(" - Remap tile %d -> %d\n", ti, destTi);
remap.map(ti, destTi);
}
else {
- remap.map(ti, destTi = doc::tile_i_notile);
+ remap.map(ti, destTi = doc::notile);
}
- if (destTi == doc::tile_i_notile && ti < usedTiles.size() && usedTiles[ti]) {
+ if (destTi == doc::notile &&
+ ti < usedTiles.size() && usedTiles[ti]) {
COLOR_BAR_TRACE(" - Remap tile %d to empty (used=%d)\n", ti, usedTiles[ti]);
existMapToEmpty = true;
}
diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp
index 700e9e249..28193d0d6 100644
--- a/src/app/ui/editor/brush_preview.cpp
+++ b/src/app/ui/editor/brush_preview.cpp
@@ -302,8 +302,8 @@ void BrushPreview::show(const gfx::Point& screenPos)
Image* extraImage = m_extraCel->image();
if (extraImage->pixelFormat() == IMAGE_TILEMAP) {
- extraImage->setMaskColor(tile_i_notile);
- clear_image(extraImage, tile_i_notile);
+ extraImage->setMaskColor(notile);
+ clear_image(extraImage, notile);
}
else {
extraImage->setMaskColor(mask_index);
diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp
index f6ac295e6..68aacc2f6 100644
--- a/src/app/ui/editor/editor.cpp
+++ b/src/app/ui/editor/editor.cpp
@@ -1161,18 +1161,21 @@ void Editor::drawTileNumbers(ui::Graphics* g, const Cel* cel)
tileSize.h/2 - g->font()->height()/2)
+ mainTilePosition();
+ int ti_offset =
+ static_cast(cel->layer())->tileset()->firstVisibleIndex() - 1;
+
const gfx::Rect rc = cel->bounds();
const doc::Image* image = cel->image();
std::string text;
for (int y=0; yheight(); ++y) {
for (int x=0; xwidth(); ++x) {
doc::tile_t t = image->getPixel(x, y);
- if (t != doc::tile_i_notile) {
+ if (t != doc::notile) {
gfx::Point pt = editorToScreen(grid.tileToCanvas(gfx::Point(x, y)));
pt -= bounds().origin();
pt += offset;
- text = fmt::format("{}", (t & doc::tile_i_mask));
+ text = fmt::format("{}", int(t & doc::tile_i_mask) + ti_offset);
pt.x -= g->measureUIText(text).w/2;
g->drawText(text, fgColor, color, pt);
}
@@ -1726,7 +1729,7 @@ doc::tile_t Editor::getTileByPosition(const gfx::Point& mousePos)
return picker.tile();
}
else
- return doc::tile_i_notile;
+ return doc::notile;
}
bool Editor::startStraightLineWithFreehandTool(const tools::Pointer* pointer)
diff --git a/src/app/ui/editor/pixels_movement.cpp b/src/app/ui/editor/pixels_movement.cpp
index 98c2ea82b..eab26c089 100644
--- a/src/app/ui/editor/pixels_movement.cpp
+++ b/src/app/ui/editor/pixels_movement.cpp
@@ -1026,7 +1026,7 @@ void PixelsMovement::drawImage(
gfx::Rect bounds = corners.bounds();
if (m_site.tilemapMode() == TilemapMode::Tiles) {
- dst->setMaskColor(doc::tile_i_notile);
+ dst->setMaskColor(doc::notile);
dst->clear(dst->maskColor());
drawTransformedTilemap(
diff --git a/src/app/ui/palette_view.cpp b/src/app/ui/palette_view.cpp
index aff0d8670..58b7dc6c1 100644
--- a/src/app/ui/palette_view.cpp
+++ b/src/app/ui/palette_view.cpp
@@ -219,6 +219,17 @@ public:
}
void clearSelection(PaletteView* paletteView,
doc::PalettePicks& picks) override {
+ // Cannot delete the empty tile (index 0)
+ int i = picks.firstPick();
+ if (i == doc::notile) {
+ picks[i] = false;
+ if (!picks.picks()) {
+ // Cannot remove empty tile
+ StatusBar::instance()->showTip(1000, "Cannot delete the empty tile");
+ return;
+ }
+ }
+
paletteView->delegate()->onTilesViewClearTiles(picks);
}
void selectIndex(PaletteView* paletteView,
@@ -234,7 +245,7 @@ public:
void dropColors(PaletteView* paletteView,
doc::PalettePicks& picks,
int& currentEntry,
- const int beforeIndex,
+ const int _beforeIndex,
const bool isCopy) override {
PAL_TRACE("dropColors");
@@ -251,6 +262,19 @@ public:
// ColorBar::onTilesetChanged() called, and finally we'll receive a
// PaletteView::deselect() that will clear the whole picks.
auto newPicks = picks;
+ int beforeIndex = _beforeIndex;
+
+ // We cannot move the empty tile (index 0) no any place
+ if (beforeIndex == 0)
+ ++beforeIndex;
+ if (!isCopy && newPicks.size() > 0 && newPicks[0])
+ newPicks[0] = false;
+ if (!newPicks.picks()) {
+ // Cannot move empty tile
+ StatusBar::instance()->showTip(1000, "Cannot move the empty tile");
+ return;
+ }
+
paletteView->delegate()->onTilesViewDragAndDrop(
tileset, newPicks, currentEntry, beforeIndex, isCopy);
@@ -488,7 +512,7 @@ doc::tile_t PaletteView::getTileByPosition(const gfx::Point& pos)
if (getPaletteEntryBounds(i).contains(relPos))
return doc::tile(i, 0);
}
- return doc::tile_i_notile;
+ return doc::notile;
}
void PaletteView::onActiveSiteChange(const Site& site)
diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp
index a5c6e62d5..14cad9497 100644
--- a/src/app/ui/status_bar.cpp
+++ b/src/app/ui/status_bar.cpp
@@ -200,7 +200,7 @@ class StatusBar::Indicators : public HBox {
public:
TileIndicator(doc::tile_t tile)
: Indicator(kTile)
- , m_tile(doc::tile_i_notile) {
+ , m_tile(doc::notile) {
updateIndicator(tile, true);
}
@@ -467,13 +467,22 @@ public:
// Color description
std::string str;
- if (tile == doc::tile_i_notile) {
+ if (tile == doc::notile) {
str += "Empty";
}
else {
+ // TODO could the site came from the Indicators or StatusBar itself
+ int firstVisibleIndex = 1;
+ Site site = UIContext::instance()->activeSite();
+ if (site.tileset())
+ firstVisibleIndex = site.tileset()->firstVisibleIndex();
+
doc::tile_index ti = doc::tile_geti(tile);
doc::tile_flags tf = doc::tile_getf(tile);
- str += fmt::format("{}", ti);
+ if (firstVisibleIndex < 0)
+ str += fmt::format("{}", ((int)ti) + firstVisibleIndex - 1);
+ else
+ str += fmt::format("{}", ti + firstVisibleIndex - 1);
if (tf) {
if (tf & doc::tile_f_flipx) str += " FlipX";
if (tf & doc::tile_f_flipy) str += " FlipY";
@@ -755,6 +764,8 @@ bool StatusBar::setStatusText(int msecs, const std::string& msg)
void StatusBar::showTip(int msecs, const std::string& msg)
{
+ ASSERT(msecs > 0);
+
if (m_tipwindow == NULL) {
m_tipwindow = new CustomizedTipWindow(msg);
}
diff --git a/src/app/ui/tile_button.cpp b/src/app/ui/tile_button.cpp
index 34c4d3427..34325a74e 100644
--- a/src/app/ui/tile_button.cpp
+++ b/src/app/ui/tile_button.cpp
@@ -144,8 +144,12 @@ void TileButton::onPaint(PaintEvent& ev)
hasMouseOver(), false);
// Draw text
- if (m_tile != doc::tile_i_notile) {
- std::string str = fmt::format("{}", doc::tile_geti(m_tile));
+ if (m_tile != doc::notile) {
+ int firstVisibleIndex = 1;
+ if (site.tileset())
+ firstVisibleIndex = site.tileset()->firstVisibleIndex();
+
+ std::string str = fmt::format("{}", doc::tile_geti(m_tile) + firstVisibleIndex - 1);
setTextQuiet(str.c_str());
// TODO calc a proper color for the text
diff --git a/src/app/ui/tile_button.h b/src/app/ui/tile_button.h
index 4d8f34899..7ad29e571 100644
--- a/src/app/ui/tile_button.h
+++ b/src/app/ui/tile_button.h
@@ -44,7 +44,7 @@ namespace app {
// ContextObserver impl
void onActiveSiteChange(const Site& site) override;
- doc::tile_t m_tile = doc::tile_i_notile;
+ doc::tile_t m_tile = doc::notile;
};
} // namespace app
diff --git a/src/app/ui/tileset_selector.cpp b/src/app/ui/tileset_selector.cpp
index 953d7108b..f6feb6a7a 100644
--- a/src/app/ui/tileset_selector.cpp
+++ b/src/app/ui/tileset_selector.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -27,6 +27,7 @@ TilesetSelector::TilesetSelector(const doc::Sprite* sprite,
gridWidth()->setTextf("%d", info.grid.tileSize().w);
gridHeight()->setTextf("%d", info.grid.tileSize().h);
+ firstVisibleIndex()->setTextf("%d", info.firstVisibleIndex);
doc::tileset_index tsi = 0;
for (doc::Tileset* tileset : *sprite->tilesets()) {
@@ -59,6 +60,7 @@ TilesetSelector::Info TilesetSelector::getInfo()
info.newTileset = true;
info.grid = doc::Grid::MakeRect(sz);
+ info.firstVisibleIndex = firstVisibleIndex()->textInt();
}
else {
info.newTileset = false;
diff --git a/src/app/ui/tileset_selector.h b/src/app/ui/tileset_selector.h
index e97e0d513..15f0037ac 100644
--- a/src/app/ui/tileset_selector.h
+++ b/src/app/ui/tileset_selector.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (c) 2019 Igara Studio S.A.
+// Copyright (c) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -25,6 +25,7 @@ namespace app {
struct Info {
bool newTileset = true;
doc::Grid grid;
+ int firstVisibleIndex = 1;
doc::tileset_index tsi = 0;
};
diff --git a/src/app/util/cel_ops.cpp b/src/app/util/cel_ops.cpp
index cadc98e3a..dc2adc6ee 100644
--- a/src/app/util/cel_ops.cpp
+++ b/src/app/util/cel_ops.cpp
@@ -371,8 +371,8 @@ void draw_image_into_new_tilemap_cel(
newTilemap.reset(doc::Image::create(IMAGE_TILEMAP,
tilemapBounds.w,
tilemapBounds.h));
- newTilemap->setMaskColor(tile_i_notile);
- newTilemap->clear(tile_i_notile);
+ newTilemap->setMaskColor(doc::notile);
+ newTilemap->clear(doc::notile);
}
else {
ASSERT(tilemapBounds.w == newTilemap->width());
@@ -390,9 +390,8 @@ void draw_image_into_new_tilemap_cel(
if (grid.hasMask())
mask_image(tileImage.get(), grid.mask().get());
- doc::tile_index tileIndex =
- tileset->findTileIndex(tileImage);
- if (tileIndex == tile_i_notile) {
+ doc::tile_index tileIndex;
+ if (!tileset->findTileIndex(tileImage, tileIndex)) {
auto addTile = new cmd::AddTile(tileset, tileImage);
if (cmds)
@@ -473,8 +472,8 @@ void modify_tilemap_cel_region(
newTilemapBounds.w,
newTilemapBounds.h));
- newTilemap->setMaskColor(tile_i_notile);
- newTilemap->clear(tile_i_notile); // TODO find the tile with empty content?
+ newTilemap->setMaskColor(doc::notile);
+ newTilemap->clear(doc::notile); // TODO find the tile with empty content?
newTilemap->copy(
cel->image(),
gfx::Clip(oldTilemapBounds.x-newTilemapBounds.x,
@@ -495,7 +494,7 @@ void modify_tilemap_cel_region(
if (tilesetMode == TilesetMode::Auto) {
for_each_tile_using_tileset(
tileset, [tileset, &tilesHistogram](const doc::tile_t t){
- if (t != doc::tile_i_notile) {
+ if (t != doc::notile) {
doc::tile_index ti = doc::tile_geti(t);
if (ti >= 0 && ti < tileset->size())
++tilesHistogram[ti];
@@ -510,29 +509,21 @@ void modify_tilemap_cel_region(
if (!newTilemap->bounds().contains(u, v))
continue;
- doc::ImageRef existentTileImage;
const doc::tile_t t = newTilemap->getPixel(u, v);
- const doc::tile_index ti = (t != tile_i_notile ? doc::tile_geti(t): tile_i_notile);
- if (t == tile_i_notile) {
- // For "no tiles" create a new temporal empty tile to draw the
- // modification.
- existentTileImage = tileset->makeEmptyTile();
- }
- else {
- existentTileImage = tileset->get(ti);
- }
+ const doc::tile_index ti = (t != doc::notile ? doc::tile_geti(t): doc::notile);
+ const doc::ImageRef existentTileImage = tileset->get(ti);
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
ImageRef tileImage(getTileImage(existentTileImage, tileInCanvasRc));
if (grid.hasMask())
mask_image(tileImage.get(), grid.mask().get());
- tile_index tileIndex = tileset->findTileIndex(tileImage);
- if (tileIndex != tile_i_notile) {
+ tile_index tileIndex;
+ if (tileset->findTileIndex(tileImage, tileIndex)) {
// We can re-use an existent tile (tileIndex) from the tileset
}
else if (tilesetMode == TilesetMode::Auto &&
- t != tile_i_notile &&
+ t != doc::notile &&
ti >= 0 && ti < tilesHistogram.size() &&
tilesHistogram[ti] == 1) {
// Common case: Re-utilize the same tile in Auto mode.
@@ -557,7 +548,7 @@ void modify_tilemap_cel_region(
// If the tile changed, we have to remove the old tile index
// (ti) from the histogram count.
if (tilesetMode == TilesetMode::Auto &&
- t != tile_i_notile &&
+ t != doc::notile &&
ti >= 0 && ti < tilesHistogram.size() &&
ti != tileIndex) {
--tilesHistogram[ti];
@@ -567,7 +558,7 @@ void modify_tilemap_cel_region(
}
OPS_TRACE(" - tile %d -> %d\n",
- (t == tile_i_notile ? -1: ti),
+ (t == doc::notile ? -1: ti),
tileIndex);
const doc::tile_t tile = doc::tile(tileIndex, 0);
@@ -615,11 +606,15 @@ void modify_tilemap_cel_region(
continue;
const doc::tile_t t = cel->image()->getPixel(tilePt.x, tilePt.y);
- if (t == tile_i_notile)
+ if (t == doc::notile)
continue;
const doc::tile_index ti = doc::tile_geti(t);
const doc::ImageRef existentTileImage = tileset->get(ti);
+ if (!existentTileImage) {
+ // TODO add support to fill the tileset with the tile "ti"
+ continue;
+ }
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
ImageRef tileImage(getTileImage(existentTileImage, tileInCanvasRc));
@@ -730,7 +725,7 @@ static void remove_unused_tiles_from_tileset(
for_each_tile_using_tileset(
tileset,
[&n](const doc::tile_t t){
- if (t != doc::tile_i_notile) {
+ if (t != doc::notile) {
const doc::tile_index ti = doc::tile_geti(t);
n = std::max(n, ti+1);
}
@@ -773,6 +768,14 @@ void move_tiles_in_tileset(
{
OPS_TRACE("move_tiles_in_tileset\n");
+ // We cannot move the empty tile (index 0) no any place
+ if (beforeIndex == 0)
+ ++beforeIndex;
+ if (picks.size() > 0 && picks[0])
+ picks[0] = false;
+ if (!picks.picks())
+ return;
+
picks.resize(std::max(picks.size(), beforeIndex));
int n = beforeIndex - tileset->size();
@@ -798,6 +801,10 @@ void copy_tiles_in_tileset(
int& currentEntry,
int beforeIndex)
{
+ // We cannot move tiles before the empty tile
+ if (beforeIndex == 0)
+ ++beforeIndex;
+
OPS_TRACE("copy_tiles_in_tileset beforeIndex=%d npicks=%d\n", beforeIndex, picks.picks());
std::vector newTiles;
diff --git a/src/app/util/expand_cel_canvas.cpp b/src/app/util/expand_cel_canvas.cpp
index f07aa6ddb..5c7b0b53b 100644
--- a/src/app/util/expand_cel_canvas.cpp
+++ b/src/app/util/expand_cel_canvas.cpp
@@ -413,7 +413,7 @@ Image* ExpandCelCanvas::getSourceCanvas()
if (m_tilemapMode == TilemapMode::Tiles) {
m_srcImage.reset(Image::create(IMAGE_TILEMAP,
m_bounds.w, m_bounds.h, src_buffer));
- m_srcImage->setMaskColor(tile_i_notile);
+ m_srcImage->setMaskColor(doc::notile);
}
else {
m_srcImage.reset(Image::create(m_sprite->pixelFormat(),
@@ -431,7 +431,7 @@ Image* ExpandCelCanvas::getDestCanvas()
if (m_tilemapMode == TilemapMode::Tiles) {
m_dstImage.reset(Image::create(IMAGE_TILEMAP,
m_bounds.w, m_bounds.h, dst_buffer));
- m_dstImage->setMaskColor(tile_i_notile);
+ m_dstImage->setMaskColor(doc::notile);
}
else {
m_dstImage.reset(Image::create(m_sprite->pixelFormat(),
diff --git a/src/dio/aseprite_common.h b/src/dio/aseprite_common.h
index 28a00f0b6..2e60ecd39 100644
--- a/src/dio/aseprite_common.h
+++ b/src/dio/aseprite_common.h
@@ -1,5 +1,5 @@
// Aseprite Document IO Library
-// Copyright (c) 2018-2019 Igara Studio S.A.
+// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@@ -60,6 +60,7 @@
#define ASE_TILESET_FLAG_EXTERNAL_FILE 1
#define ASE_TILESET_FLAG_EMBEDDED 2
+#define ASE_TILESET_FLAG_ZERO_IS_NOTILE 4
namespace dio {
diff --git a/src/dio/aseprite_decoder.cpp b/src/dio/aseprite_decoder.cpp
index 2ce9d2d79..1270f2e6d 100644
--- a/src/dio/aseprite_decoder.cpp
+++ b/src/dio/aseprite_decoder.cpp
@@ -15,15 +15,16 @@
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/fs.h"
-#include "gfx/color_space.h"
#include "dio/aseprite_common.h"
#include "dio/decode_delegate.h"
#include "dio/file_interface.h"
#include "dio/pixel_io.h"
#include "doc/doc.h"
#include "doc/user_data.h"
+#include "doc/util.h"
#include "fixmath/fixmath.h"
#include "fmt/format.h"
+#include "gfx/color_space.h"
#include "zlib.h"
#include
@@ -89,8 +90,9 @@ bool AsepriteDecoder::decode()
// Used to read user data for each tag after a Tags chunk
bool tagsInProcess = false;
+ m_allLayers.clear();
+
int current_level = -1;
- doc::LayerList allLayers;
AsepriteExternalFiles extFiles;
// Just one frame?
@@ -158,7 +160,7 @@ bool AsepriteDecoder::decode()
&last_layer,
¤t_level);
if (newLayer) {
- allLayers.push_back(newLayer);
+ m_allLayers.push_back(newLayer);
last_object_with_user_data = newLayer;
}
break;
@@ -166,7 +168,7 @@ bool AsepriteDecoder::decode()
case ASE_FILE_CHUNK_CEL: {
doc::Cel* cel =
- readCelChunk(sprite.get(), allLayers, frame,
+ readCelChunk(sprite.get(), frame,
sprite->pixelFormat(), &header,
chunk_pos+chunk_size);
if (cel) {
@@ -691,7 +693,6 @@ void read_compressed_image(FileInterface* f,
//////////////////////////////////////////////////////////////////////
doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
- doc::LayerList& allLayers,
doc::frame_t frame,
doc::PixelFormat pixelFormat,
const AsepriteHeader* header,
@@ -706,8 +707,8 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
readPadding(7);
doc::Layer* layer = nullptr;
- if (layer_index >= 0 && layer_index < doc::layer_t(allLayers.size()))
- layer = allLayers[layer_index];
+ if (layer_index >= 0 && layer_index < doc::layer_t(m_allLayers.size()))
+ layer = m_allLayers[layer_index];
if (!layer) {
delegate()->error(
@@ -803,18 +804,32 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
// Read width and height
int w = read16();
int h = read16();
- int bitsPerTile = read16();
- uint32_t tileIDkMask = read32();
+ int bitsPerTile = read16(); // TODO add support for more bpp
+ uint32_t tileIDMask = read32();
uint32_t flipxMask = read32();
- uint32_t fileyMask = read32();
+ uint32_t flipyMask = read32();
uint32_t rot90Mask = read32();
+ uint32_t flagsMask = (flipxMask | flipyMask | rot90Mask);
readPadding(10);
if (w > 0 && h > 0) {
doc::ImageRef image(doc::Image::create(doc::IMAGE_TILEMAP, w, h));
- image->setMaskColor(doc::tile_i_notile);
- image->clear(doc::tile_i_notile);
+ image->setMaskColor(doc::notile);
+ image->clear(doc::notile);
read_compressed_image(f(), delegate(), image.get(), header, chunk_end);
+
+ // Check if the tileset of this tilemap has the
+ // "ASE_TILESET_FLAG_ZERO_IS_NOTILE" we have to adjust all
+ // tile references to the new format (where empty tile is
+ // zero)
+ doc::Tileset* ts = static_cast(layer)->tileset();
+ doc::tileset_index tsi = static_cast(layer)->tilesetIndex();
+ ASSERT(tsi >= 0 && tsi < m_tilesetFlags.size());
+ if (tsi >= 0 && tsi < m_tilesetFlags.size() &&
+ (m_tilesetFlags[tsi] & ASE_TILESET_FLAG_ZERO_IS_NOTILE) == 0) {
+ doc::fix_old_tilemap(image.get(), ts, tileIDMask, flagsMask);
+ }
+
cel.reset(new doc::Cel(frame, image));
cel->setPosition(x, y);
cel->setOpacity(opacity);
@@ -1053,7 +1068,8 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
const doc::tile_index ntiles = read32();
const int w = read16();
const int h = read16();
- readPadding(16);
+ const int firstVisibleIndex = short(read16());
+ readPadding(14);
const std::string name = readString();
// Errors
@@ -1067,6 +1083,7 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
doc::Grid grid(gfx::Size(w, h));
auto tileset = new doc::Tileset(sprite, grid, ntiles);
tileset->setName(name);
+ tileset->setFirstVisibleIndex(firstVisibleIndex);
if (flags & ASE_TILESET_FLAG_EXTERNAL_FILE) {
const uint32_t extFileId = read32(); // filename ID in the external files chunk
@@ -1098,9 +1115,17 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
doc::ImageRef tile(doc::crop_image(alltiles.get(), 0, i*h, w, h, alltiles->maskColor()));
tileset->set(i, tile);
}
+
+ // If we are reading and old .aseprite file (where empty tile is not the zero]
+ if ((flags & ASE_TILESET_FLAG_ZERO_IS_NOTILE) == 0)
+ doc::fix_old_tileset(tileset);
}
sprite->tilesets()->set(id, tileset);
}
+
+ if (id >= m_tilesetFlags.size())
+ m_tilesetFlags.resize(id+1, 0);
+ m_tilesetFlags[id] = flags;
}
} // namespace dio
diff --git a/src/dio/aseprite_decoder.h b/src/dio/aseprite_decoder.h
index 4846bd6f7..52089775a 100644
--- a/src/dio/aseprite_decoder.h
+++ b/src/dio/aseprite_decoder.h
@@ -1,5 +1,5 @@
// Aseprite Document IO Library
-// Copyright (c) 2018-2019 Igara Studio S.A.
+// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
@@ -17,6 +17,7 @@
#include "doc/tags.h"
#include
+#include
namespace doc {
class Cel;
@@ -48,7 +49,6 @@ private:
doc::Palette* readPaletteChunk(doc::Palette* prevPal, doc::frame_t frame);
doc::Layer* readLayerChunk(AsepriteHeader* header, doc::Sprite* sprite, doc::Layer** previous_layer, int* current_level);
doc::Cel* readCelChunk(doc::Sprite* sprite,
- doc::LayerList& allLayers,
doc::frame_t frame,
doc::PixelFormat pixelFormat,
const AsepriteHeader* header,
@@ -64,6 +64,9 @@ private:
void readTilesetChunk(doc::Sprite* sprite,
const AsepriteHeader* header,
const AsepriteExternalFiles& extFiles);
+
+ doc::LayerList m_allLayers;
+ std::vector m_tilesetFlags;
};
} // namespace dio
diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt
index faadb1276..a3348c99c 100644
--- a/src/doc/CMakeLists.txt
+++ b/src/doc/CMakeLists.txt
@@ -74,7 +74,8 @@ add_library(doc-lib
tileset.cpp
tileset_io.cpp
tilesets.cpp
- user_data_io.cpp)
+ user_data_io.cpp
+ util.cpp)
target_link_libraries(doc-lib
laf-gfx
diff --git a/src/doc/cel.cpp b/src/doc/cel.cpp
index d29b5a5f8..89f9bb881 100644
--- a/src/doc/cel.cpp
+++ b/src/doc/cel.cpp
@@ -167,7 +167,7 @@ void Cel::fixupImage()
// Change the mask color to the sprite mask color
if (m_layer && image()) {
image()->setMaskColor((image()->pixelFormat() == IMAGE_TILEMAP) ?
- tile_i_notile : m_layer->sprite()->transparentColor());
+ notile : m_layer->sprite()->transparentColor());
ASSERT(m_data);
m_data->adjustBounds(m_layer);
}
diff --git a/src/doc/primitives.cpp b/src/doc/primitives.cpp
index ce3018237..03a390841 100644
--- a/src/doc/primitives.cpp
+++ b/src/doc/primitives.cpp
@@ -427,9 +427,8 @@ void remap_image(Image* image, const Remap& remap)
case IMAGE_TILEMAP:
transform_image(
image, [&remap](color_t c) -> color_t {
- if (c == tile_i_notile ||
- remap[c] == Remap::kNoMap)
- return tile_i_notile;
+ if (c == notile || remap[c] == Remap::kNoMap)
+ return notile;
else
return remap[c];
});
diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp
index 369a058b5..3e70a6fab 100644
--- a/src/doc/sprite.cpp
+++ b/src/doc/sprite.cpp
@@ -218,6 +218,12 @@ void Sprite::setTransparentColor(color_t color)
getImages(images);
for (ImageRef& image : images)
image->setMaskColor(color);
+
+ // Transform the empty tile of all tilemaps
+ if (hasTilesets()) {
+ for (Tileset* tileset : *tilesets())
+ tileset->notifyRegenerateEmptyTile();
+ }
}
int Sprite::getMemSize() const
diff --git a/src/doc/tile.h b/src/doc/tile.h
index f44b64d89..9407c24f2 100644
--- a/src/doc/tile.h
+++ b/src/doc/tile.h
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (c) 2019 Igara Studio S.A.
+// Copyright (c) 2019-2020 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -20,7 +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 notile = 0;
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.cpp b/src/doc/tileset.cpp
index ab2901d14..6439563a8 100644
--- a/src/doc/tileset.cpp
+++ b/src/doc/tileset.cpp
@@ -10,6 +10,7 @@
#include "doc/tileset.h"
+#include "doc/primitives.h"
#include "doc/remap.h"
#include "doc/sprite.h"
@@ -33,8 +34,9 @@ Tileset::Tileset(Sprite* sprite,
//ASSERT(sprite);
for (tile_index ti=0; ti= 0);
ASSERT(remap[ti] < m_tiles.size());
if (remap[ti] >= 0 &&
remap[ti] < m_tiles.size()) {
+ ASSERT(remap[ti] != notile);
+
m_tiles[remap[ti]] = tmp[ti];
}
}
@@ -114,11 +122,17 @@ void Tileset::remap(const Remap& remap)
void Tileset::set(const tile_index ti,
const ImageRef& image)
{
+#if _DEBUG
+ if (ti == notile && !is_empty_image(image.get())) {
+ TRACEARGS("Warning: setting tile 0 with a non-empty image");
+ }
+#endif
+
removeFromHash(ti, false);
m_tiles[ti] = image;
if (!m_hash.empty())
- m_hash[image] = ti;
+ hashImage(ti, image);
}
tile_index Tileset::add(const ImageRef& image)
@@ -127,14 +141,20 @@ tile_index Tileset::add(const ImageRef& image)
const tile_index newIndex = tile_index(m_tiles.size()-1);
if (!m_hash.empty())
- m_hash[image] = newIndex;
+ hashImage(newIndex, image);
return newIndex;
}
void Tileset::insert(const tile_index ti,
const ImageRef& image)
{
- ASSERT(ti <= size());
+#if _DEBUG
+ if (ti == notile && !is_empty_image(image.get())) {
+ TRACEARGS("Warning: inserting tile 0 with a non-empty image");
+ }
+#endif
+
+ ASSERT(ti >= 0 && ti <= m_tiles.size()+1);
m_tiles.insert(m_tiles.begin()+ti, image);
if (!m_hash.empty()) {
@@ -144,7 +164,7 @@ void Tileset::insert(const tile_index ti,
++it.second;
// And now we can add the new image with the "ti" index
- m_hash[image] = ti;
+ hashImage(ti, image);
}
}
@@ -172,20 +192,27 @@ void Tileset::setExternal(const std::string& filename,
m_external.tileset = tsi;
}
-tile_index Tileset::findTileIndex(const ImageRef& tileImage)
+bool Tileset::findTileIndex(const ImageRef& tileImage,
+ tile_index& ti)
{
ASSERT(tileImage);
- if (!tileImage)
- return tile_i_notile;
+ if (!tileImage) {
+ ti = notile;
+ return false;
+ }
auto& h = hashTable(); // Don't use m_hash directly in case that
// we've to regenerate the hash table.
auto it = h.find(tileImage);
- if (it != h.end())
- return it->second;
- else
- return tile_i_notile;
+ if (it != h.end()) {
+ ti = it->second;
+ return true;
+ }
+ else {
+ ti = notile;
+ return false;
+ }
}
void Tileset::notifyTileContentChange(const tile_index ti)
@@ -215,7 +242,8 @@ void Tileset::notifyTileContentChange(const tile_index ti)
// In other case we can do a fast-path, just removing and
// re-adding the tile to the hash table.
removeFromHash(ti, false);
- m_hash[m_tiles[ti]] = ti;
+ if (!m_hash.empty())
+ hashImage(ti, m_tiles[ti]);
#else // Regenerate the whole hash map (at the moment this is the
// only way to make it work correctly)
@@ -226,6 +254,17 @@ void Tileset::notifyTileContentChange(const tile_index ti)
#endif
}
+void Tileset::notifyRegenerateEmptyTile()
+{
+ if (size() == 0)
+ return;
+
+ ImageRef image = get(doc::notile);
+ if (image)
+ doc::clear_image(image.get(), image->maskColor());
+ rehash();
+}
+
void Tileset::removeFromHash(const tile_index ti,
const bool adjustIndexes)
{
@@ -279,6 +318,13 @@ void Tileset::assertValidHashTable()
}
#endif
+void Tileset::hashImage(const tile_index ti,
+ const ImageRef& tileImage)
+{
+ if (m_hash.find(tileImage) == m_hash.end())
+ m_hash[tileImage] = ti;
+}
+
void Tileset::rehash()
{
// Clear the hash table, we'll lazy-rehash it when
@@ -292,7 +338,7 @@ TilesetHashTable& Tileset::hashTable()
// Re-hash/create the whole hash table from scratch
tile_index ti = 0;
for (auto tile : m_tiles)
- m_hash[tile] = ti++;
+ hashImage(ti++, tile);
}
return m_hash;
}
diff --git a/src/doc/tileset.h b/src/doc/tileset.h
index 2b23954d6..03cb015c9 100644
--- a/src/doc/tileset.h
+++ b/src/doc/tileset.h
@@ -28,6 +28,9 @@ namespace doc {
typedef Tiles::iterator iterator;
typedef Tiles::const_iterator const_iterator;
+ // Creates a new tileset with "ntiles". The first tile will be
+ // always the empty tile. So ntiles must be > 1 to contain at
+ // least one non-empty tile.
Tileset(Sprite* sprite,
const Grid& grid,
const tileset_index ntiles);
@@ -42,6 +45,9 @@ namespace doc {
const std::string& name() const { return m_name; }
void setName(const std::string& name) { m_name = name; }
+ int firstVisibleIndex() const { return m_firstVisibleIndex; }
+ void setFirstVisibleIndex(int index) { m_firstVisibleIndex = index; }
+
int getMemSize() const override;
iterator begin() { return m_tiles.begin(); }
@@ -53,7 +59,7 @@ namespace doc {
void remap(const Remap& remap);
ImageRef get(const tile_index ti) const {
- if (ti < size())
+ if (ti >= 0 && ti < size())
return m_tiles[ti];
else
return ImageRef(nullptr);
@@ -87,14 +93,20 @@ namespace doc {
ImageRef makeEmptyTile();
// If there is a tile in the set that matches the pixels of the
- // given "tileImage", this function returns the index of that
- // tile. Returns tile_i_notile if the image is not in the tileset.
- tile_index findTileIndex(const ImageRef& tileImage);
+ // given "tileImage", this function returns true and the index of
+ // that tile in the "ti" parameter. Returns false if the image is
+ // not in the tileset.
+ bool findTileIndex(const ImageRef& tileImage,
+ tile_index& ti);
// Must be called when a tile image was modified externally, so
// the hash elements are re-calculated for that specific tile.
void notifyTileContentChange(const tile_index ti);
+ // Called when the mask color of the sprite is modified, so we
+ // have to regenerate the empty tile with that new mask color.
+ void notifyRegenerateEmptyTile();
+
#ifdef _DEBUG
void assertValidHashTable();
#endif
@@ -102,6 +114,8 @@ namespace doc {
private:
void removeFromHash(const tile_index ti,
const bool adjustIndexes);
+ void hashImage(const tile_index ti,
+ const ImageRef& tileImage);
void rehash();
TilesetHashTable& hashTable();
@@ -110,6 +124,7 @@ namespace doc {
Tiles m_tiles;
TilesetHashTable m_hash;
std::string m_name;
+ int m_firstVisibleIndex = 1;
struct External {
std::string filename;
tileset_index tileset;
diff --git a/src/doc/tileset_io.cpp b/src/doc/tileset_io.cpp
index b0b23b2c4..3207448b9 100644
--- a/src/doc/tileset_io.cpp
+++ b/src/doc/tileset_io.cpp
@@ -16,9 +16,14 @@
#include "doc/image_io.h"
#include "doc/subobjects_io.h"
#include "doc/tileset.h"
+#include "doc/util.h"
#include
+// Extra BYTE with special flags to check the tileset version. This
+// field didn't exist in Aseprite v1.3-alpha3 (so read8() fails = 0)
+#define TILESET_VER1 1
+
namespace doc {
using namespace base::serialization;
@@ -39,12 +44,14 @@ bool write_tileset(std::ostream& os,
write_image(os, tileset->get(ti).get(), cancel);
}
+ write8(os, TILESET_VER1);
return true;
}
Tileset* read_tileset(std::istream& is,
Sprite* sprite,
- bool setId)
+ bool setId,
+ bool* isOldVersion)
{
ObjectId id = read32(is);
tileset_index ntiles = read32(is);
@@ -58,6 +65,23 @@ Tileset* read_tileset(std::istream& is,
tileset->set(ti, image);
}
+ // Read extra version byte after tiles
+ uint32_t ver = read8(is);
+ if (ver == TILESET_VER1) {
+ if (isOldVersion)
+ *isOldVersion = false;
+
+ tileset->setFirstVisibleIndex(1);
+ }
+ // Old tileset used in internal versions (this was added to recover
+ // old files, maybe in a future we could remove this code)
+ else {
+ if (isOldVersion)
+ *isOldVersion = true;
+
+ fix_old_tileset(tileset);
+ }
+
return tileset;
}
diff --git a/src/doc/tileset_io.h b/src/doc/tileset_io.h
index 2f77e6179..eee64ee31 100644
--- a/src/doc/tileset_io.h
+++ b/src/doc/tileset_io.h
@@ -22,7 +22,8 @@ namespace doc {
Tileset* read_tileset(std::istream& is,
Sprite* sprite,
- bool setId = true);
+ bool setId = true,
+ bool* isOldVersion = nullptr);
} // namespace doc
diff --git a/src/doc/util.cpp b/src/doc/util.cpp
new file mode 100644
index 000000000..2a3f3e82b
--- /dev/null
+++ b/src/doc/util.cpp
@@ -0,0 +1,54 @@
+// Aseprite Document Library
+// Copyright (c) 2020 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#include "doc/util.h"
+
+#include "doc/image.h"
+#include "doc/image_impl.h"
+#include "doc/tileset.h"
+
+namespace doc {
+
+void fix_old_tileset(
+ Tileset* tileset)
+{
+ // Check if the first tile is already the empty tile, in this
+ // case we can use this tileset as a new tileset without any
+ // conversion.
+ if (tileset->size() > 0 && is_empty_image(tileset->get(0).get())) {
+ tileset->setFirstVisibleIndex(1);
+ }
+ else {
+ // Add the empty tile in the index = 0
+ tileset->insert(0, tileset->makeEmptyTile());
+
+ // The tile 1 will be displayed as tile 0 in the editor
+ tileset->setFirstVisibleIndex(0);
+ }
+}
+
+void fix_old_tilemap(
+ Image* image,
+ const Tileset* tileset,
+ const tile_t tileIDMask,
+ const tile_t tileFlagsMask)
+{
+ int delta = (tileset->firstVisibleIndex() == 0 ? 1: 0);
+
+ // Convert old empty tile (0xffffffff) to new empty tile (index 0 = notile)
+ transform_image(
+ image,
+ [tileIDMask, tileFlagsMask, delta](color_t c) -> color_t {
+ color_t res = c;
+ if (c == 0xffffffff)
+ res = notile;
+ else
+ res = (c & tileFlagsMask) | ((c & tileIDMask)+delta);
+ return res;
+ });
+}
+
+} // namespace dooc
diff --git a/src/doc/util.h b/src/doc/util.h
new file mode 100644
index 000000000..fb1c928b9
--- /dev/null
+++ b/src/doc/util.h
@@ -0,0 +1,34 @@
+// Aseprite Document Library
+// Copyright (c) 2020 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOC_UTIL_H_INCLUDED
+#define DOC_UTIL_H_INCLUDED
+#pragma once
+
+#include "doc/tile.h"
+
+namespace doc {
+ class Image;
+ class Tileset;
+
+ // Function used to migrate an old tileset format (from internal
+ // v1.3-alpha3) which doesn't have an empty tile in the zero index
+ // (notile).
+ void fix_old_tileset(
+ Tileset* tileset);
+
+ // Function used to migrate an old tilemap format (from internal
+ // v1.3-alpha3) which used a tileset without an empty tile in the
+ // zero index (notile).
+ void fix_old_tilemap(
+ Image* image,
+ const Tileset* tileset,
+ const tile_t tileIDMask,
+ const tile_t tileFlagsMask);
+
+} // namespace doc
+
+#endif
diff --git a/src/render/render.cpp b/src/render/render.cpp
index 18d9b955e..118983f19 100644
--- a/src/render/render.cpp
+++ b/src/render/render.cpp
@@ -1285,7 +1285,7 @@ void Render::renderCel(
continue;
const tile_t t = cel_image->getPixel(u, v);
- if (t != tile_i_notile) {
+ if (t != doc::notile) {
const tile_index i = tile_geti(t);
if (dst_image->pixelFormat() == IMAGE_TILEMAP) {