mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 15:32:38 +00:00
Add tilemap layers (#977)
This is the first commit with a simple tilemap editor. Still buggy but functional in several ways. Several changes were made: * NewLayer command can receive a tilemap=true to create a new tilemap layer * New ToggleTilesMode command added to switch between the palette and the tileset in the ColorBar (the ColorBar was expanded to show colors or tilesets with a generic AbstractPaletteViewAdapter) * All commands to create new layers were moved to Layer > New... submenu * There are a new tileset chunk to save tilesets in .aseprite files, and a new kind of cels to save tilemaps * Added doc::LayerTilemap, doc::Tileset, etc. and several other types to handle tilesets/tilemaps in the doc layer. * Added doc::Grid class with grid specifications that indicates how a tilemap <-> tileset must be drawn * Added and expanded cel operations to work with tilemaps and conversions between regular LayerImage cels <-> LayerTilemap cels (e.g. copy cels in the timeline between layer types)
This commit is contained in:
parent
9fe05a5dd5
commit
26139c4ae2
@ -138,6 +138,7 @@
|
||||
<key command="Timeline" shortcut="Tab">
|
||||
<param name="switch" value="true" />
|
||||
</key>
|
||||
<key command="ToggleTilesMode" shortcut="Shift+Tab" />
|
||||
<key command="PaletteEditor" shortcut="A" />
|
||||
<key command="PaletteEditor" shortcut="F4">
|
||||
<param name="edit" value="switch" />
|
||||
@ -807,6 +808,9 @@
|
||||
<param name="reference" value="true" />
|
||||
<param name="fromFile" value="true" />
|
||||
</item>
|
||||
<item command="NewLayer" text="@.layer_new_tilemap_layer">
|
||||
<param name="tilemap" value="true" />
|
||||
</item>
|
||||
</menu>
|
||||
<item command="RemoveLayer" text="@.layer_delete_layer" />
|
||||
<item command="BackgroundFromLayer" text="@.layer_background_from_layer" />
|
||||
|
@ -195,6 +195,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" />
|
||||
|
@ -346,6 +346,7 @@ 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
|
||||
@ -443,6 +444,7 @@ SymmetryMode = Symmetry Mode
|
||||
TiledMode = Tiled Mode
|
||||
Timeline = Switch Timeline
|
||||
TogglePreview = Toggle Preview
|
||||
ToggleTilesMode = Toggle Tiles Mode
|
||||
ToggleTimelineThumbnails = Toggle Timeline Thumbnails
|
||||
Undo = Undo
|
||||
UndoHistory = Undo History
|
||||
@ -845,6 +847,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
|
||||
|
@ -25,7 +25,9 @@ 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, 16-bit, or 32-bit
|
||||
value and there are masks related to the meaning of each bit.
|
||||
|
||||
## Introduction
|
||||
|
||||
@ -146,6 +148,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 +176,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 = 3
|
||||
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 +203,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 pixel/tile reference (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)
|
||||
|
||||
@ -326,6 +338,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
|
||||
WORD Tiles width
|
||||
WORD Tiles height
|
||||
BYTE[36] Reserved
|
||||
STRING Name of the tileset
|
||||
+ If flag 1 is set
|
||||
STRING Name of the external file (path relative to this file, in the best case)
|
||||
DWORD Tileset ID in the external file
|
||||
+ If flag 2 is set
|
||||
DWORD Number of tiles to read
|
||||
+ For each tile
|
||||
DWORD Tile flags (0)
|
||||
DWORD Compressed data length
|
||||
PIXEL[] Read tile image (tile width x height compressed pixels, see NOTE.3)
|
||||
|
||||
### Notes
|
||||
|
||||
#### NOTE.1
|
||||
@ -356,6 +388,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,
|
||||
|
@ -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
|
||||
@ -184,6 +185,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
|
||||
@ -288,6 +291,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
|
||||
@ -417,6 +421,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
|
||||
@ -446,6 +452,8 @@ add_library(app-lib
|
||||
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
|
||||
@ -485,6 +493,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
|
||||
@ -593,7 +602,7 @@ add_library(app-lib
|
||||
ui/layer_frame_comboboxes.cpp
|
||||
util/autocrop.cpp
|
||||
util/buffer_region.cpp
|
||||
util/create_cel_copy.cpp
|
||||
util/cel_ops.cpp
|
||||
util/expand_cel_canvas.cpp
|
||||
util/filetoks.cpp
|
||||
util/freetype_utils.cpp
|
||||
|
92
src/app/cmd/add_tile.cpp
Normal file
92
src/app/cmd/add_tile.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
// 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/add_tile.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(-1)
|
||||
, 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);
|
||||
|
||||
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::addTile(doc::Tileset* tileset, const doc::ImageRef& image)
|
||||
{
|
||||
m_tileIndex = tileset->add(image);
|
||||
|
||||
tileset->sprite()->incrementVersion();
|
||||
tileset->incrementVersion();
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
56
src/app/cmd/add_tile.h
Normal file
56
src/app/cmd/add_tile.h
Normal file
@ -0,0 +1,56 @@
|
||||
// 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_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;
|
||||
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
|
88
src/app/cmd/add_tileset.cpp
Normal file
88
src/app/cmd/add_tileset.cpp
Normal 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/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()
|
||||
{
|
||||
SubObjectsFromSprite io(sprite());
|
||||
doc::Tileset* tileset = read_tileset(m_stream, &io);
|
||||
|
||||
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
53
src/app/cmd/add_tileset.h
Normal 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
|
@ -19,7 +19,7 @@
|
||||
#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"
|
||||
@ -32,8 +32,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 +44,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);
|
||||
@ -114,7 +114,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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
@ -11,6 +12,7 @@
|
||||
#include "app/cmd/crop_cel.h"
|
||||
|
||||
#include "doc/cel.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/primitives.h"
|
||||
|
||||
namespace app {
|
||||
@ -22,7 +24,9 @@ CropCel::CropCel(Cel* cel, const gfx::Rect& newBounds)
|
||||
: WithCel(cel)
|
||||
, m_oldOrigin(cel->position())
|
||||
, m_newOrigin(newBounds.origin())
|
||||
, m_oldBounds(cel->bounds())
|
||||
// Instead of using cel->bounds() we use the image size because it
|
||||
// works for tilemaps too.
|
||||
, m_oldBounds(cel->position(), cel->image()->size())
|
||||
, m_newBounds(newBounds)
|
||||
{
|
||||
m_oldBounds.offset(-m_newOrigin);
|
||||
@ -59,7 +63,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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -20,7 +20,7 @@
|
||||
#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"
|
||||
@ -119,7 +119,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));
|
||||
|
42
src/app/cmd/remove_tile.cpp
Normal file
42
src/app/cmd/remove_tile.cpp
Normal 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
30
src/app/cmd/remove_tile.h
Normal 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
|
43
src/app/cmd/remove_tileset.cpp
Normal file
43
src/app/cmd/remove_tileset.cpp
Normal 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
|
31
src/app/cmd/remove_tileset.h
Normal file
31
src/app/cmd/remove_tileset.h
Normal 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
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -25,9 +25,7 @@ using namespace doc;
|
||||
TrimCel::TrimCel(Cel* cel)
|
||||
{
|
||||
gfx::Rect newBounds;
|
||||
if (algorithm::shrink_bounds(cel->image(), newBounds,
|
||||
cel->image()->maskColor())) {
|
||||
newBounds.offset(cel->position());
|
||||
if (algorithm::shrink_cel_bounds(cel, cel->image()->maskColor(), newBounds)) {
|
||||
if (cel->bounds() != newBounds) {
|
||||
add(new cmd::CropCel(cel, newBounds));
|
||||
}
|
||||
|
@ -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) {
|
||||
|
31
src/app/cmd/with_tileset.cpp
Normal file
31
src/app/cmd/with_tileset.cpp
Normal 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
|
32
src/app/cmd/with_tileset.h
Normal file
32
src/app/cmd/with_tileset.h
Normal 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
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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"
|
||||
@ -30,6 +31,7 @@
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "fmt/format.h"
|
||||
@ -52,6 +54,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" };
|
||||
@ -63,7 +66,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();
|
||||
@ -98,6 +101,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())
|
||||
@ -232,6 +239,20 @@ void NewLayerCommand::onExecute(Context* context)
|
||||
layer->setReference(true);
|
||||
afterBackground = true;
|
||||
break;
|
||||
case Type::TilemapLayer: {
|
||||
// TODO show a dialog to configure the grid
|
||||
auto grid = doc::Grid::MakeRect(gfx::Size(16, 16));
|
||||
auto tileset = new Tileset(sprite, grid, 0);
|
||||
auto addTileset = new cmd::AddTileset(sprite, tileset);
|
||||
tx(addTileset);
|
||||
|
||||
auto tsi = addTileset->tilesetIndex();
|
||||
|
||||
layer = new LayerTilemap(sprite, tsi);
|
||||
layer->setName(name);
|
||||
api.addLayer(parent, layer, parent->lastLayer());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(layer);
|
||||
@ -460,6 +481,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";
|
||||
}
|
||||
|
42
src/app/commands/cmd_toggle_tiles_mode.cpp
Normal file
42
src/app/commands/cmd_toggle_tiles_mode.cpp
Normal 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/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->inTilesMode();
|
||||
}
|
||||
|
||||
void onExecute(Context* context) override {
|
||||
auto colorBar = ColorBar::instance();
|
||||
colorBar->setTilesMode(!colorBar->inTilesMode());
|
||||
}
|
||||
};
|
||||
|
||||
Command* CommandFactory::createToggleTilesModeCommand()
|
||||
{
|
||||
return new ToggleTilesModeCommand;
|
||||
}
|
||||
|
||||
} // namespace app
|
@ -156,6 +156,7 @@ FOR_EACH_COMMAND(SymmetryMode)
|
||||
FOR_EACH_COMMAND(TiledMode)
|
||||
FOR_EACH_COMMAND(Timeline)
|
||||
FOR_EACH_COMMAND(TogglePreview)
|
||||
FOR_EACH_COMMAND(ToggleTilesMode)
|
||||
FOR_EACH_COMMAND(ToggleTimelineThumbnails)
|
||||
FOR_EACH_COMMAND(UndoHistory)
|
||||
FOR_EACH_COMMAND(UnlinkCel)
|
||||
|
@ -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"
|
||||
@ -213,6 +213,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();
|
||||
@ -395,7 +402,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()));
|
||||
|
@ -34,6 +34,7 @@ namespace doc {
|
||||
class Mask;
|
||||
class MaskBoundaries;
|
||||
class Sprite;
|
||||
class Tileset;
|
||||
}
|
||||
|
||||
namespace gfx {
|
||||
@ -103,6 +104,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
|
||||
|
@ -277,8 +277,8 @@ bool DocApi::cropCel(LayerImage* layer,
|
||||
// Try to shrink the image ignoring transparent borders
|
||||
gfx::Rect frameBounds;
|
||||
if (doc::algorithm::shrink_bounds(newImage.get(),
|
||||
frameBounds,
|
||||
newImage->maskColor())) {
|
||||
newImage->maskColor(), nullptr,
|
||||
frameBounds)) {
|
||||
// In this case the new cel image can be even smaller
|
||||
if (frameBounds != newImage->bounds()) {
|
||||
newImage = ImageRef(
|
||||
@ -534,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);
|
||||
}
|
||||
@ -692,7 +693,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()));
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -955,7 +955,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).
|
||||
|
||||
|
@ -76,6 +76,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() { }
|
||||
};
|
||||
|
@ -127,6 +127,13 @@ 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_tileset_chunks(FILE* f,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const Tilesets* tilesets);
|
||||
static void ase_file_write_tileset_chunk(FILE* f,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const Tileset* tileset,
|
||||
const tileset_index si);
|
||||
static bool ase_has_groups(LayerGroup* group);
|
||||
static void ase_ungroup_all(LayerGroup* group);
|
||||
|
||||
@ -305,6 +312,9 @@ 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, &frame_header, sprite->tilesets());
|
||||
|
||||
// Write layer chunks
|
||||
for (Layer* child : sprite->root()->layers())
|
||||
ase_file_write_layers(f, &frame_header, child, 0);
|
||||
@ -315,7 +325,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(),
|
||||
@ -585,8 +595,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);
|
||||
@ -602,6 +619,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);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -624,14 +645,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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -643,12 +662,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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -659,12 +676,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
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -685,7 +717,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, const Image* image)
|
||||
{
|
||||
PixelIO<ImageTraits> pixel_io;
|
||||
z_stream zstream;
|
||||
@ -734,6 +766,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, const Image* image)
|
||||
{
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB:
|
||||
write_compressed_image_templ<RgbTraits>(f, image);
|
||||
break;
|
||||
|
||||
case IMAGE_GRAYSCALE:
|
||||
write_compressed_image_templ<GrayscaleTraits>(f, image);
|
||||
break;
|
||||
|
||||
case IMAGE_INDEXED:
|
||||
write_compressed_image_templ<IndexedTraits>(f, image);
|
||||
break;
|
||||
|
||||
case IMAGE_TILEMAP:
|
||||
write_compressed_image_templ<TilemapTraits>(f, image);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Cel Chunk
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -762,7 +815,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);
|
||||
@ -795,6 +850,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 {
|
||||
@ -812,27 +868,12 @@ 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;
|
||||
}
|
||||
write_compressed_image(f, image);
|
||||
}
|
||||
else {
|
||||
// Width and height
|
||||
@ -841,6 +882,23 @@ 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);
|
||||
|
||||
write_compressed_image(f, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1085,6 +1143,48 @@ static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
|
||||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_tileset_chunks(FILE* f,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const Tilesets* tilesets)
|
||||
{
|
||||
tileset_index si = 0;
|
||||
for (const Tileset* tileset : *tilesets) {
|
||||
ase_file_write_tileset_chunk(f, frame_header, tileset, si);
|
||||
++si;
|
||||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_tileset_chunk(FILE* f,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const Tileset* tileset,
|
||||
const tileset_index si)
|
||||
{
|
||||
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TILESET);
|
||||
|
||||
fputl(si, f); // Tileset ID
|
||||
fputl(2, f); // Tileset Flags (2=include tiles inside file)
|
||||
fputw(tileset->grid().tileSize().w, f);
|
||||
fputw(tileset->grid().tileSize().h, f);
|
||||
ase_file_write_padding(f, 20);
|
||||
ase_file_write_string(f, tileset->name()); // tileset name
|
||||
|
||||
// Flag 2 = tileset
|
||||
fputl(tileset->size(), f);
|
||||
for (tile_index i=0; i<tileset->size(); ++i) {
|
||||
fputl(0, f); // Flags (zero)
|
||||
size_t beg = ftell(f);
|
||||
fputl(0, f);
|
||||
|
||||
ImageRef tileImg = tileset->get(i);
|
||||
write_compressed_image(f, tileImg.get());
|
||||
|
||||
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()) {
|
||||
|
@ -597,7 +597,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)
|
||||
|
@ -693,7 +693,7 @@ private:
|
||||
try {
|
||||
ImageRef celImage(Image::createCopy(m_currentImage.get()));
|
||||
try {
|
||||
cel->data()->setImage(celImage);
|
||||
cel->data()->setImage(celImage, m_layer);
|
||||
}
|
||||
catch (...) {
|
||||
throw;
|
||||
|
@ -311,6 +311,13 @@ int App_useTool(lua_State* L)
|
||||
if (!brush)
|
||||
brush.reset(new Brush(BrushType::kCircleBrushType, 1, 0));
|
||||
|
||||
// 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) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/script/security.h"
|
||||
#include "app/sprite_sheet_type.h"
|
||||
#include "app/tileset_mode.h"
|
||||
#include "base/chrono.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
@ -152,6 +153,7 @@ void register_color_space_class(lua_State* L);
|
||||
void register_dialog_class(lua_State* L);
|
||||
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);
|
||||
@ -172,6 +174,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);
|
||||
|
||||
@ -250,6 +254,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);
|
||||
@ -341,6 +346,14 @@ Engine::Engine()
|
||||
setfield_integer(L, "GRAYA", TARGET_GRAY_CHANNEL | TARGET_ALPHA_CHANNEL);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, "TilesetMode");
|
||||
setfield_integer(L, "LOCKED", TilesetMode::Locked);
|
||||
setfield_integer(L, "MODIFY_EXISTENT", TilesetMode::ModifyExistent);
|
||||
setfield_integer(L, "GENERATE_ALL_TILES", TilesetMode::GenerateAllTiles);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Register classes/prototypes
|
||||
register_brush_class(L);
|
||||
register_cel_class(L);
|
||||
@ -350,6 +363,7 @@ Engine::Engine()
|
||||
register_dialog_class(L);
|
||||
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);
|
||||
@ -370,6 +384,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);
|
||||
|
||||
|
@ -44,6 +44,8 @@ namespace doc {
|
||||
class Palette;
|
||||
class Sprite;
|
||||
class Tag;
|
||||
class Tileset;
|
||||
class Tilesets;
|
||||
class WithUserData;
|
||||
}
|
||||
|
||||
@ -142,6 +144,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, tools::Tool* tool);
|
||||
void push_userdata(lua_State* L, doc::WithUserData* userData);
|
||||
void push_version(lua_State* L, const base::Version& ver);
|
||||
|
94
src/app/script/grid_class.cpp
Normal file
94
src/app/script/grid_class.cpp
Normal 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
|
@ -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;
|
||||
}
|
||||
@ -551,6 +556,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);
|
||||
|
@ -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);
|
||||
@ -366,6 +384,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 },
|
||||
@ -376,6 +395,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 }
|
||||
};
|
||||
|
||||
|
@ -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 }
|
||||
};
|
||||
|
||||
|
@ -662,6 +662,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);
|
||||
@ -799,6 +806,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 },
|
||||
|
104
src/app/script/tileset_class.cpp
Normal file
104
src/app/script/tileset_class.cpp
Normal 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
|
62
src/app/script/tilesets_class.cpp
Normal file
62
src/app/script/tilesets_class.cpp
Normal 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
|
@ -14,8 +14,11 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/base.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 {
|
||||
@ -81,6 +84,15 @@ void Site::range(const DocRange& range)
|
||||
|
||||
gfx::Rect Site::gridBounds() const
|
||||
{
|
||||
if (m_layer && m_layer->isTilemap()) {
|
||||
const Cel* cel = (m_layer ? m_layer->cel(m_frame): nullptr);
|
||||
if (cel) {
|
||||
const Grid& grid = static_cast<LayerTilemap*>(m_layer)->tileset()->grid();
|
||||
return gfx::Rect(grid.tileOffset() + cel->bounds().origin(),
|
||||
grid.tileSize());
|
||||
}
|
||||
}
|
||||
|
||||
gfx::Rect bounds;
|
||||
if (m_sprite) {
|
||||
bounds = m_sprite->gridBounds();
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/doc_range.h"
|
||||
#include "app/tileset_mode.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/palette_picks.h"
|
||||
#include "doc/selected_objects.h"
|
||||
@ -49,7 +50,8 @@ namespace app {
|
||||
, m_document(nullptr)
|
||||
, m_sprite(nullptr)
|
||||
, m_layer(nullptr)
|
||||
, m_frame(0) { }
|
||||
, m_frame(0)
|
||||
, m_tilesetMode(TilesetMode::Locked) { }
|
||||
|
||||
const Focus focus() const { return m_focus; }
|
||||
bool inEditor() const { return m_focus == InEditor; }
|
||||
@ -101,6 +103,9 @@ namespace app {
|
||||
|
||||
gfx::Rect gridBounds() const;
|
||||
|
||||
void tilesetMode(const TilesetMode& mode) { m_tilesetMode = mode; }
|
||||
const TilesetMode& tilesetMode() const { return m_tilesetMode; }
|
||||
|
||||
private:
|
||||
Focus m_focus;
|
||||
Doc* m_document;
|
||||
@ -110,6 +115,7 @@ namespace app {
|
||||
DocRange m_range;
|
||||
doc::PalettePicks m_selectedColors;
|
||||
doc::SelectedObjects m_selectedSlices;
|
||||
TilesetMode m_tilesetMode;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
24
src/app/tileset_mode.h
Normal file
24
src/app/tileset_mode.h
Normal file
@ -0,0 +1,24 @@
|
||||
// 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_TILES_MODE_H_INCLUDED
|
||||
#define APP_TILES_MODE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
namespace app {
|
||||
|
||||
// These modes are available edition modes for the tileset when an
|
||||
// tilemap is edited.
|
||||
enum class TilesetMode {
|
||||
Locked, // Cannot edit the tileset (don't modify or add new tiles)
|
||||
ModifyExistent, // Modify existent tiles (don't create new ones)
|
||||
//GenerateNewTiles, // Auto-generate new tiles, edit existent ones
|
||||
GenerateAllTiles, // Auto-generate new and modified tiles
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -9,6 +9,7 @@
|
||||
#define APP_TRANSACTION_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd_transaction.h"
|
||||
#include "app/doc_observer.h"
|
||||
|
||||
#include <string>
|
||||
@ -16,7 +17,6 @@
|
||||
namespace app {
|
||||
|
||||
class Cmd;
|
||||
class CmdTransaction;
|
||||
class Context;
|
||||
class DocRange;
|
||||
class DocUndo;
|
||||
@ -82,6 +82,8 @@ namespace app {
|
||||
|
||||
void execute(Cmd* cmd);
|
||||
|
||||
CmdTransaction* cmds() { return m_cmds; }
|
||||
|
||||
private:
|
||||
// List of changes during the execution of this transaction
|
||||
enum class Changes {
|
||||
|
@ -73,6 +73,10 @@ namespace app {
|
||||
return *m_transaction;
|
||||
}
|
||||
|
||||
operator CmdTransaction*() {
|
||||
return m_transaction->cmds();
|
||||
}
|
||||
|
||||
private:
|
||||
Doc* m_doc;
|
||||
Transaction* m_transaction;
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/cmd/remap_colors.h"
|
||||
#include "app/cmd/remove_tile.h"
|
||||
#include "app/cmd/replace_image.h"
|
||||
#include "app/cmd/set_palette.h"
|
||||
#include "app/cmd/set_transparent_color.h"
|
||||
@ -27,8 +28,8 @@
|
||||
#include "app/commands/quick_command.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc_undo.h"
|
||||
#include "app/doc_api.h"
|
||||
#include "app/doc_undo.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/modules/editors.h"
|
||||
@ -56,6 +57,7 @@
|
||||
#include "doc/cels_range.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/remap.h"
|
||||
@ -86,6 +88,12 @@ enum class PalButton {
|
||||
MAX
|
||||
};
|
||||
|
||||
enum class TilesButton {
|
||||
MODE,
|
||||
AUTO,
|
||||
MAX
|
||||
};
|
||||
|
||||
using namespace app::skin;
|
||||
using namespace ui;
|
||||
|
||||
@ -111,13 +119,24 @@ ColorBar::ScrollableView::ScrollableView()
|
||||
|
||||
void ColorBar::ScrollableView::onInitTheme(InitThemeEvent& ev)
|
||||
{
|
||||
auto hbar = horizontalBar();
|
||||
auto vbar = verticalBar();
|
||||
setup_mini_look(hbar);
|
||||
setup_mini_look(vbar);
|
||||
|
||||
View::onInitTheme(ev);
|
||||
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
setStyle(theme->styles.colorbarView());
|
||||
|
||||
horizontalBar()->setStyle(theme->styles.miniScrollbar());
|
||||
verticalBar()->setStyle(theme->styles.miniScrollbar());
|
||||
horizontalBar()->setThumbStyle(theme->styles.miniScrollbarThumb());
|
||||
verticalBar()->setThumbStyle(theme->styles.miniScrollbarThumb());
|
||||
hbar->setStyle(theme->styles.miniScrollbar());
|
||||
vbar->setStyle(theme->styles.miniScrollbar());
|
||||
hbar->setThumbStyle(theme->styles.miniScrollbarThumb());
|
||||
vbar->setThumbStyle(theme->styles.miniScrollbarThumb());
|
||||
|
||||
const int scrollBarWidth = theme->dimensions.miniScrollbarSize();
|
||||
hbar->setBarWidth(scrollBarWidth);
|
||||
vbar->setBarWidth(scrollBarWidth);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -128,8 +147,10 @@ ColorBar* ColorBar::m_instance = NULL;
|
||||
ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
: Box(align)
|
||||
, m_buttons(int(PalButton::MAX))
|
||||
, m_tilesButtons(int(TilesButton::MAX))
|
||||
, m_splitter(Splitter::ByPercentage, VERTICAL)
|
||||
, m_paletteView(true, PaletteView::FgBgColors, this, 16)
|
||||
, m_tilesView(true, PaletteView::FgBgTiles, this, 16)
|
||||
, m_remapButton("Remap")
|
||||
, m_selector(ColorSelector::NONE)
|
||||
, m_tintShadeTone(nullptr)
|
||||
@ -144,9 +165,12 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
, m_fromFgButton(false)
|
||||
, m_fromBgButton(false)
|
||||
, m_lastDocument(nullptr)
|
||||
, m_lastTilesetId(doc::NullId)
|
||||
, m_ascending(true)
|
||||
, m_lastButtons(kButtonLeft)
|
||||
, m_editMode(false)
|
||||
, m_tilesMode(false)
|
||||
, m_autoTilesMode(true)
|
||||
, m_redrawTimer(250, this)
|
||||
, m_redrawAll(false)
|
||||
, m_implantChange(false)
|
||||
@ -161,17 +185,24 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
m_buttons.addItem(theme->parts.palPresets());
|
||||
m_buttons.addItem(theme->parts.palOptions());
|
||||
|
||||
m_tilesButtons.addItem("Tiles");
|
||||
m_tilesButtons.addItem("Auto");
|
||||
m_tilesButtons.setVisible(false);
|
||||
m_tilesButtons.getItem((int)TilesButton::AUTO)->setHotColor(theme->colors.editPalFace());
|
||||
|
||||
m_paletteView.setColumns(8);
|
||||
m_tilesView.setColumns(8);
|
||||
|
||||
setup_mini_look(m_scrollableView.horizontalBar());
|
||||
setup_mini_look(m_scrollableView.verticalBar());
|
||||
|
||||
m_scrollableView.attachToView(&m_paletteView);
|
||||
m_scrollableView.setExpansive(true);
|
||||
m_scrollablePalView.attachToView(&m_paletteView);
|
||||
m_scrollableTilesView.attachToView(&m_tilesView);
|
||||
m_scrollablePalView.setExpansive(true);
|
||||
m_scrollableTilesView.setExpansive(true);
|
||||
|
||||
m_scrollableTilesView.setVisible(false);
|
||||
m_remapButton.setVisible(false);
|
||||
|
||||
m_palettePlaceholder.addChild(&m_scrollableView);
|
||||
m_palettePlaceholder.addChild(&m_scrollablePalView);
|
||||
m_palettePlaceholder.addChild(&m_scrollableTilesView);
|
||||
m_palettePlaceholder.addChild(&m_remapButton);
|
||||
m_splitter.setId("palette_spectrum_splitter");
|
||||
m_splitter.setPosition(80);
|
||||
@ -183,6 +214,7 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
Preferences::instance().colorBar.selector());
|
||||
|
||||
addChild(&m_buttons);
|
||||
addChild(&m_tilesButtons);
|
||||
addChild(&m_splitter);
|
||||
|
||||
HBox* fgBox = new HBox;
|
||||
@ -207,6 +239,7 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
m_bgWarningIcon->Click.connect(base::Bind<void>(&ColorBar::onFixWarningClick, this, &m_bgColor, m_bgWarningIcon));
|
||||
m_redrawTimer.Tick.connect(base::Bind<void>(&ColorBar::onTimerTick, this));
|
||||
m_buttons.ItemChange.connect(base::Bind<void>(&ColorBar::onPaletteButtonClick, this));
|
||||
m_tilesButtons.ItemChange.connect(base::Bind<void>(&ColorBar::onTilesButtonClick, this));
|
||||
|
||||
tooltipManager->addTooltipFor(&m_fgColor, "Foreground color", LEFT);
|
||||
tooltipManager->addTooltipFor(&m_bgColor, "Background color", LEFT);
|
||||
@ -230,17 +263,16 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
m_buttons.setMaxSize(gfx::Size(m_buttons.sizeHint().w,
|
||||
theme->dimensions.colorBarButtonsHeight()));
|
||||
|
||||
int scrollBarWidth = theme->dimensions.miniScrollbarSize();
|
||||
m_scrollableView.horizontalBar()->setBarWidth(scrollBarWidth);
|
||||
m_scrollableView.verticalBar()->setBarWidth(scrollBarWidth);
|
||||
|
||||
// Change color-bar background color (not ColorBar::setBgColor)
|
||||
this->Widget::setBgColor(theme->colors.tabActiveFace());
|
||||
m_paletteView.setBgColor(theme->colors.tabActiveFace());
|
||||
m_paletteView.setBoxSize(
|
||||
Preferences::instance().colorBar.boxSize());
|
||||
m_paletteView.setBoxSize(Preferences::instance().colorBar.boxSize());
|
||||
m_paletteView.initTheme();
|
||||
|
||||
m_tilesView.setBgColor(theme->colors.tabActiveFace());
|
||||
m_tilesView.setBoxSize(Preferences::instance().colorBar.tilesBoxSize());
|
||||
m_tilesView.initTheme();
|
||||
|
||||
// Styles
|
||||
m_splitter.setStyle(theme->styles.workspaceSplitter());
|
||||
|
||||
@ -420,6 +452,66 @@ void ColorBar::setEditMode(bool state)
|
||||
m_paletteView.deselect();
|
||||
}
|
||||
|
||||
bool ColorBar::inTilesMode() const
|
||||
{
|
||||
return
|
||||
(m_tilesMode &&
|
||||
m_tilesButtons.isVisible() &&
|
||||
m_lastDocument &&
|
||||
m_lastDocument->sprite());
|
||||
}
|
||||
|
||||
void ColorBar::setTilesMode(bool state)
|
||||
{
|
||||
const Site site = UIContext::instance()->activeSite();
|
||||
const bool isTilemap = (site.layer() && site.layer()->isTilemap());
|
||||
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
ButtonSet::Item* item = m_tilesButtons.getItem((int)TilesButton::MODE);
|
||||
|
||||
m_tilesMode = state;
|
||||
item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
|
||||
|
||||
if (state && isTilemap) {
|
||||
manager()->freeWidget(&m_paletteView);
|
||||
m_scrollablePalView.setVisible(false);
|
||||
m_scrollableTilesView.setVisible(true);
|
||||
}
|
||||
else {
|
||||
manager()->freeWidget(&m_tilesView);
|
||||
m_scrollablePalView.setVisible(true);
|
||||
m_scrollableTilesView.setVisible(false);
|
||||
}
|
||||
|
||||
layout();
|
||||
}
|
||||
|
||||
bool ColorBar::inAutoTilesMode() const
|
||||
{
|
||||
return
|
||||
(m_autoTilesMode &&
|
||||
m_tilesButtons.isVisible() &&
|
||||
m_lastDocument &&
|
||||
m_lastDocument->sprite());
|
||||
}
|
||||
|
||||
void ColorBar::setAutoTilesMode(bool state)
|
||||
{
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
ButtonSet::Item* item = m_tilesButtons.getItem((int)TilesButton::AUTO);
|
||||
|
||||
m_autoTilesMode = state;
|
||||
item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
|
||||
}
|
||||
|
||||
TilesetMode ColorBar::tilesetMode() const
|
||||
{
|
||||
if (inAutoTilesMode())
|
||||
return TilesetMode::GenerateAllTiles;
|
||||
else
|
||||
return TilesetMode::ModifyExistent;
|
||||
}
|
||||
|
||||
void ColorBar::onActiveSiteChange(const Site& site)
|
||||
{
|
||||
if (m_lastDocument != site.document()) {
|
||||
@ -433,6 +525,29 @@ void ColorBar::onActiveSiteChange(const Site& site)
|
||||
|
||||
hideRemap();
|
||||
}
|
||||
|
||||
bool isTilemap = false;
|
||||
if (site.layer()) {
|
||||
isTilemap = site.layer()->isTilemap();
|
||||
if (m_tilesButtons.isVisible() != isTilemap) {
|
||||
m_tilesButtons.setVisible(isTilemap);
|
||||
layout();
|
||||
}
|
||||
|
||||
if (isTilemap) {
|
||||
doc::ObjectId newTilesetId =
|
||||
static_cast<const doc::LayerTilemap*>(site.layer())->tileset()->id();
|
||||
if (m_lastTilesetId != newTilesetId) {
|
||||
m_lastTilesetId = newTilesetId;
|
||||
m_scrollableTilesView.updateView();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isTilemap) {
|
||||
m_lastTilesetId = doc::NullId;
|
||||
if (inTilesMode())
|
||||
setTilesMode(m_tilesMode);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorBar::onGeneralUpdate(DocEvent& ev)
|
||||
@ -441,6 +556,13 @@ void ColorBar::onGeneralUpdate(DocEvent& ev)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void ColorBar::onTilesetChanged(DocEvent& ev)
|
||||
{
|
||||
m_tilesButtons.deselectItems();
|
||||
if (m_scrollableTilesView.isVisible())
|
||||
m_scrollableTilesView.updateView();
|
||||
}
|
||||
|
||||
void ColorBar::onAppPaletteChange()
|
||||
{
|
||||
COLOR_BAR_TRACE("ColorBar::onAppPaletteChange()\n");
|
||||
@ -508,6 +630,24 @@ void ColorBar::onPaletteButtonClick()
|
||||
}
|
||||
}
|
||||
|
||||
void ColorBar::onTilesButtonClick()
|
||||
{
|
||||
int item = m_tilesButtons.selectedItem();
|
||||
m_tilesButtons.deselectItems();
|
||||
|
||||
switch (static_cast<TilesButton>(item)) {
|
||||
|
||||
case TilesButton::MODE:
|
||||
setTilesMode(!inTilesMode());
|
||||
break;
|
||||
|
||||
case TilesButton::AUTO:
|
||||
setAutoTilesMode(!inAutoTilesMode());
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ColorBar::onRemapButtonClick()
|
||||
{
|
||||
ASSERT(m_oldPalette);
|
||||
@ -589,6 +729,10 @@ void ColorBar::onPaletteViewIndexChange(int index, ui::MouseButtons buttons)
|
||||
{
|
||||
COLOR_BAR_TRACE("ColorBar::onPaletteViewIndexChange(%d)\n", index);
|
||||
|
||||
// TODO select tiles to stamp
|
||||
if (inTilesMode())
|
||||
return;
|
||||
|
||||
base::ScopedValue<bool> lock(m_fromPalView, true, m_fromPalView);
|
||||
|
||||
app::Color color = app::Color::fromIndex(index);
|
||||
@ -659,7 +803,10 @@ void ColorBar::setTransparentIndex(int index)
|
||||
|
||||
void ColorBar::onPaletteViewChangeSize(int boxsize)
|
||||
{
|
||||
Preferences::instance().colorBar.boxSize(boxsize);
|
||||
if (inTilesMode())
|
||||
Preferences::instance().colorBar.tilesBoxSize(boxsize);
|
||||
else
|
||||
Preferences::instance().colorBar.boxSize(boxsize);
|
||||
}
|
||||
|
||||
void ColorBar::onPaletteViewPasteColors(
|
||||
@ -714,6 +861,26 @@ app::Color ColorBar::onPaletteViewGetBackgroundIndex()
|
||||
return getBgColor();
|
||||
}
|
||||
|
||||
void ColorBar::onPaletteViewClearTiles(const doc::PalettePicks& picks)
|
||||
{
|
||||
try {
|
||||
ContextWriter writer(UIContext::instance(), 500);
|
||||
Sprite* sprite = writer.sprite();
|
||||
ASSERT(writer.layer()->isTilemap());
|
||||
if (sprite) {
|
||||
auto tileset = m_tilesView.tileset();
|
||||
|
||||
Tx tx(writer.context(), "Clear Tiles", ModifyDocument);
|
||||
for (auto ti : picks)
|
||||
tx(new cmd::RemoveTile(tileset, ti));
|
||||
tx.commit();
|
||||
}
|
||||
}
|
||||
catch (base::Exception& e) {
|
||||
Console::showException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorBar::onFgColorChangeFromPreferences()
|
||||
{
|
||||
COLOR_BAR_TRACE("ColorBar::onFgColorChangeFromPreferences() -> %s\n",
|
||||
@ -998,30 +1165,44 @@ bool ColorBar::onCanClear(Context* ctx)
|
||||
|
||||
bool ColorBar::onCut(Context* ctx)
|
||||
{
|
||||
m_paletteView.cutToClipboard();
|
||||
if (m_tilesMode)
|
||||
m_tilesView.cutToClipboard();
|
||||
else
|
||||
m_paletteView.cutToClipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ColorBar::onCopy(Context* ctx)
|
||||
{
|
||||
m_paletteView.copyToClipboard();
|
||||
if (m_tilesMode)
|
||||
m_tilesView.copyToClipboard();
|
||||
else
|
||||
m_paletteView.copyToClipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ColorBar::onPaste(Context* ctx)
|
||||
{
|
||||
m_paletteView.pasteFromClipboard();
|
||||
if (m_tilesMode)
|
||||
m_tilesView.pasteFromClipboard();
|
||||
else
|
||||
m_paletteView.pasteFromClipboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ColorBar::onClear(Context* ctx)
|
||||
{
|
||||
m_paletteView.clearSelection();
|
||||
if (m_tilesMode)
|
||||
m_tilesView.clearSelection();
|
||||
else
|
||||
m_paletteView.clearSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ColorBar::onCancel(Context* ctx)
|
||||
{
|
||||
m_tilesView.deselect();
|
||||
m_tilesView.discardClipboardSelection();
|
||||
m_paletteView.deselect();
|
||||
m_paletteView.discardClipboardSelection();
|
||||
invalidate();
|
||||
@ -1207,6 +1388,11 @@ void ColorBar::setupTooltips(TooltipManager* tooltipManager)
|
||||
tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::PRESETS), "Presets", BOTTOM);
|
||||
tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::OPTIONS), "Options", BOTTOM);
|
||||
tooltipManager->addTooltipFor(&m_remapButton, "Matches old indexes with new indexes", BOTTOM);
|
||||
|
||||
tooltipManager->addTooltipFor(
|
||||
m_tilesButtons.getItem((int)TilesButton::MODE), "Show Tileset", BOTTOM);
|
||||
tooltipManager->addTooltipFor(
|
||||
m_tilesButtons.getItem((int)TilesButton::AUTO), "Generate new tiles automatically", BOTTOM);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -13,10 +13,12 @@
|
||||
#include "app/context_observer.h"
|
||||
#include "app/doc_observer.h"
|
||||
#include "app/docs_observer.h"
|
||||
#include "app/tileset_mode.h"
|
||||
#include "app/ui/button_set.h"
|
||||
#include "app/ui/color_button.h"
|
||||
#include "app/ui/input_chain_element.h"
|
||||
#include "app/ui/palette_view.h"
|
||||
#include "doc/object_id.h"
|
||||
#include "doc/pixel_format.h"
|
||||
#include "doc/sort_palette.h"
|
||||
#include "obs/connection.h"
|
||||
@ -78,6 +80,14 @@ namespace app {
|
||||
bool inEditMode() const;
|
||||
void setEditMode(bool state);
|
||||
|
||||
bool inTilesMode() const;
|
||||
void setTilesMode(bool state);
|
||||
|
||||
bool inAutoTilesMode() const;
|
||||
void setAutoTilesMode(bool state);
|
||||
|
||||
TilesetMode tilesetMode() const;
|
||||
|
||||
ColorButton* fgColorButton() { return &m_fgColor; }
|
||||
ColorButton* bgColorButton() { return &m_bgColor; }
|
||||
|
||||
@ -86,6 +96,7 @@ namespace app {
|
||||
|
||||
// DocObserver impl
|
||||
void onGeneralUpdate(DocEvent& ev) override;
|
||||
void onTilesetChanged(DocEvent& ev) override;
|
||||
|
||||
// InputChainElement impl
|
||||
void onNewInputPriority(InputChainElement* element,
|
||||
@ -108,6 +119,7 @@ namespace app {
|
||||
void onBeforeExecuteCommand(CommandExecutionEvent& ev);
|
||||
void onAfterExecuteCommand(CommandExecutionEvent& ev);
|
||||
void onPaletteButtonClick();
|
||||
void onTilesButtonClick();
|
||||
void onRemapButtonClick();
|
||||
void onPaletteIndexChange(PaletteIndexChangeEvent& ev);
|
||||
void onFgColorChangeFromPreferences();
|
||||
@ -131,6 +143,7 @@ namespace app {
|
||||
void onPaletteViewPasteColors(const Palette* fromPal, const doc::PalettePicks& from, const doc::PalettePicks& to) override;
|
||||
app::Color onPaletteViewGetForegroundIndex() override;
|
||||
app::Color onPaletteViewGetBackgroundIndex() override;
|
||||
void onPaletteViewClearTiles(const doc::PalettePicks& picks) override;
|
||||
|
||||
private:
|
||||
void showRemap();
|
||||
@ -157,12 +170,15 @@ namespace app {
|
||||
class WarningIcon;
|
||||
|
||||
ButtonSet m_buttons;
|
||||
ButtonSet m_tilesButtons;
|
||||
std::unique_ptr<PalettePopup> m_palettePopup;
|
||||
ui::Splitter m_splitter;
|
||||
ui::VBox m_palettePlaceholder;
|
||||
ui::VBox m_selectorPlaceholder;
|
||||
ScrollableView m_scrollableView;
|
||||
ScrollableView m_scrollablePalView;
|
||||
ScrollableView m_scrollableTilesView;
|
||||
PaletteView m_paletteView;
|
||||
PaletteView m_tilesView;
|
||||
ui::Button m_remapButton;
|
||||
ColorSelector m_selector;
|
||||
ColorTintShadeTone* m_tintShadeTone;
|
||||
@ -186,6 +202,7 @@ namespace app {
|
||||
|
||||
std::unique_ptr<doc::Palette> m_oldPalette;
|
||||
Doc* m_lastDocument;
|
||||
doc::ObjectId m_lastTilesetId;
|
||||
bool m_ascending;
|
||||
obs::scoped_connection m_beforeCmdConn;
|
||||
obs::scoped_connection m_afterCmdConn;
|
||||
@ -195,9 +212,13 @@ namespace app {
|
||||
obs::scoped_connection m_appPalChangeConn;
|
||||
ui::MouseButtons m_lastButtons;
|
||||
|
||||
// True if we the editing mode is on.
|
||||
// True if the editing mode is on.
|
||||
bool m_editMode;
|
||||
|
||||
// True if we should be putting/setting tiles.
|
||||
bool m_tilesMode;
|
||||
bool m_autoTilesMode;
|
||||
|
||||
// Timer to redraw editors after a palette change.
|
||||
ui::Timer m_redrawTimer;
|
||||
bool m_redrawAll;
|
||||
|
@ -468,6 +468,11 @@ void DocView::onLayerRestacked(DocEvent& ev)
|
||||
m_editor->invalidate();
|
||||
}
|
||||
|
||||
void DocView::onTilesetChanged(DocEvent& ev)
|
||||
{
|
||||
m_editor->invalidate();
|
||||
}
|
||||
|
||||
void DocView::onNewInputPriority(InputChainElement* element,
|
||||
const ui::Message* msg)
|
||||
{
|
||||
@ -587,6 +592,7 @@ bool DocView::onClear(Context* ctx)
|
||||
if (cels.empty()) // No cels to modify
|
||||
return false;
|
||||
|
||||
// TODO This code is similar to clipboard::cut()
|
||||
{
|
||||
Tx tx(writer.context(), "Clear");
|
||||
const bool deselectMask =
|
||||
|
@ -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
|
||||
@ -79,6 +80,7 @@ namespace app {
|
||||
void onRemoveCel(DocEvent& ev) override;
|
||||
void onTotalFramesChanged(DocEvent& ev) override;
|
||||
void onLayerRestacked(DocEvent& ev) override;
|
||||
void onTilesetChanged(DocEvent& ev) override;
|
||||
|
||||
// InputChainElement impl
|
||||
void onNewInputPriority(InputChainElement* element,
|
||||
|
@ -287,6 +287,16 @@ void DrawingState::onExposeSpritePixels(const gfx::Region& rgn)
|
||||
m_toolLoop->validateDstImage(rgn);
|
||||
}
|
||||
|
||||
bool DrawingState::getGridBounds(Editor* editor, gfx::Rect& gridBounds)
|
||||
{
|
||||
if (m_toolLoop) {
|
||||
gridBounds = m_toolLoop->getGridBounds();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void DrawingState::handleMouseMovement(const tools::Pointer& pointer)
|
||||
{
|
||||
m_mouseMoveReceived = true;
|
||||
|
@ -42,6 +42,8 @@ namespace app {
|
||||
// already drawing (viewing the real trace).
|
||||
virtual bool requireBrushPreview() override { return false; }
|
||||
|
||||
virtual bool getGridBounds(Editor* editor, gfx::Rect& gridBounds) override;
|
||||
|
||||
void initToolLoop(Editor* editor,
|
||||
const tools::Pointer& pointer);
|
||||
|
||||
|
@ -382,6 +382,7 @@ void Editor::getSite(Site* site) const
|
||||
site->sprite(m_sprite);
|
||||
site->layer(m_layer);
|
||||
site->frame(m_frame);
|
||||
|
||||
if (!m_selectedSlices.empty() &&
|
||||
getCurrentEditorInk()->isSlice()) {
|
||||
site->selectedSlices(m_selectedSlices);
|
||||
@ -393,6 +394,15 @@ void Editor::getSite(Site* site) const
|
||||
timeline->range().enabled()) {
|
||||
site->range(timeline->range());
|
||||
}
|
||||
|
||||
if (m_layer && m_layer->isTilemap()) {
|
||||
TilesetMode mode = site->tilesetMode();
|
||||
const ColorBar* colorbar = ColorBar::instance();
|
||||
ASSERT(colorbar);
|
||||
if (colorbar)
|
||||
mode = colorbar->tilesetMode();
|
||||
site->tilesetMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
Site Editor::getSite() const
|
||||
@ -737,7 +747,10 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
|
||||
|
||||
// Draw the grid
|
||||
if (m_docPref.show.grid()) {
|
||||
gfx::Rect gridrc = m_sprite->gridBounds();
|
||||
gfx::Rect gridrc;
|
||||
if (!m_state->getGridBounds(this, gridrc))
|
||||
gridrc = getSite().gridBounds();
|
||||
|
||||
if (m_proj.applyX(gridrc.w) > 2 &&
|
||||
m_proj.applyY(gridrc.h) > 2) {
|
||||
int alpha = m_docPref.grid.opacity();
|
||||
|
@ -10,6 +10,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "base/disable_copying.h"
|
||||
#include "base/shared_ptr.h"
|
||||
#include "gfx/fwd.h"
|
||||
#include "gfx/point.h"
|
||||
|
||||
#include <memory>
|
||||
@ -132,6 +134,10 @@ namespace app {
|
||||
// Called when a tag is deleted.
|
||||
virtual void onRemoveTag(Editor* editor, doc::Tag* tag) { }
|
||||
|
||||
// Used to adjust the grid origin point for temporal cels created
|
||||
// by states like DrawingState + ExpandCelCanvas.
|
||||
virtual bool getGridBounds(Editor* editor, gfx::Rect& gridBounds) { return false; }
|
||||
|
||||
private:
|
||||
DISABLE_COPYING(EditorState);
|
||||
};
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "app/ui/editor/pivot_helpers.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/cel_ops.h"
|
||||
#include "app/util/expand_cel_canvas.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/range_utils.h"
|
||||
@ -278,7 +279,9 @@ void PixelsMovement::cutMask()
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
if (writer.cel()) {
|
||||
m_tx(new cmd::ClearMask(writer.cel()));
|
||||
clear_mask_from_cel(m_tx,
|
||||
writer.cel(),
|
||||
m_site.tilesetMode());
|
||||
|
||||
// Do not trim here so we don't lost the information about all
|
||||
// linked cels related to "writer.cel()"
|
||||
@ -1080,7 +1083,9 @@ void PixelsMovement::reproduceAllTransformationsWithInnerCmds()
|
||||
for (const InnerCmd& c : m_innerCmds) {
|
||||
switch (c.type) {
|
||||
case InnerCmd::Clear:
|
||||
m_tx(new cmd::ClearMask(m_site.cel()));
|
||||
clear_mask_from_cel(m_tx,
|
||||
m_site.cel(),
|
||||
m_site.tilesetMode());
|
||||
break;
|
||||
case InnerCmd::Flip:
|
||||
flipOriginalImage(c.data.flip.type);
|
||||
|
@ -17,21 +17,28 @@
|
||||
#include "app/modules/gfx.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/modules/palettes.h"
|
||||
#include "app/site.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/palette_view.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/pal_ops.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "doc/conversion_to_surface.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/remap.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "gfx/color.h"
|
||||
#include "gfx/point.h"
|
||||
#include "os/font.h"
|
||||
#include "os/surface.h"
|
||||
#include "os/surface.h"
|
||||
#include "os/system.h"
|
||||
#include "ui/graphics.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/message.h"
|
||||
@ -51,12 +58,186 @@ namespace app {
|
||||
using namespace ui;
|
||||
using namespace app::skin;
|
||||
|
||||
// Interface used to adapt the PaletteView widget to see tilesets too.
|
||||
class AbstractPaletteViewAdapter {
|
||||
public:
|
||||
virtual ~AbstractPaletteViewAdapter() { }
|
||||
virtual int size() const = 0;
|
||||
virtual void paletteChange(doc::PalettePicks& picks) = 0;
|
||||
virtual void activeSiteChange(const Site& site, doc::PalettePicks& picks) = 0;
|
||||
virtual void clearSelection(PaletteView* paletteView,
|
||||
doc::PalettePicks& picks) = 0;
|
||||
virtual void showEntryInStatusBar(StatusBar* statusBar, int index) = 0;
|
||||
virtual void showDragInfoInStatusBar(StatusBar* statusBar, bool copy, int destIndex, int newSize) = 0;
|
||||
virtual void showResizeInfoInStatusBar(StatusBar* statusBar, int newSize) = 0;
|
||||
virtual void drawEntry(ui::Graphics* g,
|
||||
SkinTheme* theme,
|
||||
const int palIdx,
|
||||
const int offIdx,
|
||||
const int childSpacing,
|
||||
gfx::Rect& box,
|
||||
gfx::Color& negColor) = 0;
|
||||
virtual doc::Tileset* tileset() const { return nullptr; }
|
||||
};
|
||||
|
||||
// This default adapter uses the default behavior to use the
|
||||
// PaletteView as just a doc::Palette view.
|
||||
class PaletteViewAdapter : public AbstractPaletteViewAdapter {
|
||||
public:
|
||||
int size() const override { return palette()->size(); }
|
||||
void paletteChange(doc::PalettePicks& picks) override {
|
||||
picks.resize(palette()->size());
|
||||
}
|
||||
void activeSiteChange(const Site& site, doc::PalettePicks& picks) override {
|
||||
// Do nothing
|
||||
}
|
||||
void clearSelection(PaletteView* paletteView,
|
||||
doc::PalettePicks& picks) override {
|
||||
Palette palette(*this->palette());
|
||||
Palette newPalette(palette);
|
||||
newPalette.resize(MAX(1, newPalette.size() - picks.picks()));
|
||||
|
||||
Remap remap = create_remap_to_move_picks(picks, palette.size());
|
||||
for (int i=0; i<palette.size(); ++i) {
|
||||
if (!picks[i])
|
||||
newPalette.setEntry(remap[i], palette.getEntry(i));
|
||||
}
|
||||
|
||||
paletteView->setNewPalette(&palette, &newPalette, PaletteViewModification::CLEAR);
|
||||
}
|
||||
void showEntryInStatusBar(StatusBar* statusBar, int index) override {
|
||||
statusBar->showColor(
|
||||
0, "", app::Color::fromIndex(index));
|
||||
}
|
||||
void showDragInfoInStatusBar(StatusBar* statusBar, bool copy, int destIndex, int newSize) override {
|
||||
statusBar->setStatusText(
|
||||
0, "%s to %d - New Palette Size %d",
|
||||
(copy ? "Copy": "Move"),
|
||||
destIndex, newSize);
|
||||
}
|
||||
void showResizeInfoInStatusBar(StatusBar* statusBar, int newSize) override {
|
||||
statusBar->setStatusText(
|
||||
0, "New Palette Size %d", newSize);
|
||||
}
|
||||
void drawEntry(ui::Graphics* g,
|
||||
SkinTheme* theme,
|
||||
const int palIdx,
|
||||
const int offIdx,
|
||||
const int childSpacing,
|
||||
gfx::Rect& box,
|
||||
gfx::Color& negColor) override {
|
||||
doc::color_t palColor =
|
||||
(palIdx < palette()->size() ? palette()->getEntry(palIdx):
|
||||
rgba(0, 0, 0, 255));
|
||||
app::Color appColor = app::Color::fromRgb(
|
||||
rgba_getr(palColor),
|
||||
rgba_getg(palColor),
|
||||
rgba_getb(palColor),
|
||||
rgba_geta(palColor));
|
||||
|
||||
if (childSpacing > 0) {
|
||||
gfx::Color color = theme->colors.paletteEntriesSeparator();
|
||||
g->fillRect(color, gfx::Rect(box).enlarge(childSpacing));
|
||||
}
|
||||
draw_color(g, box, appColor, doc::ColorMode::RGB);
|
||||
|
||||
const gfx::Color gfxColor = gfx::rgba(
|
||||
rgba_getr(palColor),
|
||||
rgba_getg(palColor),
|
||||
rgba_getb(palColor),
|
||||
rgba_geta(palColor));
|
||||
negColor = color_utils::blackandwhite_neg(gfxColor);
|
||||
}
|
||||
private:
|
||||
doc::Palette* palette() const {
|
||||
return get_current_palette();
|
||||
}
|
||||
};
|
||||
|
||||
// This adapter makes it possible to use a PaletteView to edit a
|
||||
// doc::Tileset.
|
||||
class TilesetViewAdapter : public AbstractPaletteViewAdapter {
|
||||
public:
|
||||
int size() const override {
|
||||
if (auto t = tileset())
|
||||
return t->size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
void paletteChange(doc::PalettePicks& picks) override {
|
||||
// Do nothing
|
||||
}
|
||||
void activeSiteChange(const Site& site, doc::PalettePicks& picks) override {
|
||||
if (auto tileset = this->tileset())
|
||||
picks.resize(tileset->size());
|
||||
else
|
||||
picks.clear();
|
||||
}
|
||||
void clearSelection(PaletteView* paletteView,
|
||||
doc::PalettePicks& picks) override {
|
||||
paletteView->delegate()->onPaletteViewClearTiles(picks);
|
||||
}
|
||||
void showEntryInStatusBar(StatusBar* statusBar, int index) override {
|
||||
statusBar->setStatusText(
|
||||
0, "Tile %d", index);
|
||||
}
|
||||
void showDragInfoInStatusBar(StatusBar* statusBar, bool copy, int destIndex, int newSize) override {
|
||||
statusBar->setStatusText(
|
||||
0, "%s to %d - New Tileset Size %d",
|
||||
(copy ? "Copy": "Move"),
|
||||
destIndex, newSize);
|
||||
}
|
||||
void showResizeInfoInStatusBar(StatusBar* statusBar, int newSize) override {
|
||||
statusBar->setStatusText(
|
||||
0, "New Tileset Size %d", newSize);
|
||||
}
|
||||
void drawEntry(ui::Graphics* g,
|
||||
SkinTheme* theme,
|
||||
const int palIdx,
|
||||
const int offIdx,
|
||||
const int childSpacing,
|
||||
gfx::Rect& box,
|
||||
gfx::Color& negColor) override {
|
||||
if (childSpacing > 0) {
|
||||
gfx::Color color = theme->colors.paletteEntriesSeparator();
|
||||
g->fillRect(color, gfx::Rect(box).enlarge(childSpacing));
|
||||
}
|
||||
draw_color(g, box, app::Color::fromMask(), doc::ColorMode::RGB);
|
||||
|
||||
doc::ImageRef tileImage;
|
||||
if (auto t = this->tileset())
|
||||
tileImage = t->get(palIdx);
|
||||
if (tileImage) {
|
||||
int w = tileImage->width();
|
||||
int h = tileImage->height();
|
||||
os::Surface* surface = os::instance()->createRgbaSurface(w, h);
|
||||
doc::convert_image_to_surface(tileImage.get(), get_current_palette(),
|
||||
surface, 0, 0, 0, 0, w, h);
|
||||
g->drawRgbaSurface(surface, gfx::Rect(0, 0, w, h), box);
|
||||
surface->dispose();
|
||||
}
|
||||
negColor = gfx::rgba(255, 255, 255);
|
||||
}
|
||||
doc::Tileset* tileset() const override {
|
||||
Site site = App::instance()->context()->activeSite();
|
||||
if (site.layer() &&
|
||||
site.layer()->isTilemap()) {
|
||||
return static_cast<LayerTilemap*>(site.layer())->tileset();
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
private:
|
||||
};
|
||||
|
||||
PaletteView::PaletteView(bool editable, PaletteViewStyle style, PaletteViewDelegate* delegate, int boxsize)
|
||||
: Widget(kGenericWidget)
|
||||
, m_state(State::WAITING)
|
||||
, m_editable(editable)
|
||||
, m_style(style)
|
||||
, m_delegate(delegate)
|
||||
, m_adapter(isTiles() ? (AbstractPaletteViewAdapter*)new TilesetViewAdapter:
|
||||
(AbstractPaletteViewAdapter*)new PaletteViewAdapter)
|
||||
, m_columns(16)
|
||||
, m_boxsize(boxsize)
|
||||
, m_currentEntry(-1)
|
||||
@ -82,6 +263,9 @@ PaletteView::PaletteView(bool editable, PaletteViewStyle style, PaletteViewDeleg
|
||||
});
|
||||
}
|
||||
|
||||
if (isTiles())
|
||||
UIContext::instance()->add_observer(this);
|
||||
|
||||
InitTheme.connect(
|
||||
[this]{
|
||||
updateBorderAndChildSpacing();
|
||||
@ -89,6 +273,12 @@ PaletteView::PaletteView(bool editable, PaletteViewStyle style, PaletteViewDeleg
|
||||
initTheme();
|
||||
}
|
||||
|
||||
PaletteView::~PaletteView()
|
||||
{
|
||||
if (isTiles())
|
||||
UIContext::instance()->remove_observer(this);
|
||||
}
|
||||
|
||||
void PaletteView::setColumns(int columns)
|
||||
{
|
||||
int old_columns = m_columns;
|
||||
@ -107,7 +297,7 @@ void PaletteView::deselect()
|
||||
{
|
||||
bool invalidate = (m_selectedEntries.picks() > 0);
|
||||
|
||||
m_selectedEntries.resize(currentPalette()->size());
|
||||
m_selectedEntries.resize(m_adapter->size());
|
||||
m_selectedEntries.clear();
|
||||
|
||||
if (invalidate)
|
||||
@ -116,7 +306,7 @@ void PaletteView::deselect()
|
||||
|
||||
void PaletteView::selectColor(int index)
|
||||
{
|
||||
if (index < 0 || index >= currentPalette()->size())
|
||||
if (index < 0 || index >= m_adapter->size())
|
||||
return;
|
||||
|
||||
if (m_currentEntry != index || !m_selectedEntries[index]) {
|
||||
@ -202,17 +392,27 @@ void PaletteView::setSelectedEntries(const doc::PalettePicks& entries)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
doc::Tileset* PaletteView::tileset() const
|
||||
{
|
||||
return m_adapter->tileset();
|
||||
}
|
||||
|
||||
app::Color PaletteView::getColorByPosition(const gfx::Point& pos)
|
||||
{
|
||||
gfx::Point relPos = pos - bounds().origin();
|
||||
Palette* palette = currentPalette();
|
||||
for (int i=0; i<palette->size(); ++i) {
|
||||
for (int i=0; i<m_adapter->size(); ++i) {
|
||||
if (getPaletteEntryBounds(i).contains(relPos))
|
||||
return app::Color::fromIndex(i);
|
||||
}
|
||||
return app::Color::fromMask();
|
||||
}
|
||||
|
||||
void PaletteView::onActiveSiteChange(const Site& site)
|
||||
{
|
||||
ASSERT(isTiles());
|
||||
m_adapter->activeSiteChange(site, m_selectedEntries);
|
||||
}
|
||||
|
||||
int PaletteView::getBoxSize() const
|
||||
{
|
||||
return int(m_boxsize);
|
||||
@ -220,7 +420,10 @@ int PaletteView::getBoxSize() const
|
||||
|
||||
void PaletteView::setBoxSize(double boxsize)
|
||||
{
|
||||
m_boxsize = MID(4.0, boxsize, 32.0);
|
||||
if (isTiles())
|
||||
m_boxsize = MID(4.0, boxsize, 64.0);
|
||||
else
|
||||
m_boxsize = MID(4.0, boxsize, 32.0);
|
||||
|
||||
if (m_delegate)
|
||||
m_delegate->onPaletteViewChangeSize(int(m_boxsize));
|
||||
@ -235,21 +438,11 @@ void PaletteView::clearSelection()
|
||||
if (!m_selectedEntries.picks())
|
||||
return;
|
||||
|
||||
Palette palette(*currentPalette());
|
||||
Palette newPalette(palette);
|
||||
newPalette.resize(MAX(1, newPalette.size() - m_selectedEntries.picks()));
|
||||
|
||||
Remap remap = create_remap_to_move_picks(m_selectedEntries, palette.size());
|
||||
for (int i=0; i<palette.size(); ++i) {
|
||||
if (!m_selectedEntries[i])
|
||||
newPalette.setEntry(remap[i], palette.getEntry(i));
|
||||
}
|
||||
m_adapter->clearSelection(this, m_selectedEntries);
|
||||
|
||||
m_currentEntry = m_selectedEntries.firstPick();
|
||||
m_selectedEntries.clear();
|
||||
stopMarchingAnts();
|
||||
|
||||
setNewPalette(&palette, &newPalette, PaletteViewModification::CLEAR);
|
||||
}
|
||||
|
||||
void PaletteView::cutToClipboard()
|
||||
@ -342,7 +535,7 @@ bool PaletteView::onProcessMessage(Message* msg)
|
||||
if (m_state == State::SELECTING_COLOR &&
|
||||
m_hot.part == Hit::COLOR) {
|
||||
int idx = m_hot.color;
|
||||
idx = MID(0, idx, currentPalette()->size()-1);
|
||||
idx = MID(0, idx, m_adapter->size()-1);
|
||||
|
||||
MouseButtons buttons = mouseMsg->buttons();
|
||||
|
||||
@ -497,11 +690,11 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
|
||||
|
||||
g->fillRect(theme->colors.editorFace(), bounds);
|
||||
|
||||
// Draw palette entries
|
||||
// Draw palette/tileset entries
|
||||
int picksCount = m_selectedEntries.picks();
|
||||
int idxOffset = 0;
|
||||
int boxOffset = 0;
|
||||
int palSize = palette->size();
|
||||
int palSize = m_adapter->size();
|
||||
if (dragging && !m_copy) palSize -= picksCount;
|
||||
if (resizing) palSize = m_hot.color;
|
||||
|
||||
@ -519,7 +712,8 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
|
||||
|
||||
gfx::Rect box = getPaletteEntryBounds(i + boxOffset);
|
||||
gfx::Color negColor;
|
||||
drawEntry(g, i + idxOffset, i + boxOffset, box, negColor);
|
||||
m_adapter->drawEntry(g, theme, i + idxOffset, i + boxOffset,
|
||||
childSpacing(), box, negColor);
|
||||
const int boxsize = boxSizePx();
|
||||
|
||||
switch (m_style) {
|
||||
@ -570,7 +764,8 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
|
||||
}
|
||||
PalettePicks& picks = (dragging ? dragPicks: m_selectedEntries);
|
||||
|
||||
for (int i=0; i<palette->size(); ++i) {
|
||||
const int size = m_adapter->size();
|
||||
for (int i=0; i<size; ++i) {
|
||||
if (!m_selectedEntries[i])
|
||||
continue;
|
||||
|
||||
@ -585,13 +780,13 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
|
||||
if (dragging) {
|
||||
gfx::Rect box2 = getPaletteEntryBounds(k);
|
||||
gfx::Color negColor;
|
||||
drawEntry(g, i, k, box2, negColor);
|
||||
m_adapter->drawEntry(g, theme, i, k, childSpacing(),
|
||||
box2, negColor);
|
||||
|
||||
gfx::Color neg = color_utils::blackandwhite_neg(negColor);
|
||||
os::Font* minifont = theme->getMiniFont();
|
||||
const std::string text = base::convert_to<std::string>(k);
|
||||
g->setFont(minifont);
|
||||
g->drawText(text, neg, gfx::ColorNone,
|
||||
g->drawText(text, negColor, gfx::ColorNone,
|
||||
gfx::Point(box2.x + box2.w/2 - minifont->textLength(text)/2,
|
||||
box2.y + box2.h/2 - minifont->height()/2));
|
||||
}
|
||||
@ -654,7 +849,7 @@ void PaletteView::onResize(ui::ResizeEvent& ev)
|
||||
|
||||
void PaletteView::onSizeHint(ui::SizeHintEvent& ev)
|
||||
{
|
||||
div_t d = div(currentPalette()->size(), m_columns);
|
||||
div_t d = div(m_adapter->size(), m_columns);
|
||||
int cols = m_columns;
|
||||
int rows = d.quot + ((d.rem)? 1: 0);
|
||||
|
||||
@ -688,7 +883,7 @@ void PaletteView::update_scroll(int color)
|
||||
|
||||
scroll = view->viewScroll();
|
||||
|
||||
d = div(currentPalette()->size(), m_columns);
|
||||
d = div(m_adapter->size(), m_columns);
|
||||
cols = m_columns;
|
||||
|
||||
y = (boxsize+childSpacing()) * (color / cols);
|
||||
@ -709,7 +904,7 @@ void PaletteView::update_scroll(int color)
|
||||
|
||||
void PaletteView::onAppPaletteChange()
|
||||
{
|
||||
m_selectedEntries.resize(currentPalette()->size());
|
||||
m_adapter->paletteChange(m_selectedEntries);
|
||||
|
||||
View* view = View::getView(this);
|
||||
if (view)
|
||||
@ -734,19 +929,18 @@ PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos)
|
||||
{
|
||||
auto theme = static_cast<SkinTheme*>(this->theme());
|
||||
const int outlineWidth = theme->dimensions.paletteOutlineWidth();
|
||||
Palette* palette = currentPalette();
|
||||
const int size = m_adapter->size();
|
||||
|
||||
if (m_state == State::WAITING && m_editable) {
|
||||
// First check if the mouse is inside the selection outline.
|
||||
for (int i=0; i<palette->size(); ++i) {
|
||||
for (int i=0; i<size; ++i) {
|
||||
if (!m_selectedEntries[i])
|
||||
continue;
|
||||
|
||||
const int max = palette->size();
|
||||
bool top = (i >= m_columns && i-m_columns >= 0 ? m_selectedEntries[i-m_columns]: false);
|
||||
bool bottom = (i < max-m_columns && i+m_columns < max ? m_selectedEntries[i+m_columns]: false);
|
||||
bool left = ((i%m_columns)>0 && i-1 >= 0 ? m_selectedEntries[i-1]: false);
|
||||
bool right = ((i%m_columns)<m_columns-1 && i+1 < max ? m_selectedEntries[i+1]: false);
|
||||
bool top = (i >= m_columns && i-m_columns >= 0 ? m_selectedEntries[i-m_columns]: false);
|
||||
bool bottom = (i < size-m_columns && i+m_columns < size ? m_selectedEntries[i+m_columns]: false);
|
||||
bool left = ((i%m_columns)>0 && i-1 >= 0 ? m_selectedEntries[i-1]: false);
|
||||
bool right = ((i%m_columns)<m_columns-1 && i+1 < size ? m_selectedEntries[i+1]: false);
|
||||
|
||||
gfx::Rect box = getPaletteEntryBounds(i);
|
||||
box.enlarge(outlineWidth);
|
||||
@ -759,17 +953,18 @@ PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos)
|
||||
}
|
||||
|
||||
// Check if we are in the resize handle
|
||||
if (getPaletteEntryBounds(palette->size()).contains(pos)) {
|
||||
return Hit(Hit::RESIZE_HANDLE, palette->size());
|
||||
if (getPaletteEntryBounds(size).contains(pos)) {
|
||||
return Hit(Hit::RESIZE_HANDLE, size);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are inside a color.
|
||||
View* view = View::getView(this);
|
||||
ASSERT(view);
|
||||
gfx::Rect vp = view->viewportBounds();
|
||||
for (int i=0; ; ++i) {
|
||||
gfx::Rect box = getPaletteEntryBounds(i);
|
||||
if (i >= palette->size() && box.y2() > vp.h)
|
||||
if (i >= size && box.y2() > vp.h)
|
||||
break;
|
||||
|
||||
box.w += childSpacing();
|
||||
@ -912,11 +1107,10 @@ void PaletteView::setStatusBar()
|
||||
if ((m_hot.part == Hit::COLOR ||
|
||||
m_hot.part == Hit::OUTLINE ||
|
||||
m_hot.part == Hit::POSSIBLE_COLOR) &&
|
||||
(m_hot.color < currentPalette()->size())) {
|
||||
int i = MAX(0, m_hot.color);
|
||||
(m_hot.color < m_adapter->size())) {
|
||||
const int index = MAX(0, m_hot.color);
|
||||
|
||||
statusBar->showColor(
|
||||
0, "", app::Color::fromIndex(i));
|
||||
m_adapter->showEntryInStatusBar(statusBar, index);
|
||||
}
|
||||
else {
|
||||
statusBar->showDefaultText();
|
||||
@ -927,15 +1121,13 @@ void PaletteView::setStatusBar()
|
||||
if (m_hot.part == Hit::COLOR) {
|
||||
const int picks = m_selectedEntries.picks();
|
||||
const int destIndex = MAX(0, m_hot.color);
|
||||
const int palSize = currentPalette()->size();
|
||||
const int palSize = m_adapter->size();
|
||||
const int newPalSize =
|
||||
(m_copy ? MAX(palSize + picks, destIndex + picks):
|
||||
MAX(palSize, destIndex + picks));
|
||||
|
||||
statusBar->setStatusText(
|
||||
0, "%s to %d - New Palette Size %d",
|
||||
(m_copy ? "Copy": "Move"),
|
||||
destIndex, newPalSize);
|
||||
m_adapter->showDragInfoInStatusBar(
|
||||
statusBar, m_copy, destIndex, newPalSize);
|
||||
}
|
||||
else {
|
||||
statusBar->showDefaultText();
|
||||
@ -946,10 +1138,9 @@ void PaletteView::setStatusBar()
|
||||
if (m_hot.part == Hit::COLOR ||
|
||||
m_hot.part == Hit::POSSIBLE_COLOR ||
|
||||
m_hot.part == Hit::RESIZE_HANDLE) {
|
||||
int newPalSize = MAX(1, m_hot.color);
|
||||
statusBar->setStatusText(
|
||||
0, "New Palette Size %d",
|
||||
newPalSize);
|
||||
const int newSize = MAX(1, m_hot.color);
|
||||
|
||||
m_adapter->showResizeInfoInStatusBar(statusBar, newSize);
|
||||
}
|
||||
else {
|
||||
statusBar->showDefaultText();
|
||||
@ -1003,36 +1194,6 @@ void PaletteView::setNewPalette(doc::Palette* oldPalette,
|
||||
manager()->invalidate();
|
||||
}
|
||||
|
||||
void PaletteView::drawEntry(ui::Graphics* g,
|
||||
const int palIdx,
|
||||
const int offIdx,
|
||||
gfx::Rect& box,
|
||||
gfx::Color& negColor)
|
||||
{
|
||||
doc::color_t palColor =
|
||||
(palIdx < currentPalette()->size() ? currentPalette()->getEntry(palIdx):
|
||||
rgba(0, 0, 0, 255));
|
||||
app::Color appColor = app::Color::fromRgb(
|
||||
rgba_getr(palColor),
|
||||
rgba_getg(palColor),
|
||||
rgba_getb(palColor),
|
||||
rgba_geta(palColor));
|
||||
|
||||
if (m_withSeparator) {
|
||||
auto theme = static_cast<SkinTheme*>(this->theme());
|
||||
gfx::Color color = theme->colors.paletteEntriesSeparator();
|
||||
g->fillRect(color, gfx::Rect(box).enlarge(childSpacing()));
|
||||
}
|
||||
draw_color(g, box, appColor, doc::ColorMode::RGB);
|
||||
|
||||
const gfx::Color gfxColor = gfx::rgba(
|
||||
rgba_getr(palColor),
|
||||
rgba_getg(palColor),
|
||||
rgba_getb(palColor),
|
||||
rgba_geta(palColor));
|
||||
negColor = color_utils::blackandwhite_neg(gfxColor);
|
||||
}
|
||||
|
||||
int PaletteView::boxSizePx() const
|
||||
{
|
||||
return m_boxsize*guiscale()
|
||||
|
@ -10,19 +10,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/color.h"
|
||||
#include "app/context_observer.h"
|
||||
#include "app/ui/color_source.h"
|
||||
#include "app/ui/marching_ants.h"
|
||||
#include "doc/palette_picks.h"
|
||||
#include "doc/tile.h"
|
||||
#include "obs/connection.h"
|
||||
#include "obs/signal.h"
|
||||
#include "ui/event.h"
|
||||
#include "ui/mouse_buttons.h"
|
||||
#include "ui/widget.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace doc {
|
||||
class Palette;
|
||||
class Tileset;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
@ -43,20 +47,32 @@ namespace app {
|
||||
const doc::Palette* fromPal, const doc::PalettePicks& from, const doc::PalettePicks& to) { }
|
||||
virtual app::Color onPaletteViewGetForegroundIndex() { return app::Color::fromMask(); }
|
||||
virtual app::Color onPaletteViewGetBackgroundIndex() { return app::Color::fromMask(); }
|
||||
virtual void onPaletteViewClearTiles(const doc::PalettePicks& tiles) { }
|
||||
};
|
||||
|
||||
class AbstractPaletteViewAdapter;
|
||||
class PaletteViewAdapter;
|
||||
|
||||
class PaletteView : public ui::Widget
|
||||
, public MarchingAnts
|
||||
, public IColorSource {
|
||||
, public IColorSource
|
||||
, public ContextObserver {
|
||||
friend class PaletteViewAdapter;
|
||||
public:
|
||||
enum PaletteViewStyle {
|
||||
SelectOneColor,
|
||||
FgBgColors
|
||||
FgBgColors,
|
||||
FgBgTiles,
|
||||
};
|
||||
|
||||
PaletteView(bool editable, PaletteViewStyle style, PaletteViewDelegate* delegate, int boxsize);
|
||||
~PaletteView();
|
||||
|
||||
PaletteViewDelegate* delegate() { return m_delegate; }
|
||||
|
||||
bool isEditable() const { return m_editable; }
|
||||
bool isPalette() const { return m_style != FgBgTiles; }
|
||||
bool isTiles() const { return m_style == FgBgTiles; }
|
||||
|
||||
int getColumns() const { return m_columns; }
|
||||
void setColumns(int columns);
|
||||
@ -72,9 +88,14 @@ namespace app {
|
||||
int getSelectedEntriesCount() const;
|
||||
void setSelectedEntries(const doc::PalettePicks& entries);
|
||||
|
||||
doc::Tileset* tileset() const;
|
||||
|
||||
// IColorSource
|
||||
app::Color getColorByPosition(const gfx::Point& pos) override;
|
||||
|
||||
// ContextObserver impl
|
||||
void onActiveSiteChange(const Site& site) override;
|
||||
|
||||
int getBoxSize() const;
|
||||
void setBoxSize(double boxsize);
|
||||
|
||||
@ -143,11 +164,6 @@ namespace app {
|
||||
int findExactIndex(const app::Color& color) const;
|
||||
void setNewPalette(doc::Palette* oldPalette, doc::Palette* newPalette,
|
||||
PaletteViewModification mod);
|
||||
void drawEntry(ui::Graphics* g,
|
||||
const int palIdx,
|
||||
const int offIdx,
|
||||
gfx::Rect& box,
|
||||
gfx::Color& negColor);
|
||||
int boxSizePx() const;
|
||||
void updateBorderAndChildSpacing();
|
||||
|
||||
@ -155,6 +171,7 @@ namespace app {
|
||||
bool m_editable;
|
||||
PaletteViewStyle m_style;
|
||||
PaletteViewDelegate* m_delegate;
|
||||
std::unique_ptr<AbstractPaletteViewAdapter> m_adapter;
|
||||
int m_columns;
|
||||
double m_boxsize;
|
||||
int m_currentEntry;
|
||||
|
@ -276,7 +276,7 @@ gfx::Rect get_trimmed_bounds(
|
||||
gfx::Rect frameBounds;
|
||||
doc::color_t refColor;
|
||||
if (get_best_refcolor_for_trimming(image, refColor) &&
|
||||
doc::algorithm::shrink_bounds(image, frameBounds, refColor)) {
|
||||
doc::algorithm::shrink_bounds(image, refColor, nullptr, frameBounds)) {
|
||||
bounds = bounds.createUnion(frameBounds);
|
||||
}
|
||||
|
||||
|
540
src/app/util/cel_ops.cpp
Normal file
540
src/app/util/cel_ops.cpp
Normal file
@ -0,0 +1,540 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// 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/util/cel_ops.h"
|
||||
|
||||
#include "app/cmd/add_tile.h"
|
||||
#include "app/cmd/clear_cel.h"
|
||||
#include "app/cmd/clear_mask.h"
|
||||
#include "app/cmd/copy_region.h"
|
||||
#include "app/cmd/replace_image.h"
|
||||
#include "app/cmd/set_cel_position.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "app/doc.h"
|
||||
#include "doc/algorithm/fill_selection.h"
|
||||
#include "doc/algorithm/resize_image.h"
|
||||
#include "doc/algorithm/shrink_bounds.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tileset_hash_table.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "gfx/region.h"
|
||||
#include "render/dithering.h"
|
||||
#include "render/ordered_dither.h"
|
||||
#include "render/quantization.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
#define OPS_TRACE(...)
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
template<typename ImageTraits>
|
||||
static void mask_image_templ(Image* image, const Image* bitmap)
|
||||
{
|
||||
LockImageBits<ImageTraits> bits1(image);
|
||||
const LockImageBits<BitmapTraits> bits2(bitmap);
|
||||
typename LockImageBits<ImageTraits>::iterator it1, end1;
|
||||
LockImageBits<BitmapTraits>::const_iterator it2, end2;
|
||||
for (it1 = bits1.begin(), end1 = bits1.end(),
|
||||
it2 = bits2.begin(), end2 = bits2.end();
|
||||
it1 != end1 && it2 != end2; ++it1, ++it2) {
|
||||
if (!*it2)
|
||||
*it1 = image->maskColor();
|
||||
}
|
||||
ASSERT(it1 == end1);
|
||||
ASSERT(it2 == end2);
|
||||
}
|
||||
|
||||
static void mask_image(Image* image, Image* bitmap)
|
||||
{
|
||||
ASSERT(image->bounds() == bitmap->bounds());
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB: return mask_image_templ<RgbTraits>(image, bitmap);
|
||||
case IMAGE_GRAYSCALE: return mask_image_templ<GrayscaleTraits>(image, bitmap);
|
||||
case IMAGE_INDEXED: return mask_image_templ<IndexedTraits>(image, bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
Cel* create_cel_copy(CmdSequence* cmds,
|
||||
const Cel* srcCel,
|
||||
const Sprite* dstSprite,
|
||||
Layer* dstLayer,
|
||||
const frame_t dstFrame)
|
||||
{
|
||||
const Image* srcImage = srcCel->image();
|
||||
doc::PixelFormat dstPixelFormat =
|
||||
(dstLayer->isTilemap() ? IMAGE_TILEMAP:
|
||||
dstSprite->pixelFormat());
|
||||
gfx::Size dstSize(srcImage->width(),
|
||||
srcImage->height());
|
||||
|
||||
// From Tilemap -> Image
|
||||
if (srcCel->layer()->isTilemap() && !dstLayer->isTilemap()) {
|
||||
auto layerTilemap = static_cast<doc::LayerTilemap*>(srcCel->layer());
|
||||
const auto& grid = layerTilemap->tileset()->grid();
|
||||
dstSize = grid.tilemapSizeToCanvas(dstSize);
|
||||
}
|
||||
// From Image or Tilemap -> Tilemap
|
||||
else if (dstLayer->isTilemap()) {
|
||||
auto layerTilemap = static_cast<doc::LayerTilemap*>(dstLayer);
|
||||
auto grid = layerTilemap->tileset()->grid();
|
||||
if (srcCel->layer()->isTilemap()) // TODO check if this is correct
|
||||
grid.origin(grid.origin() + srcCel->position());
|
||||
const gfx::Rect tilemapBounds = grid.canvasToTile(srcCel->bounds());
|
||||
dstSize = tilemapBounds.size();
|
||||
}
|
||||
|
||||
// New cel
|
||||
std::unique_ptr<Cel> dstCel(
|
||||
new Cel(dstFrame, ImageRef(Image::create(dstPixelFormat, dstSize.w, dstSize.h))));
|
||||
|
||||
dstCel->setOpacity(srcCel->opacity());
|
||||
dstCel->data()->setUserData(srcCel->data()->userData());
|
||||
|
||||
// Special case were we copy from a tilemap...
|
||||
if (srcCel->layer()->isTilemap()) {
|
||||
if (dstLayer->isTilemap()) {
|
||||
// Tilemap -> Tilemap (with same tileset)
|
||||
// Best case, copy a cel in the same layer (we have the same
|
||||
// tileset available, so we just copy the tilemap as it is).
|
||||
if (srcCel->layer() == dstLayer) {
|
||||
dstCel->image()->copy(srcImage, gfx::Clip(0, 0, srcImage->bounds()));
|
||||
}
|
||||
// Tilemap -> Tilemap (with different tilesets)
|
||||
else {
|
||||
doc::ImageSpec spec = dstSprite->spec();
|
||||
spec.setSize(srcCel->bounds().size());
|
||||
doc::ImageRef tmpImage(doc::Image::create(spec));
|
||||
render::Render().renderCel(
|
||||
tmpImage.get(),
|
||||
dstSprite,
|
||||
srcImage,
|
||||
srcCel->layer(),
|
||||
dstSprite->palette(dstCel->frame()),
|
||||
gfx::Rect(gfx::Point(0, 0), srcCel->bounds().size()),
|
||||
gfx::Clip(0, 0, tmpImage->bounds()),
|
||||
255, BlendMode::NORMAL);
|
||||
|
||||
doc::ImageRef tilemap = dstCel->imageRef();
|
||||
|
||||
draw_image_into_new_tilemap_cel(
|
||||
cmds, static_cast<doc::LayerTilemap*>(dstLayer), dstCel.get(),
|
||||
tmpImage.get(),
|
||||
srcCel->bounds().origin(),
|
||||
srcCel->bounds().origin(),
|
||||
srcCel->bounds(),
|
||||
tilemap);
|
||||
}
|
||||
dstCel->setPosition(srcCel->position());
|
||||
}
|
||||
// Tilemap -> Image (so we convert the tilemap to a regular image)
|
||||
else {
|
||||
render::Render().renderCel(
|
||||
dstCel->image(),
|
||||
dstSprite,
|
||||
srcImage,
|
||||
srcCel->layer(),
|
||||
dstSprite->palette(dstCel->frame()),
|
||||
gfx::Rect(gfx::Point(0, 0), srcCel->bounds().size()),
|
||||
gfx::Clip(0, 0, dstCel->image()->bounds()),
|
||||
255, BlendMode::NORMAL);
|
||||
|
||||
// Shrink image
|
||||
if (dstLayer->isTransparent()) {
|
||||
auto bg = dstCel->image()->maskColor();
|
||||
gfx::Rect bounds;
|
||||
if (algorithm::shrink_bounds(dstCel->image(), bg, dstLayer, bounds)) {
|
||||
ImageRef trimmed(doc::crop_image(dstCel->image(), bounds, bg));
|
||||
dstCel->data()->setImage(trimmed, dstLayer);
|
||||
dstCel->setPosition(srcCel->position() + bounds.origin());
|
||||
return dstCel.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Image -> Tilemap (we'll need to generate new tilesets)
|
||||
else if (dstLayer->isTilemap()) {
|
||||
doc::ImageRef tilemap = dstCel->imageRef();
|
||||
draw_image_into_new_tilemap_cel(
|
||||
cmds, static_cast<doc::LayerTilemap*>(dstLayer), dstCel.get(),
|
||||
srcImage,
|
||||
gfx::Point(0, 0),
|
||||
srcCel->bounds().origin(),
|
||||
srcCel->bounds(),
|
||||
tilemap);
|
||||
}
|
||||
else if ((dstSprite->pixelFormat() != srcImage->pixelFormat()) ||
|
||||
// If both images are indexed but with different palette, we can
|
||||
// convert the source cel to RGB first.
|
||||
(dstSprite->pixelFormat() == IMAGE_INDEXED &&
|
||||
srcImage->pixelFormat() == IMAGE_INDEXED &&
|
||||
srcCel->sprite()->palette(srcCel->frame())->countDiff(
|
||||
dstSprite->palette(dstFrame), nullptr, nullptr))) {
|
||||
ImageRef tmpImage(Image::create(IMAGE_RGB, srcImage->width(), srcImage->height()));
|
||||
tmpImage->clear(0);
|
||||
|
||||
render::convert_pixel_format(
|
||||
srcImage,
|
||||
tmpImage.get(),
|
||||
IMAGE_RGB,
|
||||
render::Dithering(),
|
||||
srcCel->sprite()->rgbMap(srcCel->frame()),
|
||||
srcCel->sprite()->palette(srcCel->frame()),
|
||||
srcCel->layer()->isBackground(),
|
||||
0);
|
||||
|
||||
render::convert_pixel_format(
|
||||
tmpImage.get(),
|
||||
dstCel->image(),
|
||||
IMAGE_INDEXED,
|
||||
render::Dithering(),
|
||||
dstSprite->rgbMap(dstFrame),
|
||||
dstSprite->palette(dstFrame),
|
||||
srcCel->layer()->isBackground(),
|
||||
dstSprite->transparentColor());
|
||||
}
|
||||
// Simple case, where we copy both images
|
||||
else {
|
||||
render::composite_image(
|
||||
dstCel->image(),
|
||||
srcImage,
|
||||
srcCel->sprite()->palette(srcCel->frame()),
|
||||
0, 0, 255, BlendMode::SRC);
|
||||
}
|
||||
|
||||
// Resize a referece cel to a non-reference layer
|
||||
if (srcCel->layer()->isReference() && !dstLayer->isReference()) {
|
||||
gfx::RectF srcBounds = srcCel->boundsF();
|
||||
|
||||
std::unique_ptr<Cel> dstCel2(
|
||||
new Cel(dstFrame,
|
||||
ImageRef(Image::create(dstSprite->pixelFormat(),
|
||||
std::ceil(srcBounds.w),
|
||||
std::ceil(srcBounds.h)))));
|
||||
algorithm::resize_image(
|
||||
dstCel->image(), dstCel2->image(),
|
||||
algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR,
|
||||
nullptr, nullptr, 0);
|
||||
|
||||
dstCel.reset(dstCel2.release());
|
||||
dstCel->setPosition(gfx::Point(srcBounds.origin()));
|
||||
}
|
||||
// Copy original cel bounds
|
||||
else if (!dstLayer->isTilemap()) {
|
||||
if (srcCel->layer() &&
|
||||
srcCel->layer()->isReference()) {
|
||||
dstCel->setBoundsF(srcCel->boundsF());
|
||||
}
|
||||
else {
|
||||
dstCel->setPosition(srcCel->position());
|
||||
}
|
||||
}
|
||||
|
||||
return dstCel.release();
|
||||
}
|
||||
|
||||
void draw_image_into_new_tilemap_cel(
|
||||
CmdSequence* cmds,
|
||||
doc::LayerTilemap* dstLayer,
|
||||
doc::Cel* dstCel,
|
||||
const doc::Image* srcImage,
|
||||
const gfx::Point& originOffset,
|
||||
const gfx::Point& srcImagePos,
|
||||
const gfx::Rect& canvasBounds,
|
||||
doc::ImageRef& newTilemap)
|
||||
{
|
||||
ASSERT(dstLayer->isTilemap());
|
||||
|
||||
doc::Tileset* tileset = dstLayer->tileset();
|
||||
doc::Grid grid = tileset->grid();
|
||||
grid.origin(grid.origin() + originOffset);
|
||||
|
||||
doc::TilesetHashTable hashImages; // TODO the hashImages should be inside the Tileset
|
||||
{
|
||||
// Add existent tiles in the hash table
|
||||
int i = 0;
|
||||
for (auto& image : *tileset)
|
||||
hashImages[image] = i++;
|
||||
}
|
||||
|
||||
gfx::Size tileSize = grid.tileSize();
|
||||
const gfx::Rect tilemapBounds = grid.canvasToTile(canvasBounds);
|
||||
|
||||
if (!newTilemap) {
|
||||
newTilemap.reset(doc::Image::create(IMAGE_TILEMAP,
|
||||
tilemapBounds.w,
|
||||
tilemapBounds.h));
|
||||
newTilemap->clear(0); // <- This should not necessary
|
||||
}
|
||||
else {
|
||||
ASSERT(tilemapBounds.w == newTilemap->width());
|
||||
ASSERT(tilemapBounds.h == newTilemap->height());
|
||||
}
|
||||
|
||||
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(gfx::Region(canvasBounds))) {
|
||||
const gfx::Point tilePtInCanvas = grid.tileToCanvas(tilePt);
|
||||
doc::ImageRef tileImage(
|
||||
doc::crop_image(srcImage,
|
||||
tilePtInCanvas.x-srcImagePos.x,
|
||||
tilePtInCanvas.y-srcImagePos.y,
|
||||
tileSize.w, tileSize.h,
|
||||
srcImage->maskColor()));
|
||||
if (grid.hasMask())
|
||||
mask_image(tileImage.get(), grid.mask().get());
|
||||
|
||||
doc::tile_index tileIndex = 0;
|
||||
|
||||
auto it = hashImages.find(tileImage);
|
||||
if (it != hashImages.end()) {
|
||||
tileIndex = it->second; // TODO
|
||||
}
|
||||
else {
|
||||
auto addTile = new cmd::AddTile(tileset, tileImage);
|
||||
cmds->executeAndAdd(addTile);
|
||||
|
||||
tileIndex = addTile->tileIndex();
|
||||
hashImages[tileImage] = tileIndex;
|
||||
}
|
||||
|
||||
newTilemap->putPixel(
|
||||
tilePt.x-tilemapBounds.x,
|
||||
tilePt.y-tilemapBounds.y, doc::tile(tileIndex, 0));
|
||||
}
|
||||
|
||||
static_cast<Doc*>(dstLayer->sprite()->document())
|
||||
->notifyTilesetChanged(tileset);
|
||||
|
||||
dstCel->data()->setImage(newTilemap, dstLayer);
|
||||
dstCel->setPosition(grid.tileToCanvas(tilemapBounds.origin()));
|
||||
}
|
||||
|
||||
void modify_tilemap_cel_region(
|
||||
CmdSequence* cmds,
|
||||
doc::Cel* cel,
|
||||
const gfx::Region& region,
|
||||
const TilesetMode tilesetMode,
|
||||
const GetTileImageFunc& getTileImage)
|
||||
{
|
||||
OPS_TRACE("modify_tilemap_cel_region %d %d %d %d\n",
|
||||
region.bounds().x, region.bounds().y,
|
||||
region.bounds().w, region.bounds().h);
|
||||
|
||||
if (region.isEmpty())
|
||||
return;
|
||||
|
||||
ASSERT(cel->layer() && cel->layer()->isTilemap());
|
||||
ASSERT(cel->image()->pixelFormat() == IMAGE_TILEMAP);
|
||||
|
||||
doc::LayerTilemap* tilemapLayer = static_cast<doc::LayerTilemap*>(cel->layer());
|
||||
|
||||
Doc* doc = static_cast<Doc*>(tilemapLayer->sprite()->document());
|
||||
doc::Tileset* tileset = tilemapLayer->tileset();
|
||||
doc::Grid grid = tileset->grid();
|
||||
grid.origin(grid.origin() + cel->position());
|
||||
|
||||
const gfx::Size tileSize = grid.tileSize();
|
||||
const gfx::Rect oldTilemapBounds(grid.canvasToTile(cel->position()),
|
||||
cel->image()->bounds().size());
|
||||
const gfx::Rect patchTilemapBounds = grid.canvasToTile(region.bounds());
|
||||
const gfx::Rect newTilemapBounds = (oldTilemapBounds | patchTilemapBounds);
|
||||
|
||||
OPS_TRACE("modify_tilemap_cel_region:\n"
|
||||
" - grid.origin =%d %d\n"
|
||||
" - cel.position =%d %d\n"
|
||||
" - oldTilemapBounds =%d %d %d %d\n"
|
||||
" - patchTilemapBounds=%d %d %d %d (region.bounds = %d %d %d %d)\n"
|
||||
" - newTilemapBounds =%d %d %d %d\n",
|
||||
grid.origin().x, grid.origin().y,
|
||||
cel->position().x, cel->position().y,
|
||||
oldTilemapBounds.x, oldTilemapBounds.y, oldTilemapBounds.w, oldTilemapBounds.h,
|
||||
patchTilemapBounds.x, patchTilemapBounds.y, patchTilemapBounds.w, patchTilemapBounds.h,
|
||||
region.bounds().x, region.bounds().y, region.bounds().w, region.bounds().h,
|
||||
newTilemapBounds.x, newTilemapBounds.y, newTilemapBounds.w, newTilemapBounds.h);
|
||||
|
||||
// Autogenerate tiles
|
||||
if (tilesetMode == TilesetMode::GenerateAllTiles) {
|
||||
doc::TilesetHashTable hashImages; // TODO the hashImages should be inside the Tileset
|
||||
{
|
||||
// Add existent tiles in the hash table
|
||||
int i = 0;
|
||||
for (auto& image : *tileset)
|
||||
hashImages[image] = i++;
|
||||
}
|
||||
|
||||
// TODO create a smaller image
|
||||
doc::ImageRef newTilemap(
|
||||
doc::Image::create(IMAGE_TILEMAP,
|
||||
newTilemapBounds.w,
|
||||
newTilemapBounds.h));
|
||||
|
||||
newTilemap->clear(0); // TODO find the tile with empty content?
|
||||
newTilemap->copy(
|
||||
cel->image(),
|
||||
gfx::Clip(oldTilemapBounds.x-newTilemapBounds.x,
|
||||
oldTilemapBounds.y-newTilemapBounds.y, 0, 0,
|
||||
oldTilemapBounds.w, oldTilemapBounds.h));
|
||||
|
||||
gfx::Region tilePtsRgn;
|
||||
|
||||
// This region includes the modified region by the user + the
|
||||
// extra region added as we've incremented the tilemap size
|
||||
// (newTilemapBounds).
|
||||
gfx::Region regionToPatch(grid.tileToCanvas(newTilemapBounds));
|
||||
regionToPatch -= gfx::Region(grid.tileToCanvas(oldTilemapBounds));
|
||||
regionToPatch |= region;
|
||||
|
||||
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(regionToPatch)) {
|
||||
const int u = tilePt.x-newTilemapBounds.x;
|
||||
const int v = tilePt.y-newTilemapBounds.y;
|
||||
OPS_TRACE(" - modify tile xy=%d %d uv=%d %d\n", tilePt.x, tilePt.y, u, v);
|
||||
if (!newTilemap->bounds().contains(u, v))
|
||||
continue;
|
||||
|
||||
const doc::tile_t t = newTilemap->getPixel(u, v);
|
||||
const doc::tile_index ti = doc::tile_geti(t);
|
||||
const doc::ImageRef existenTileImage = tileset->get(ti);
|
||||
|
||||
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
|
||||
ImageRef tileImage(getTileImage(existenTileImage, tileInCanvasRc));
|
||||
if (grid.hasMask())
|
||||
mask_image(tileImage.get(), grid.mask().get());
|
||||
|
||||
tile_index tileIndex = 0;
|
||||
|
||||
auto it = hashImages.find(tileImage);
|
||||
if (it != hashImages.end()) {
|
||||
tileIndex = it->second; // TODO
|
||||
}
|
||||
else {
|
||||
auto addTile = new cmd::AddTile(tileset, tileImage);
|
||||
cmds->executeAndAdd(addTile);
|
||||
|
||||
tileIndex = addTile->tileIndex();
|
||||
hashImages[tileImage] = tileIndex;
|
||||
}
|
||||
|
||||
const doc::tile_t tile = doc::tile(tileIndex, 0);
|
||||
if (t != tile) {
|
||||
newTilemap->putPixel(u, v, tile);
|
||||
tilePtsRgn |= gfx::Region(gfx::Rect(u, v, 1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
doc->notifyTilesetChanged(tileset);
|
||||
|
||||
if (newTilemap->width() != cel->image()->width() ||
|
||||
newTilemap->height() != cel->image()->height()) {
|
||||
gfx::Point newPos = grid.tileToCanvas(newTilemapBounds.origin());
|
||||
if (cel->position() != newPos) {
|
||||
cmds->executeAndAdd(
|
||||
new cmd::SetCelPosition(cel, newPos.x, newPos.y));
|
||||
}
|
||||
cmds->executeAndAdd(
|
||||
new cmd::ReplaceImage(cel->sprite(), cel->imageRef(), newTilemap));
|
||||
}
|
||||
else {
|
||||
cmds->executeAndAdd(
|
||||
new cmd::CopyRegion(
|
||||
cel->image(),
|
||||
newTilemap.get(),
|
||||
tilePtsRgn,
|
||||
gfx::Point(0, 0)));
|
||||
}
|
||||
}
|
||||
// Modify active set of tiles manually / don't auto-generate new tiles
|
||||
else if (tilesetMode == TilesetMode::ModifyExistent) {
|
||||
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(region)) {
|
||||
// Ignore modifications outside the tilemap
|
||||
if (!cel->image()->bounds().contains(tilePt.x, tilePt.y))
|
||||
continue;
|
||||
|
||||
const doc::tile_t t = cel->image()->getPixel(tilePt.x, tilePt.y);
|
||||
const doc::tile_index ti = doc::tile_geti(t);
|
||||
const doc::ImageRef existenTileImage = tileset->get(ti);
|
||||
|
||||
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
|
||||
ImageRef tileImage(getTileImage(existenTileImage, tileInCanvasRc));
|
||||
if (grid.hasMask())
|
||||
mask_image(tileImage.get(), grid.mask().get());
|
||||
|
||||
gfx::Region tileRgn(tileInCanvasRc);
|
||||
tileRgn.createIntersection(tileRgn, region);
|
||||
tileRgn.offset(-tileInCanvasRc.origin());
|
||||
|
||||
ImageRef tileOrigImage = tileset->get(ti);
|
||||
|
||||
cmds->executeAndAdd(
|
||||
new cmd::CopyRegion(
|
||||
tileOrigImage.get(),
|
||||
tileImage.get(),
|
||||
tileRgn,
|
||||
gfx::Point(0, 0)));
|
||||
}
|
||||
|
||||
doc->notifyTilesetChanged(tileset);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_mask_from_cel(CmdSequence* cmds,
|
||||
doc::Cel* cel,
|
||||
const TilesetMode tilesetMode)
|
||||
{
|
||||
ASSERT(cmds);
|
||||
ASSERT(cel);
|
||||
ASSERT(cel->layer());
|
||||
|
||||
if (cel->layer()->isTilemap()) {
|
||||
Doc* doc = static_cast<Doc*>(cel->document());
|
||||
|
||||
// Simple case (there is no visible selection, so we remove the
|
||||
// whole cel)
|
||||
if (!doc->isMaskVisible()) {
|
||||
cmds->executeAndAdd(new cmd::ClearCel(cel));
|
||||
return;
|
||||
}
|
||||
|
||||
color_t bgcolor = doc->bgColor(cel->layer());
|
||||
doc::Mask* mask = doc->mask();
|
||||
|
||||
modify_tilemap_cel_region(
|
||||
cmds, cel,
|
||||
gfx::Region(doc->mask()->bounds()),
|
||||
tilesetMode,
|
||||
[bgcolor, mask](const doc::ImageRef& origTile,
|
||||
const gfx::Rect& tileBoundsInCanvas) -> doc::ImageRef {
|
||||
doc::ImageRef modified(doc::Image::createCopy(origTile.get()));
|
||||
doc::algorithm::fill_selection(
|
||||
modified.get(),
|
||||
mask->bounds().origin() - tileBoundsInCanvas.origin(),
|
||||
mask,
|
||||
bgcolor);
|
||||
return modified;
|
||||
});
|
||||
}
|
||||
else {
|
||||
cmds->executeAndAdd(new cmd::ClearMask(cel));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
67
src/app/util/cel_ops.h
Normal file
67
src/app/util/cel_ops.h
Normal file
@ -0,0 +1,67 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UTIL_CEL_OPS_H_INCLUDED
|
||||
#define APP_UTIL_CEL_OPS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/tileset_mode.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/region.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
class Layer;
|
||||
class LayerTilemap;
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class CmdSequence;
|
||||
|
||||
typedef std::function<doc::ImageRef(const doc::ImageRef& origTile,
|
||||
const gfx::Rect& tileBoundsInCanvas)> GetTileImageFunc;
|
||||
|
||||
// The "cmds" is used in case that new tiles must be added in the
|
||||
// dstLayer tilesets.
|
||||
doc::Cel* create_cel_copy(
|
||||
CmdSequence* cmds,
|
||||
const doc::Cel* srcCel,
|
||||
const doc::Sprite* dstSprite,
|
||||
doc::Layer* dstLayer,
|
||||
const doc::frame_t dstFrame);
|
||||
|
||||
// Draws an image creating new tiles.
|
||||
void draw_image_into_new_tilemap_cel(
|
||||
CmdSequence* cmds,
|
||||
doc::LayerTilemap* dstLayer,
|
||||
doc::Cel* dstCel,
|
||||
const doc::Image* srcImage,
|
||||
const gfx::Point& originOffset,
|
||||
const gfx::Point& srcImagePos,
|
||||
const gfx::Rect& canvasBounds,
|
||||
doc::ImageRef& newTilemap);
|
||||
|
||||
void modify_tilemap_cel_region(
|
||||
CmdSequence* cmds,
|
||||
doc::Cel* cel,
|
||||
const gfx::Region& region,
|
||||
const TilesetMode tilesetMode,
|
||||
const GetTileImageFunc& getTileImage);
|
||||
|
||||
void clear_mask_from_cel(
|
||||
CmdSequence* cmds,
|
||||
doc::Cel* cel,
|
||||
const TilesetMode tilesetMode);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -30,6 +30,7 @@
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/cel_ops.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/clipboard_native.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
@ -248,12 +249,14 @@ void clear_mask_from_cels(Tx& tx,
|
||||
for (Cel* cel : cels) {
|
||||
ObjectId celId = cel->id();
|
||||
|
||||
tx(new cmd::ClearMask(cel));
|
||||
clear_mask_from_cel(
|
||||
tx, cel, ColorBar::instance()->tilesetMode());
|
||||
|
||||
// Get cel again just in case the cmd::ClearMask() called cmd::ClearCel()
|
||||
cel = doc::get<Cel>(celId);
|
||||
if (cel &&
|
||||
cel->layer()->isTransparent()) {
|
||||
cel->layer()->isTransparent() &&
|
||||
!cel->layer()->isTilemap()) {
|
||||
tx(new cmd::TrimCel(cel));
|
||||
}
|
||||
}
|
||||
@ -278,6 +281,7 @@ void cut(ContextWriter& writer)
|
||||
console.printf("Can't copying an image portion from the current layer\n");
|
||||
}
|
||||
else {
|
||||
// TODO This code is similar to DocView::onClear()
|
||||
{
|
||||
Tx tx(writer.context(), "Cut");
|
||||
Site site = writer.context()->activeSite();
|
||||
|
@ -1,116 +0,0 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "doc/algorithm/resize_image.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "render/dithering.h"
|
||||
#include "render/ordered_dither.h"
|
||||
#include "render/quantization.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
Cel* create_cel_copy(const Cel* srcCel,
|
||||
const Sprite* dstSprite,
|
||||
const Layer* dstLayer,
|
||||
const frame_t dstFrame)
|
||||
{
|
||||
const Image* celImage = srcCel->image();
|
||||
|
||||
std::unique_ptr<Cel> dstCel(
|
||||
new Cel(dstFrame,
|
||||
ImageRef(Image::create(dstSprite->pixelFormat(),
|
||||
celImage->width(),
|
||||
celImage->height()))));
|
||||
|
||||
if ((dstSprite->pixelFormat() != celImage->pixelFormat()) ||
|
||||
// If both images are indexed but with different palette, we can
|
||||
// convert the source cel to RGB first.
|
||||
(dstSprite->pixelFormat() == IMAGE_INDEXED &&
|
||||
celImage->pixelFormat() == IMAGE_INDEXED &&
|
||||
srcCel->sprite()->palette(srcCel->frame())->countDiff(
|
||||
dstSprite->palette(dstFrame), nullptr, nullptr))) {
|
||||
ImageRef tmpImage(Image::create(IMAGE_RGB, celImage->width(), celImage->height()));
|
||||
tmpImage->clear(0);
|
||||
|
||||
render::convert_pixel_format(
|
||||
celImage,
|
||||
tmpImage.get(),
|
||||
IMAGE_RGB,
|
||||
render::Dithering(),
|
||||
srcCel->sprite()->rgbMap(srcCel->frame()),
|
||||
srcCel->sprite()->palette(srcCel->frame()),
|
||||
srcCel->layer()->isBackground(),
|
||||
0);
|
||||
|
||||
render::convert_pixel_format(
|
||||
tmpImage.get(),
|
||||
dstCel->image(),
|
||||
IMAGE_INDEXED,
|
||||
render::Dithering(),
|
||||
dstSprite->rgbMap(dstFrame),
|
||||
dstSprite->palette(dstFrame),
|
||||
srcCel->layer()->isBackground(),
|
||||
dstSprite->transparentColor());
|
||||
}
|
||||
else {
|
||||
render::composite_image(
|
||||
dstCel->image(),
|
||||
celImage,
|
||||
srcCel->sprite()->palette(srcCel->frame()),
|
||||
0, 0, 255, BlendMode::SRC);
|
||||
}
|
||||
|
||||
// Resize a referecen cel to a non-reference layer
|
||||
if (srcCel->layer()->isReference() && !dstLayer->isReference()) {
|
||||
gfx::RectF srcBounds = srcCel->boundsF();
|
||||
|
||||
std::unique_ptr<Cel> dstCel2(
|
||||
new Cel(dstFrame,
|
||||
ImageRef(Image::create(dstSprite->pixelFormat(),
|
||||
std::ceil(srcBounds.w),
|
||||
std::ceil(srcBounds.h)))));
|
||||
algorithm::resize_image(
|
||||
dstCel->image(), dstCel2->image(),
|
||||
algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR,
|
||||
nullptr, nullptr, 0);
|
||||
|
||||
dstCel.reset(dstCel2.release());
|
||||
dstCel->setPosition(gfx::Point(srcBounds.origin()));
|
||||
}
|
||||
// Copy original cel bounds
|
||||
else {
|
||||
if (srcCel->layer() &&
|
||||
srcCel->layer()->isReference()) {
|
||||
dstCel->setBoundsF(srcCel->boundsF());
|
||||
}
|
||||
else {
|
||||
dstCel->setPosition(srcCel->position());
|
||||
}
|
||||
}
|
||||
|
||||
dstCel->setOpacity(srcCel->opacity());
|
||||
dstCel->data()->setUserData(srcCel->data()->userData());
|
||||
|
||||
return dstCel.release();
|
||||
}
|
||||
|
||||
} // namespace app
|
@ -1,27 +0,0 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UTIL_CREATE_CEL_COPY_H_INCLUDED
|
||||
#define APP_UTIL_CREATE_CEL_COPY_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/frame.h"
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
Cel* create_cel_copy(const Cel* srcCel,
|
||||
const Sprite* dstSprite,
|
||||
const Layer* dstLayer,
|
||||
const frame_t dstFrame);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -13,21 +13,28 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/add_cel.h"
|
||||
#include "app/cmd/clear_cel.h"
|
||||
#include "app/cmd/copy_rect.h"
|
||||
#include "app/cmd/copy_region.h"
|
||||
#include "app/cmd/patch_cel.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "app/context.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/site.h"
|
||||
#include "app/transaction.h"
|
||||
#include "app/util/cel_ops.h"
|
||||
#include "app/util/range_utils.h"
|
||||
#include "doc/algorithm/shrink_bounds.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tileset_hash_table.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "gfx/rect_io.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#define EXP_TRACE(...) // TRACEARGS
|
||||
|
||||
@ -61,8 +68,11 @@ static void create_buffers()
|
||||
namespace app {
|
||||
|
||||
ExpandCelCanvas::ExpandCelCanvas(
|
||||
Site site, Layer* layer,
|
||||
TiledMode tiledMode, Transaction& transaction, Flags flags)
|
||||
Site site,
|
||||
Layer* layer,
|
||||
const TiledMode tiledMode,
|
||||
CmdSequence* cmds,
|
||||
const Flags flags)
|
||||
: m_document(site.document())
|
||||
, m_sprite(site.sprite())
|
||||
, m_layer(layer)
|
||||
@ -75,9 +85,14 @@ ExpandCelCanvas::ExpandCelCanvas(
|
||||
, m_dstImage(NULL)
|
||||
, m_closed(false)
|
||||
, m_committed(false)
|
||||
, m_transaction(transaction)
|
||||
, m_canCompareSrcVsDst((m_flags & NeedsSource) == NeedsSource)
|
||||
, m_cmds(cmds)
|
||||
, m_tilesetMode(site.tilesetMode())
|
||||
{
|
||||
if (m_layer && m_layer->isTilemap()) {
|
||||
m_flags = Flags(m_flags | NeedsSource);
|
||||
}
|
||||
m_canCompareSrcVsDst = ((m_flags & NeedsSource) == NeedsSource);
|
||||
|
||||
ASSERT(!singleton);
|
||||
singleton = this;
|
||||
|
||||
@ -98,11 +113,7 @@ ExpandCelCanvas::ExpandCelCanvas(
|
||||
m_origCelPos = m_cel->position();
|
||||
|
||||
// Region to draw
|
||||
gfx::Rect celBounds(
|
||||
m_cel->x(),
|
||||
m_cel->y(),
|
||||
m_celImage ? m_celImage->width(): m_sprite->width(),
|
||||
m_celImage ? m_celImage->height(): m_sprite->height());
|
||||
gfx::Rect celBounds = (m_celCreated ? m_sprite->bounds(): m_cel->bounds());
|
||||
|
||||
gfx::Rect spriteBounds(0, 0,
|
||||
m_sprite->width(),
|
||||
@ -122,11 +133,18 @@ ExpandCelCanvas::ExpandCelCanvas(
|
||||
|
||||
if (m_celCreated) {
|
||||
getDestCanvas();
|
||||
m_cel->data()->setImage(m_dstImage);
|
||||
m_cel->data()->setImage(m_dstImage, m_layer);
|
||||
|
||||
if (m_layer && m_layer->isImage())
|
||||
static_cast<LayerImage*>(m_layer)->addCel(m_cel);
|
||||
}
|
||||
// If we are in a tilemap, we use m_dstImage to draw pixels (instead
|
||||
// of the tilemap image).
|
||||
else if (m_layer->isTilemap()) {
|
||||
// Calling "getDestCanvas()" we create the m_dstImage
|
||||
getDestCanvas();
|
||||
m_cel->data()->setImage(m_dstImage, m_layer);
|
||||
}
|
||||
}
|
||||
|
||||
ExpandCelCanvas::~ExpandCelCanvas()
|
||||
@ -166,21 +184,34 @@ void ExpandCelCanvas::commit()
|
||||
// don't have a m_celImage)
|
||||
validateDestCanvas(gfx::Region(m_bounds));
|
||||
|
||||
// We can temporary remove the cel.
|
||||
if (m_layer->isImage()) {
|
||||
// We can temporary remove the cel.
|
||||
static_cast<LayerImage*>(m_layer)->removeCel(m_cel);
|
||||
|
||||
// Add a copy of m_dstImage in the sprite's image stock
|
||||
gfx::Rect trimBounds = getTrimDstImageBounds();
|
||||
if (!trimBounds.isEmpty()) {
|
||||
ImageRef newImage(trimDstImage(trimBounds));
|
||||
ASSERT(newImage);
|
||||
// Convert the image to tiles
|
||||
if (m_layer->isTilemap()) {
|
||||
doc::ImageRef newTilemap;
|
||||
draw_image_into_new_tilemap_cel(
|
||||
m_cmds, static_cast<doc::LayerTilemap*>(m_layer), m_cel,
|
||||
// Draw the dst image in the tilemap
|
||||
m_dstImage.get(),
|
||||
m_origCelPos,
|
||||
gfx::Point(0, 0), // m_dstImage->bounds(),
|
||||
trimBounds,
|
||||
newTilemap);
|
||||
}
|
||||
else {
|
||||
ImageRef newImage(trimDstImage(trimBounds));
|
||||
ASSERT(newImage);
|
||||
|
||||
m_cel->data()->setImage(newImage);
|
||||
m_cel->setPosition(m_cel->position() + trimBounds.origin());
|
||||
m_cel->data()->setImage(newImage, m_layer);
|
||||
m_cel->setPosition(m_cel->position() + trimBounds.origin());
|
||||
}
|
||||
|
||||
// And finally we add the cel again in the layer.
|
||||
m_transaction.execute(new cmd::AddCel(m_layer, m_cel));
|
||||
// And add the cel again in the layer.
|
||||
m_cmds->executeAndAdd(new cmd::AddCel(m_layer, m_cel));
|
||||
}
|
||||
}
|
||||
// We are selecting inside a layer group...
|
||||
@ -194,7 +225,14 @@ void ExpandCelCanvas::commit()
|
||||
// Restore cel position to its original position
|
||||
m_cel->setPosition(m_origCelPos);
|
||||
|
||||
ASSERT(m_cel->image() == m_celImage.get());
|
||||
#ifdef _DEBUG
|
||||
if (m_layer->isTilemap()) {
|
||||
ASSERT(m_cel->image() != m_celImage.get());
|
||||
}
|
||||
else {
|
||||
ASSERT(m_cel->image() == m_celImage.get());
|
||||
}
|
||||
#endif
|
||||
|
||||
gfx::Region* regionToPatch = &m_validDstRegion;
|
||||
gfx::Region reduced;
|
||||
@ -214,11 +252,40 @@ void ExpandCelCanvas::commit()
|
||||
|
||||
EXP_TRACE(" - regionToPatch", regionToPatch->bounds());
|
||||
|
||||
// Convert the image to tiles again
|
||||
if (m_layer->isTilemap()) {
|
||||
ASSERT(m_celImage.get() != m_cel->image());
|
||||
ASSERT(m_celImage->pixelFormat() == IMAGE_TILEMAP);
|
||||
|
||||
// Validate the whole m_dstImage (invalid areas are cleared, as we
|
||||
// don't have a m_celImage)
|
||||
validateDestCanvas(gfx::Region(m_bounds));
|
||||
|
||||
// Restore the original m_celImage, because the cel contained
|
||||
// the m_dstImage temporally for drawing purposes. No undo
|
||||
// information is required at this moment.
|
||||
m_cel->data()->setImage(m_celImage, m_layer);
|
||||
|
||||
// Put the region in absolute sprite canvas coordinates (instead
|
||||
// of relative to the m_cel).
|
||||
regionToPatch->offset(m_bounds.origin());
|
||||
|
||||
modify_tilemap_cel_region(
|
||||
m_cmds, m_cel,
|
||||
*regionToPatch,
|
||||
m_tilesetMode,
|
||||
[this](const doc::ImageRef& origTile,
|
||||
const gfx::Rect& tileBoundsInCanvas) -> doc::ImageRef {
|
||||
return trimDstImage(tileBoundsInCanvas);
|
||||
});
|
||||
}
|
||||
// Check that the region to copy or patch is not empty before we
|
||||
// create the new cmd
|
||||
if (!regionToPatch->isEmpty()) {
|
||||
else if (!regionToPatch->isEmpty()) {
|
||||
ASSERT(m_celImage.get() == m_cel->image());
|
||||
|
||||
if (m_layer->isBackground()) {
|
||||
m_transaction.execute(
|
||||
m_cmds->executeAndAdd(
|
||||
new cmd::CopyRegion(
|
||||
m_cel->image(),
|
||||
m_dstImage.get(),
|
||||
@ -226,7 +293,7 @@ void ExpandCelCanvas::commit()
|
||||
m_bounds.origin()));
|
||||
}
|
||||
else {
|
||||
m_transaction.execute(
|
||||
m_cmds->executeAndAdd(
|
||||
new cmd::PatchCel(
|
||||
m_cel,
|
||||
m_dstImage.get(),
|
||||
@ -257,6 +324,13 @@ void ExpandCelCanvas::rollback()
|
||||
delete m_cel;
|
||||
m_celImage.reset();
|
||||
}
|
||||
// Restore the original tilemap
|
||||
else if (m_layer->isTilemap()) {
|
||||
ASSERT(m_cel->image()->pixelFormat() == m_sprite->pixelFormat());
|
||||
ASSERT(m_celImage->pixelFormat() == IMAGE_TILEMAP);
|
||||
m_cel->data()->setImage(m_celImage,
|
||||
m_cel->layer());
|
||||
}
|
||||
|
||||
m_closed = true;
|
||||
}
|
||||
@ -298,18 +372,41 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
|
||||
|
||||
if (m_celImage) {
|
||||
gfx::Region rgnToClear;
|
||||
rgnToClear.createSubtraction(rgnToValidate,
|
||||
rgnToClear.createSubtraction(
|
||||
rgnToValidate,
|
||||
gfx::Region(m_celImage->bounds()
|
||||
.offset(m_origCelPos)
|
||||
.offset(-m_bounds.origin())));
|
||||
.offset(m_origCelPos)
|
||||
.offset(-m_bounds.origin())));
|
||||
for (const auto& rc : rgnToClear)
|
||||
fill_rect(m_srcImage.get(), rc, m_srcImage->maskColor());
|
||||
|
||||
for (const auto& rc : rgnToValidate)
|
||||
m_srcImage->copy(m_celImage.get(),
|
||||
gfx::Clip(rc.x, rc.y,
|
||||
rc.x+m_bounds.x-m_origCelPos.x,
|
||||
rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h));
|
||||
if (m_celImage->pixelFormat() == IMAGE_TILEMAP) {
|
||||
// For tilemaps, we can use the Render class to render visible
|
||||
// tiles in the rgnToValidate of this cel.
|
||||
render::Render subRender;
|
||||
for (const auto& rc : rgnToValidate) {
|
||||
subRender.renderCel(
|
||||
m_srcImage.get(),
|
||||
m_sprite,
|
||||
m_celImage.get(),
|
||||
m_layer,
|
||||
m_sprite->palette(m_frame),
|
||||
gfx::RectF(0, 0, m_bounds.w, m_bounds.h),
|
||||
gfx::Clip(rc.x, rc.y,
|
||||
rc.x+m_bounds.x-m_origCelPos.x,
|
||||
rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h),
|
||||
255, BlendMode::NORMAL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We can copy the cel image directly
|
||||
for (const auto& rc : rgnToValidate)
|
||||
m_srcImage->copy(
|
||||
m_celImage.get(),
|
||||
gfx::Clip(rc.x, rc.y,
|
||||
rc.x+m_bounds.x-m_origCelPos.x,
|
||||
rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const auto& rc : rgnToValidate)
|
||||
@ -332,18 +429,19 @@ void ExpandCelCanvas::validateDestCanvas(const gfx::Region& rgn)
|
||||
src_y = m_bounds.y;
|
||||
}
|
||||
else {
|
||||
src = m_celImage.get();
|
||||
src = m_cel->image();
|
||||
src_x = m_origCelPos.x;
|
||||
src_y = m_origCelPos.y;
|
||||
}
|
||||
|
||||
getDestCanvas();
|
||||
getDestCanvas(); // Create m_dstImage
|
||||
|
||||
gfx::Region rgnToValidate(rgn);
|
||||
rgnToValidate.offset(-m_bounds.origin());
|
||||
rgnToValidate.createSubtraction(rgnToValidate, m_validDstRegion);
|
||||
rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_dstImage->bounds()));
|
||||
|
||||
// ASSERT(src); // TODO is it always true?
|
||||
if (src) {
|
||||
gfx::Region rgnToClear;
|
||||
rgnToClear.createSubtraction(rgnToValidate,
|
||||
@ -406,8 +504,8 @@ gfx::Rect ExpandCelCanvas::getTrimDstImageBounds() const
|
||||
return m_dstImage->bounds();
|
||||
else {
|
||||
gfx::Rect bounds;
|
||||
algorithm::shrink_bounds(m_dstImage.get(), bounds,
|
||||
m_dstImage->maskColor());
|
||||
algorithm::shrink_bounds(m_dstImage.get(),
|
||||
m_dstImage->maskColor(), nullptr, bounds);
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
@ -416,7 +514,8 @@ ImageRef ExpandCelCanvas::trimDstImage(const gfx::Rect& bounds) const
|
||||
{
|
||||
return ImageRef(
|
||||
crop_image(m_dstImage.get(),
|
||||
bounds.x, bounds.y,
|
||||
bounds.x-m_bounds.x,
|
||||
bounds.y-m_bounds.y,
|
||||
bounds.w, bounds.h,
|
||||
m_dstImage->maskColor()));
|
||||
}
|
||||
|
@ -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
|
||||
@ -8,6 +9,7 @@
|
||||
#define APP_UTIL_EXPAND_CEL_CANVAS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/tileset_mode.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "filters/tiled_mode.h"
|
||||
@ -24,9 +26,9 @@ namespace doc {
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class CmdSequence;
|
||||
class Doc;
|
||||
class Site;
|
||||
class Transaction;
|
||||
|
||||
using namespace filters;
|
||||
using namespace doc;
|
||||
@ -46,7 +48,9 @@ namespace app {
|
||||
};
|
||||
|
||||
ExpandCelCanvas(Site site, Layer* layer,
|
||||
TiledMode tiledMode, Transaction& undo, Flags flags);
|
||||
const TiledMode tiledMode,
|
||||
CmdSequence* cmds,
|
||||
const Flags flags);
|
||||
~ExpandCelCanvas();
|
||||
|
||||
// Commit changes made in getDestCanvas() in the cel's image. Adds
|
||||
@ -87,7 +91,7 @@ namespace app {
|
||||
ImageRef m_dstImage;
|
||||
bool m_closed;
|
||||
bool m_committed;
|
||||
Transaction& m_transaction;
|
||||
CmdSequence* m_cmds;
|
||||
gfx::Region m_validSrcRegion;
|
||||
gfx::Region m_validDstRegion;
|
||||
|
||||
@ -95,6 +99,8 @@ namespace app {
|
||||
// cel. This is false when dst is copied to the src, so we cannot
|
||||
// reduce the patched region because both images will be the same.
|
||||
bool m_canCompareSrcVsDst;
|
||||
|
||||
TilesetMode m_tilesetMode;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "app/doc.h"
|
||||
#include "app/site.h"
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "render/render.h"
|
||||
@ -53,7 +54,7 @@ doc::Image* new_image_from_mask(const Site& site,
|
||||
|
||||
const Image* src = nullptr;
|
||||
int x = 0, y = 0;
|
||||
if (merged) {
|
||||
if (merged || site.layer()->isTilemap()) {
|
||||
render::Render render;
|
||||
render.setNewBlend(newBlend);
|
||||
render.renderSprite(dst.get(), srcSprite, site.frame(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Igara Studio S.A.
|
||||
Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
Copyright (c) 2016-2018 David Capello
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
|
@ -31,10 +31,12 @@
|
||||
|
||||
#define ASE_FILE_LAYER_IMAGE 0
|
||||
#define ASE_FILE_LAYER_GROUP 1
|
||||
#define ASE_FILE_LAYER_TILEMAP 2
|
||||
|
||||
#define ASE_FILE_RAW_CEL 0
|
||||
#define ASE_FILE_LINK_CEL 1
|
||||
#define ASE_FILE_COMPRESSED_CEL 2
|
||||
#define ASE_FILE_COMPRESSED_TILEMAP 3
|
||||
|
||||
#define ASE_FILE_NO_COLOR_PROFILE 0
|
||||
#define ASE_FILE_SRGB_COLOR_PROFILE 1
|
||||
|
@ -201,8 +201,7 @@ bool AsepriteDecoder::decode()
|
||||
}
|
||||
|
||||
case ASE_FILE_CHUNK_TILESET: {
|
||||
delegate()->error(
|
||||
"Warning: The given file contains a tileset.\nThis version of Aseprite doesn't support tilemap layers.\n");
|
||||
readTilesetChunk(sprite.get(), &header);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -439,6 +438,12 @@ doc::Layer* AsepriteDecoder::readLayerChunk(AsepriteHeader* header,
|
||||
case ASE_FILE_LAYER_GROUP:
|
||||
layer = new doc::LayerGroup(sprite);
|
||||
break;
|
||||
|
||||
case ASE_FILE_LAYER_TILEMAP: {
|
||||
doc::tileset_index tsi = read32();
|
||||
layer = new doc::LayerTilemap(sprite, tsi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (layer) {
|
||||
@ -481,11 +486,13 @@ doc::Layer* AsepriteDecoder::readLayerChunk(AsepriteHeader* header,
|
||||
// Raw Image
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename ImageTraits>
|
||||
void read_raw_image(FileInterface* f,
|
||||
DecodeDelegate* delegate,
|
||||
doc::Image* image,
|
||||
AsepriteHeader* header)
|
||||
const AsepriteHeader* header)
|
||||
{
|
||||
PixelIO<ImageTraits> pixel_io;
|
||||
int x, y;
|
||||
@ -505,11 +512,11 @@ void read_raw_image(FileInterface* f,
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<typename ImageTraits>
|
||||
void read_compressed_image(FileInterface* f,
|
||||
DecodeDelegate* delegate,
|
||||
doc::Image* image,
|
||||
size_t chunk_end,
|
||||
AsepriteHeader* header)
|
||||
void read_compressed_image_templ(FileInterface* f,
|
||||
DecodeDelegate* delegate,
|
||||
doc::Image* image,
|
||||
const AsepriteHeader* header,
|
||||
const size_t chunk_end)
|
||||
{
|
||||
PixelIO<ImageTraits> pixel_io;
|
||||
z_stream zstream;
|
||||
@ -538,8 +545,9 @@ void read_compressed_image(FileInterface* f,
|
||||
if (input_bytes == 0)
|
||||
break; // Done, we consumed all chunk
|
||||
}
|
||||
else
|
||||
else {
|
||||
input_bytes = compressed.size();
|
||||
}
|
||||
|
||||
size_t bytes_read = f->readBytes(&compressed[0], input_bytes);
|
||||
zstream.next_in = (Bytef*)&compressed[0];
|
||||
@ -583,6 +591,46 @@ void read_compressed_image(FileInterface* f,
|
||||
throw base::Exception("ZLib error %d in inflateEnd().", err);
|
||||
}
|
||||
|
||||
void read_compressed_image(FileInterface* f,
|
||||
DecodeDelegate* delegate,
|
||||
doc::Image* image,
|
||||
const AsepriteHeader* header,
|
||||
const size_t chunk_end)
|
||||
{
|
||||
// Try to read pixel data
|
||||
try {
|
||||
switch (image->pixelFormat()) {
|
||||
|
||||
case doc::IMAGE_RGB:
|
||||
read_compressed_image_templ<doc::RgbTraits>(
|
||||
f, delegate, image, header, chunk_end);
|
||||
break;
|
||||
|
||||
case doc::IMAGE_GRAYSCALE:
|
||||
read_compressed_image_templ<doc::GrayscaleTraits>(
|
||||
f, delegate, image, header, chunk_end);
|
||||
break;
|
||||
|
||||
case doc::IMAGE_INDEXED:
|
||||
read_compressed_image_templ<doc::IndexedTraits>(
|
||||
f, delegate, image, header, chunk_end);
|
||||
break;
|
||||
|
||||
case doc::IMAGE_TILEMAP:
|
||||
read_compressed_image_templ<doc::TilemapTraits>(
|
||||
f, delegate, image, header, chunk_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// OK, in case of error we can show the problem, but continue
|
||||
// loading more cels.
|
||||
catch (const std::exception& e) {
|
||||
delegate->error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Cel Chunk
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -591,8 +639,8 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
|
||||
doc::LayerList& allLayers,
|
||||
doc::frame_t frame,
|
||||
doc::PixelFormat pixelFormat,
|
||||
AsepriteHeader* header,
|
||||
size_t chunk_end)
|
||||
const AsepriteHeader* header,
|
||||
const size_t chunk_end)
|
||||
{
|
||||
// Read chunk data
|
||||
doc::layer_t layer_index = read16();
|
||||
@ -687,33 +735,29 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
|
||||
|
||||
if (w > 0 && h > 0) {
|
||||
doc::ImageRef image(doc::Image::create(pixelFormat, w, h));
|
||||
read_compressed_image(f(), delegate(), image.get(), header, chunk_end);
|
||||
|
||||
// Try to read pixel data
|
||||
try {
|
||||
switch (image->pixelFormat()) {
|
||||
cel.reset(new doc::Cel(frame, image));
|
||||
cel->setPosition(x, y);
|
||||
cel->setOpacity(opacity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case doc::IMAGE_RGB:
|
||||
read_compressed_image<doc::RgbTraits>(
|
||||
f(), delegate(), image.get(), chunk_end, header);
|
||||
break;
|
||||
|
||||
case doc::IMAGE_GRAYSCALE:
|
||||
read_compressed_image<doc::GrayscaleTraits>(
|
||||
f(), delegate(), image.get(), chunk_end, header);
|
||||
break;
|
||||
|
||||
case doc::IMAGE_INDEXED:
|
||||
read_compressed_image<doc::IndexedTraits>(
|
||||
f(), delegate(), image.get(), chunk_end, header);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// OK, in case of error we can show the problem, but continue
|
||||
// loading more cels.
|
||||
catch (const std::exception& e) {
|
||||
delegate()->error(e.what());
|
||||
}
|
||||
case ASE_FILE_COMPRESSED_TILEMAP: {
|
||||
// Read width and height
|
||||
int w = read16();
|
||||
int h = read16();
|
||||
int bitsPerTile = read16();
|
||||
uint32_t tileIDkMask = read32();
|
||||
uint32_t flipxMask = read32();
|
||||
uint32_t fileyMask = read32();
|
||||
uint32_t rot90Mask = read32();
|
||||
readPadding(10);
|
||||
|
||||
if (w > 0 && h > 0) {
|
||||
doc::ImageRef image(doc::Image::create(doc::IMAGE_TILEMAP, w, h));
|
||||
read_compressed_image(f(), delegate(), image.get(), header, chunk_end);
|
||||
cel.reset(new doc::Cel(frame, image));
|
||||
cel->setPosition(x, y);
|
||||
cel->setOpacity(opacity);
|
||||
@ -925,4 +969,45 @@ doc::Slice* AsepriteDecoder::readSliceChunk(doc::Slices& slices)
|
||||
return slice.release();
|
||||
}
|
||||
|
||||
void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
|
||||
const AsepriteHeader* header)
|
||||
{
|
||||
const doc::tileset_index id = read32();
|
||||
const uint32_t flags = read32();
|
||||
const int w = read16();
|
||||
const int h = read16();
|
||||
readPadding(20);
|
||||
const std::string name = readString();
|
||||
|
||||
// TODO add support to load the external filename
|
||||
if (flags & 1) {
|
||||
// Ignore fields
|
||||
readString(); // external filename
|
||||
read32(); // tileset ID in the external file
|
||||
}
|
||||
|
||||
if (flags & 2) {
|
||||
const doc::tile_index ntiles = read32();
|
||||
|
||||
doc::Grid grid(gfx::Size(w, h));
|
||||
auto tileset = new doc::Tileset(sprite, grid, ntiles);
|
||||
tileset->setName(name);
|
||||
|
||||
for (doc::tile_index i=0; i<ntiles; ++i) {
|
||||
read32(); // Flags (ignore)
|
||||
const size_t dataSize = read32(); // Size of compressed data
|
||||
const size_t dataBeg = f()->tell();
|
||||
const size_t dataEnd = dataBeg+dataSize;
|
||||
|
||||
doc::ImageRef tile(doc::Image::create(sprite->pixelFormat(), w, h));
|
||||
read_compressed_image(f(), delegate(), tile.get(), header, dataEnd);
|
||||
tileset->set(i, tile);
|
||||
|
||||
f()->seek(dataEnd);
|
||||
}
|
||||
|
||||
sprite->tilesets()->set(id, tileset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dio
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document IO Library
|
||||
// Copyright (c) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (c) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -50,8 +50,8 @@ private:
|
||||
doc::LayerList& allLayers,
|
||||
doc::frame_t frame,
|
||||
doc::PixelFormat pixelFormat,
|
||||
AsepriteHeader* header,
|
||||
size_t chunk_end);
|
||||
const AsepriteHeader* header,
|
||||
const size_t chunk_end);
|
||||
void readCelExtraChunk(doc::Cel* cel);
|
||||
void readColorProfile(doc::Sprite* sprite);
|
||||
doc::Mask* readMaskChunk();
|
||||
@ -59,6 +59,8 @@ private:
|
||||
void readSlicesChunk(doc::Slices& slices);
|
||||
doc::Slice* readSliceChunk(doc::Slices& slices);
|
||||
void readUserDataChunk(doc::UserData* userData);
|
||||
void readTilesetChunk(doc::Sprite* sprite,
|
||||
const AsepriteHeader* header);
|
||||
};
|
||||
|
||||
} // namespace dio
|
||||
|
@ -46,22 +46,21 @@ public:
|
||||
}
|
||||
void read_scanline(doc::RgbTraits::address_t address,
|
||||
int w, uint8_t* buffer) {
|
||||
for (int x=0; x<w; ++x) {
|
||||
for (int x=0; x<w; ++x, ++address) {
|
||||
r = *(buffer++);
|
||||
g = *(buffer++);
|
||||
b = *(buffer++);
|
||||
a = *(buffer++);
|
||||
*(address++) = doc::rgba(r, g, b, a);
|
||||
*address = doc::rgba(r, g, b, a);
|
||||
}
|
||||
}
|
||||
void write_scanline(doc::RgbTraits::address_t address,
|
||||
int w, uint8_t* buffer) {
|
||||
for (int x=0; x<w; ++x) {
|
||||
for (int x=0; x<w; ++x, ++address) {
|
||||
*(buffer++) = doc::rgba_getr(*address);
|
||||
*(buffer++) = doc::rgba_getg(*address);
|
||||
*(buffer++) = doc::rgba_getb(*address);
|
||||
*(buffer++) = doc::rgba_geta(*address);
|
||||
++address;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -82,19 +81,18 @@ public:
|
||||
void read_scanline(doc::GrayscaleTraits::address_t address,
|
||||
int w, uint8_t* buffer)
|
||||
{
|
||||
for (int x=0; x<w; ++x) {
|
||||
for (int x=0; x<w; ++x, ++address) {
|
||||
k = *(buffer++);
|
||||
a = *(buffer++);
|
||||
*(address++) = doc::graya(k, a);
|
||||
*address = doc::graya(k, a);
|
||||
}
|
||||
}
|
||||
void write_scanline(doc::GrayscaleTraits::address_t address,
|
||||
int w, uint8_t* buffer)
|
||||
{
|
||||
for (int x=0; x<w; ++x) {
|
||||
for (int x=0; x<w; ++x, ++address) {
|
||||
*(buffer++) = doc::graya_getv(*address);
|
||||
*(buffer++) = doc::graya_geta(*address);
|
||||
++address;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -118,6 +116,35 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class PixelIO<doc::TilemapTraits> {
|
||||
int b1, b2, b3, b4;
|
||||
public:
|
||||
doc::TilemapTraits::pixel_t read_pixel(FileInterface* f) {
|
||||
int b1 = f->read8();
|
||||
int b2 = f->read8();
|
||||
int b3 = f->read8();
|
||||
int b4 = f->read8();
|
||||
|
||||
if (f->ok()) {
|
||||
// Little endian
|
||||
return ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
void read_scanline(doc::TilemapTraits::address_t address,
|
||||
int w, uint8_t* buffer) {
|
||||
for (int x=0; x<w; ++x, ++address) {
|
||||
b1 = *(buffer++);
|
||||
b2 = *(buffer++);
|
||||
b3 = *(buffer++);
|
||||
b4 = *(buffer++);
|
||||
*address = ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dio
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Aseprite Document Library
|
||||
# Copyright (C) 2001-2018 David Capello
|
||||
# Copyright (C) 2019 Igara Studio S.A.
|
||||
# Copyright (C) 2001-2018 David Capello
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
@ -36,6 +37,8 @@ add_library(doc-lib
|
||||
file/gpl_file.cpp
|
||||
file/hex_file.cpp
|
||||
file/pal_file.cpp
|
||||
grid.cpp
|
||||
grid_io.cpp
|
||||
handle_anidir.cpp
|
||||
image.cpp
|
||||
image_impl.cpp
|
||||
@ -43,6 +46,7 @@ add_library(doc-lib
|
||||
layer.cpp
|
||||
layer_io.cpp
|
||||
layer_list.cpp
|
||||
layer_tilemap.cpp
|
||||
mask.cpp
|
||||
mask_boundaries.cpp
|
||||
mask_io.cpp
|
||||
@ -66,6 +70,9 @@ add_library(doc-lib
|
||||
tag.cpp
|
||||
tag_io.cpp
|
||||
tags.cpp
|
||||
tileset.cpp
|
||||
tileset_io.cpp
|
||||
tilesets.cpp
|
||||
user_data_io.cpp)
|
||||
|
||||
# TODO Remove 'os' as dependency and move conversion_to_surface.cpp/h files
|
||||
|
@ -35,6 +35,7 @@ void fill_selection(Image* image,
|
||||
for (int u=0; u<maskBounds.w; ++u, ++it) {
|
||||
ASSERT(it != maskBits.end());
|
||||
if (*it) {
|
||||
// TODO use iterators
|
||||
put_pixel(image,
|
||||
u + offset.x,
|
||||
v + offset.y, color);
|
||||
|
@ -106,7 +106,10 @@ ImageRef shift_image_with_mask(const Cel* cel,
|
||||
|
||||
// Bounds and Image shrinking (we have to fit compound image (compImage) and bounds (compCelBounds))
|
||||
gfx::Rect newBounds = compImage->bounds();
|
||||
if (algorithm::shrink_bounds(compImage.get(), newBounds, compImage->maskColor())) {
|
||||
if (algorithm::shrink_bounds(compImage.get(),
|
||||
compImage->maskColor(),
|
||||
nullptr,
|
||||
newBounds)) {
|
||||
compCelBounds.offset(newBounds.x, newBounds.y);
|
||||
compCelBounds.setSize(newBounds.size());
|
||||
}
|
||||
|
@ -11,9 +11,15 @@
|
||||
|
||||
#include "doc/algorithm/shrink_bounds.h"
|
||||
|
||||
#include "doc/cel.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/primitives_fast.h"
|
||||
#include "doc/tileset.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
@ -47,6 +53,12 @@ bool is_same_pixel<IndexedTraits>(color_t pixel1, color_t pixel2)
|
||||
return pixel1 == pixel2;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool is_same_pixel<BitmapTraits>(color_t pixel1, color_t pixel2)
|
||||
{
|
||||
return pixel1 == pixel2;
|
||||
}
|
||||
|
||||
template<typename ImageTraits>
|
||||
bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowSize)
|
||||
{
|
||||
@ -223,46 +235,171 @@ bool shrink_bounds_templ2(const Image* a, const Image* b, gfx::Rect& bounds)
|
||||
return (!bounds.isEmpty());
|
||||
}
|
||||
|
||||
bool shrink_bounds_tilemap(const Image* image,
|
||||
const color_t refpixel,
|
||||
const Layer* layer,
|
||||
gfx::Rect& bounds)
|
||||
{
|
||||
ASSERT(layer);
|
||||
if (!layer)
|
||||
return false;
|
||||
|
||||
ASSERT(layer->isTilemap());
|
||||
if (!layer->isTilemap())
|
||||
return false;
|
||||
|
||||
const Tileset* tileset =
|
||||
static_cast<const LayerTilemap*>(layer)->tileset();
|
||||
|
||||
bool shrink;
|
||||
int u, v;
|
||||
|
||||
// Shrink left side
|
||||
for (u=bounds.x; u<bounds.x+bounds.w; ++u) {
|
||||
shrink = true;
|
||||
for (v=bounds.y; v<bounds.y+bounds.h; ++v) {
|
||||
const tile_t tile = get_pixel_fast<TilemapTraits>(image, u, v);
|
||||
const tile_t tileIndex = tile_geti(tile);
|
||||
const ImageRef tileImg = tileset->get(tileIndex);
|
||||
|
||||
if (tileImg && !is_plain_image(tileImg.get(), refpixel)) {
|
||||
shrink = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shrink)
|
||||
break;
|
||||
++bounds.x;
|
||||
--bounds.w;
|
||||
}
|
||||
|
||||
// Shrink right side
|
||||
for (u=bounds.x+bounds.w-1; u>=bounds.x; --u) {
|
||||
shrink = true;
|
||||
for (v=bounds.y; v<bounds.y+bounds.h; ++v) {
|
||||
const tile_t tile = get_pixel_fast<TilemapTraits>(image, u, v);
|
||||
const tile_t tileIndex = tile_geti(tile);
|
||||
const ImageRef tileImg = tileset->get(tileIndex);
|
||||
|
||||
if (tileImg && !is_plain_image(tileImg.get(), refpixel)) {
|
||||
shrink = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shrink)
|
||||
break;
|
||||
--bounds.w;
|
||||
}
|
||||
|
||||
// Shrink top side
|
||||
for (v=bounds.y; v<bounds.y+bounds.h; ++v) {
|
||||
shrink = true;
|
||||
for (u=bounds.x; u<bounds.x+bounds.w; ++u) {
|
||||
const tile_t tile = get_pixel_fast<TilemapTraits>(image, u, v);
|
||||
const tile_t tileIndex = tile_geti(tile);
|
||||
const ImageRef tileImg = tileset->get(tileIndex);
|
||||
|
||||
if (tileImg && !is_plain_image(tileImg.get(), refpixel)) {
|
||||
shrink = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shrink)
|
||||
break;
|
||||
++bounds.y;
|
||||
--bounds.h;
|
||||
}
|
||||
|
||||
// Shrink bottom side
|
||||
for (v=bounds.y+bounds.h-1; v>=bounds.y; --v) {
|
||||
shrink = true;
|
||||
for (u=bounds.x; u<bounds.x+bounds.w; ++u) {
|
||||
const tile_t tile = get_pixel_fast<TilemapTraits>(image, u, v);
|
||||
const tile_t tileIndex = tile_geti(tile);
|
||||
const ImageRef tileImg = tileset->get(tileIndex);
|
||||
|
||||
if (tileImg && !is_plain_image(tileImg.get(), refpixel)) {
|
||||
shrink = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shrink)
|
||||
break;
|
||||
--bounds.h;
|
||||
}
|
||||
|
||||
return (!bounds.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool shrink_bounds(const Image* image,
|
||||
const gfx::Rect& start_bounds,
|
||||
gfx::Rect& bounds,
|
||||
color_t refpixel)
|
||||
const color_t refpixel,
|
||||
const Layer* layer,
|
||||
const gfx::Rect& startBounds,
|
||||
gfx::Rect& bounds)
|
||||
{
|
||||
bounds = (start_bounds & image->bounds());
|
||||
bounds = (startBounds & image->bounds());
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB: return shrink_bounds_templ<RgbTraits>(image, bounds, refpixel);
|
||||
case IMAGE_GRAYSCALE: return shrink_bounds_templ<GrayscaleTraits>(image, bounds, refpixel);
|
||||
case IMAGE_INDEXED: return shrink_bounds_templ<IndexedTraits>(image, bounds, refpixel);
|
||||
case IMAGE_BITMAP:
|
||||
// Not supported
|
||||
break;
|
||||
case IMAGE_BITMAP: return shrink_bounds_templ<BitmapTraits>(image, bounds, refpixel);
|
||||
case IMAGE_TILEMAP: return shrink_bounds_tilemap(image, refpixel, layer, bounds);
|
||||
}
|
||||
ASSERT(false);
|
||||
bounds = start_bounds;
|
||||
bounds = startBounds;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shrink_bounds(const Image* image, gfx::Rect& bounds, color_t refpixel)
|
||||
bool shrink_bounds(const Image* image,
|
||||
const color_t refpixel,
|
||||
const Layer* layer,
|
||||
gfx::Rect& bounds)
|
||||
{
|
||||
return shrink_bounds(image, image->bounds(), bounds, refpixel);
|
||||
return shrink_bounds(image, refpixel, layer, image->bounds(), bounds);
|
||||
}
|
||||
|
||||
bool shrink_bounds2(const Image* a, const Image* b,
|
||||
const gfx::Rect& start_bounds,
|
||||
bool shrink_cel_bounds(const Cel* cel,
|
||||
const color_t refpixel,
|
||||
gfx::Rect& bounds)
|
||||
{
|
||||
if (shrink_bounds(cel->image(), refpixel, cel->layer(), bounds)) {
|
||||
// For tilemaps, we have to convert imgBounds (in tiles
|
||||
// coordinates) to canvas coordinates using the Grid specs.
|
||||
if (cel->layer()->isTilemap()) {
|
||||
doc::LayerTilemap* tilemapLayer = static_cast<doc::LayerTilemap*>(cel->layer());
|
||||
doc::Tileset* tileset = tilemapLayer->tileset();
|
||||
doc::Grid grid = tileset->grid();
|
||||
grid.origin(grid.origin() + cel->position());
|
||||
bounds = grid.tileToCanvas(bounds);
|
||||
}
|
||||
else {
|
||||
bounds.offset(cel->position());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool shrink_bounds2(const Image* a,
|
||||
const Image* b,
|
||||
const gfx::Rect& startBounds,
|
||||
gfx::Rect& bounds)
|
||||
{
|
||||
ASSERT(a && b);
|
||||
ASSERT(a->bounds() == b->bounds());
|
||||
|
||||
bounds = (start_bounds & a->bounds());
|
||||
bounds = (startBounds & a->bounds());
|
||||
|
||||
switch (a->pixelFormat()) {
|
||||
case IMAGE_RGB: return shrink_bounds_templ2<RgbTraits>(a, b, bounds);
|
||||
case IMAGE_GRAYSCALE: return shrink_bounds_templ2<GrayscaleTraits>(a, b, bounds);
|
||||
case IMAGE_INDEXED: return shrink_bounds_templ2<IndexedTraits>(a, b, bounds);
|
||||
case IMAGE_BITMAP: return shrink_bounds_templ2<BitmapTraits>(a, b, bounds);
|
||||
// case IMAGE_TILEMAP: return shrink_bounds_templ2<TilemapTraits>(a, b, bounds);
|
||||
}
|
||||
ASSERT(false);
|
||||
return false;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -13,22 +14,31 @@
|
||||
#include "doc/color.h"
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
class Image;
|
||||
class Layer;
|
||||
|
||||
namespace algorithm {
|
||||
|
||||
// The layer is used in case the image is a tilemap and we need its tileset.
|
||||
bool shrink_bounds(const Image* image,
|
||||
const gfx::Rect& start_bounds,
|
||||
gfx::Rect& bounds,
|
||||
color_t refpixel);
|
||||
const color_t refpixel,
|
||||
const Layer* layer,
|
||||
const gfx::Rect& startBounds,
|
||||
gfx::Rect& bounds);
|
||||
|
||||
bool shrink_bounds(const Image* image,
|
||||
gfx::Rect& bounds,
|
||||
color_t refpixel);
|
||||
const color_t refpixel,
|
||||
const Layer* layer,
|
||||
gfx::Rect& bounds);
|
||||
|
||||
bool shrink_cel_bounds(const Cel* cel,
|
||||
const color_t refpixel,
|
||||
gfx::Rect& bounds);
|
||||
|
||||
bool shrink_bounds2(const Image* a,
|
||||
const Image* b,
|
||||
const gfx::Rect& start_bounds,
|
||||
const gfx::Rect& startBounds,
|
||||
gfx::Rect& bounds);
|
||||
|
||||
} // algorithm
|
||||
|
@ -148,8 +148,11 @@ void Cel::setParentLayer(LayerImage* layer)
|
||||
void Cel::fixupImage()
|
||||
{
|
||||
// Change the mask color to the sprite mask color
|
||||
if (m_layer && image())
|
||||
if (m_layer && image()) {
|
||||
image()->setMaskColor(m_layer->sprite()->transparentColor());
|
||||
ASSERT(m_data);
|
||||
m_data->adjustBounds(m_layer);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -10,10 +11,12 @@
|
||||
|
||||
#include "doc/cel_data.h"
|
||||
|
||||
#include "gfx/rect.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
namespace doc {
|
||||
|
||||
@ -43,13 +46,38 @@ CelData::~CelData()
|
||||
delete m_boundsF;
|
||||
}
|
||||
|
||||
void CelData::setImage(const ImageRef& image)
|
||||
void CelData::setImage(const ImageRef& image, Layer* layer)
|
||||
{
|
||||
ASSERT(image.get());
|
||||
|
||||
m_image = image;
|
||||
m_bounds.w = image->width();
|
||||
m_bounds.h = image->height();
|
||||
adjustBounds(layer);
|
||||
}
|
||||
|
||||
void CelData::setPosition(const gfx::Point& pos)
|
||||
{
|
||||
m_bounds.setOrigin(pos);
|
||||
}
|
||||
|
||||
void CelData::adjustBounds(Layer* layer)
|
||||
{
|
||||
ASSERT(m_image);
|
||||
if (m_image->pixelFormat() == IMAGE_TILEMAP) {
|
||||
Tileset* tileset = nullptr;
|
||||
if (layer && layer->isTilemap())
|
||||
tileset = static_cast<LayerTilemap*>(layer)->tileset();
|
||||
if (tileset) {
|
||||
gfx::Size canvasSize =
|
||||
tileset->grid().tilemapSizeToCanvas(
|
||||
gfx::Size(m_image->width(),
|
||||
m_image->height()));
|
||||
m_bounds.w = canvasSize.w;
|
||||
m_bounds.h = canvasSize.h;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_bounds.w = m_image->width();
|
||||
m_bounds.h = m_image->height();
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -18,6 +18,9 @@
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Layer;
|
||||
class Tileset;
|
||||
|
||||
class CelData : public WithUserData {
|
||||
public:
|
||||
CelData(const ImageRef& image);
|
||||
@ -30,8 +33,8 @@ namespace doc {
|
||||
Image* image() const { return const_cast<Image*>(m_image.get()); };
|
||||
ImageRef imageRef() const { return m_image; }
|
||||
|
||||
void setImage(const ImageRef& image);
|
||||
void setPosition(const gfx::Point& pos) { m_bounds.setOrigin(pos); }
|
||||
void setImage(const ImageRef& image, Layer* layer);
|
||||
void setPosition(const gfx::Point& pos);
|
||||
void setOpacity(int opacity) { m_opacity = opacity; }
|
||||
|
||||
void setBounds(const gfx::Rect& bounds) {
|
||||
@ -60,6 +63,8 @@ namespace doc {
|
||||
return sizeof(CelData) + m_image->getMemSize();
|
||||
}
|
||||
|
||||
void adjustBounds(Layer* layer);
|
||||
|
||||
private:
|
||||
ImageRef m_image;
|
||||
int m_opacity;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2014 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -14,7 +15,8 @@ namespace doc {
|
||||
RGB,
|
||||
GRAYSCALE,
|
||||
INDEXED,
|
||||
BITMAP
|
||||
BITMAP,
|
||||
TILEMAP,
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/images_map.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/object.h"
|
||||
#include "doc/palette.h"
|
||||
@ -38,5 +39,8 @@
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tag.h"
|
||||
#include "doc/tags.h"
|
||||
#include "doc/tile.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tilesets.h"
|
||||
|
||||
#endif
|
||||
|
163
src/doc/grid.cpp
Normal file
163
src/doc/grid.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "doc/grid.h"
|
||||
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/rect.h"
|
||||
#include "gfx/region.h"
|
||||
#include "gfx/size.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace doc {
|
||||
|
||||
// static
|
||||
Grid Grid::MakeRect(const gfx::Size& sz)
|
||||
{
|
||||
return Grid(sz);
|
||||
}
|
||||
|
||||
// Converts a tile position into a canvas position
|
||||
gfx::Point Grid::tileToCanvas(const gfx::Point& tile) const
|
||||
{
|
||||
gfx::Point result;
|
||||
result.x = tile.x * m_tileOffset.x + m_origin.x;
|
||||
result.y = tile.y * m_tileOffset.y + m_origin.y;
|
||||
if (tile.y & 1) // Odd row
|
||||
result += m_oddRowOffset;
|
||||
if (tile.x & 1) // Odd column
|
||||
result += m_oddColOffset;
|
||||
return result;
|
||||
}
|
||||
|
||||
gfx::Rect Grid::tileToCanvas(const gfx::Rect& tileBounds) const
|
||||
{
|
||||
gfx::Point pt1 = tileToCanvas(tileBounds.origin());
|
||||
gfx::Point pt2 = tileToCanvas(tileBounds.point2());
|
||||
return gfx::Rect(pt1, pt2);
|
||||
}
|
||||
|
||||
gfx::Point Grid::canvasToTile(const gfx::Point& canvasPoint) const
|
||||
{
|
||||
gfx::Point tile;
|
||||
std::div_t divx = std::div((canvasPoint.x - m_origin.x), m_tileSize.w);
|
||||
std::div_t divy = std::div((canvasPoint.y - m_origin.y), m_tileSize.h);
|
||||
tile.x = divx.quot;
|
||||
tile.y = divy.quot;
|
||||
if (canvasPoint.x < m_origin.x && divx.rem) --tile.x;
|
||||
if (canvasPoint.y < m_origin.y && divy.rem) --tile.y;
|
||||
|
||||
if (m_oddRowOffset.x != 0 || m_oddRowOffset.y != 0 ||
|
||||
m_oddColOffset.x != 0 || m_oddColOffset.y != 0) {
|
||||
gfx::Point bestTile = tile;
|
||||
int bestDist = std::numeric_limits<int>::max();
|
||||
for (int v=-1; v<=2; ++v) {
|
||||
for (int u=-1; u<=2; ++u) {
|
||||
gfx::Point neighbor(tile.x+u, tile.y+v);
|
||||
gfx::Point neighborCanvas = tileToCanvas(neighbor);
|
||||
|
||||
if (hasMask()) {
|
||||
if (doc::get_pixel(m_mask.get(),
|
||||
canvasPoint.x-neighborCanvas.x,
|
||||
canvasPoint.y-neighborCanvas.y))
|
||||
return neighbor;
|
||||
}
|
||||
|
||||
gfx::Point delta = neighborCanvas+m_tileCenter-canvasPoint;
|
||||
int dist = delta.x*delta.x + delta.y*delta.y;
|
||||
if (bestDist > dist) {
|
||||
bestDist = dist;
|
||||
bestTile = neighbor;
|
||||
}
|
||||
}
|
||||
}
|
||||
tile = bestTile;
|
||||
}
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
gfx::Rect Grid::canvasToTile(const gfx::Rect& canvasBounds) const
|
||||
{
|
||||
gfx::Point pt1 = canvasToTile(canvasBounds.origin());
|
||||
gfx::Point pt2 = canvasToTile(gfx::Point(canvasBounds.x2()-1,
|
||||
canvasBounds.y2()-1));
|
||||
return gfx::Rect(pt1, gfx::Size(pt2.x - pt1.x + 1,
|
||||
pt2.y - pt1.y + 1));
|
||||
}
|
||||
|
||||
gfx::Size Grid::tilemapSizeToCanvas(const gfx::Size& tilemapSize) const
|
||||
{
|
||||
gfx::Point pt = tileToCanvas(gfx::Point(tilemapSize.w-1,
|
||||
tilemapSize.h-1));
|
||||
return gfx::Size(pt.x + m_tileSize.w,
|
||||
pt.y + m_tileSize.h);
|
||||
}
|
||||
|
||||
gfx::Rect Grid::tileBoundsInCanvas(const gfx::Point& tile) const
|
||||
{
|
||||
return gfx::Rect(tileToCanvas(tile), m_tileSize);
|
||||
}
|
||||
|
||||
gfx::Rect Grid::alignBounds(const gfx::Rect& bounds) const
|
||||
{
|
||||
gfx::Point pt1 = canvasToTile(bounds.origin());
|
||||
gfx::Point pt2 = canvasToTile(gfx::Point(bounds.x2()-1,
|
||||
bounds.y2()-1));
|
||||
return
|
||||
tileBoundsInCanvas(pt1) |
|
||||
tileBoundsInCanvas(pt2);
|
||||
}
|
||||
|
||||
std::vector<gfx::Point> Grid::tilesInCanvasRegion(const gfx::Region& rgn) const
|
||||
{
|
||||
std::vector<gfx::Point> result;
|
||||
if (rgn.isEmpty())
|
||||
return result;
|
||||
|
||||
const gfx::Rect bounds = canvasToTile(rgn.bounds());
|
||||
if (bounds.w < 1 ||
|
||||
bounds.h < 1)
|
||||
return result;
|
||||
|
||||
ImageRef tmp(Image::create(IMAGE_BITMAP, bounds.w, bounds.h));
|
||||
const gfx::Rect tmpBounds = tmp->bounds();
|
||||
tmp->clear(0);
|
||||
|
||||
for (const gfx::Rect& rc : rgn) {
|
||||
gfx::Rect tileBounds = canvasToTile(rc);
|
||||
tileBounds.x -= bounds.x;
|
||||
tileBounds.y -= bounds.y;
|
||||
tileBounds &= tmpBounds;
|
||||
if (!tileBounds.isEmpty())
|
||||
tmp->fillRect(tileBounds.x,
|
||||
tileBounds.y,
|
||||
tileBounds.x2()-1,
|
||||
tileBounds.y2()-1, 1);
|
||||
}
|
||||
|
||||
const LockImageBits<BitmapTraits> bits(tmp.get());
|
||||
for (auto it=bits.begin(), end=bits.end(); it!=end; ++it) {
|
||||
if (*it)
|
||||
result.push_back(gfx::Point(it.x()+bounds.x,
|
||||
it.y()+bounds.y));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace doc
|
76
src/doc/grid.h
Normal file
76
src/doc/grid.h
Normal file
@ -0,0 +1,76 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef DOC_GRID_H_INCLUDED
|
||||
#define DOC_GRID_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/fwd.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/size.h"
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Grid {
|
||||
public:
|
||||
Grid(const gfx::Size& sz = gfx::Size(16, 16))
|
||||
: m_tileSize(sz)
|
||||
, m_origin(0, 0)
|
||||
, m_tileCenter(gfx::Point(sz.w/2, sz.h/2))
|
||||
, m_tileOffset(gfx::Point(sz.w, sz.h))
|
||||
, m_oddRowOffset(0, 0)
|
||||
, m_oddColOffset(0, 0)
|
||||
, m_mask(nullptr) { }
|
||||
|
||||
static Grid MakeRect(const gfx::Size& sz);
|
||||
|
||||
gfx::Size tileSize() const { return m_tileSize; }
|
||||
gfx::Point origin() const { return m_origin; }
|
||||
gfx::Point tileCenter() const { return m_tileCenter; }
|
||||
gfx::Point tileOffset() const { return m_tileOffset; }
|
||||
gfx::Point oddRowOffset() const { return m_oddRowOffset; }
|
||||
gfx::Point oddColOffset() const { return m_oddColOffset; }
|
||||
|
||||
void tileSize(const gfx::Size& tileSize) { m_tileSize = tileSize; }
|
||||
void origin(const gfx::Point& origin) { m_origin = origin; }
|
||||
void tileCenter(const gfx::Point& tileCenter) { m_tileCenter = tileCenter; }
|
||||
void tileOffset(const gfx::Point& tileOffset) { m_tileOffset = tileOffset; }
|
||||
void oddRowOffset(const gfx::Point& oddRowOffset) { m_oddRowOffset = oddRowOffset; }
|
||||
void oddColOffset(const gfx::Point& oddColOffset) { m_oddColOffset = oddColOffset; }
|
||||
|
||||
// Converts a tile position into a canvas position
|
||||
gfx::Point tileToCanvas(const gfx::Point& tile) const;
|
||||
gfx::Rect tileToCanvas(const gfx::Rect& tileBounds) const;
|
||||
|
||||
// Converts a canvas position/rectangle into a tile position
|
||||
gfx::Point canvasToTile(const gfx::Point& canvasPoint) const;
|
||||
gfx::Rect canvasToTile(const gfx::Rect& canvasBounds) const;
|
||||
|
||||
gfx::Size tilemapSizeToCanvas(const gfx::Size& tilemapSize) const;
|
||||
gfx::Rect tileBoundsInCanvas(const gfx::Point& tile) const;
|
||||
gfx::Rect alignBounds(const gfx::Rect& bounds) const;
|
||||
|
||||
bool hasMask() const { return m_mask.get() != nullptr; }
|
||||
doc::ImageRef mask() const { return m_mask; }
|
||||
void setMask(const doc::ImageRef& mask) { m_mask = mask; }
|
||||
|
||||
// Returns an array of tile positions that are touching the given region in the canvas
|
||||
std::vector<gfx::Point> tilesInCanvasRegion(const gfx::Region& rgn) const;
|
||||
|
||||
private:
|
||||
gfx::Size m_tileSize;
|
||||
gfx::Point m_origin;
|
||||
gfx::Point m_tileCenter;
|
||||
gfx::Point m_tileOffset;
|
||||
gfx::Point m_oddRowOffset;
|
||||
gfx::Point m_oddColOffset;
|
||||
doc::ImageRef m_mask;
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
84
src/doc/grid_io.cpp
Normal file
84
src/doc/grid_io.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "doc/grid_io.h"
|
||||
|
||||
#include "base/serialization.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_io.h"
|
||||
#include "doc/grid.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace doc {
|
||||
|
||||
using namespace base::serialization;
|
||||
using namespace base::serialization::little_endian;
|
||||
|
||||
bool write_grid(std::ostream& os, const Grid& grid)
|
||||
{
|
||||
write32(os, grid.tileSize().w);
|
||||
write32(os, grid.tileSize().h);
|
||||
write32(os, grid.origin().x);
|
||||
write32(os, grid.origin().y);
|
||||
write32(os, grid.tileCenter().x);
|
||||
write32(os, grid.tileCenter().y);
|
||||
write32(os, grid.tileOffset().x);
|
||||
write32(os, grid.tileOffset().y);
|
||||
write32(os, grid.oddRowOffset().x);
|
||||
write32(os, grid.oddRowOffset().y);
|
||||
write32(os, grid.oddColOffset().x);
|
||||
write32(os, grid.oddColOffset().y);
|
||||
write8(os, grid.hasMask());
|
||||
if (grid.hasMask())
|
||||
return write_image(os, grid.mask().get());
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
Grid read_grid(std::istream& is, bool setId)
|
||||
{
|
||||
gfx::Size tileSize;
|
||||
gfx::Point origin;
|
||||
gfx::Point tileCenter;
|
||||
gfx::Point tileOffset;
|
||||
gfx::Point oddRowOffset;
|
||||
gfx::Point oddColOffset;
|
||||
tileSize.w = read32(is);
|
||||
tileSize.h = read32(is);
|
||||
origin.x = read32(is);
|
||||
origin.y = read32(is);
|
||||
tileCenter.x = read32(is);
|
||||
tileCenter.y = read32(is);
|
||||
tileOffset.x = read32(is);
|
||||
tileOffset.y = read32(is);
|
||||
oddRowOffset.x = read32(is);
|
||||
oddRowOffset.y = read32(is);
|
||||
oddColOffset.x = read32(is);
|
||||
oddColOffset.y = read32(is);
|
||||
bool hasMask = read8(is);
|
||||
|
||||
Grid grid;
|
||||
grid.tileSize(tileSize);
|
||||
grid.origin(origin);
|
||||
grid.tileCenter(tileCenter);
|
||||
grid.tileOffset(tileOffset);
|
||||
grid.oddRowOffset(oddRowOffset);
|
||||
grid.oddColOffset(oddColOffset);
|
||||
|
||||
if (hasMask) {
|
||||
ImageRef mask(read_image(is));
|
||||
if (mask)
|
||||
grid.setMask(mask);
|
||||
}
|
||||
return grid;
|
||||
}
|
||||
|
||||
} // namespace doc
|
22
src/doc/grid_io.h
Normal file
22
src/doc/grid_io.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef DOC_GRID_IO_H_INCLUDED
|
||||
#define DOC_GRID_IO_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Grid;
|
||||
|
||||
bool write_grid(std::ostream& os, const Grid& grid);
|
||||
Grid read_grid(std::istream& is, bool setId = true);
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
86
src/doc/grid_tests.cpp
Normal file
86
src/doc/grid_tests.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "doc/grid.h"
|
||||
#include "gfx/rect_io.h"
|
||||
#include "gfx/region.h"
|
||||
|
||||
// TODO add gfx/point_io.h and gfx/size_io.h
|
||||
namespace gfx {
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const Point& pt) {
|
||||
return os << "("
|
||||
<< pt.x << ", "
|
||||
<< pt.y << ")";
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const Size& sz) {
|
||||
return os << "("
|
||||
<< sz.w << ", "
|
||||
<< sz.h << ")";
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
using namespace doc;
|
||||
using namespace gfx;
|
||||
|
||||
TEST(Grid, Rect)
|
||||
{
|
||||
auto grid = Grid::MakeRect(Size(16, 16));
|
||||
|
||||
EXPECT_EQ(Size(16, 16), grid.tileSize());
|
||||
EXPECT_EQ(Point(16, 16), grid.tileOffset());
|
||||
|
||||
EXPECT_EQ(Point(16, 16), grid.tileToCanvas(Point(1, 1)));
|
||||
EXPECT_EQ(Point(32, 16), grid.tileToCanvas(Point(2, 1)));
|
||||
EXPECT_EQ(Point(0, 0), grid.canvasToTile(Point(5, 5)));
|
||||
EXPECT_EQ(Point(1, 1), grid.canvasToTile(Point(16, 16)));
|
||||
EXPECT_EQ(Rect(1, 0, 1, 2), grid.canvasToTile(Rect(16, 5, 16, 16)));
|
||||
|
||||
EXPECT_EQ(Rect(0, 0, 32, 32), grid.alignBounds(Rect(4, 5, 16, 17)));
|
||||
EXPECT_EQ(Rect(16, 16, 16, 16), grid.alignBounds(Rect(16, 16, 16, 16)));
|
||||
EXPECT_EQ(Rect(16, 16, 32, 16), grid.alignBounds(Rect(16, 16, 17, 16)));
|
||||
EXPECT_EQ(Rect(16, 16, 48, 16), grid.alignBounds(Rect(17, 16, 32, 16)));
|
||||
|
||||
gfx::Region rgn;
|
||||
rgn |= gfx::Region(gfx::Rect(5, 17, 8, 8));
|
||||
rgn |= gfx::Region(gfx::Rect(17, 5, 16, 8));
|
||||
auto pts = grid.tilesInCanvasRegion(rgn);
|
||||
ASSERT_EQ(3, pts.size());
|
||||
EXPECT_EQ(gfx::Point(1, 0), pts[0]);
|
||||
EXPECT_EQ(gfx::Point(2, 0), pts[1]);
|
||||
EXPECT_EQ(gfx::Point(0, 1), pts[2]);
|
||||
}
|
||||
|
||||
TEST(Grid, RectWithOffset)
|
||||
{
|
||||
auto grid = Grid::MakeRect(Size(16, 16));
|
||||
grid.origin(gfx::Point(17, 16));
|
||||
|
||||
EXPECT_EQ(Point(0, 0), grid.canvasToTile(Point(17, 16)));
|
||||
EXPECT_EQ(Point(0, 0), grid.canvasToTile(Point(17+15, 16)));
|
||||
EXPECT_EQ(Point(1, 0), grid.canvasToTile(Point(17+16, 16)));
|
||||
|
||||
EXPECT_EQ(Point(-1, 0), grid.canvasToTile(Point(16, 16)));
|
||||
EXPECT_EQ(Point(-1, 0), grid.canvasToTile(Point(2, 16)));
|
||||
EXPECT_EQ(Point(-1, 0), grid.canvasToTile(Point(1, 16)));
|
||||
|
||||
grid.origin(gfx::Point(-1, -1));
|
||||
EXPECT_EQ(Rect(1, 1, 1, 1), grid.canvasToTile(Rect(30, 30, 1, 1)));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -61,6 +61,7 @@ Image* Image::create(const ImageSpec& spec,
|
||||
case ColorMode::GRAYSCALE: return new ImageImpl<GrayscaleTraits>(spec, buffer);
|
||||
case ColorMode::INDEXED: return new ImageImpl<IndexedTraits>(spec, buffer);
|
||||
case ColorMode::BITMAP: return new ImageImpl<BitmapTraits>(spec, buffer);
|
||||
case ColorMode::TILEMAP: return new ImageImpl<TilemapTraits>(spec, buffer);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -107,6 +107,7 @@ namespace doc {
|
||||
case IMAGE_GRAYSCALE: return GrayscaleTraits::getRowStrideBytes(pixels_per_row);
|
||||
case IMAGE_INDEXED: return IndexedTraits::getRowStrideBytes(pixels_per_row);
|
||||
case IMAGE_BITMAP: return BitmapTraits::getRowStrideBytes(pixels_per_row);
|
||||
case IMAGE_TILEMAP: return TilemapTraits::getRowStrideBytes(pixels_per_row);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user