Simplify tilesets impl using tile index 0 = an empty tile

In this way we always have an empty tile available in the drawing
process. We've also added the Tileset::firstVisibleIndex field to
change the visible index of the tile 1 so we can offset the visible
number by the user (just as a visual aid / simulate old tilesets with
index=0=non-empty tile).
This commit is contained in:
David Capello 2020-10-30 16:33:34 -03:00
parent 9113a690f0
commit c2e5e69882
43 changed files with 446 additions and 124 deletions

View File

@ -221,8 +221,8 @@
<option id="tiles_box_size" type="int" default="16" />
<option id="fg_color" type="app::Color" default="app::Color::fromRgb(255, 255, 255)" />
<option id="bg_color" type="app::Color" default="app::Color::fromRgb(0, 0, 0)" />
<option id="fg_tile" type="doc::tile_t" default="doc::tile_i_notile" />
<option id="bg_tile" type="doc::tile_t" default="doc::tile_i_notile" />
<option id="fg_tile" type="doc::tile_t" default="doc::notile" />
<option id="bg_tile" type="doc::tile_t" default="doc::notile" />
<option id="selector" type="app::ColorBar::ColorSelector" default="app::ColorBar::ColorSelector::TINT_SHADE_TONE" />
<option id="discrete_wheel" type="bool" default="false" />
<option id="wheel_model" type="int" default="0" />

View File

@ -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 = <<<END
Change how you see the tile of index 1 (you can set it to 0 or any value).
END
[new_sprite]
title = New Sprite

View File

@ -1,17 +1,21 @@
<!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2019-2020 Igara Studio S.A. -->
<gui>
<vbox id="tileset_selector">
<combobox id="tilesets">
<listitem text="@.new_tileset" value="-1" />
</combobox>
<hbox id="grid_options">
<grid id="grid_options" columns="4">
<label text="@.grid_width" />
<expr id="grid_width" text="" />
<label text="@.grid_height" />
<expr id="grid_height" text="" />
</hbox>
<label text="@.first_visible_index" />
<expr id="first_visible_index" text="1" tooltip="@.first_visible_index_tooltip" />
<booxfiller cell_hspan="2" />
</grid>
</vbox>
</gui>

View File

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

View File

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

View File

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

View File

@ -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<Tileset>(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<Doc*>(tileset->sprite()->document())
->notifyTilesetChanged(tileset);
}

View File

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

View File

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

View File

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

View File

@ -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 <fstream>
@ -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<LayerTilemap*>(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<std::pair<ObjectId, ObjectId> > m_celsToLoad;
std::map<ObjectId, ImageRef> m_images;
std::map<ObjectId, CelDataRef> 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<ObjectId> m_updateOldTilemapWithTileset;
base::task_token* m_taskToken;
};

View File

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

View File

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

View File

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

View File

@ -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<Tileset>(L, 1);
lua_pushinteger(L, tileset->firstVisibleIndex());
return 1;
}
int Tileset_set_firstVisibleIndex(lua_State* L)
{
auto tileset = get_docobj<Tileset>(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 }
};

View File

@ -336,7 +336,7 @@ public:
setProc(get_ink_proc<BrushEraserInkProcessing>(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<TilemapTraits>(loop));

View File

@ -821,7 +821,7 @@ void ColorBar::onRemapTilesButtonClick()
if (n > 0) {
for (const ImageRef& tilemap : tilemaps) {
for (const doc::tile_t t : LockImageBits<TilemapTraits>(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; ti<n; ++ti) {
auto img = m_oldTileset->get(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;
}

View File

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

View File

@ -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<LayerTilemap*>(cel->layer())->tileset()->firstVisibleIndex() - 1;
const gfx::Rect rc = cel->bounds();
const doc::Image* image = cel->image();
std::string text;
for (int y=0; y<image->height(); ++y) {
for (int x=0; x<image->width(); ++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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<int>(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<int>(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<ImageRef> newTiles;

View File

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

View File

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

View File

@ -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 <cstdio>
@ -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,
&current_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<doc::LayerTilemap*>(layer)->tileset();
doc::tileset_index tsi = static_cast<doc::LayerTilemap*>(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

View File

@ -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 <string>
#include <vector>
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<uint32_t> m_tilesetFlags;
};
} // namespace dio

View File

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

View File

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

View File

@ -427,9 +427,8 @@ void remap_image(Image* image, const Remap& remap)
case IMAGE_TILEMAP:
transform_image<TilemapTraits>(
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];
});

View File

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

View File

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

View File

@ -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<ntiles; ++ti) {
m_tiles[ti] = makeEmptyTile();
m_hash[m_tiles[ti]] = ti;
ImageRef tile = makeEmptyTile();
m_tiles[ti] = tile;
hashImage(ti, tile);
}
}
@ -97,13 +99,19 @@ void Tileset::resize(const tile_index ntiles)
void Tileset::remap(const Remap& remap)
{
Tiles tmp = m_tiles;
for (tile_index ti=0; ti<size(); ++ti) {
// The notile cannot be remapped
ASSERT(remap[0] == 0);
for (tile_index ti=1; ti<size(); ++ti) {
TS_TRACE("m_tiles[%d] = tmp[%d]\n", remap[ti], ti);
ASSERT(remap[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;
}

View File

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

View File

@ -16,9 +16,14 @@
#include "doc/image_io.h"
#include "doc/subobjects_io.h"
#include "doc/tileset.h"
#include "doc/util.h"
#include <iostream>
// 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;
}

View File

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

54
src/doc/util.cpp Normal file
View File

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

34
src/doc/util.h Normal file
View File

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

View File

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