mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-01 10:13:22 +00:00
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:
parent
9113a690f0
commit
c2e5e69882
@ -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" />
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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 }
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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,
|
||||
¤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<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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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];
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
54
src/doc/util.cpp
Normal 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
34
src/doc/util.h
Normal 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
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user