Merge branch 'tilemap-editor' into beta

This commit is contained in:
David Capello 2020-08-14 21:08:59 -03:00
commit e37ddbd7de
199 changed files with 7489 additions and 1005 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -419,6 +419,10 @@
<part id="outline_empty_pixel" x="208" y="224" w="5" h="5" />
<part id="outline_full_pixel" x="214" y="224" w="5" h="5" />
<part id="dynamics" x="176" y="144" w="16" h="16" />
<part id="tiles" x="144" y="208" w="6" h="6" />
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
<part id="tiles_auto" x="156" y="208" w="6" h="6" />
<part id="tiles_stack" x="162" y="208" w="6" h="6" />
</parts>
<styles>
<style id="box" />

View File

@ -83,6 +83,10 @@
<key command="NewLayer" shortcut="Ctrl+Shift+J" mac="Cmd+Shift+J">
<param name="viaCut" value="true" />
</key>
<key command="NewLayer" shortcut="Space+N">
<param name="ask" value="true" />
<param name="tilemap" value="true" />
</key>
<key command="GotoPreviousLayer" shortcut="Down" context="Normal" />
<key command="GotoNextLayer" shortcut="Up" context="Normal" />
<!-- Frame -->
@ -138,6 +142,16 @@
<key command="Timeline" shortcut="Tab">
<param name="switch" value="true" />
</key>
<key command="ToggleTilesMode" shortcut="Space+Tab" />
<key command="TilesetMode" shortcut="Space+1">
<param name="mode" value="manual" />
</key>
<key command="TilesetMode" shortcut="Space+2">
<param name="mode" value="auto" />
</key>
<key command="TilesetMode" shortcut="Space+3">
<param name="mode" value="stack" />
</key>
<key command="PaletteEditor" shortcut="A" />
<key command="PaletteEditor" shortcut="F4">
<param name="edit" value="switch" />
@ -424,6 +438,9 @@
<key command="NewSpriteFromSelection" shortcut="Ctrl+Alt+N" mac="Cmd+Alt+N" />
<!-- Commands not associated to menu items and without shortcuts by default -->
<key command="NewLayer">
<param name="tilemap" value="true" />
</key>
<key command="PixelPerfectMode" />
<key command="ContiguousFill" />
<key command="SetInkType"><param name="type" value="simple" /></key>
@ -811,6 +828,10 @@
<param name="reference" value="true" />
<param name="fromFile" value="true" />
</item>
<item command="NewLayer" text="@.layer_new_tilemap_layer">
<param name="tilemap" value="true" />
<param name="ask" value="true" />
</item>
</menu>
<item command="RemoveLayer" text="@.layer_delete_layer" group="layer_remove" />
<item command="BackgroundFromLayer" text="@.layer_background_from_layer" />

View File

@ -209,6 +209,7 @@
</section>
<section id="color_bar">
<option id="box_size" type="int" default="11" />
<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="selector" type="app::ColorBar::ColorSelector" default="app::ColorBar::ColorSelector::TINT_SHADE_TONE" />

View File

@ -203,6 +203,36 @@ clear = &Delete
unlink = &Unlink
link_cels = &Link Cels
[color_bar]
fg = Foreground Color
bg = Background Color
fg_warning = Add foreground color to the palette
bg_warning = Add background color to the palette
edit_color = Edit Color
sort_and_gradients = Sort & Gradients
presets = Presets
options = Options
switch_tileset = Show/Hide Tileset
tileset_mode_manual = <<<END
Manual: Modify existent tiles,
don't create new tiles automatically
END
tileset_mode_auto = <<<END
Auto: Modify and reuse existent tiles,
create/delete tiles if needed/possible
END
tileset_mode_stack = <<<END
Stack: Don't modify existent tiles,
generate and stack new tiles automatically
END
remap_palette = Remap Palette
remap_palette_tooltip = Matches old indexes with new indexes
remap_tiles = Remap Tiles
remap_tiles_tooltip = Matches old tiles with new tiles
clear_tiles = Clear Tiles
resize_tiles = Resize Tiles
drag_and_drop_tiles = Drag And Drop Tiles
[commands]
About = About
AddColor = Add {0} Color to Palette
@ -252,6 +282,7 @@ Copy = Copy
CopyCel = Copy Cel
CopyColors = Copy Colors
CopyMerged = Copy Merged
CopyTiles = Copy Tiles
CropSprite = Crop Sprite
Cut = Cut
DeselectMask = Deselect Mask
@ -334,6 +365,7 @@ MoveColors = Move Colors
MoveMask = Move {0} {1}
MoveMask_Boundaries = Selection Boundaries
MoveMask_Content = Selection Content
MoveTiles = Move Tiles
NewBrush = New Brush
NewFile = New File
NewFile_FromClipboard = New File from Clipboard
@ -348,9 +380,11 @@ NewLayer_BeforeActiveLayer = New {} Below
NewLayer_Layer = Layer
NewLayer_Group = Group
NewLayer_ReferenceLayer = Reference Layer
NewLayer_TilemapLayer = Tilemap Layer
NewLayer_FromClipboard = {} from Clipboard
NewLayer_ViaCopy = {} via Copy
NewLayer_ViaCut = {} via Cut
NewLayer_WithDialog = {} (with dialog)
NewSpriteFromSelection = New Sprite from Selection
OpenBrowser = Open Browser
OpenFile = Open Sprite
@ -448,6 +482,11 @@ SymmetryMode = Symmetry Mode
TiledMode = Tiled Mode
Timeline = Switch Timeline
TogglePreview = Toggle Preview
ToggleTilesMode = Toggle Tiles Mode
TilesetMode = Tileset Mode: {}
TilesetMode_Manual = Manual
TilesetMode_Auto = Auto
TilesetMode_Stack = Stack
ToggleTimelineThumbnails = Toggle Timeline Thumbnails
Undo = Undo
UndoHistory = Undo History
@ -880,6 +919,7 @@ layer_new_group = New &Group
layer_new_layer_via_copy = New Layer via &Copy
layer_new_layer_via_cut = New Layer via Cu&t
layer_new_reference_layer_from_file = New &Reference Layer from File
layer_new_tilemap_layer = New Tilemap Layer
layer_delete_layer = Delete Laye&r
layer_background_from_layer = &Background from Layer
layer_layer_from_background = &Layer from Background
@ -974,8 +1014,14 @@ default_new_folder_name = New Folder
[new_layer]
title = New Layer
name = Name:
tileset = Tileset:
default_new_layer_name = New Layer
[tileset_selector]
new_tileset = New Tileset
grid_width = Grid Width:
grid_height = Grid Height:
[new_sprite]
title = New Sprite
size = Size:

View File

@ -1,20 +1,25 @@
<!-- Aseprite -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<gui>
<window id="new_layer" text="@.title">
<box vertical="true">
<box horizontal="true">
<box vertical="true" homogeneous="true">
<label text="@.name" />
</box>
<box vertical="true" homogeneous="true">
<entry maxsize="256" text="@.default_new_layer_name" id="name" magnet="true" />
</box>
</box>
<box horizontal="true" homogeneous="true">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" />
<button text="@general.cancel" closewindow="true" />
</box>
</box>
</window>
</gui>
<!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 David Capello -->
<gui>
<window id="new_layer" text="@.title">
<vbox>
<grid columns="2">
<label text="@.name" />
<entry maxsize="256" text="@.default_new_layer_name" id="name" magnet="true" />
<vbox>
<label id="tileset_label" text="@.tileset" />
</vbox>
<hbox id="tileset_options" />
</grid>
<hbox>
<boxfiller />
<hbox homogeneous="true">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
<button text="@general.cancel" closewindow="true" />
</hbox>
</hbox>
</vbox>
</window>
</gui>

View File

@ -0,0 +1,17 @@
<!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<gui>
<vbox id="tileset_selector">
<combobox id="tilesets">
<listitem text="@.new_tileset" value="-1" />
</combobox>
<hbox id="grid_options">
<label text="@.grid_width" />
<expr id="grid_width" text="" />
<label text="@.grid_height" />
<expr id="grid_height" text="" />
</hbox>
</vbox>
</gui>

View File

@ -25,7 +25,10 @@ ASE files use Intel (little-endian) byte order.
* `PIXEL`: One pixel, depending on the image pixel format:
- **RGBA**: `BYTE[4]`, each pixel have 4 bytes in this order Red, Green, Blue, Alpha.
- **Grayscale**: `BYTE[2]`, each pixel have 2 bytes in the order Value, Alpha.
- **Indexed**: `BYTE`, Each pixel uses 1 byte (the index).
- **Indexed**: `BYTE`, each pixel uses 1 byte (the index).
* `TILE`: **Tilemaps**: Each tile can be a 8-bit (`BYTE`), 16-bit
(`WORD`), or 32-bit (`DWORD`) value and there are masks related to
the meaning of each bit.
## Introduction
@ -146,6 +149,7 @@ Ignore this chunk if you find the new palette chunk (0x2019)
WORD Layer type
0 = Normal (image) layer
1 = Group
2 = Tilemap
WORD Layer child level (see NOTE.1)
WORD Default layer width in pixels (ignored)
WORD Default layer height in pixels (ignored)
@ -173,19 +177,24 @@ Ignore this chunk if you find the new palette chunk (0x2019)
Note: valid only if file header flags field has bit 1 set
BYTE[3] For future (set to zero)
STRING Layer name
+ If layer type = 2
DWORD Tileset index
### Cel Chunk (0x2005)
This chunk determine where to put a cel in the specified
layer/frame.
This chunk determine where to put a cel in the specified layer/frame.
WORD Layer index (see NOTE.2)
SHORT X position
SHORT Y position
BYTE Opacity level
WORD Cel type
WORD Cel Type
0 - Raw Image Data (unused, compressed image is preferred)
1 - Linked Cel
2 - Compressed Image
3 - Compressed Tilemap
BYTE[7] For future (set to zero)
+ For cel type = 0 (Raw Cel)
+ For cel type = 0 (Raw Image Data)
WORD Width in pixels
WORD Height in pixels
PIXEL[] Raw pixel data: row by row from top to bottom,
@ -195,14 +204,18 @@ Ignore this chunk if you find the new palette chunk (0x2019)
+ For cel type = 2 (Compressed Image)
WORD Width in pixels
WORD Height in pixels
BYTE[] "Raw Cel" data compressed with ZLIB method
Details about the ZLIB and DEFLATE compression methods:
* https://www.ietf.org/rfc/rfc1950
* https://www.ietf.org/rfc/rfc1951
* Some extra notes that might help you to decode the data:
http://george.chiramattel.com/blog/2007/09/deflatestream-block-length-does-not-match.html
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)
DWORD Bitmask for tile ID (e.g. 0x1fffffff for 32-bit tiles)
DWORD Bitmask for X flip
DWORD Bitmask for Y flip
DWORD Bitmask for 90CW rotation
BYTE[10] Reserved
TILE[] Row by row, from top to bottom tile by tile
compressed with ZLIB method (see NOTE.3)
### Cel Extra Chunk (0x2006)
@ -231,11 +244,23 @@ Color profile for RGB or grayscale values.
this fixed gamma, because sRGB uses different gamma sections
(linear and non-linear). If sRGB is specified with a fixed
gamma = 1.0, it means that this is Linear sRGB.
BYTE[8] Reserved (set to zero]
BYTE[8] Reserved (set to zero)
+ If type = ICC:
DWORD ICC profile data length
BYTE[] ICC profile data. More info: http://www.color.org/ICC1V42.pdf
### External Files Chunk (0x2008)
A list of external files linked with this file. It might be used to
reference external palettes or tilesets.
DWORD Number of entries
BYTE[8] Reserved (set to zero)
+ For each entry
DWORD Entry ID (this ID is referenced by tilesets or palettes)
BYTE[8] Reserved (set to zero)
STRING External file name
### Mask Chunk (0x2016) DEPRECATED
SHORT X position
@ -326,6 +351,26 @@ belongs to that cel, etc.
LONG Pivot X position (relative to the slice origin)
LONG Pivot Y position (relative to the slice origin)
### Tileset Chunk (0x2023)
DWORD Tileset ID
DWORD Tileset flags
1 - Include link to external file
2 - Include tiles inside this file
DWORD Number of tiles
WORD Tile Width
WORD Tile Height
BYTE[16] Reserved
STRING Name of the tileset
+ If flag 1 is set
DWORD ID of the external file. This ID is one entry
of the the External Files Chunk.
DWORD Tileset ID in the external file
+ If flag 2 is set
DWORD Compressed data length
PIXEL[] Compressed Tileset image (see NOTE.3):
(Tile Width) x (Tile Height x Number of Tiles)
### Notes
#### NOTE.1
@ -356,6 +401,15 @@ example:
| `- Layer2 4
`- Layer3 5
#### NOTE.3
Details about the ZLIB and DEFLATE compression methods:
* https://www.ietf.org/rfc/rfc1950
* https://www.ietf.org/rfc/rfc1951
* Some extra notes that might help you to decode the data:
http://george.chiramattel.com/blog/2007/09/deflatestream-block-length-does-not-match.html
## File Format Changes
1. The first change from the first release of the new .ase format,

View File

@ -160,6 +160,7 @@ if(ENABLE_SCRIPTING)
script/engine.cpp
script/frame_class.cpp
script/frames_class.cpp
script/grid_class.cpp
script/image_class.cpp
script/image_iterator_class.cpp
script/image_spec_class.cpp
@ -185,6 +186,8 @@ if(ENABLE_SCRIPTING)
script/sprites_class.cpp
script/tag_class.cpp
script/tags_class.cpp
script/tileset_class.cpp
script/tilesets_class.cpp
script/tool_class.cpp
script/values.cpp
script/version_class.cpp
@ -290,6 +293,7 @@ if(ENABLE_UI)
commands/cmd_tiled_mode.cpp
commands/cmd_timeline.cpp
commands/cmd_toggle_preview.cpp
commands/cmd_toggle_tiles_mode.cpp
commands/cmd_toggle_timeline_thumbnails.cpp
commands/cmd_undo_history.cpp
commands/cmd_unlink_cel.cpp
@ -298,6 +302,7 @@ if(ENABLE_UI)
commands/filters/filter_target_buttons.cpp
commands/filters/filter_window.cpp
commands/screenshot.cpp
commands/tileset_mode.cpp
file_selector.cpp
modules/editors.cpp
modules/gfx.cpp
@ -383,6 +388,7 @@ if(ENABLE_UI)
ui/tabs.cpp
ui/tag_window.cpp
ui/task_widget.cpp
ui/tileset_selector.cpp
ui/timeline/ani_controls.cpp
ui/timeline/timeline.cpp
ui/toolbar.cpp
@ -420,6 +426,8 @@ add_library(app-lib
cmd/add_palette.cpp
cmd/add_slice.cpp
cmd/add_tag.cpp
cmd/add_tile.cpp
cmd/add_tileset.cpp
cmd/assign_color_profile.cpp
cmd/background_from_layer.cpp
cmd/clear_cel.cpp
@ -443,12 +451,16 @@ add_library(app-lib
cmd/move_layer.cpp
cmd/patch_cel.cpp
cmd/remap_colors.cpp
cmd/remap_tilemaps.cpp
cmd/remap_tileset.cpp
cmd/remove_cel.cpp
cmd/remove_frame.cpp
cmd/remove_layer.cpp
cmd/remove_palette.cpp
cmd/remove_slice.cpp
cmd/remove_tag.cpp
cmd/remove_tile.cpp
cmd/remove_tileset.cpp
cmd/replace_image.cpp
cmd/reselect_mask.cpp
cmd/set_cel_bounds.cpp
@ -488,6 +500,7 @@ add_library(app-lib
cmd/with_slice.cpp
cmd/with_sprite.cpp
cmd/with_tag.cpp
cmd/with_tileset.cpp
cmd_sequence.cpp
cmd_transaction.cpp
color.cpp
@ -532,6 +545,7 @@ add_library(app-lib
commands/filters/filter_worker.cpp
commands/move_colors_command.cpp
commands/move_thing.cpp
commands/move_tiles_command.cpp
commands/new_params.cpp
commands/quick_command.cpp
console.cpp
@ -599,8 +613,8 @@ add_library(app-lib
ui/layer_frame_comboboxes.cpp
util/autocrop.cpp
util/buffer_region.cpp
util/cel_ops.cpp
util/conversion_to_surface.cpp
util/create_cel_copy.cpp
util/expand_cel_canvas.cpp
util/filetoks.cpp
util/freetype_utils.cpp

View File

@ -67,6 +67,7 @@ void ActiveSiteHandler::getActiveSiteForDoc(Doc* doc, Site* site)
site->frame(data.frame);
site->range(data.range);
site->selectedColors(data.selectedColors);
site->selectedTiles(data.selectedTiles);
}
void ActiveSiteHandler::setActiveLayerInDoc(Doc* doc, doc::Layer* layer)
@ -109,6 +110,12 @@ void ActiveSiteHandler::setSelectedColorsInDoc(Doc* doc, const doc::PalettePicks
data.selectedColors = picks;
}
void ActiveSiteHandler::setSelectedTilesInDoc(Doc* doc, const doc::PalettePicks& picks)
{
Data& data = getData(doc);
data.selectedTiles = picks;
}
void ActiveSiteHandler::onAddLayer(DocEvent& ev)
{
Data& data = getData(ev.document());

View File

@ -41,6 +41,7 @@ namespace app {
void setActiveFrameInDoc(Doc* doc, doc::frame_t frame);
void setRangeInDoc(Doc* doc, const DocRange& range);
void setSelectedColorsInDoc(Doc* doc, const doc::PalettePicks& picks);
void setSelectedTilesInDoc(Doc* doc, const doc::PalettePicks& picks);
private:
// DocObserver impl
@ -55,6 +56,7 @@ namespace app {
doc::frame_t frame;
DocRange range;
doc::PalettePicks selectedColors;
doc::PalettePicks selectedTiles;
};
Data& getData(Doc* doc);

View File

@ -288,6 +288,14 @@ os::Shortcut get_os_shortcut_from_key(const Key* key)
{
if (key && !key->accels().empty()) {
const ui::Accelerator& accel = key->accels().front();
#ifdef __APPLE__
// Shortcuts with spacebar as modifier do not work well in macOS
// (they will be called when the space bar is unpressed too).
if ((accel.modifiers() & ui::kKeySpaceModifier) == ui::kKeySpaceModifier)
return os::Shortcut();
#endif
return os::Shortcut(
(accel.unicodeChar() ? accel.unicodeChar():
from_scancode_to_unicode(accel.scancode())),

112
src/app/cmd/add_tile.cpp Normal file
View File

@ -0,0 +1,112 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/add_tile.h"
#include "app/doc.h"
#include "doc/image_io.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
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_imageRef(image)
{
}
AddTile::AddTile(doc::Tileset* tileset,
const doc::tile_index ti)
: WithTileset(tileset)
, WithImage(tileset->get(ti).get())
, m_size(0)
, m_tileIndex(ti)
, m_imageRef(nullptr)
{
}
void AddTile::onExecute()
{
doc::Tileset* tileset = this->tileset();
ASSERT(tileset);
if (m_tileIndex != doc::tile_i_notile) {
ASSERT(!m_imageRef);
tileset->sprite()->incrementVersion();
tileset->incrementVersion();
}
else {
ASSERT(m_imageRef);
addTile(tileset, m_imageRef);
m_imageRef.reset();
}
}
void AddTile::onUndo()
{
doc::Tileset* tileset = this->tileset();
ASSERT(tileset);
write_image(m_stream, image());
m_size = size_t(m_stream.tellp());
tileset->erase(m_tileIndex);
tileset->sprite()->incrementVersion();
tileset->incrementVersion();
}
void AddTile::onRedo()
{
doc::Tileset* tileset = this->tileset();
ASSERT(!m_imageRef);
m_imageRef.reset(read_image(m_stream));
ASSERT(m_imageRef);
addTile(tileset, m_imageRef);
m_imageRef.reset();
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddTile::onFireNotifications()
{
doc::Tileset* tileset = this->tileset();
// Notify that the tileset's changed
static_cast<Doc*>(tileset->sprite()->document())
->notifyTilesetChanged(tileset);
}
void AddTile::addTile(doc::Tileset* tileset, const doc::ImageRef& image)
{
if (m_tileIndex == doc::tile_i_notile)
m_tileIndex = tileset->add(image);
else
tileset->insert(m_tileIndex, image);
tileset->sprite()->incrementVersion();
tileset->incrementVersion();
}
} // namespace cmd
} // namespace app

57
src/app/cmd/add_tile.h Normal file
View File

@ -0,0 +1,57 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_ADD_TILE_H_INCLUDED
#define APP_CMD_ADD_TILE_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_image.h"
#include "app/cmd/with_tileset.h"
#include "doc/image_ref.h"
#include "doc/tile.h"
#include <sstream>
namespace doc {
class Tileset;
}
namespace app {
namespace cmd {
class AddTile : public Cmd
, public WithTileset
, public WithImage {
public:
AddTile(doc::Tileset* tileset, const doc::ImageRef& image);
AddTile(doc::Tileset* tileset, const doc::tile_index ti);
doc::tile_index tileIndex() const { return m_tileIndex; }
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
void onFireNotifications() override;
size_t onMemSize() const override {
return sizeof(*this) + m_size;
}
private:
void addTile(doc::Tileset* tileset,
const doc::ImageRef& image);
size_t m_size;
std::stringstream m_stream;
doc::tile_index m_tileIndex;
doc::ImageRef m_imageRef;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,88 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/add_tileset.h"
#include "doc/sprite.h"
#include "doc/subobjects_io.h"
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
namespace app {
namespace cmd {
using namespace doc;
AddTileset::AddTileset(doc::Sprite* sprite, doc::Tileset* tileset)
: WithSprite(sprite)
, WithTileset(tileset)
, m_size(0)
, m_tilesetIndex(-1)
{
}
AddTileset::AddTileset(doc::Sprite* sprite, const doc::tileset_index tsi)
: WithSprite(sprite)
, WithTileset(sprite->tilesets()->get(tsi))
, m_size(0)
, m_tilesetIndex(tsi)
{
}
void AddTileset::onExecute()
{
Tileset* tileset = this->tileset();
addTileset(tileset);
}
void AddTileset::onUndo()
{
doc::Tileset* tileset = this->tileset();
write_tileset(m_stream, tileset);
m_size = size_t(m_stream.tellp());
doc::Sprite* sprite = this->sprite();
sprite->tilesets()->erase(m_tilesetIndex);
sprite->incrementVersion();
sprite->tilesets()->incrementVersion();
delete tileset;
}
void AddTileset::onRedo()
{
auto sprite = this->sprite();
doc::Tileset* tileset = read_tileset(m_stream, sprite);
addTileset(tileset);
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddTileset::addTileset(doc::Tileset* tileset)
{
auto sprite = this->sprite();
if (m_tilesetIndex == -1)
m_tilesetIndex = sprite->tilesets()->add(tileset);
else
sprite->tilesets()->set(m_tilesetIndex, tileset);
sprite->incrementVersion();
sprite->tilesets()->incrementVersion();
}
} // namespace cmd
} // namespace app

53
src/app/cmd/add_tileset.h Normal file
View File

@ -0,0 +1,53 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_ADD_TILESET_H_INCLUDED
#define APP_CMD_ADD_TILESET_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "app/cmd/with_tileset.h"
#include "doc/tile.h"
#include <sstream>
namespace doc {
class Tileset;
}
namespace app {
namespace cmd {
class AddTileset : public Cmd
, public WithSprite
, public WithTileset {
public:
AddTileset(doc::Sprite* sprite, doc::Tileset* tileset);
AddTileset(doc::Sprite* sprite, const doc::tileset_index tsi);
doc::tileset_index tilesetIndex() const { return m_tilesetIndex; }
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override {
return sizeof(*this) + m_size;
}
private:
void addTileset(doc::Tileset* tileset);
size_t m_size;
std::stringstream m_stream;
doc::tileset_index m_tilesetIndex;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -43,21 +43,22 @@ BackgroundFromLayer::BackgroundFromLayer(Layer* layer)
void BackgroundFromLayer::onExecute()
{
Layer* layer = this->layer();
ASSERT(!layer->isTilemap()); // TODO support background tilemaps
Sprite* sprite = layer->sprite();
auto doc = static_cast<Doc*>(sprite->document());
color_t bgcolor = doc->bgColor();
// Create a temporary image to draw each cel of the new Background
// layer.
ImageRef bg_image(Image::create(sprite->pixelFormat(),
sprite->width(),
sprite->height()));
ImageRef bg_image(Image::create(sprite->spec()));
CelList cels;
layer->getCels(cels);
for (Cel* cel : cels) {
Image* cel_image = cel->image();
ASSERT(cel_image);
ASSERT(cel_image->pixelFormat() != IMAGE_TILEMAP);
clear_image(bg_image.get(), bgcolor);
render::composite_image(

View File

@ -19,11 +19,12 @@
#include "app/cmd/set_cel_data.h"
#include "app/cmd/unlink_cel.h"
#include "app/doc.h"
#include "app/util/create_cel_copy.h"
#include "app/util/cel_ops.h"
#include "doc/cel.h"
#include "doc/layer.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "render/rasterize.h"
#include "render/render.h"
namespace app {
@ -32,8 +33,8 @@ namespace cmd {
using namespace doc;
CopyCel::CopyCel(
LayerImage* srcLayer, frame_t srcFrame,
LayerImage* dstLayer, frame_t dstFrame, bool continuous)
Layer* srcLayer, frame_t srcFrame,
Layer* dstLayer, frame_t dstFrame, bool continuous)
: m_srcLayer(srcLayer)
, m_dstLayer(dstLayer)
, m_srcFrame(srcFrame)
@ -44,8 +45,8 @@ CopyCel::CopyCel(
void CopyCel::onExecute()
{
LayerImage* srcLayer = static_cast<LayerImage*>(m_srcLayer.layer());
LayerImage* dstLayer = static_cast<LayerImage*>(m_dstLayer.layer());
Layer* srcLayer = m_srcLayer.layer();
Layer* dstLayer = m_dstLayer.layer();
ASSERT(srcLayer);
ASSERT(dstLayer);
@ -89,9 +90,17 @@ void CopyCel::onExecute()
!srcCel || !srcImage)
return;
ASSERT(!dstLayer->isTilemap()); // TODO support background tilemaps
if (createLink) {
executeAndAdd(new cmd::SetCelData(dstCel, srcCel->dataRef()));
}
// Rasterize tilemap into the regular image background layer
else if (srcLayer->isTilemap()) {
ImageRef tmp(Image::createCopy(dstImage.get()));
render::rasterize(tmp.get(), srcCel, 0, 0, false);
executeAndAdd(new cmd::CopyRect(dstImage.get(), tmp.get(), gfx::Clip(tmp->bounds())));
}
else {
BlendMode blend = (srcLayer->isBackground() ?
BlendMode::SRC:
@ -114,7 +123,7 @@ void CopyCel::onExecute()
if (createLink)
dstCel = Cel::MakeLink(m_dstFrame, srcCel);
else
dstCel = create_cel_copy(srcCel, dstSprite, dstLayer, m_dstFrame);
dstCel = create_cel_copy(this, srcCel, dstSprite, dstLayer, m_dstFrame);
executeAndAdd(new cmd::AddCel(dstLayer, dstCel));
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -13,10 +14,6 @@
#include "doc/color.h"
#include "doc/frame.h"
namespace doc {
class LayerImage;
}
namespace app {
namespace cmd {
using namespace doc;
@ -24,8 +21,8 @@ namespace cmd {
class CopyCel : public CmdSequence {
public:
CopyCel(
LayerImage* srcLayer, frame_t srcFrame,
LayerImage* dstLayer, frame_t dstFrame, bool continuous);
Layer* srcLayer, frame_t srcFrame,
Layer* dstLayer, frame_t dstFrame, bool continuous);
protected:
void onExecute() override;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -11,8 +11,11 @@
#include "app/cmd/copy_region.h"
#include "app/doc.h"
#include "app/util/buffer_region.h"
#include "doc/image.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
namespace app {
namespace cmd {
@ -42,6 +45,18 @@ CopyRegion::CopyRegion(Image* dst, const Image* src,
save_image_region_in_buffer(m_region, src, dstPos, m_buffer);
}
CopyTileRegion::CopyTileRegion(Image* dst, const Image* src,
const gfx::Region& region,
const gfx::Point& dstPos,
bool alreadyCopied,
const doc::tile_index tileIndex,
const doc::Tileset* tileset)
: CopyRegion(dst, src, region, dstPos, alreadyCopied)
, m_tileIndex(tileIndex)
, m_tilesetId(tileset ? tileset->id(): NullId)
{
}
void CopyRegion::onExecute()
{
if (!m_alreadyCopied)
@ -65,6 +80,26 @@ void CopyRegion::swap()
swap_image_region_with_buffer(m_region, image, m_buffer);
image->incrementVersion();
rehash();
}
void CopyTileRegion::rehash()
{
ASSERT(m_tileIndex != tile_i_notile);
ASSERT(m_tilesetId != NullId);
if (m_tilesetId != NullId) {
auto tileset = get<Tileset>(m_tilesetId);
ASSERT(tileset);
if (tileset) {
tileset->incrementVersion();
tileset->notifyTileContentChange(m_tileIndex);
// Notify thath the tileset changed
static_cast<Doc*>(tileset->sprite()->document())
->notifyTilesetChanged(tileset);
}
}
}
} // namespace cmd

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -12,9 +12,14 @@
#include "app/cmd.h"
#include "app/cmd/with_image.h"
#include "base/buffer.h"
#include "doc/tile.h"
#include "gfx/point.h"
#include "gfx/region.h"
namespace doc {
class Tileset;
}
namespace app {
namespace cmd {
using namespace doc;
@ -41,12 +46,29 @@ namespace cmd {
private:
void swap();
virtual void rehash() { }
bool m_alreadyCopied;
gfx::Region m_region;
base::buffer m_buffer;
};
class CopyTileRegion : public CopyRegion {
public:
CopyTileRegion(Image* dst, const Image* src,
const gfx::Region& region,
const gfx::Point& dstPos,
bool alreadyCopied,
const doc::tile_index tileIndex,
const doc::Tileset* tileset);
private:
void rehash() override;
doc::tile_index m_tileIndex;
doc::ObjectId m_tilesetId;
};
} // namespace cmd
} // namespace app

View File

@ -1,5 +1,6 @@
// Aseprite
// Copyright (C) 2016 David Capello
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -11,6 +12,8 @@
#include "app/cmd/crop_cel.h"
#include "doc/cel.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
namespace app {
@ -47,10 +50,18 @@ void CropCel::cropImage(const gfx::Point& origin,
{
Cel* cel = this->cel();
gfx::Rect localBounds(bounds);
if (cel->layer()->isTilemap()) {
doc::Tileset* tileset = static_cast<LayerTilemap*>(cel->layer())->tileset();
if (tileset) {
doc::Grid grid = tileset->grid();
localBounds = grid.canvasToTile(bounds);
}
}
if (bounds != cel->image()->bounds()) {
ImageRef image(crop_image(cel->image(),
bounds.x, bounds.y,
bounds.w, bounds.h,
localBounds.x, localBounds.y,
localBounds.w, localBounds.h,
cel->image()->maskColor()));
ObjectId id = cel->image()->id();
ObjectVersion ver = cel->image()->version();
@ -59,7 +70,7 @@ void CropCel::cropImage(const gfx::Point& origin,
image->setId(id);
image->setVersion(ver);
image->incrementVersion();
cel->data()->setImage(image);
cel->data()->setImage(image, cel->layer());
cel->data()->incrementVersion();
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of

View File

@ -124,7 +124,7 @@ void FlattenLayers::onExecute()
else {
gfx::Rect bounds(image->bounds());
if (doc::algorithm::shrink_bounds(
image.get(), bounds, image->maskColor())) {
image.get(), image->maskColor(), nullptr, bounds)) {
cel_image.reset(
doc::crop_image(image.get(), bounds, image->maskColor()));
cel = new Cel(frame, cel_image);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -20,11 +21,12 @@
#include "app/cmd/set_cel_frame.h"
#include "app/cmd/unlink_cel.h"
#include "app/doc.h"
#include "app/util/create_cel_copy.h"
#include "app/util/cel_ops.h"
#include "doc/cel.h"
#include "doc/layer.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "render/rasterize.h"
#include "render/render.h"
namespace app {
@ -90,10 +92,18 @@ void MoveCel::onExecute()
!srcCel || !srcImage)
return;
ASSERT(!dstLayer->isTilemap()); // TODO support background tilemaps
if (createLink) {
executeAndAdd(new cmd::SetCelData(dstCel, srcCel->dataRef()));
executeAndAdd(new cmd::UnlinkCel(srcCel));
}
// Rasterize tilemap into the regular image background layer
else if (srcLayer->isTilemap()) {
ImageRef tmp(Image::createCopy(dstImage.get()));
render::rasterize(tmp.get(), srcCel, 0, 0, false);
executeAndAdd(new cmd::CopyRect(dstImage.get(), tmp.get(), gfx::Clip(tmp->bounds())));
}
else {
BlendMode blend = (srcLayer->isBackground() ?
BlendMode::SRC:
@ -119,7 +129,7 @@ void MoveCel::onExecute()
executeAndAdd(new cmd::SetCelFrame(srcCel, m_dstFrame));
}
else {
dstCel = create_cel_copy(srcCel, dstSprite, dstLayer, m_dstFrame);
dstCel = create_cel_copy(this, srcCel, dstSprite, dstLayer, m_dstFrame);
executeAndAdd(new cmd::AddCel(dstLayer, dstCel));
executeAndAdd(new cmd::ClearCel(srcCel));

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
@ -14,6 +15,7 @@
#include "app/cmd/crop_cel.h"
#include "app/cmd/trim_cel.h"
#include "doc/cel.h"
#include "doc/layer_tilemap.h"
namespace app {
namespace cmd {
@ -36,17 +38,37 @@ void PatchCel::onExecute()
{
Cel* cel = this->cel();
const gfx::Rect newBounds =
cel->bounds() | gfx::Rect(m_region.bounds()).offset(m_pos);
if (cel->bounds() != newBounds) {
executeAndAdd(new CropCel(cel, newBounds));
gfx::Rect newBounds;
gfx::Region regionInTiles;
doc::Grid grid;
if (cel->image()->pixelFormat() == IMAGE_TILEMAP) {
newBounds = cel->bounds() | m_region.bounds();
auto tileset = static_cast<LayerTilemap*>(cel->layer())->tileset();
grid = tileset->grid();
grid.origin(m_pos);
regionInTiles = grid.canvasToTile(m_region);
}
else {
newBounds = cel->bounds() | gfx::Rect(m_region.bounds()).offset(m_pos);
}
executeAndAdd(
new CopyRegion(cel->image(),
m_patch,
m_region,
m_pos - cel->position()));
if (cel->bounds() != newBounds)
executeAndAdd(new CropCel(cel, newBounds));
if (cel->image()->pixelFormat() == IMAGE_TILEMAP) {
executeAndAdd(
new CopyRegion(cel->image(),
m_patch,
regionInTiles,
-grid.canvasToTile(cel->position())));
}
else {
executeAndAdd(
new CopyRegion(cel->image(),
m_patch,
m_region,
m_pos - cel->position()));
}
executeAndAdd(new TrimCel(cel));

View File

@ -31,7 +31,7 @@ void RemapColors::onExecute()
{
Sprite* spr = sprite();
if (spr->pixelFormat() == IMAGE_INDEXED) {
spr->remapImages(0, spr->lastFrame(), m_remap);
spr->remapImages(m_remap);
incrementVersions(spr);
}
}
@ -40,7 +40,7 @@ void RemapColors::onUndo()
{
Sprite* spr = this->sprite();
if (spr->pixelFormat() == IMAGE_INDEXED) {
spr->remapImages(0, spr->lastFrame(), m_remap.invert());
spr->remapImages(m_remap.invert());
incrementVersions(spr);
}
}

View File

@ -0,0 +1,62 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/remap_tilemaps.h"
#include "doc/cel.h"
#include "doc/cels_range.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/remap.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
namespace app {
namespace cmd {
using namespace doc;
RemapTilemaps::RemapTilemaps(Tileset* tileset,
const Remap& remap)
: WithTileset(tileset)
, m_remap(remap)
{
}
void RemapTilemaps::onExecute()
{
Tileset* tileset = this->tileset();
Sprite* spr = tileset->sprite();
spr->remapTilemaps(tileset, m_remap);
incrementVersions(tileset);
}
void RemapTilemaps::onUndo()
{
Tileset* tileset = this->tileset();
Sprite* spr = tileset->sprite();
spr->remapTilemaps(tileset, m_remap.invert());
incrementVersions(tileset);
}
void RemapTilemaps::incrementVersions(Tileset* tileset)
{
Sprite* spr = tileset->sprite();
for (const Cel* cel : spr->uniqueCels()) {
if (cel->layer()->isTilemap() &&
static_cast<LayerTilemap*>(cel->layer())->tileset() == tileset) {
cel->image()->incrementVersion();
}
}
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_REMAP_TILEMAPS_H_INCLUDED
#define APP_CMD_REMAP_TILEMAPS_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_tileset.h"
#include "doc/remap.h"
namespace app {
namespace cmd {
using namespace doc;
class RemapTilemaps : public Cmd
, public WithTileset {
public:
RemapTilemaps(Tileset* tileset,
const Remap& remap);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this) + m_remap.getMemSize();
}
private:
void incrementVersions(Tileset* tileset);
Remap m_remap;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,54 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/remap_tileset.h"
#include "doc/cel.h"
#include "doc/cels_range.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/remap.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
namespace app {
namespace cmd {
using namespace doc;
RemapTileset::RemapTileset(Tileset* tileset,
const Remap& remap)
: WithTileset(tileset)
, m_remap(remap)
{
}
void RemapTileset::onExecute()
{
Tileset* tileset = this->tileset();
applyRemap(tileset, m_remap);
}
void RemapTileset::onUndo()
{
Tileset* tileset = this->tileset();
applyRemap(tileset, m_remap.invert());
}
void RemapTileset::applyRemap(Tileset* tileset, const Remap& remap)
{
tileset->remap(remap);
tileset->incrementVersion();
tileset->sprite()->incrementVersion();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_REMAP_TILESET_H_INCLUDED
#define APP_CMD_REMAP_TILESET_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_tileset.h"
#include "doc/remap.h"
namespace app {
namespace cmd {
using namespace doc;
class RemapTileset : public Cmd
, public WithTileset {
public:
RemapTileset(Tileset* tileset,
const Remap& remap);
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override {
return sizeof(*this) + m_remap.getMemSize();
}
private:
void applyRemap(Tileset* tileset, const Remap& remap);
Remap m_remap;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,42 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/remove_tile.h"
#include "doc/cel.h"
#include "doc/layer.h"
namespace app {
namespace cmd {
using namespace doc;
RemoveTile::RemoveTile(Tileset* tileset, const tile_index ti)
: AddTile(tileset, ti)
{
}
void RemoveTile::onExecute()
{
AddTile::onUndo();
}
void RemoveTile::onUndo()
{
AddTile::onRedo();
}
void RemoveTile::onRedo()
{
AddTile::onUndo();
}
} // namespace cmd
} // namespace app

30
src/app/cmd/remove_tile.h Normal file
View File

@ -0,0 +1,30 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_REMOVE_TILE_H_INCLUDED
#define APP_CMD_REMOVE_TILE_H_INCLUDED
#pragma once
#include "app/cmd/add_tile.h"
namespace app {
namespace cmd {
using namespace doc;
class RemoveTile : public AddTile {
public:
RemoveTile(Tileset* tileset, const tile_index ti);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,43 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/remove_tileset.h"
#include "doc/cel.h"
#include "doc/layer.h"
namespace app {
namespace cmd {
using namespace doc;
RemoveTileset::RemoveTileset(Sprite* sprite,
const tileset_index si)
: AddTileset(sprite, si)
{
}
void RemoveTileset::onExecute()
{
AddTileset::onUndo();
}
void RemoveTileset::onUndo()
{
AddTileset::onRedo();
}
void RemoveTileset::onRedo()
{
AddTileset::onUndo();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,31 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_REMOVE_TILESET_H_INCLUDED
#define APP_CMD_REMOVE_TILESET_H_INCLUDED
#pragma once
#include "app/cmd/add_tileset.h"
namespace app {
namespace cmd {
using namespace doc;
class RemoveTileset : public AddTileset {
public:
RemoveTileset(Sprite* sprite,
const tileset_index si);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -17,6 +17,7 @@
#include "doc/image_ref.h"
#include "doc/sprite.h"
#include "doc/subobjects_io.h"
#include "doc/tilesets.h"
namespace app {
namespace cmd {
@ -75,6 +76,16 @@ void ReplaceImage::replaceImage(ObjectId oldId, const ImageRef& newImage)
cel->data()->incrementVersion();
}
if (spr->hasTilesets()) {
for (Tileset* tileset : *spr->tilesets()) {
for (tile_index i=0; i<tileset->size(); ++i) {
ImageRef image = tileset->get(i);
if (image && image->id() == oldId)
tileset->incrementVersion();
}
}
}
spr->replaceImage(oldId, newImage);
}

View File

@ -14,6 +14,7 @@
#include "doc/image.h"
#include "doc/image_io.h"
#include "doc/image_ref.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "doc/subobjects_io.h"
@ -81,7 +82,9 @@ void SetCelData::createCopy()
ASSERT(!m_dataCopy);
m_dataCopy.reset(new CelData(*cel->data()));
m_dataCopy->setImage(ImageRef(Image::createCopy(cel->image())));
m_dataCopy->setImage(
ImageRef(Image::createCopy(cel->image())),
cel->layer());
}
} // namespace cmd

View File

@ -23,6 +23,7 @@
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "doc/tilesets.h"
#include "render/quantization.h"
#include "render/task_delegate.h"
@ -35,16 +36,16 @@ namespace {
class SuperDelegate : public render::TaskDelegate {
public:
SuperDelegate(int ncels, render::TaskDelegate* delegate)
: m_ncels(ncels)
, m_curCel(0)
SuperDelegate(int nimages, render::TaskDelegate* delegate)
: m_nimages(nimages)
, m_curImage(0)
, m_delegate(delegate) {
}
void notifyTaskProgress(double progress) override {
if (m_delegate)
m_delegate->notifyTaskProgress(
(progress + m_curCel) / m_ncels);
(progress + m_curImage) / m_nimages);
}
bool continueTask() override {
@ -54,13 +55,13 @@ public:
return true;
}
void nextCel() {
++m_curCel;
void nextImage() {
++m_curImage;
}
private:
int m_ncels;
int m_curCel;
int m_nimages;
int m_curImage;
TaskDelegate* m_delegate;
};
@ -79,24 +80,53 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
if (sprite->pixelFormat() == newFormat)
return;
SuperDelegate superDel(sprite->uniqueCels().size(), delegate);
const auto rgbMapFor = sprite->rgbMapForSprite();
// Calculate the number of images to convert just to show a proper
// progress bar.
tile_index nimages = 0;
for (Cel* cel : sprite->uniqueCels())
if (!cel->layer()->isTilemap())
++nimages;
if (sprite->hasTilesets()) {
for (Tileset* tileset : *sprite->tilesets())
nimages += tileset->size();
}
SuperDelegate superDel(nimages, delegate);
// Convert cel images
for (Cel* cel : sprite->uniqueCels()) {
ImageRef old_image = cel->imageRef();
ImageRef new_image(
render::convert_pixel_format
(old_image.get(), nullptr, newFormat,
dithering,
sprite->rgbMap(cel->frame(), rgbMapFor, mapAlgorithm),
sprite->palette(cel->frame()),
cel->layer()->isBackground(),
old_image->maskColor(),
toGray,
&superDel));
if (cel->layer()->isTilemap())
continue;
m_seq.add(new cmd::ReplaceImage(sprite, old_image, new_image));
superDel.nextCel();
ImageRef oldImage = cel->imageRef();
convertImage(sprite, dithering,
oldImage,
cel->frame(),
cel->layer()->isBackground(),
mapAlgorithm,
toGray,
&superDel);
superDel.nextImage();
}
// Convert tileset images
if (sprite->hasTilesets()) {
for (Tileset* tileset : *sprite->tilesets()) {
for (tile_index i=0; i<tileset->size(); ++i) {
ImageRef oldImage = tileset->get(i);
if (oldImage) {
convertImage(sprite, dithering,
oldImage,
0, // TODO select a frame or generate other tilesets?
false, // TODO is background? it depends of the layer where this tileset is used
mapAlgorithm,
toGray,
&superDel);
}
superDel.nextImage();
}
}
}
// Set all cels opacity to 100% if we are converting to indexed.
@ -159,5 +189,31 @@ void SetPixelFormat::setFormat(PixelFormat format)
doc->notify_observers<DocEvent&>(&DocObserver::onPixelFormatChanged, ev);
}
void SetPixelFormat::convertImage(doc::Sprite* sprite,
const render::Dithering& dithering,
const doc::ImageRef& oldImage,
const doc::frame_t frame,
const bool isBackground,
const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate)
{
ASSERT(oldImage);
ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP);
ImageRef newImage(
render::convert_pixel_format
(oldImage.get(), nullptr, m_newFormat,
dithering,
sprite->rgbMap(frame, sprite->rgbMapForSprite(), mapAlgorithm),
sprite->palette(frame),
isBackground,
oldImage->maskColor(),
toGray,
delegate));
m_seq.add(new cmd::ReplaceImage(sprite, oldImage, newImage));
}
} // namespace cmd
} // namespace app

View File

@ -12,6 +12,8 @@
#include "app/cmd/with_sprite.h"
#include "app/cmd_sequence.h"
#include "doc/color.h"
#include "doc/frame.h"
#include "doc/image_ref.h"
#include "doc/pixel_format.h"
#include "doc/rgbmap_algorithm.h"
@ -47,6 +49,14 @@ namespace cmd {
private:
void setFormat(doc::PixelFormat format);
void convertImage(doc::Sprite* sprite,
const render::Dithering& dithering,
const doc::ImageRef& oldImage,
const doc::frame_t frame,
const bool isBackground,
const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate);
doc::PixelFormat m_oldFormat;
doc::PixelFormat m_newFormat;

View File

@ -59,7 +59,7 @@ void ShiftMaskedCel::shift(int dx, int dy)
newImage->setId(id);
newImage->setVersion(ver);
newImage->incrementVersion();
cel->data()->setImage(newImage);
cel->data()->setImage(newImage, cel->layer());
}
cel->data()->setBounds(newBounds);
cel->data()->incrementVersion();

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
@ -25,10 +26,11 @@ using namespace doc;
TrimCel::TrimCel(Cel* cel)
{
gfx::Rect newBounds;
if (algorithm::shrink_bounds(cel->image(), newBounds,
cel->image()->maskColor())) {
if (algorithm::shrink_bounds(cel->image(),
cel->image()->maskColor(),
cel->layer(), newBounds)) {
newBounds.offset(cel->position());
if (cel->bounds() != newBounds) {
if (cel->imageBounds() != newBounds) {
add(new cmd::CropCel(cel, newBounds));
}
}

View File

@ -12,6 +12,7 @@
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/sprite.h"
namespace app {
@ -36,7 +37,7 @@ void UnlinkCel::onExecute()
ImageRef imgCopy(Image::createCopy(oldCelData->image()));
CelDataRef celDataCopy(new CelData(*oldCelData));
celDataCopy->setImage(imgCopy);
celDataCopy->setImage(imgCopy, cel->layer());
celDataCopy->setUserData(oldCelData->userData());
if (m_newImageId) {

View File

@ -0,0 +1,31 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/with_tileset.h"
#include "doc/tileset.h"
namespace app {
namespace cmd {
using namespace doc;
WithTileset::WithTileset(Tileset* tileset)
: m_tilesetId(tileset->id())
{
}
Tileset* WithTileset::tileset()
{
return get<Tileset>(m_tilesetId);
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,32 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_WITH_TILESET_H_INCLUDED
#define APP_CMD_WITH_TILESET_H_INCLUDED
#pragma once
#include "doc/object_id.h"
namespace doc {
class Tileset;
}
namespace app {
namespace cmd {
class WithTileset {
public:
WithTileset(doc::Tileset* tileset);
doc::Tileset* tileset();
private:
doc::ObjectId m_tilesetId;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -21,16 +21,16 @@ namespace app {
void add(Cmd* cmd);
// Helper to create a CmdSequence in the same onExecute() member
// function.
void executeAndAdd(Cmd* cmd);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override;
// Helper to create a CmdSequence in the same onExecute() member
// function.
void executeAndAdd(Cmd* cmd);
private:
std::vector<Cmd*> m_cmds;
};

View File

@ -18,6 +18,7 @@
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/tile.h"
#include "gfx/hsl.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
@ -83,7 +84,7 @@ Color Color::fromGray(int g, int a)
// static
Color Color::fromIndex(int index)
{
ASSERT(index >= 0);
ASSERT(index >= 0 || index == tile_i_notile);
Color color(Color::IndexType);
color.m_value.index = index;
@ -114,6 +115,7 @@ Color Color::fromImage(PixelFormat pixelFormat, color_t c)
break;
case IMAGE_INDEXED:
case IMAGE_TILEMAP:
color = Color::fromIndex(c);
break;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -17,11 +17,15 @@
#include "app/util/wrap_point.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "gfx/point.h"
#include "render/get_sprite_pixel.h"
#define PICKER_TRACE(...) // TRACE
namespace app {
namespace {
@ -45,14 +49,48 @@ bool get_cel_pixel(const Cel* cel,
if (!celBounds.contains(pos))
return false;
pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w;
pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h;
const gfx::Point ipos(pos);
if (!image->bounds().contains(ipos))
return false;
// For tilemaps
if (image->pixelFormat() == IMAGE_TILEMAP) {
ASSERT(cel->layer()->isTilemap());
output = get_pixel(image, ipos.x, ipos.y);
return true;
auto layerTilemap = static_cast<doc::LayerTilemap*>(cel->layer());
doc::Grid grid = layerTilemap->tileset()->grid();
grid.origin(grid.origin() + cel->position());
gfx::Point tilePos = grid.canvasToTile(gfx::Point(pos));
PICKER_TRACE("PICKER: tilePos=(%d %d)\n", tilePos.x,tilePos.y);
if (!image->bounds().contains(tilePos))
return false;
const doc::tile_index ti =
get_pixel(image, tilePos.x, tilePos.y);
PICKER_TRACE("PICKER: tile index=%d\n", ti);
doc::ImageRef tile = layerTilemap->tileset()->get(ti);
if (!tile)
return false;
const gfx::Point ipos =
gfx::Point(pos) - grid.tileToCanvas(tilePos);
PICKER_TRACE("PICKER: ipos=%d %d\n", ipos.x, ipos.y);
output = get_pixel(tile.get(), ipos.x, ipos.y);
PICKER_TRACE("PICKER: output=%d\n", output);
return true;
}
// Regular images
else {
pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w;
pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h;
const gfx::Point ipos(pos);
if (!image->bounds().contains(ipos))
return false;
output = get_pixel(image, ipos.x, ipos.y);
return true;
}
}
}
@ -88,31 +126,54 @@ void ColorPicker::pickColor(const Site& site,
// Pick from the composed image
case FromComposition: {
m_color = app::Color::fromImage(
sprite->pixelFormat(),
render::get_sprite_pixel(sprite, pos.x, pos.y,
site.frame(), proj,
Preferences::instance().experimental.newBlend()));
doc::CelList cels;
sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
sprite->allVisibleLayers(), cels);
if (!cels.empty())
m_layer = cels.front()->layer();
if (site.tilemapMode() == TilemapMode::Tiles) {
if (!cels.empty()) {
const gfx::Point tilePos = site.grid().canvasToTile(gfx::Point(pos));
if (cels.front()->image()->bounds().contains(tilePos)) {
m_color = app::Color::fromIndex(
doc::get_pixel(cels.front()->image(), tilePos.x, tilePos.y));
}
}
}
else if (site.tilemapMode() == TilemapMode::Pixels) {
m_color = app::Color::fromImage(
sprite->pixelFormat(),
render::get_sprite_pixel(sprite, pos.x, pos.y,
site.frame(), proj,
Preferences::instance().experimental.newBlend()));
}
break;
}
// Pick from the current layer
case FromActiveLayer: {
const Cel* cel = site.cel();
if (cel) {
if (!cel)
return;
if (site.tilemapMode() == TilemapMode::Tiles) {
const gfx::Point tilePos = site.grid().canvasToTile(gfx::Point(pos));
if (cel->image()->bounds().contains(tilePos)) {
m_color = app::Color::fromIndex(
doc::get_pixel(cel->image(), tilePos.x, tilePos.y));
}
}
else if (site.tilemapMode() == TilemapMode::Pixels) {
doc::color_t imageColor;
if (!get_cel_pixel(cel, pos.x, pos.y,
site.frame(), imageColor))
return;
const doc::Image* image = cel->image();
switch (image->pixelFormat()) {
doc::PixelFormat pixelFormat =
(cel->layer()->isTilemap() ? sprite->pixelFormat():
cel->image()->pixelFormat());
switch (pixelFormat) {
case IMAGE_RGB:
m_alpha = doc::rgba_geta(imageColor);
break;
@ -121,7 +182,7 @@ void ColorPicker::pickColor(const Site& site,
break;
}
m_color = app::Color::fromImage(image->pixelFormat(), imageColor);
m_color = app::Color::fromImage(pixelFormat, imageColor);
m_layer = const_cast<Layer*>(site.layer());
}
break;

View File

@ -100,6 +100,9 @@ doc::color_t color_utils::color_for_image(const app::Color& color, PixelFormat f
case IMAGE_INDEXED:
c = color.getIndex();
break;
case IMAGE_TILEMAP:
c = color.getIndex(); // TODO Add app::Color::getTile() ?
break;
}
return c;
@ -122,6 +125,9 @@ doc::color_t color_utils::color_for_image_without_alpha(const app::Color& color,
case IMAGE_INDEXED:
c = color.getIndex();
break;
case IMAGE_TILEMAP:
c = color.getIndex(); // TODO Add app::Color::getTile() ?
break;
}
return c;
@ -165,6 +171,9 @@ doc::color_t color_utils::color_for_target_mask(const app::Color& color, const C
c = get_current_palette()->findBestfit(r, g, b, a, mask);
}
break;
case IMAGE_TILEMAP:
c = color.getIndex(); // TODO Add app::Color::getTile() ?
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -43,7 +43,10 @@ bool BackgroundFromLayerCommand::onEnabled(Context* context)
// Doesn't have a background layer
!context->checkFlags(ContextFlags::HasBackgroundLayer) &&
// Isn't a reference layer
!context->checkFlags(ContextFlags::ActiveLayerIsReference);
!context->checkFlags(ContextFlags::ActiveLayerIsReference) &&
// Isn't a tilemap layer
// TODO support background tilemaps
!context->checkFlags(ContextFlags::ActiveLayerIsTilemap);
}
void BackgroundFromLayerCommand::onExecute(Context* context)

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -61,11 +62,9 @@ void ClearCelCommand::onExecute(Context* context)
continue;
}
LayerImage* layerImage = static_cast<LayerImage*>(layer);
for (frame_t frame : site->selectedFrames().reversed()) {
if (layerImage->cel(frame))
document->getApi(tx).clearCel(layerImage, frame);
if (Cel* cel = layer->cel(frame))
document->getApi(tx).clearCel(cel);
}
}
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -8,22 +9,18 @@
#include "config.h"
#endif
#include "app/app.h"
#include "app/color.h"
#include "app/color_picker.h"
#include "app/commands/cmd_eyedropper.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/modules/editors.h"
#include "app/context.h"
#include "app/pref/preferences.h"
#include "app/site.h"
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/ui/color_bar.h"
#include "app/ui/editor/editor.h"
#include "app/ui_context.h"
#include "doc/image.h"
#include "doc/sprite.h"
#include "ui/manager.h"
#include "ui/system.h"
@ -65,6 +62,11 @@ void EyedropperCommand::pickSample(const Site& site,
app::Color picked = picker.color();
if (site.tilemapMode() == TilemapMode::Tiles) {
color = app::Color::fromIndex(picked.getIndex());
return;
}
switch (channel) {
case app::gen::EyedropperChannel::COLOR_ALPHA:
color = picked;

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of

View File

@ -75,8 +75,8 @@ void MaskContentCommand::onExecute(Context* context)
Mask newMask;
gfx::Rect imgBounds = cel->image()->bounds();
if (algorithm::shrink_bounds(cel->image(), imgBounds, color)) {
newMask.replace(imgBounds.offset(cel->bounds().origin()));
if (algorithm::shrink_cel_bounds(cel, color, imgBounds)) {
newMask.replace(imgBounds);
}
else {
newMask.replace(cel->bounds());

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -26,7 +26,7 @@
#include "doc/layer.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "render/render.h"
#include "render/rasterize.h"
#include "ui/ui.h"
namespace app {
@ -57,11 +57,15 @@ bool MergeDownLayerCommand::onEnabled(Context* context)
return false;
const Layer* src_layer = reader.layer();
if (!src_layer || !src_layer->isImage())
if (!src_layer ||
!src_layer->isImage() ||
src_layer->isTilemap()) // TODO Add support to merge tilemaps (and groups!)
return false;
const Layer* dst_layer = src_layer->getPrevious();
if (!dst_layer || !dst_layer->isImage())
if (!dst_layer ||
!dst_layer->isImage() ||
dst_layer->isTilemap()) // TODO Add support to merge tilemaps
return false;
return true;
@ -72,10 +76,11 @@ void MergeDownLayerCommand::onExecute(Context* context)
ContextWriter writer(context);
Doc* document(writer.document());
Sprite* sprite(writer.sprite());
Tx tx(writer.context(), "Merge Down Layer", ModifyDocument);
LayerImage* src_layer = static_cast<LayerImage*>(writer.layer());
Layer* dst_layer = src_layer->getPrevious();
Tx tx(writer.context(), friendlyName(), ModifyDocument);
for (frame_t frpos = 0; frpos<sprite->totalFrames(); ++frpos) {
// Get frames
Cel* src_cel = src_layer->cel(frpos);
@ -103,7 +108,8 @@ void MergeDownLayerCommand::onExecute(Context* context)
// Copy this cel to the destination layer...
// Creating a copy of the image
dst_image.reset(Image::createCopy(src_image));
dst_image.reset(
render::rasterize_with_cel_bounds(src_cel));
// Creating a copy of the cell
dst_cel = new Cel(frpos, dst_image);
@ -133,14 +139,10 @@ void MergeDownLayerCommand::onExecute(Context* context)
bounds.y-dst_cel->y(),
bounds.w, bounds.h, bgcolor));
// Merge src_image in new_image
render::composite_image(
new_image.get(), src_image,
sprite->palette(src_cel->frame()),
src_cel->x()-bounds.x,
src_cel->y()-bounds.y,
opacity,
src_layer->blendMode());
// Draw src_cel on new_image
render::rasterize(
new_image.get(), src_cel,
-bounds.x, -bounds.y, false);
// First unlink the dst_cel
if (dst_cel->links())

View File

@ -10,6 +10,7 @@
#endif
#include "app/app.h"
#include "app/cmd/add_tileset.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/move_layer.h"
#include "app/cmd/trim_cel.h"
@ -27,11 +28,13 @@
#include "app/tx.h"
#include "app/ui/main_window.h"
#include "app/ui/status_bar.h"
#include "app/ui/tileset_selector.h"
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/util/new_image_from_mask.h"
#include "app/util/range_utils.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "fmt/format.h"
@ -56,6 +59,7 @@ struct NewLayerParams : public NewParams {
Param<std::string> name { this, std::string(), "name" };
Param<bool> group { this, false, "group" };
Param<bool> reference { this, false, "reference" };
Param<bool> tilemap { this, false, "tilemap" };
Param<bool> ask { this, false, "ask" };
Param<bool> fromFile { this, false, { "fromFile", "from-file" } };
Param<bool> fromClipboard { this, false, "fromClipboard" };
@ -67,7 +71,7 @@ struct NewLayerParams : public NewParams {
class NewLayerCommand : public CommandWithNewParams<NewLayerParams> {
public:
enum class Type { Layer, Group, ReferenceLayer };
enum class Type { Layer, Group, ReferenceLayer, TilemapLayer };
enum class Place { AfterActiveLayer, BeforeActiveLayer, Top };
NewLayerCommand();
@ -102,6 +106,10 @@ void NewLayerCommand::onLoadParams(const Params& commandParams)
m_type = Type::Group;
else if (params().reference())
m_type = Type::ReferenceLayer;
else if (params().tilemap())
m_type = Type::TilemapLayer;
else
m_type = Type::Layer;
m_place = Place::AfterActiveLayer;
if (params().top())
@ -142,10 +150,10 @@ private:
void NewLayerCommand::onExecute(Context* context)
{
ContextWriter writer(context);
ContextReader reader(context);
Site site = context->activeSite();
Doc* document(writer.document());
Sprite* sprite(writer.sprite());
Doc* document(reader.document());
Sprite* sprite(reader.sprite());
std::string name;
Doc* pasteDoc = nullptr;
@ -183,21 +191,40 @@ void NewLayerCommand::onExecute(Context* context)
return;
}
// Information about the tileset to be used for new tilemaps
TilesetSelector::Info tilesetInfo;
tilesetInfo.newTileset = true;
tilesetInfo.grid = context->activeSite().grid();
#ifdef ENABLE_UI
// If params specify to ask the user about the name...
if (params().ask() && context->isUIAvailable()) {
// We open the window to ask the name
app::gen::NewLayer window;
TilesetSelector* tilesetSelector = nullptr;
window.name()->setText(name.c_str());
window.name()->setMinSize(gfx::Size(128, 0));
// Tileset selector for new tilemaps
const bool isTilemap = (m_type == Type::TilemapLayer);
window.tilesetLabel()->setVisible(isTilemap);
window.tilesetOptions()->setVisible(isTilemap);
if (isTilemap) {
tilesetSelector = new TilesetSelector(sprite, tilesetInfo);
window.tilesetOptions()->addChild(tilesetSelector);
}
window.openWindowInForeground();
if (window.closer() != window.ok())
return;
name = window.name()->text();
if (tilesetSelector)
tilesetInfo = tilesetSelector->getInfo();
}
#endif
ContextWriter writer(reader);
LayerGroup* parent = sprite->root();
Layer* activeLayer = writer.layer();
SelectedLayers selLayers = site.selectedLayers();
@ -236,6 +263,24 @@ void NewLayerCommand::onExecute(Context* context)
layer->setReference(true);
afterBackground = true;
break;
case Type::TilemapLayer: {
tileset_index tsi;
if (tilesetInfo.newTileset) {
auto tileset = new Tileset(sprite, tilesetInfo.grid, 0);
auto addTileset = new cmd::AddTileset(sprite, tileset);
tx(addTileset);
tsi = addTileset->tilesetIndex();
}
else {
tsi = tilesetInfo.tsi;
}
layer = new LayerTilemap(sprite, tsi);
layer->setName(name);
api.addLayer(parent, layer, parent->lastLayer());
break;
}
}
ASSERT(layer);
@ -443,6 +488,8 @@ std::string NewLayerCommand::onGetFriendlyName() const
text = fmt::format(Strings::commands_NewLayer_ViaCopy(), text);
if (params().viaCut())
text = fmt::format(Strings::commands_NewLayer_ViaCut(), text);
if (params().ask())
text = fmt::format(Strings::commands_NewLayer_WithDialog(), text);
return text;
}
@ -490,6 +537,7 @@ std::string NewLayerCommand::layerPrefix() const
case Type::Layer: return Strings::commands_NewLayer_Layer();
case Type::Group: return Strings::commands_NewLayer_Group();
case Type::ReferenceLayer: return Strings::commands_NewLayer_ReferenceLayer();
case Type::TilemapLayer: return Strings::commands_NewLayer_TilemapLayer();
}
return "Unknown";
}

View File

@ -81,7 +81,7 @@ void SelectTileCommand::onExecute(Context* ctx)
mask->copyFrom(doc->mask());
{
gfx::Rect gridBounds = doc->sprite()->gridBounds();
gfx::Rect gridBounds = writer.site()->gridBounds();
gfx::Point pos = current_editor->screenToEditor(ui::get_mouse_position());
pos = snap_to_grid(gridBounds, pos, PreferSnapTo::BoxOrigin);
gridBounds.setOrigin(pos);

View File

@ -0,0 +1,45 @@
// Aseprite
// Copyright (c) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/commands/command.h"
#include "app/ui/color_bar.h"
namespace app {
using namespace gfx;
class ToggleTilesModeCommand : public Command {
public:
ToggleTilesModeCommand()
: Command(CommandId::ToggleTilesMode(), CmdUIOnlyFlag) {
}
protected:
bool onChecked(Context* context) override {
auto colorBar = ColorBar::instance();
return (colorBar->tilemapMode() == TilemapMode::Tiles);
}
void onExecute(Context* context) override {
auto colorBar = ColorBar::instance();
colorBar->setTilemapMode(
colorBar->tilemapMode() == TilemapMode::Pixels ?
TilemapMode::Tiles:
TilemapMode::Pixels);
}
};
Command* CommandFactory::createToggleTilesModeCommand()
{
return new ToggleTilesModeCommand;
}
} // namespace app

View File

@ -16,6 +16,7 @@ FOR_EACH_COMMAND(ColorCurve)
FOR_EACH_COMMAND(ColorQuantization)
FOR_EACH_COMMAND(ConvolutionMatrix)
FOR_EACH_COMMAND(CopyColors)
FOR_EACH_COMMAND(CopyTiles)
FOR_EACH_COMMAND(CropSprite)
FOR_EACH_COMMAND(Despeckle)
FOR_EACH_COMMAND(ExportSpriteSheet)
@ -27,6 +28,7 @@ FOR_EACH_COMMAND(LayerFromBackground)
FOR_EACH_COMMAND(LoadPalette)
FOR_EACH_COMMAND(MergeDownLayer)
FOR_EACH_COMMAND(MoveColors)
FOR_EACH_COMMAND(MoveTiles)
FOR_EACH_COMMAND(NewFile)
FOR_EACH_COMMAND(NewFrame)
FOR_EACH_COMMAND(NewLayer)
@ -156,8 +158,10 @@ FOR_EACH_COMMAND(SwapCheckerboardColors)
FOR_EACH_COMMAND(SwitchColors)
FOR_EACH_COMMAND(SymmetryMode)
FOR_EACH_COMMAND(TiledMode)
FOR_EACH_COMMAND(TilesetMode)
FOR_EACH_COMMAND(Timeline)
FOR_EACH_COMMAND(TogglePreview)
FOR_EACH_COMMAND(ToggleTilesMode)
FOR_EACH_COMMAND(ToggleTimelineThumbnails)
FOR_EACH_COMMAND(UndoHistory)
FOR_EACH_COMMAND(UnlinkCel)

View File

@ -27,6 +27,7 @@
#include "app/ui/palette_view.h"
#include "app/ui/timeline/timeline.h"
#include "app/ui_context.h"
#include "app/util/cel_ops.h"
#include "app/util/range_utils.h"
#include "doc/algorithm/shrink_bounds.h"
#include "doc/cel.h"
@ -222,7 +223,24 @@ void FilterManagerImpl::apply()
gfx::Rect output;
if (algorithm::shrink_bounds2(m_src.get(), m_dst.get(),
m_bounds, output)) {
if (m_cel->layer()->isBackground()) {
if (m_cel->layer()->isTilemap()) {
modify_tilemap_cel_region(
*m_tx,
m_cel,
gfx::Region(output),
m_site.tilesetMode(),
[this](const doc::ImageRef& origTile,
const gfx::Rect& tileBoundsInCanvas) -> doc::ImageRef {
return ImageRef(
crop_image(m_dst.get(),
tileBoundsInCanvas.x,
tileBoundsInCanvas.y,
tileBoundsInCanvas.w,
tileBoundsInCanvas.h,
m_dst->maskColor()));
});
}
else if (m_cel->layer()->isBackground()) {
(*m_tx)(
new cmd::CopyRegion(
m_cel->image(),
@ -459,10 +477,7 @@ void FilterManagerImpl::init(Cel* cel)
throw InvalidAreaException();
m_cel = cel;
m_src.reset(
crop_image(
cel->image(),
gfx::Rect(m_site.sprite()->bounds()).offset(-cel->position()), 0));
m_src = crop_cel_image(cel, 0);
m_dst.reset(Image::createCopy(m_src.get()));
m_row = -1;

View File

@ -0,0 +1,88 @@
// Aseprite
// Copyright (c) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/commands/new_params.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/tx.h"
#include "app/util/cel_ops.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/remap.h"
#include "doc/tileset.h"
namespace app {
using namespace ui;
struct MoveTilesParams : public NewParams {
Param<int> before { this, 0, "before" };
};
class MoveTilesCommand : public CommandWithNewParams<MoveTilesParams> {
public:
MoveTilesCommand(const bool copy)
: CommandWithNewParams<MoveTilesParams>(
(copy ? CommandId::CopyTiles():
CommandId::MoveTiles()), CmdRecordableFlag)
, m_copy(copy) { }
protected:
bool onEnabled(Context* ctx) override {
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveLayer |
ContextFlags::ActiveLayerIsTilemap);
}
void onExecute(Context* ctx) override {
ContextWriter writer(ctx);
doc::Layer* layer = writer.layer();
if (!layer || !layer->isTilemap())
return;
doc::Tileset* tileset = static_cast<LayerTilemap*>(layer)->tileset();
ASSERT(tileset);
if (!tileset)
return;
PalettePicks picks = writer.site()->selectedTiles();
if (picks.picks() == 0)
return;
Tx tx(writer.context(), onGetFriendlyName(), ModifyDocument);
const int beforeIndex = params().before();
int currentEntry = picks.firstPick();
if (m_copy)
copy_tiles_in_tileset(tx, tileset, picks, currentEntry, beforeIndex);
else
move_tiles_in_tileset(tx, tileset, picks, currentEntry, beforeIndex);
tx.commit();
ctx->setSelectedTiles(picks);
}
private:
bool m_copy;
};
Command* CommandFactory::createMoveTilesCommand()
{
return new MoveTilesCommand(false);
}
Command* CommandFactory::createCopyTilesCommand()
{
return new MoveTilesCommand(true);
}
} // namespace app

View File

@ -0,0 +1,67 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/commands/command.h"
#include "app/commands/params.h"
#include "app/i18n/strings.h"
#include "app/ui/color_bar.h"
#include "fmt/format.h"
namespace app {
using namespace gfx;
class TilesetModeCommand : public Command {
public:
TilesetModeCommand()
: Command(CommandId::TilesetMode(), CmdUIOnlyFlag) {
m_mode = TilesetMode::Auto;
}
protected:
void onLoadParams(const Params& params) override {
std::string mode = params.get("mode");
if (mode == "manual") m_mode = TilesetMode::Manual;
else if (mode == "stack") m_mode = TilesetMode::Stack;
else m_mode = TilesetMode::Auto;
}
bool onChecked(Context* context) override {
auto colorBar = ColorBar::instance();
return (colorBar->tilesetMode() == m_mode);
}
void onExecute(Context* context) override {
auto colorBar = ColorBar::instance();
colorBar->setTilesetMode(m_mode);
}
std::string onGetFriendlyName() const override {
std::string mode;
switch (m_mode) {
case TilesetMode::Manual: mode = Strings::commands_TilesetMode_Manual(); break;
case TilesetMode::Auto: mode = Strings::commands_TilesetMode_Auto(); break;
case TilesetMode::Stack: mode = Strings::commands_TilesetMode_Stack(); break;
}
return fmt::format(getBaseFriendlyName(), mode);
}
private:
TilesetMode m_mode;
};
Command* CommandFactory::createTilesetModeCommand()
{
return new TilesetModeCommand;
}
} // namespace app

View File

@ -44,7 +44,7 @@ public:
m_view.attachToView(&m_textbox);
m_button.setMinSize(gfx::Size(60*ui::guiscale(), 0));
Grid* grid = new Grid(1, false);
ui::Grid* grid = new ui::Grid(1, false);
grid->addChildInCell(&m_view, 1, 1, HORIZONTAL | VERTICAL);
grid->addChildInCell(&m_button, 1, 1, CENTER);
addChild(grid);

View File

@ -23,6 +23,12 @@
#include "doc/layer.h"
#include "ui/system.h"
#ifdef _DEBUG
#include "doc/layer_tilemap.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
#endif
#include <algorithm>
#include <stdexcept>
@ -104,6 +110,11 @@ void Context::setSelectedColors(const doc::PalettePicks& picks)
onSetSelectedColors(picks);
}
void Context::setSelectedTiles(const doc::PalettePicks& picks)
{
onSetSelectedTiles(picks);
}
bool Context::hasModifiedDocuments() const
{
for (auto doc : documents())
@ -175,6 +186,20 @@ void Context::executeCommand(Command* command, const Params& params)
// TODO move this code to another place (e.g. a Workplace/Tabs widget)
if (isUIAvailable())
app_rebuild_documents_tabs();
#ifdef _DEBUG // Special checks for debugging purposes
{
Site site = activeSite();
// Check that all tileset hash tables are valid
if (site.sprite() &&
site.sprite()->hasTilesets()) {
for (Tileset* tileset : *site.sprite()->tilesets()) {
if (tileset)
tileset->assertValidHashTable();
}
}
}
#endif
}
catch (base::Exception& e) {
LOG(ERROR, "CTXT: Exception caught executing %s command\n%s\n",
@ -256,6 +281,12 @@ void Context::onSetSelectedColors(const doc::PalettePicks& picks)
activeSiteHandler()->setSelectedColorsInDoc(m_lastSelectedDoc, picks);
}
void Context::onSetSelectedTiles(const doc::PalettePicks& picks)
{
if (m_lastSelectedDoc)
activeSiteHandler()->setSelectedTilesInDoc(m_lastSelectedDoc, picks);
}
ActiveSiteHandler* Context::activeSiteHandler() const
{
if (!m_activeSiteHandler)

View File

@ -91,6 +91,7 @@ namespace app {
void setActiveFrame(doc::frame_t frame);
void setRange(const DocRange& range);
void setSelectedColors(const doc::PalettePicks& picks);
void setSelectedTiles(const doc::PalettePicks& picks);
bool hasModifiedDocuments() const;
void notifyActiveSiteChanged();
@ -115,6 +116,7 @@ namespace app {
virtual void onSetActiveFrame(const doc::frame_t frame);
virtual void onSetRange(const DocRange& range);
virtual void onSetSelectedColors(const doc::PalettePicks& picks);
virtual void onSetSelectedTiles(const doc::PalettePicks& picks);
virtual void onCloseDocument(Doc* doc);
Doc* lastSelectedDoc() { return m_lastSelectedDoc; }

View File

@ -99,6 +99,9 @@ void ContextFlags::updateFlagsFromSite(const Site& site)
if (layer->isReference())
m_flags |= ActiveLayerIsReference;
if (layer->isTilemap())
m_flags |= ActiveLayerIsTilemap;
if (layer->isImage()) {
m_flags |= ActiveLayerIsImage;
@ -113,6 +116,9 @@ void ContextFlags::updateFlagsFromSite(const Site& site)
if (site.selectedColors().picks() > 0)
m_flags |= HasSelectedColors;
if (site.selectedTiles().picks() > 0)
m_flags |= HasSelectedTiles;
}
} // namespace app

View File

@ -33,7 +33,9 @@ namespace app {
ActiveLayerIsVisible = 1 << 11,
ActiveLayerIsEditable = 1 << 12,
ActiveLayerIsReference = 1 << 13,
HasSelectedColors = 1 << 14,
ActiveLayerIsTilemap = 1 << 14,
HasSelectedColors = 1 << 15,
HasSelectedTiles = 1 << 16,
};
ContextFlags();

View File

@ -198,6 +198,7 @@ bool BackupObserver::saveDocData(Doc* doc)
diff.frameDuration ? "frameDuration": "",
diff.tags ? "tags": "",
diff.palettes ? "palettes": "",
diff.tilesets ? "tilesets": "",
diff.layers ? "layers": "",
diff.cels ? "cels": "",
diff.images ? "images": "",

View File

@ -0,0 +1,15 @@
// Aseprite
// Copyright (c) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CRASH_DOC_FORMAT_H_INCLUDED
#define APP_CRASH_DOC_FORMAT_H_INCLUDED
#pragma once
#define DOC_FORMAT_VERSION_0 0 // Old version
#define DOC_FORMAT_VERSION_1 1 // New version with tilesets
#define DOC_FORMAT_VERSION_LAST 1
#endif

View File

@ -12,6 +12,7 @@
#include "app/crash/read_document.h"
#include "app/console.h"
#include "app/crash/doc_format.h"
#include "app/crash/internals.h"
#include "app/doc.h"
#include "base/clamp.h"
@ -28,6 +29,7 @@
#include "doc/frame.h"
#include "doc/image_io.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/palette.h"
#include "doc/palette_io.h"
#include "doc/slice.h"
@ -37,6 +39,9 @@
#include "doc/subobjects_io.h"
#include "doc/tag.h"
#include "doc/tag_io.h"
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
#include "doc/user_data_io.h"
#include "fixmath/fixmath.h"
@ -56,7 +61,8 @@ class Reader : public SubObjectsIO {
public:
Reader(const std::string& dir,
base::task_token* t)
: m_sprite(nullptr)
: m_docFormatVer(DOC_FORMAT_VERSION_0)
, m_sprite(nullptr)
, m_dir(dir)
, m_docId(0)
, m_docVersions(nullptr)
@ -178,6 +184,10 @@ private:
Doc* readDocument(std::ifstream& s) {
ObjectId sprId = read32(s);
std::string filename = read_string(s);
m_docFormatVer = read16(s);
if (s.eof()) m_docFormatVer = DOC_FORMAT_VERSION_0;
TRACE("RECO: internal format version=%d\n", m_docFormatVer);
// Load DocumentInfo only
if (m_loadInfo) {
@ -199,6 +209,7 @@ private:
}
Sprite* readSprite(std::ifstream& s) {
// Header
ColorMode mode = (ColorMode)read8(s);
int w = read16(s);
int h = read16(s);
@ -242,6 +253,19 @@ private:
Console().printf("Invalid number of frames #%d\n", nframes);
}
// IDs of all tilesets
if (m_docFormatVer >= DOC_FORMAT_VERSION_1) {
int ntilesets = read32(s);
if (ntilesets > 0 && ntilesets < 0xffffff) {
for (int i=0; i<ntilesets; ++i) {
ObjectId tilesetId = read32(s);
Tileset* tileset = loadObject<Tileset*>("tset", tilesetId, &Reader::readTileset);
if (tileset)
spr->tilesets()->add(tileset);
}
}
}
// Read layers
int nlayers = read32(s);
if (nlayers >= 1 && nlayers < 0xfffff) {
@ -395,14 +419,27 @@ private:
LayerFlags flags = (LayerFlags)read32(s);
ObjectType type = (ObjectType)read16(s);
ASSERT(type == ObjectType::LayerImage ||
type == ObjectType::LayerGroup);
type == ObjectType::LayerGroup ||
type == ObjectType::LayerTilemap);
std::string name = read_string(s);
std::unique_ptr<Layer> lay;
switch (type) {
case ObjectType::LayerImage: {
lay.reset(new LayerImage(m_sprite));
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
switch (type) {
case ObjectType::LayerImage:
lay.reset(new LayerImage(m_sprite));
break;
case ObjectType::LayerTilemap: {
tileset_index tilesetIndex = read32(s);
lay.reset(new LayerTilemap(m_sprite, tilesetIndex));
break;
}
}
lay->setName(name);
lay->setFlags(flags);
@ -460,6 +497,10 @@ private:
return read_palette(s);
}
Tileset* readTileset(std::ifstream& s) {
return read_tileset(s, m_sprite, false);
}
Tag* readTag(std::ifstream& s) {
return read_tag(s, false);
}
@ -498,6 +539,7 @@ private:
return false;
}
int m_docFormatVer;
Sprite* m_sprite; // Used to pass the sprite in LayerImage() ctor
std::string m_dir;
ObjectVersion m_docId;

View File

@ -11,6 +11,7 @@
#include "app/crash/write_document.h"
#include "app/crash/doc_format.h"
#include "app/crash/internals.h"
#include "app/doc.h"
#include "base/convert_to.h"
@ -26,6 +27,7 @@
#include "doc/frame.h"
#include "doc/image_io.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/palette.h"
#include "doc/palette_io.h"
#include "doc/slice.h"
@ -34,6 +36,9 @@
#include "doc/string_io.h"
#include "doc/tag.h"
#include "doc/tag_io.h"
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
#include "doc/user_data_io.h"
#include "fixmath/fixmath.h"
@ -72,6 +77,12 @@ public:
if (!saveObject("pal", pal, &Writer::writePalette))
return false;
if (spr->hasTilesets()) {
for (Tileset* tset : *spr->tilesets())
if (!saveObject("tset", tset, &Writer::writeTileset))
return false;
}
for (Tag* frtag : spr->tags())
if (!saveObject("frtag", frtag, &Writer::writeFrameTag))
return false;
@ -135,20 +146,29 @@ private:
bool writeDocumentFile(std::ofstream& s, Doc* doc) {
write32(s, doc->sprite()->id());
write_string(s, doc->filename());
write16(s, DOC_FORMAT_VERSION_LAST);
return true;
}
bool writeSprite(std::ofstream& s, Sprite* spr) {
// Header
write8(s, int(spr->colorMode()));
write16(s, spr->width());
write16(s, spr->height());
write32(s, spr->transparentColor());
write32(s, spr->totalFrames());
// Frame durations
write32(s, spr->totalFrames());
for (frame_t fr = 0; fr < spr->totalFrames(); ++fr)
write32(s, spr->frameDuration(fr));
// IDs of all tilesets
write32(s, spr->hasTilesets() ? spr->tilesets()->size(): 0);
if (spr->hasTilesets()) {
for (Tileset* tileset : *spr->tilesets())
write32(s, tileset->id());
}
// IDs of all main layers
write32(s, spr->allLayersCount());
writeAllLayersID(s, 0, spr->root());
@ -216,7 +236,12 @@ private:
switch (lay->type()) {
case ObjectType::LayerImage: {
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
// Tileset index
if (lay->type() == ObjectType::LayerTilemap)
write32(s, static_cast<const LayerTilemap*>(lay)->tilesetIndex());
CelConstIterator it, begin = static_cast<const LayerImage*>(lay)->getCelBegin();
CelConstIterator end = static_cast<const LayerImage*>(lay)->getCelEnd();
@ -263,6 +288,11 @@ private:
return true;
}
bool writeTileset(std::ofstream& s, Tileset* tileset) {
write_tileset(s, tileset);
return true;
}
bool writeFrameTag(std::ofstream& s, Tag* frameTag) {
write_tag(s, frameTag);
return true;

View File

@ -23,7 +23,7 @@
#include "app/file/format_options.h"
#include "app/flatten.h"
#include "app/pref/preferences.h"
#include "app/util/create_cel_copy.h"
#include "app/util/cel_ops.h"
#include "base/memory.h"
#include "doc/cel.h"
#include "doc/layer.h"
@ -259,6 +259,13 @@ void Doc::notifySelectionBoundariesChanged()
notify_observers<DocEvent&>(&DocObserver::onSelectionBoundariesChanged, ev);
}
void Doc::notifyTilesetChanged(Tileset* tileset)
{
DocEvent ev(this);
ev.tileset(tileset);
notify_observers<DocEvent&>(&DocObserver::onTilesetChanged, ev);
}
bool Doc::isModified() const
{
return !m_undo->isSavedState();
@ -443,7 +450,8 @@ void Doc::copyLayerContent(const Layer* sourceLayer0, Doc* destDoc, Layer* destL
it->second));
}
else {
newCel.reset(create_cel_copy(sourceCel,
newCel.reset(create_cel_copy(nullptr, // TODO add undo information?
sourceCel,
destLayer->sprite(),
destLayer,
sourceCel->frame()));

View File

@ -35,6 +35,7 @@ namespace doc {
class Layer;
class Mask;
class Sprite;
class Tileset;
}
namespace gfx {
@ -114,6 +115,7 @@ namespace app {
void notifyCelCopied(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame);
void notifySelectionChanged();
void notifySelectionBoundariesChanged();
void notifyTilesetChanged(Tileset* tileset);
//////////////////////////////////////////////////////////////////////
// File related properties

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -270,20 +270,22 @@ bool DocApi::cropCel(LayerImage* layer,
crop_image(image,
paintPos.x, paintPos.y,
newCelBounds.w, newCelBounds.h,
m_document->bgColor(layer)));
image->pixelFormat() == IMAGE_TILEMAP ?
tile_i_notile : m_document->bgColor(layer)));
// Try to shrink the image ignoring transparent borders
gfx::Rect frameBounds;
if (doc::algorithm::shrink_bounds(newImage.get(),
frameBounds,
newImage->maskColor())) {
newImage->maskColor(),
layer, frameBounds)) {
// In this case the new cel image can be even smaller
if (frameBounds != newImage->bounds()) {
newImage = ImageRef(
crop_image(newImage.get(),
frameBounds.x, frameBounds.y,
frameBounds.w, frameBounds.h,
m_document->bgColor(layer)));
image->pixelFormat() == IMAGE_TILEMAP ?
tile_i_notile : m_document->bgColor(layer)));
newCelPos += frameBounds.origin();
}
@ -532,8 +534,9 @@ void DocApi::setCelOpacity(Sprite* sprite, Cel* cel, int newOpacity)
m_transaction.execute(new cmd::SetCelOpacity(cel, newOpacity));
}
void DocApi::clearCel(LayerImage* layer, frame_t frame)
void DocApi::clearCel(Layer* layer, frame_t frame)
{
ASSERT(layer->isImage());
if (Cel* cel = layer->cel(frame))
clearCel(cel);
}
@ -680,7 +683,11 @@ Layer* DocApi::duplicateLayerAfter(Layer* sourceLayer, LayerGroup* parent, Layer
ASSERT(parent);
std::unique_ptr<Layer> newLayerPtr;
if (sourceLayer->isImage())
if (sourceLayer->isTilemap()) {
newLayerPtr.reset(new LayerTilemap(sourceLayer->sprite(),
static_cast<LayerTilemap*>(sourceLayer)->tilesetIndex()));
}
else if (sourceLayer->isImage())
newLayerPtr.reset(new LayerImage(sourceLayer->sprite()));
else if (sourceLayer->isGroup())
newLayerPtr.reset(new LayerGroup(sourceLayer->sprite()));

View File

@ -75,7 +75,7 @@ namespace app {
// Cels API
void addCel(LayerImage* layer, Cel* cel);
Cel* addCel(LayerImage* layer, frame_t frameNumber, const ImageRef& image);
void clearCel(LayerImage* layer, frame_t frame);
void clearCel(Layer* layer, frame_t frame);
void clearCel(Cel* cel);
void clearCelAndAllLinks(Cel* cel);
void setCelPosition(Sprite* sprite, Cel* cel, int x, int y);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -15,10 +15,13 @@
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
namespace app {
@ -70,7 +73,7 @@ DocDiff compare_docs(const Doc* a,
}
}
// Palettes layers
// Palettes
if (a->sprite()->getPalettes().size() != b->sprite()->getPalettes().size()) {
const PalettesList& aPals = a->sprite()->getPalettes();
const PalettesList& bPals = b->sprite()->getPalettes();
@ -88,6 +91,35 @@ DocDiff compare_docs(const Doc* a,
}
}
// Compare tilesets
const tile_index aTilesetSize = (a->sprite()->hasTilesets() ? a->sprite()->tilesets()->size(): 0);
const tile_index bTilesetSize = (b->sprite()->hasTilesets() ? b->sprite()->tilesets()->size(): 0);
if (aTilesetSize != bTilesetSize) {
diff.anything = diff.tilesets = true;
}
else {
for (int i=0; i<aTilesetSize; ++i) {
Tileset* aTileset = a->sprite()->tilesets()->get(i);
Tileset* bTileset = b->sprite()->tilesets()->get(i);
if (aTileset->grid().tileSize() != bTileset->grid().tileSize() ||
aTileset->size() != bTileset->size()) {
diff.anything = diff.tilesets = true;
break;
}
else {
for (tile_index ti=0; ti<aTileset->size(); ++ti) {
if (!is_same_image(aTileset->get(ti).get(),
bTileset->get(ti).get())) {
diff.anything = diff.tilesets = true;
goto done;
}
}
}
}
done:;
}
// Compare layers
if (a->sprite()->allLayersCount() != b->sprite()->allLayersCount()) {
diff.anything = diff.layers = true;
@ -106,7 +138,9 @@ DocDiff compare_docs(const Doc* a,
aLay->name() != bLay->name() ||
aLay->flags() != bLay->flags() ||
(aLay->isImage() && bLay->isImage() &&
(((const LayerImage*)aLay)->opacity() != ((const LayerImage*)bLay)->opacity()))) {
(((const LayerImage*)aLay)->opacity() != ((const LayerImage*)bLay)->opacity())) ||
(aLay->isTilemap() && bLay->isTilemap() &&
(((const LayerTilemap*)aLay)->tilesetIndex() != ((const LayerTilemap*)bLay)->tilesetIndex()))) {
diff.anything = diff.layers = true;
break;
}
@ -128,7 +162,7 @@ DocDiff compare_docs(const Doc* a,
}
if (aCel->image() && bCel->image()) {
if (aCel->image()->bounds() != bCel->image()->bounds() ||
count_diff_between_images(aCel->image(), bCel->image()))
!is_same_image(aCel->image(), bCel->image()))
diff.anything = diff.images = true;
}
else if (aCel->image() != bCel->image())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -19,6 +19,7 @@ namespace app {
bool frameDuration : 1;
bool tags : 1;
bool palettes : 1;
bool tilesets : 1;
bool layers : 1;
bool cels : 1;
bool images : 1;
@ -32,6 +33,7 @@ namespace app {
frameDuration(false),
tags(false),
palettes(false),
tilesets(false),
layers(false),
cels(false),
images(false),

View File

@ -20,6 +20,7 @@ namespace doc {
class Slice;
class Sprite;
class Tag;
class Tileset;
}
namespace app {
@ -37,6 +38,7 @@ namespace app {
, m_frame(0)
, m_tag(nullptr)
, m_slice(nullptr)
, m_tileset(nullptr)
, m_targetLayer(nullptr)
, m_targetFrame(0) {
}
@ -51,6 +53,7 @@ namespace app {
doc::frame_t frame() const { return m_frame; }
doc::Tag* tag() const { return m_tag; }
doc::Slice* slice() const { return m_slice; }
doc::Tileset* tileset() const { return m_tileset; }
const gfx::Region& region() const { return m_region; }
void sprite(doc::Sprite* sprite) { m_sprite = sprite; }
@ -61,6 +64,7 @@ namespace app {
void frame(doc::frame_t frame) { m_frame = frame; }
void tag(doc::Tag* tag) { m_tag = tag; }
void slice(doc::Slice* slice) { m_slice = slice; }
void tileset(doc::Tileset* tileset) { m_tileset = tileset; }
void region(const gfx::Region& rgn) { m_region = rgn; }
// Destination of the operation.
@ -80,6 +84,7 @@ namespace app {
doc::frame_t m_frame;
doc::Tag* m_tag;
doc::Slice* m_slice;
doc::Tileset* m_tileset;
gfx::Region m_region;
// For copy/move commands, the m_layer/m_frame are source of the

View File

@ -958,7 +958,11 @@ void DocExporter::captureSamples(Samples& samples,
else if (m_ignoreEmptyCels)
refColor = sprite->transparentColor();
if (!algorithm::shrink_bounds(sampleRender.get(), spriteBounds, frameBounds, refColor)) {
if (!algorithm::shrink_bounds(sampleRender.get(),
refColor,
nullptr, // layer
spriteBounds, // startBounds
frameBounds)) { // output bounds
// If shrink_bounds() returns false, it's because the whole
// image is transparent (equal to the mask color).

View File

@ -80,6 +80,9 @@ namespace app {
// Slices
virtual void onSliceNameChange(DocEvent& ev) { }
// The tileset has changed.
virtual void onTilesetChanged(DocEvent& ev) { }
// Called to destroy the observable. (Here you could call "delete this".)
virtual void dispose() { }
};

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -20,21 +21,29 @@ ExtraCel::ExtraCel()
{
}
void ExtraCel::create(doc::Sprite* sprite,
void ExtraCel::create(const TilemapMode tilemapMode,
doc::Sprite* sprite,
const gfx::Rect& bounds,
doc::frame_t frame,
int opacity)
const gfx::Size& imageSize,
const doc::frame_t frame,
const int opacity)
{
ASSERT(sprite);
doc::PixelFormat pixelFormat;
if (tilemapMode == TilemapMode::Tiles)
pixelFormat = doc::IMAGE_TILEMAP;
else
pixelFormat = sprite->pixelFormat();
if (!m_image ||
m_image->pixelFormat() != sprite->pixelFormat() ||
m_image->width() != bounds.w ||
m_image->height() != bounds.h) {
m_image->pixelFormat() != pixelFormat ||
m_image->width() != imageSize.w ||
m_image->height() != imageSize.h) {
if (!m_imageBuffer)
m_imageBuffer.reset(new doc::ImageBuffer(1));
doc::Image* newImage = doc::Image::create(sprite->pixelFormat(),
bounds.w, bounds.h,
doc::Image* newImage = doc::Image::create(pixelFormat,
imageSize.w, imageSize.h,
m_imageBuffer);
m_image.reset(newImage);
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -9,6 +9,7 @@
#define APP_EXTRA_CEL_H_INCLUDED
#pragma once
#include "app/tilemap_mode.h"
#include "base/disable_copying.h"
#include "doc/blend_mode.h"
#include "doc/cel.h"
@ -30,7 +31,12 @@ namespace app {
public:
ExtraCel();
void create(doc::Sprite* sprite, const gfx::Rect& bounds, doc::frame_t frame, int opacity);
void create(const TilemapMode tilemapMode,
doc::Sprite* sprite,
const gfx::Rect& bounds,
const gfx::Size& imageSize,
const doc::frame_t frame,
const int opacity);
render::ExtraType type() const { return m_type; }
void setType(render::ExtraType type) { m_type = type; }

View File

@ -82,6 +82,58 @@ private:
doc::Sprite* m_sprite;
};
class ScanlinesGen {
public:
virtual ~ScanlinesGen() { }
virtual gfx::Size getImageSize() = 0;
virtual int getScanlineSize() = 0;
virtual const uint8_t* getScanlineAddress(int y) = 0;
};
class ImageScanlines : public ScanlinesGen {
const Image* m_image;
public:
ImageScanlines(const Image* image) : m_image(image) { }
gfx::Size getImageSize() override {
return gfx::Size(m_image->width(),
m_image->height());
}
int getScanlineSize() override {
return doc::calculate_rowstride_bytes(
m_image->pixelFormat(),
m_image->width());
}
const uint8_t* getScanlineAddress(int y) override {
return m_image->getPixelAddress(0, y);
}
};
class TilesetScanlines : public ScanlinesGen {
const Tileset* m_tileset;
public:
TilesetScanlines(const Tileset* tileset) : m_tileset(tileset) { }
gfx::Size getImageSize() override {
return gfx::Size(m_tileset->grid().tileSize().w,
m_tileset->grid().tileSize().h * m_tileset->size());
}
int getScanlineSize() override {
return doc::calculate_rowstride_bytes(
m_tileset->sprite()->pixelFormat(),
m_tileset->grid().tileSize().w);
}
const uint8_t* getScanlineAddress(int y) override {
const int h = m_tileset->grid().tileSize().h;
const tile_index ti = (y / h);
ASSERT(ti >= 0 && ti < m_tileset->size());
ImageRef image = m_tileset->get(ti);
ASSERT(image);
if (image)
return image->getPixelAddress(0, y % h);
else
return nullptr;
}
};
} // anonymous namespace
static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite,
@ -129,6 +181,19 @@ static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame
static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Slice* slice,
const frame_t fromFrame, const frame_t toFrame);
static void ase_file_write_user_data_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const UserData* userData);
static void ase_file_write_external_files_chunk(FILE* f,
dio::AsepriteFrameHeader* frame_header,
dio::AsepriteExternalFiles& ext_files,
const Sprite* sprite);
static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tilesets* tilesets);
static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tileset* tileset,
const tileset_index si);
static bool ase_has_groups(LayerGroup* group);
static void ase_ungroup_all(LayerGroup* group);
@ -276,6 +341,7 @@ bool AseFormat::onSave(FileOp* fop)
// Write frames
int outputFrame = 0;
dio::AsepriteExternalFiles ext_files;
for (frame_t frame : fop->roi().selectedFrames()) {
// Prepare the frame header
dio::AsepriteFrameHeader frame_header;
@ -284,9 +350,14 @@ bool AseFormat::onSave(FileOp* fop)
// Frame duration
frame_header.duration = sprite->frameDuration(frame);
// Save color profile in first frame
if (outputFrame == 0 && fop->preserveColorProfile())
ase_file_write_color_profile(f, &frame_header, sprite);
if (outputFrame == 0) {
// Check if we need the "external files" chunk
ase_file_write_external_files_chunk(f, &frame_header, ext_files, sprite);
// Save color profile in first frame
if (fop->preserveColorProfile())
ase_file_write_color_profile(f, &frame_header, sprite);
}
// is the first frame or did the palette change?
Palette* pal = sprite->palette(frame);
@ -307,6 +378,10 @@ bool AseFormat::onSave(FileOp* fop)
// Write extra chunks in the first frame
if (frame == fop->roi().fromFrame()) {
// Write tilesets
ase_file_write_tileset_chunks(f, fop, &frame_header, ext_files,
sprite->tilesets());
// Write layer chunks
for (Layer* child : sprite->root()->layers())
ase_file_write_layers(f, &frame_header, child, 0);
@ -317,7 +392,7 @@ bool AseFormat::onSave(FileOp* fop)
fop->roi().fromFrame(),
fop->roi().toFrame());
// Writer slice chunks
// Write slice chunks
ase_file_write_slice_chunks(f, &frame_header,
sprite->slices(),
fop->roi().fromFrame(),
@ -587,8 +662,15 @@ static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
static_cast<int>(doc::LayerFlags::PersistentFlagsMask), f);
// Layer type
fputw((layer->isImage() ? ASE_FILE_LAYER_IMAGE:
(layer->isGroup() ? ASE_FILE_LAYER_GROUP: -1)), f);
int layerType = ASE_FILE_LAYER_IMAGE;
if (layer->isImage()) {
if (layer->isTilemap())
layerType = ASE_FILE_LAYER_TILEMAP;
}
else if (layer->isGroup()) {
layerType = ASE_FILE_LAYER_GROUP;
}
fputw(layerType, f);
// Layer child level
fputw(child_level, f);
@ -604,6 +686,10 @@ static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
// Layer name
ase_file_write_string(f, layer->name());
// Tileset index
if (layer->isTilemap())
fputl(static_cast<const LayerTilemap*>(layer)->tilesetIndex(), f);
}
//////////////////////////////////////////////////////////////////////
@ -626,14 +712,12 @@ public:
fputc(rgba_getb(c), f);
fputc(rgba_geta(c), f);
}
void write_scanline(RgbTraits::address_t address, int w, uint8_t* buffer)
{
for (int x=0; x<w; ++x) {
void write_scanline(RgbTraits::address_t address, int w, uint8_t* buffer) {
for (int x=0; x<w; ++x, ++address) {
*(buffer++) = rgba_getr(*address);
*(buffer++) = rgba_getg(*address);
*(buffer++) = rgba_getb(*address);
*(buffer++) = rgba_geta(*address);
++address;
}
}
};
@ -645,12 +729,10 @@ public:
fputc(graya_getv(c), f);
fputc(graya_geta(c), f);
}
void write_scanline(GrayscaleTraits::address_t address, int w, uint8_t* buffer)
{
for (int x=0; x<w; ++x) {
void write_scanline(GrayscaleTraits::address_t address, int w, uint8_t* buffer) {
for (int x=0; x<w; ++x, ++address) {
*(buffer++) = graya_getv(*address);
*(buffer++) = graya_geta(*address);
++address;
}
}
};
@ -661,12 +743,27 @@ public:
void write_pixel(FILE* f, IndexedTraits::pixel_t c) {
fputc(c, f);
}
void write_scanline(IndexedTraits::address_t address, int w, uint8_t* buffer)
{
void write_scanline(IndexedTraits::address_t address, int w, uint8_t* buffer) {
memcpy(buffer, address, w);
}
};
template<>
class PixelIO<TilemapTraits> {
public:
void write_pixel(FILE* f, TilemapTraits::pixel_t c) {
fputl(c, f);
}
void write_scanline(TilemapTraits::address_t address, int w, uint8_t* buffer) {
for (int x=0; x<w; ++x, ++address) {
*(buffer++) = ((*address) & 0x000000ffl);
*(buffer++) = ((*address) & 0x0000ff00l) >> 8;
*(buffer++) = ((*address) & 0x00ff0000l) >> 16;
*(buffer++) = ((*address) & 0xff000000l) >> 24;
}
}
};
//////////////////////////////////////////////////////////////////////
// Raw Image
//////////////////////////////////////////////////////////////////////
@ -687,7 +784,7 @@ static void write_raw_image(FILE* f, const Image* image)
//////////////////////////////////////////////////////////////////////
template<typename ImageTraits>
static void write_compressed_image(FILE* f, const Image* image)
static void write_compressed_image_templ(FILE* f, ScanlinesGen* gen)
{
PixelIO<ImageTraits> pixel_io;
z_stream zstream;
@ -700,18 +797,19 @@ static void write_compressed_image(FILE* f, const Image* image)
if (err != Z_OK)
throw base::Exception("ZLib error %d in deflateInit().", err);
std::vector<uint8_t> scanline(ImageTraits::getRowStrideBytes(image->width()));
std::vector<uint8_t> scanline(gen->getScanlineSize());
std::vector<uint8_t> compressed(4096);
for (y=0; y<image->height(); y++) {
const gfx::Size imgSize = gen->getImageSize();
for (y=0; y<imgSize.h; ++y) {
typename ImageTraits::address_t address =
(typename ImageTraits::address_t)image->getPixelAddress(0, y);
(typename ImageTraits::address_t)gen->getScanlineAddress(y);
pixel_io.write_scanline(address, image->width(), &scanline[0]);
pixel_io.write_scanline(address, imgSize.w, &scanline[0]);
zstream.next_in = (Bytef*)&scanline[0];
zstream.avail_in = scanline.size();
int flush = (y == image->height()-1 ? Z_FINISH: Z_NO_FLUSH);
int flush = (y == imgSize.h-1 ? Z_FINISH: Z_NO_FLUSH);
do {
zstream.next_out = (Bytef*)&compressed[0];
@ -736,6 +834,27 @@ static void write_compressed_image(FILE* f, const Image* image)
throw base::Exception("ZLib error %d in deflateEnd().", err);
}
static void write_compressed_image(FILE* f, ScanlinesGen* gen, PixelFormat pixelFormat)
{
switch (pixelFormat) {
case IMAGE_RGB:
write_compressed_image_templ<RgbTraits>(f, gen);
break;
case IMAGE_GRAYSCALE:
write_compressed_image_templ<GrayscaleTraits>(f, gen);
break;
case IMAGE_INDEXED:
write_compressed_image_templ<IndexedTraits>(f, gen);
break;
case IMAGE_TILEMAP:
write_compressed_image_templ<TilemapTraits>(f, gen);
break;
}
}
//////////////////////////////////////////////////////////////////////
// Cel Chunk
//////////////////////////////////////////////////////////////////////
@ -764,7 +883,9 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
link = nullptr;
}
int cel_type = (link ? ASE_FILE_LINK_CEL: ASE_FILE_COMPRESSED_CEL);
int cel_type = (link ? ASE_FILE_LINK_CEL:
cel->layer()->isTilemap() ? ASE_FILE_COMPRESSED_TILEMAP:
ASE_FILE_COMPRESSED_CEL);
fputw(layer_index, f);
fputw(cel->x(), f);
@ -797,6 +918,7 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
case IMAGE_INDEXED:
write_raw_image<IndexedTraits>(f, image);
break;
}
}
else {
@ -814,27 +936,14 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
case ASE_FILE_COMPRESSED_CEL: {
const Image* image = cel->image();
ASSERT(image);
if (image) {
// Width and height
fputw(image->width(), f);
fputw(image->height(), f);
// Pixel data
switch (image->pixelFormat()) {
case IMAGE_RGB:
write_compressed_image<RgbTraits>(f, image);
break;
case IMAGE_GRAYSCALE:
write_compressed_image<GrayscaleTraits>(f, image);
break;
case IMAGE_INDEXED:
write_compressed_image<IndexedTraits>(f, image);
break;
}
ImageScanlines scan(image);
write_compressed_image(f, &scan, image->pixelFormat());
}
else {
// Width and height
@ -843,6 +952,24 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
}
break;
}
case ASE_FILE_COMPRESSED_TILEMAP: {
const Image* image = cel->image();
ASSERT(image);
ASSERT(image->pixelFormat() == IMAGE_TILEMAP);
fputw(image->width(), f);
fputw(image->height(), f);
fputw(32, f); // TODO use different bpp when possible
fputl(tile_i_mask, f);
fputl(tile_f_flipx, f);
fputl(tile_f_flipy, f);
fputl(tile_f_90cw, f);
ase_file_write_padding(f, 10);
ImageScanlines scan(image);
write_compressed_image(f, &scan, IMAGE_TILEMAP);
}
}
}
@ -1087,6 +1214,101 @@ static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
}
}
static void ase_file_write_external_files_chunk(
FILE* f,
dio::AsepriteFrameHeader* frame_header,
dio::AsepriteExternalFiles& ext_files,
const Sprite* sprite)
{
for (const Tileset* tileset : *sprite->tilesets()) {
if (!tileset->externalFilename().empty()) {
auto id = ++ext_files.lastid;
auto fn = tileset->externalFilename();
ext_files.to_fn[id] = fn;
ext_files.to_id[fn] = id;
}
}
// No external files to write
if (ext_files.lastid == 0)
return;
fputl(ext_files.to_fn.size(), f); // Number of entries
ase_file_write_padding(f, 8);
for (auto item : ext_files.to_fn) {
fputl(item.first, f); // ID
ase_file_write_padding(f, 8);
ase_file_write_string(f, item.second); // Filename
}
}
static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tilesets* tilesets)
{
tileset_index si = 0;
for (const Tileset* tileset : *tilesets) {
ase_file_write_tileset_chunk(f, fop, frame_header, ext_files,
tileset, si);
++si;
}
}
static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tileset* tileset,
const tileset_index si)
{
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TILESET);
int flags = 0;
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(tileset->size(), f);
fputw(tileset->grid().tileSize().w, f);
fputw(tileset->grid().tileSize().h, f);
ase_file_write_padding(f, 16);
ase_file_write_string(f, tileset->name()); // tileset name
// Flag 1 = external tileset
if (flags & ASE_TILESET_FLAG_EXTERNAL_FILE) {
auto it = ext_files.to_id.find(tileset->externalFilename());
if (it != ext_files.to_id.end()) {
auto file_id = it->second;
fputl(file_id, f);
fputl(tileset->externalTileset(), f);
}
else {
ASSERT(false); // Impossible state (corrupted memory or we
// forgot to add the tileset external file to
// "ext_files")
fputl(0, f);
fputl(0, f);
fop->setError("Error writing tileset external reference.\n");
}
}
// Flag 2 = tileset
if (flags & ASE_TILESET_FLAG_EMBEDDED) {
size_t beg = ftell(f);
fputl(0, f); // Field for compressed data length (completed later)
TilesetScanlines gen(tileset);
write_compressed_image(f, &gen, tileset->sprite()->pixelFormat());
size_t end = ftell(f);
fseek(f, beg, SEEK_SET);
fputl(end-beg-4, f); // Save the compressed data length
fseek(f, end, SEEK_SET);
}
}
static bool ase_has_groups(LayerGroup* group)
{
for (Layer* child : group->layers()) {

View File

@ -596,7 +596,8 @@ void FileOp::operate(IFileOpProgress* progress)
auto add_image = [&]() {
canvasSize |= m_seq.image->size();
m_seq.last_cel->data()->setImage(m_seq.image);
m_seq.last_cel->data()->setImage(m_seq.image,
m_seq.layer);
m_seq.layer->addCel(m_seq.last_cel);
if (m_document->sprite()->palette(frame)

View File

@ -721,7 +721,7 @@ private:
try {
ImageRef celImage(Image::createCopy(m_currentImage.get()));
try {
cel->data()->setImage(celImage);
cel->data()->setImage(celImage, m_layer);
}
catch (...) {
throw;

View File

@ -388,6 +388,20 @@ int App_useTool(lua_State* L)
params.freehandAlgorithm = get_value_from_lua<tools::FreehandAlgorithm>(L, -1);
lua_pop(L, 1);
// Are we going to modify pixels or tiles?
type = lua_getfield(L, 1, "tilemapMode");
if (type != LUA_TNIL) {
site.tilemapMode(TilemapMode(lua_tointeger(L, -1)));
}
lua_pop(L, 1);
// How the tileset must be modified depending on this tool usage
type = lua_getfield(L, 1, "tilesetMode");
if (type != LUA_TNIL) {
site.tilesetMode(TilesetMode(lua_tointeger(L, -1)));
}
lua_pop(L, 1);
// Do the tool loop
type = lua_getfield(L, 1, "points");
if (type == LUA_TTABLE) {

View File

@ -135,11 +135,22 @@ app::Color Color_new(lua_State* L, int index)
}
else
lua_pop(L, 1);
// Convert { index } into a Color
if (lua_getfield(L, index, "index") != LUA_TNIL) {
color = app::Color::fromIndex(lua_tonumber(L, -1));
lua_pop(L, 1);
return color;
}
else
lua_pop(L, 1);
}
// raw color into app color
else if (!lua_isnone(L, index)) {
if (lua_isinteger(L, index) && (index < 0 || lua_isnone(L, index+1))) {
doc::color_t docColor = lua_tointeger(L, index);
// TODO depending on current pixel format?
switch (app_get_current_pixel_format()) {
case IMAGE_RGB:
color = app::Color::fromRgb(doc::rgba_getr(docColor),

View File

@ -18,6 +18,8 @@
#include "app/script/luacpp.h"
#include "app/script/security.h"
#include "app/sprite_sheet_type.h"
#include "app/tilemap_mode.h"
#include "app/tileset_mode.h"
#include "app/tools/ink_type.h"
#include "base/chrono.h"
#include "base/file_handle.h"
@ -156,6 +158,7 @@ void register_dialog_class(lua_State* L);
#endif
void register_frame_class(lua_State* L);
void register_frames_class(lua_State* L);
void register_grid_class(lua_State* L);
void register_image_class(lua_State* L);
void register_image_iterator_class(lua_State* L);
void register_image_spec_class(lua_State* L);
@ -177,6 +180,8 @@ void register_sprite_class(lua_State* L);
void register_sprites_class(lua_State* L);
void register_tag_class(lua_State* L);
void register_tags_class(lua_State* L);
void register_tileset_class(lua_State* L);
void register_tilesets_class(lua_State* L);
void register_tool_class(lua_State* L);
void register_version_class(lua_State* L);
@ -255,6 +260,7 @@ Engine::Engine()
setfield_integer(L, "GRAY", doc::ColorMode::GRAYSCALE);
setfield_integer(L, "GRAYSCALE", doc::ColorMode::GRAYSCALE);
setfield_integer(L, "INDEXED", doc::ColorMode::INDEXED);
setfield_integer(L, "TILEMAP", doc::ColorMode::TILEMAP);
lua_pop(L, 1);
lua_newtable(L);
@ -367,6 +373,21 @@ Engine::Engine()
setfield_integer(L, "X2", (int)ui::kButtonX2);
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "TilemapMode");
setfield_integer(L, "PIXELS", TilemapMode::Pixels);
setfield_integer(L, "TILES", TilemapMode::Tiles);
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "TilesetMode");
setfield_integer(L, "MANUAL", TilesetMode::Manual);
setfield_integer(L, "AUTO", TilesetMode::Auto);
setfield_integer(L, "STACK", TilesetMode::Stack);
lua_pop(L, 1);
// Register classes/prototypes
register_brush_class(L);
register_cel_class(L);
@ -378,6 +399,7 @@ Engine::Engine()
#endif
register_frame_class(L);
register_frames_class(L);
register_grid_class(L);
register_image_class(L);
register_image_iterator_class(L);
register_image_spec_class(L);
@ -399,6 +421,8 @@ Engine::Engine()
register_sprites_class(L);
register_tag_class(L);
register_tags_class(L);
register_tileset_class(L);
register_tilesets_class(L);
register_tool_class(L);
register_version_class(L);

View File

@ -45,6 +45,8 @@ namespace doc {
class Palette;
class Sprite;
class Tag;
class Tileset;
class Tilesets;
class WithUserData;
}
@ -146,6 +148,9 @@ namespace app {
void push_sprite_slices(lua_State* L, doc::Sprite* sprite);
void push_sprite_tags(lua_State* L, doc::Sprite* sprite);
void push_sprites(lua_State* L);
void push_tileset(lua_State* L, doc::Tileset* tileset);
void push_tileset_image(lua_State* L, doc::Tileset* tileset, doc::Image* image);
void push_tilesets(lua_State* L, doc::Tilesets* tilesets);
void push_tool(lua_State* L, app::tools::Tool* tool);
void push_userdata(lua_State* L, doc::WithUserData* userData);
void push_version(lua_State* L, const base::Version& ver);

View File

@ -0,0 +1,94 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "doc/grid.h"
namespace app {
namespace script {
using namespace doc;
namespace {
doc::Grid Grid_new(lua_State* L, int index)
{
doc::Grid grid;
// Copy other size
if (auto grid2 = may_get_obj<doc::Grid>(L, index)) {
grid = *grid2;
}
// Convert Rectangle into a Grid
else if (lua_istable(L, index)) {
gfx::Rect rect = convert_args_into_rect(L, index);
doc::Grid grid(rect.size());
grid.origin(rect.origin());
}
return grid;
}
int Grid_new(lua_State* L)
{
push_obj(L, Grid_new(L, 1));
return 1;
}
int Grid_gc(lua_State* L)
{
get_obj<doc::Grid>(L, 1)->~Grid();
return 0;
}
int Grid_get_tileSize(lua_State* L)
{
auto grid = get_obj<doc::Grid>(L, 1);
push_obj(L, grid->tileSize());
return 1;
}
int Grid_get_origin(lua_State* L)
{
auto grid = get_obj<doc::Grid>(L, 1);
push_obj(L, grid->origin());
return 1;
}
const luaL_Reg Grid_methods[] = {
{ "__gc", Grid_gc },
{ nullptr, nullptr }
};
const Property Grid_properties[] = {
{ "tileSize", Grid_get_tileSize, nullptr },
{ "origin", Grid_get_origin, nullptr },
{ nullptr, nullptr, nullptr }
};
} // anonymous namespace
DEF_MTNAME(Grid);
void register_grid_class(lua_State* L)
{
using Grid = doc::Grid;
REG_CLASS(L, Grid);
REG_CLASS_NEW(L, Grid);
REG_CLASS_PROPERTIES(L, Grid);
}
doc::Grid convert_args_into_grid(lua_State* L, int index)
{
return Grid_new(L, index);
}
} // namespace script
} // namespace app

View File

@ -43,6 +43,7 @@ namespace {
struct ImageObj {
doc::ObjectId imageId = 0;
doc::ObjectId celId = 0;
doc::ObjectId tilesetId = 0;
ImageObj(doc::Image* image)
: imageId(image->id()) {
}
@ -50,6 +51,10 @@ struct ImageObj {
: imageId(cel->image()->id())
, celId(cel->id()) {
}
ImageObj(doc::Tileset* tileset, doc::Image* image)
: imageId(image->id())
, tilesetId(tileset->id()) {
}
ImageObj(const ImageObj&) = delete;
ImageObj& operator=(const ImageObj&) = delete;
@ -58,7 +63,7 @@ struct ImageObj {
}
void gc(lua_State* L) {
if (!celId)
if (!celId && !tilesetId)
delete this->image(L);
imageId = 0;
}
@ -372,11 +377,11 @@ int Image_saveAs(lua_State* L)
std::unique_ptr<Sprite> sprite(Sprite::MakeStdSprite(img->spec(), 256));
std::vector<Image*> oneImage;
std::vector<ImageRef> oneImage;
sprite->getImages(oneImage);
ASSERT(oneImage.size() == 1);
if (!oneImage.empty())
copy_image(oneImage.front(), img);
copy_image(oneImage.front().get(), img);
if (pal)
sprite->setPalette(pal, false);
@ -568,6 +573,11 @@ void push_image(lua_State* L, doc::Image* image)
push_new<ImageObj>(L, image);
}
void push_tileset_image(lua_State* L, doc::Tileset* tileset, doc::Image* image)
{
push_new<ImageObj>(L, tileset, image);
}
doc::Image* may_get_image_from_arg(lua_State* L, int index)
{
auto obj = may_get_obj<ImageObj>(L, index);

View File

@ -21,6 +21,7 @@
#include "app/tx.h"
#include "base/clamp.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/sprite.h"
namespace app {
@ -152,6 +153,13 @@ int Layer_get_isGroup(lua_State* L)
return 1;
}
int Layer_get_isTilemap(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 1);
lua_pushboolean(L, layer->isTilemap());
return 1;
}
int Layer_get_isTransparent(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 1);
@ -208,6 +216,16 @@ int Layer_get_cels(lua_State* L)
return 1;
}
int Layer_get_tileset(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 1);
if (layer->isTilemap())
push_tileset(L, static_cast<doc::LayerTilemap*>(layer)->tileset());
else
lua_pushnil(L);
return 1;
}
int Layer_set_name(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 1);
@ -370,6 +388,7 @@ const Property Layer_properties[] = {
{ "blendMode", Layer_get_blendMode, Layer_set_blendMode },
{ "isImage", Layer_get_isImage, nullptr },
{ "isGroup", Layer_get_isGroup, nullptr },
{ "isTilemap", Layer_get_isTilemap, nullptr },
{ "isTransparent", Layer_get_isTransparent, nullptr },
{ "isBackground", Layer_get_isBackground, nullptr },
{ "isEditable", Layer_get_isEditable, Layer_set_isEditable },
@ -380,6 +399,7 @@ const Property Layer_properties[] = {
{ "cels", Layer_get_cels, nullptr },
{ "color", UserData_get_color<Layer>, UserData_set_color<Layer> },
{ "data", UserData_get_text<Layer>, UserData_set_text<Layer> },
{ "tileset", Layer_get_tileset, nullptr },
{ nullptr, nullptr, nullptr }
};

View File

@ -10,6 +10,7 @@
#include "app/script/luacpp.h"
#include "doc/color.h"
#include "doc/tile.h"
namespace app {
namespace script {
@ -70,6 +71,26 @@ int PixelColor_grayaA(lua_State* L)
return 1;
}
int PixelColor_tile(lua_State* L)
{
const int i = lua_tointeger(L, 1);
const int f = lua_tointeger(L, 2);
lua_pushinteger(L, doc::tile(i, f));
return 1;
}
int PixelColor_tileI(lua_State* L)
{
lua_pushinteger(L, doc::tile_geti(lua_tointeger(L, 1)));
return 1;
}
int PixelColor_tileF(lua_State* L)
{
lua_pushinteger(L, doc::tile_getf(lua_tointeger(L, 1)));
return 1;
}
const luaL_Reg PixelColor_methods[] = {
{ "rgba", PixelColor_rgba },
{ "rgbaR", PixelColor_rgbaR },
@ -79,6 +100,9 @@ const luaL_Reg PixelColor_methods[] = {
{ "graya", PixelColor_graya },
{ "grayaV", PixelColor_grayaV },
{ "grayaA", PixelColor_grayaA },
{ "tile", PixelColor_tile },
{ "tileI", PixelColor_tileI },
{ "tileF", PixelColor_tileF },
{ nullptr, nullptr }
};

View File

@ -20,6 +20,7 @@
#include "doc/layer.h"
#include "doc/object_ids.h"
#include "doc/sprite.h"
#include "doc/tile.h"
#include <set>
#include <vector>
@ -36,6 +37,7 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
std::vector<frame_t> frames;
std::set<ObjectId> cels;
std::vector<color_t> colors;
std::vector<tile_index> tiles;
RangeObj(Site& site) {
updateFromSite(site);
@ -83,6 +85,9 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
if (site.selectedColors().picks() > 0)
colors = site.selectedColors().toVectorOfIndexes();
if (site.selectedTiles().picks() > 0)
tiles = site.selectedTiles().toVectorOfIndexes();
}
Sprite* sprite(lua_State* L) { return check_docobj(L, doc::get<Sprite>(spriteId)); }
@ -99,6 +104,9 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
bool containsColor(const color_t color) const {
return (std::find(colors.begin(), colors.end(), color) != colors.end());
}
bool containsTile(const tile_t tile) const {
return (std::find(tiles.begin(), tiles.end(), tile) != tiles.end());
}
};
int Range_gc(lua_State* L)
@ -142,11 +150,19 @@ int Range_contains(lua_State* L)
int Range_containsColor(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
color_t color = lua_tointeger(L, 2);
const color_t color = lua_tointeger(L, 2);
lua_pushboolean(L, obj->containsColor(color));
return 1;
}
int Range_containsTile(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
const tile_index tile = lua_tointeger(L, 2);
lua_pushboolean(L, obj->containsTile(tile));
return 1;
}
int Range_clear(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
@ -243,6 +259,18 @@ int Range_get_colors(lua_State* L)
return 1;
}
int Range_get_tiles(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
lua_newtable(L);
int j = 1;
for (tile_index i : obj->tiles) {
lua_pushinteger(L, i);
lua_rawseti(L, -2, j++);
}
return 1;
}
int Range_set_layers(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
@ -308,10 +336,29 @@ int Range_set_colors(lua_State* L)
return 0;
}
int Range_set_tiles(lua_State* L)
{
app::Context* ctx = App::instance()->context();
doc::PalettePicks picks;
if (lua_istable(L, 2)) {
lua_pushnil(L);
while (lua_next(L, 2) != 0) {
int i = lua_tointeger(L, -1);
if (i >= picks.size())
picks.resize(i+1);
picks[i] = true;
lua_pop(L, 1);
}
}
ctx->setSelectedTiles(picks);
return 0;
}
const luaL_Reg Range_methods[] = {
{ "__gc", Range_gc },
{ "contains", Range_contains },
{ "containsColor", Range_containsColor },
{ "containsTile", Range_containsTile },
{ "clear", Range_clear },
{ nullptr, nullptr }
};
@ -326,6 +373,7 @@ const Property Range_properties[] = {
{ "images", Range_get_images, nullptr },
{ "editableImages", Range_get_editableImages, nullptr },
{ "colors", Range_get_colors, Range_set_colors },
{ "tiles", Range_get_tiles, Range_set_tiles },
{ nullptr, nullptr, nullptr }
};

View File

@ -683,6 +683,13 @@ int Sprite_get_slices(lua_State* L)
return 1;
}
int Sprite_get_tilesets(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
push_tilesets(L, sprite->tilesets());
return 1;
}
int Sprite_get_backgroundLayer(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
@ -820,6 +827,7 @@ const Property Sprite_properties[] = {
{ "cels", Sprite_get_cels, nullptr },
{ "tags", Sprite_get_tags, nullptr },
{ "slices", Sprite_get_slices, nullptr },
{ "tilesets", Sprite_get_tilesets, nullptr },
{ "backgroundLayer", Sprite_get_backgroundLayer, nullptr },
{ "transparentColor", Sprite_get_transparentColor, Sprite_set_transparentColor },
{ "bounds", Sprite_get_bounds, nullptr },

View File

@ -0,0 +1,104 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "doc/tileset.h"
namespace app {
namespace script {
using namespace doc;
namespace {
int Tileset_eq(lua_State* L)
{
const auto a = get_docobj<Tileset>(L, 1);
const auto b = get_docobj<Tileset>(L, 2);
lua_pushboolean(L, a->id() == b->id());
return 1;
}
int Tileset_len(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
lua_pushinteger(L, tileset->size());
return 1;
}
int Tileset_getTile(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
tile_index i = lua_tointeger(L, 2);
ImageRef image = tileset->get(i);
if (image)
push_tileset_image(L, tileset, image.get());
else
lua_pushnil(L);
return 1;
}
int Tileset_get_name(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
lua_pushstring(L, tileset->name().c_str());
return 1;
}
int Tileset_set_name(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
if (const char* newName = lua_tostring(L, 2))
tileset->setName(newName);
return 0;
}
int Tileset_get_grid(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
push_obj(L, tileset->grid());
return 1;
}
const luaL_Reg Tileset_methods[] = {
{ "__eq", Tileset_eq },
{ "__len", Tileset_len },
{ "getTile", Tileset_getTile },
// TODO
// { "setTile", Tileset_setTile },
{ nullptr, nullptr }
};
const Property Tileset_properties[] = {
{ "name", Tileset_get_name, Tileset_set_name },
{ "grid", Tileset_get_grid, nullptr },
{ nullptr, nullptr, nullptr }
};
} // anonymous namespace
DEF_MTNAME(Tileset);
void register_tileset_class(lua_State* L)
{
using Tileset = doc::Tileset;
REG_CLASS(L, Tileset);
REG_CLASS_PROPERTIES(L, Tileset);
}
void push_tileset(lua_State* L, Tileset* tileset)
{
push_docobj(L, tileset);
}
} // namespace script
} // namespace app

View File

@ -0,0 +1,62 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "doc/tilesets.h"
namespace app {
namespace script {
using namespace doc;
namespace {
int Tilesets_len(lua_State* L)
{
auto obj = get_docobj<Tilesets>(L, 1);
lua_pushinteger(L, obj->size());
return 1;
}
int Tilesets_index(lua_State* L)
{
auto obj = get_docobj<Tilesets>(L, 1);
const int i = lua_tonumber(L, 2);
if (i >= 1 && i <= int(obj->size()))
push_docobj(L, *(obj->begin()+i-1));
else
lua_pushnil(L);
return 1;
}
const luaL_Reg Tilesets_methods[] = {
{ "__len", Tilesets_len },
{ "__index", Tilesets_index },
{ nullptr, nullptr }
};
} // anonymous namespace
DEF_MTNAME(Tilesets);
void register_tilesets_class(lua_State* L)
{
REG_CLASS(L, Tilesets);
}
void push_tilesets(lua_State* L, Tilesets* tilesets)
{
push_docobj(L, tilesets);
}
} // namespace script
} // namespace app

View File

@ -14,8 +14,11 @@
#include "app/pref/preferences.h"
#include "base/clamp.h"
#include "doc/cel.h"
#include "doc/grid.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "ui/system.h"
namespace app {
@ -32,15 +35,7 @@ RgbMap* Site::rgbMap() const
return (m_sprite ? m_sprite->rgbMap(m_frame): nullptr);
}
const Cel* Site::cel() const
{
if (m_layer)
return m_layer->cel(m_frame);
else
return nullptr;
}
Cel* Site::cel()
Cel* Site::cel() const
{
if (m_layer)
return m_layer->cel(m_frame);
@ -79,8 +74,31 @@ void Site::range(const DocRange& range)
}
}
Grid Site::grid() const
{
if (m_layer && m_layer->isTilemap()) {
doc::Grid grid = static_cast<LayerTilemap*>(m_layer)->tileset()->grid();
if (const Cel* cel = m_layer->cel(m_frame))
grid.origin(grid.origin() + cel->position());
return grid;
}
gfx::Rect rc = gridBounds();
doc::Grid grid = Grid(rc.size());
grid.origin(gfx::Point(rc.x % rc.w, rc.y % rc.h));
return grid;
}
gfx::Rect Site::gridBounds() const
{
if (m_layer && m_layer->isTilemap()) {
const Grid& grid = static_cast<LayerTilemap*>(m_layer)->tileset()->grid();
gfx::Point offset = grid.tileOffset();
if (const Cel* cel = m_layer->cel(m_frame))
offset += cel->bounds().origin();
return gfx::Rect(offset, grid.tileSize());
}
gfx::Rect bounds;
if (m_sprite) {
bounds = m_sprite->gridBounds();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -10,6 +10,8 @@
#pragma once
#include "app/doc_range.h"
#include "app/tilemap_mode.h"
#include "app/tileset_mode.h"
#include "doc/frame.h"
#include "doc/palette_picks.h"
#include "doc/selected_objects.h"
@ -17,6 +19,7 @@
namespace doc {
class Cel;
class Grid;
class Image;
class Layer;
class Palette;
@ -49,7 +52,9 @@ namespace app {
, m_document(nullptr)
, m_sprite(nullptr)
, m_layer(nullptr)
, m_frame(0) { }
, m_frame(0)
, m_tilemapMode(TilemapMode::Pixels)
, m_tilesetMode(TilesetMode::Manual) { }
const Focus focus() const { return m_focus; }
bool inEditor() const { return m_focus == InEditor; }
@ -59,16 +64,11 @@ namespace app {
bool inColorBar() const { return m_focus == InColorBar; }
bool inTimeline() const { return (inLayers() || inFrames() || inCels()); }
const Doc* document() const { return m_document; }
const doc::Sprite* sprite() const { return m_sprite; }
const doc::Layer* layer() const { return m_layer; }
Doc* document() const { return m_document; }
doc::Sprite* sprite() const { return m_sprite; }
doc::Layer* layer() const { return m_layer; }
doc::frame_t frame() const { return m_frame; }
const doc::Cel* cel() const;
Doc* document() { return m_document; }
doc::Sprite* sprite() { return m_sprite; }
doc::Layer* layer() { return m_layer; }
doc::Cel* cel();
doc::Cel* cel() const;
const DocRange& range() const { return m_range; }
void focus(Focus focus) { m_focus = focus; }
@ -88,6 +88,13 @@ namespace app {
m_selectedColors = colors;
}
// Selected tiles selected in the ColorBar
const doc::PalettePicks& selectedTiles() const { return m_selectedTiles; }
doc::PalettePicks& selectedTiles() { return m_selectedTiles; }
void selectedTiles(const doc::PalettePicks& tiles) {
m_selectedTiles = tiles;
}
const doc::SelectedObjects& selectedSlices() const { return m_selectedSlices; }
doc::SelectedObjects& selectedSlices() { return m_selectedSlices; }
void selectedSlices(const doc::SelectedObjects& set) {
@ -99,8 +106,14 @@ namespace app {
doc::Palette* palette() const;
doc::RgbMap* rgbMap() const;
doc::Grid grid() const;
gfx::Rect gridBounds() const;
void tilemapMode(const TilemapMode mode) { m_tilemapMode = mode; }
void tilesetMode(const TilesetMode mode) { m_tilesetMode = mode; }
TilemapMode tilemapMode() const { return m_tilemapMode; }
TilesetMode tilesetMode() const { return m_tilesetMode; }
private:
Focus m_focus;
Doc* m_document;
@ -109,7 +122,10 @@ namespace app {
doc::frame_t m_frame;
DocRange m_range;
doc::PalettePicks m_selectedColors;
doc::PalettePicks m_selectedTiles;
doc::SelectedObjects m_selectedSlices;
TilemapMode m_tilemapMode;
TilesetMode m_tilesetMode;
};
} // namespace app

View File

@ -43,7 +43,7 @@ os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
render::Render render;
render::Projection proj(cel->sprite()->pixelRatio(),
render::Zoom(newSize.w, cel->image()->width()));
render::Zoom(newSize.w, cel->bounds().w));
render.setProjection(proj);
const doc::Palette* palette = cel->sprite()->palette(cel->frame());

21
src/app/tilemap_mode.h Normal file
View File

@ -0,0 +1,21 @@
// Aseprite
// Copyright (c) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_TILEMAP_MODE_H_INCLUDED
#define APP_TILEMAP_MODE_H_INCLUDED
#pragma once
namespace app {
// Should we edit the pixels or the tiles of the tilemap?
enum class TilemapMode {
Pixels, // Edit tile pixels
Tiles, // Edit tiles
};
} // namespace app
#endif

Some files were not shown because too many files have changed in this diff Show More