Merge branch 'fix-tile-mode' into tilemap-editor

This commit is contained in:
David Capello 2020-08-14 20:30:52 -03:00
commit 24a2d9527b
21 changed files with 343 additions and 158 deletions

View File

@ -1,6 +1,6 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2016 David Capello
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -13,6 +13,7 @@
#include "doc/cel.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
namespace app {
@ -24,9 +25,7 @@ CropCel::CropCel(Cel* cel, const gfx::Rect& newBounds)
: WithCel(cel)
, m_oldOrigin(cel->position())
, m_newOrigin(newBounds.origin())
// Instead of using cel->bounds() we use the image size because it
// works for tilemaps too.
, m_oldBounds(cel->position(), cel->image()->size())
, m_oldBounds(cel->bounds())
, m_newBounds(newBounds)
{
m_oldBounds.offset(-m_newOrigin);
@ -51,10 +50,18 @@ void CropCel::cropImage(const gfx::Point& origin,
{
Cel* cel = this->cel();
gfx::Rect localBounds(bounds);
if (cel->layer()->isTilemap()) {
doc::Tileset* tileset = static_cast<LayerTilemap*>(cel->layer())->tileset();
if (tileset) {
doc::Grid grid = tileset->grid();
localBounds = grid.canvasToTile(bounds);
}
}
if (bounds != cel->image()->bounds()) {
ImageRef image(crop_image(cel->image(),
bounds.x, bounds.y,
bounds.w, bounds.h,
localBounds.x, localBounds.y,
localBounds.w, localBounds.h,
cel->image()->maskColor()));
ObjectId id = cel->image()->id();
ObjectVersion ver = cel->image()->version();

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -270,7 +270,8 @@ bool DocApi::cropCel(LayerImage* layer,
crop_image(image,
paintPos.x, paintPos.y,
newCelBounds.w, newCelBounds.h,
m_document->bgColor(layer)));
image->pixelFormat() == IMAGE_TILEMAP ?
tile_i_notile : m_document->bgColor(layer)));
// Try to shrink the image ignoring transparent borders
gfx::Rect frameBounds;
@ -283,7 +284,8 @@ bool DocApi::cropCel(LayerImage* layer,
crop_image(newImage.get(),
frameBounds.x, frameBounds.y,
frameBounds.w, frameBounds.h,
m_document->bgColor(layer)));
image->pixelFormat() == IMAGE_TILEMAP ?
tile_i_notile : m_document->bgColor(layer)));
newCelPos += frameBounds.origin();
}

View File

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

View File

@ -145,7 +145,7 @@ public:
}
void processPixel(int x, int y) {
*SimpleInkProcessing<CopyInkProcessing<ImageTraits>, ImageTraits>::m_dstAddress = m_color;
*this->m_dstAddress = m_color;
}
private:

View File

@ -96,7 +96,13 @@ public:
break;
}
if (loop->getBrush()->type() == doc::kImageBrushType) {
// TODO support different ink types for tilemaps (even custom brushes,
// and custom inks script-driven)
if (loop->getDstImage()->pixelFormat() == IMAGE_TILEMAP) {
setProc(new CopyInkProcessing<TilemapTraits>(loop));
}
// Custom brushes
else if (loop->getBrush()->type() == doc::kImageBrushType) {
switch (m_type) {
case Simple:
setProc(get_ink_proc<BrushSimpleInkProcessing>(loop));
@ -158,12 +164,7 @@ public:
break;
}
case Copy:
if (loop->getDstImage()->pixelFormat() == IMAGE_TILEMAP) {
setProc(new CopyInkProcessing<TilemapTraits>(loop));
}
else {
setProc(get_ink_proc<CopyInkProcessing>(loop));
}
setProc(get_ink_proc<CopyInkProcessing>(loop));
break;
case LockAlpha:
setProc(get_ink_proc<LockAlphaInkProcessing>(loop));

View File

@ -14,6 +14,7 @@
#include "app/tools/ink.h"
#include "app/tools/tool_loop.h"
#include "app/util/wrap_value.h"
#include "base/clamp.h"
#include "doc/brush.h"
#include "doc/image.h"
#include "doc/sprite.h"
@ -24,14 +25,19 @@ namespace tools {
using namespace doc;
using namespace filters;
void PointShape::doInkHline(int x1, int y, int x2, ToolLoop* loop)
void PointShape::doInkHline(int x1, int y, int x2, ToolLoop* loop,
bool adjustCoordinates)
{
Ink* ink = loop->getInk();
TiledMode tiledMode = loop->getTiledMode();
const int dstw = loop->getDstImage()->width();
const int dsth = loop->getDstImage()->height();
int x, w, size; // width or height
// In case the ink needs original cel coordinates, we have to
// translate the x1/y/x2 coordinate.
if (loop->getInk()->needsCelCoordinates()) {
if (adjustCoordinates &&
ink->needsCelCoordinates()) {
gfx::Point origin = loop->getCelOrigin();
x1 -= origin.x;
x2 -= origin.x;
@ -40,52 +46,49 @@ void PointShape::doInkHline(int x1, int y, int x2, ToolLoop* loop)
// Tiled in Y axis
if (int(tiledMode) & int(TiledMode::Y_AXIS)) {
size = loop->getDstImage()->height(); // size = image height
size = dsth; // size = image height
y = wrap_value(y, size);
}
else if (y < 0 || y >= loop->getDstImage()->height())
else if (y < 0 || y >= dsth) {
return;
}
// Tiled in X axis
if (int(tiledMode) & int(TiledMode::X_AXIS)) {
if (x1 > x2)
return;
size = loop->getDstImage()->width(); // size = image width
size = dstw; // size = image width
w = x2-x1+1;
if (w >= size)
loop->getInk()->inkHline(0, y, size-1, loop);
ink->inkHline(0, y, size-1, loop);
else {
x = wrap_value(x1, loop->sprite()->width());
if (x+w <= loop->sprite()->width()) {
// Here we asure that tile limit line does not bisect the current
// scanline, i.e. the scanline is enterely contained inside the tile.
loop->getInk()->prepareUForPointShapeWholeScanline(loop, x1);
loop->getInk()->inkHline(x, y, x+w-1, loop);
ink->prepareUForPointShapeWholeScanline(loop, x1);
ink->inkHline(x, y, x+w-1, loop);
}
else {
// Here the tile limit line bisect the current scanline.
// So we need to execute TWO times the inkHline function, each one with a different m_u.
loop->getInk()->prepareUForPointShapeSlicedScanline(loop, true, x1);// true = left slice
loop->getInk()->inkHline(x, y, size-1, loop);
ink->prepareUForPointShapeSlicedScanline(loop, true, x1);// true = left slice
ink->inkHline(x, y, size-1, loop);
loop->getInk()->prepareUForPointShapeSlicedScanline(loop, false, x1);// false = right slice
loop->getInk()->inkHline(0, y, w-(size-x)-1, loop);
ink->prepareUForPointShapeSlicedScanline(loop, false, x1);// false = right slice
ink->inkHline(0, y, w-(size-x)-1, loop);
}
}
}
// Clipped in X axis
else {
if (x1 < 0)
x1 = 0;
if (x2 >= loop->getDstImage()->width())
x2 = loop->getDstImage()->width()-1;
x1 = base::clamp(x1, 0, dstw-1);
x2 = base::clamp(x2, 0, dstw-1);
if (x2-x1+1 < 1)
return;
loop->getInk()->inkHline(x1, y, x2, loop);
ink->inkHline(x1, y, x2, loop);
}
}

View File

@ -33,7 +33,8 @@ namespace app {
protected:
// Calls loop->getInk()->inkHline() function for each horizontal-scanline
// that should be drawn (applying the "tiled" mode loop->getTiledMode())
static void doInkHline(int x1, int y, int x2, ToolLoop* loop);
static void doInkHline(int x1, int y, int x2, ToolLoop* loop,
const bool adjustCoordinates = true);
};
} // namespace tools

View File

@ -48,7 +48,11 @@ public:
gfx::Point newPos = grid.canvasToTile(pt.toPoint());
loop->getInk()->prepareForPointShape(loop, true, newPos.x, newPos.y);
doInkHline(newPos.x, newPos.y, newPos.x, loop);
doInkHline(
newPos.x, newPos.y, newPos.x, loop,
// Don't adjust by loop->getCelOrigin() because loop->getGrid()
// is already adjusted to the new cel position.
false);
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {

View File

@ -5,6 +5,8 @@
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#define BP_TRACE(...) // TRACEARGS(__VA_ARGS__)
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@ -28,6 +30,7 @@
#include "app/ui/editor/tool_loop_impl.h"
#include "app/ui_context.h"
#include "app/util/wrap_value.h"
#include "base/debug.h"
#include "doc/algo.h"
#include "doc/blend_internals.h"
#include "doc/brush.h"
@ -35,6 +38,7 @@
#include "doc/image_impl.h"
#include "doc/layer.h"
#include "doc/primitives.h"
#include "gfx/rect_io.h"
#include "os/display.h"
#include "render/render.h"
#include "ui/manager.h"
@ -230,38 +234,44 @@ void BrushPreview::show(const gfx::Point& screenPos)
brush->bounds());
gfx::Rect brushBounds = origBrushBounds;
brushBounds.offset(spritePos);
gfx::Rect extraCelBounds = brushBounds;
gfx::Rect extraCelBoundsInCanvas = brushBounds;
// Tiled mode might require a bigger extra cel (to show the tiled)
if (int(m_editor->docPref().tiled.mode()) & int(filters::TiledMode::X_AXIS)) {
brushBounds.x = wrap_value(brushBounds.x, sprite->width());
extraCelBounds.x = brushBounds.x;
if ((extraCelBounds.x < 0 && extraCelBounds.x2() > 0) ||
(extraCelBounds.x < sprite->width() && extraCelBounds.x2() > sprite->width())) {
extraCelBounds.x = 0;
extraCelBounds.w = sprite->width();
extraCelBoundsInCanvas.x = brushBounds.x;
if ((extraCelBoundsInCanvas.x < 0 && extraCelBoundsInCanvas.x2() > 0) ||
(extraCelBoundsInCanvas.x < sprite->width() && extraCelBoundsInCanvas.x2() > sprite->width())) {
extraCelBoundsInCanvas.x = 0;
extraCelBoundsInCanvas.w = sprite->width();
}
}
if (int(m_editor->docPref().tiled.mode()) & int(filters::TiledMode::Y_AXIS)) {
brushBounds.y = wrap_value(brushBounds.y, sprite->height());
extraCelBounds.y = brushBounds.y;
if ((extraCelBounds.y < 0 && extraCelBounds.y2() > 0) ||
(extraCelBounds.y < sprite->height() && extraCelBounds.y2() > sprite->height())) {
extraCelBounds.y = 0;
extraCelBounds.h = sprite->height();
extraCelBoundsInCanvas.y = brushBounds.y;
if ((extraCelBoundsInCanvas.y < 0 && extraCelBoundsInCanvas.y2() > 0) ||
(extraCelBoundsInCanvas.y < sprite->height() && extraCelBoundsInCanvas.y2() > sprite->height())) {
extraCelBoundsInCanvas.y = 0;
extraCelBoundsInCanvas.h = sprite->height();
}
}
gfx::Rect extraCelBoundsInCanvas;
gfx::Rect extraCelBounds;
if (site.tilemapMode() == TilemapMode::Tiles) {
ASSERT(layer->isTilemap());
extraCelBounds.setOrigin(site.grid().canvasToTile(extraCelBounds.origin()));
extraCelBoundsInCanvas = site.grid().tileToCanvas(extraCelBounds);
doc::Grid grid = site.grid();
extraCelBounds = grid.canvasToTile(extraCelBoundsInCanvas);
extraCelBoundsInCanvas = grid.tileToCanvas(extraCelBounds);
}
else {
extraCelBoundsInCanvas = extraCelBounds;
extraCelBounds = extraCelBoundsInCanvas;
}
BP_TRACE("BrushPreview:",
"brushBounds", brushBounds,
"extraCelBounds", extraCelBounds,
"extraCelBoundsInCanvas", extraCelBoundsInCanvas);
// Create the extra cel to show the brush preview
Cel* cel = site.cel();
@ -287,9 +297,15 @@ void BrushPreview::show(const gfx::Point& screenPos)
document->setExtraCel(m_extraCel);
Image* extraImage = m_extraCel->image();
extraImage->setMaskColor(mask_index);
clear_image(extraImage,
(extraImage->pixelFormat() == IMAGE_INDEXED ? mask_index: 0));
if (extraImage->pixelFormat() == IMAGE_TILEMAP) {
extraImage->setMaskColor(tile_i_notile);
clear_image(extraImage, tile_i_notile);
}
else {
extraImage->setMaskColor(mask_index);
clear_image(extraImage,
(extraImage->pixelFormat() == IMAGE_INDEXED ? mask_index: 0));
}
if (layer) {
render::Render().renderLayer(

View File

@ -124,7 +124,7 @@ protected:
public:
ToolLoopBase(Editor* editor,
Site& site, const doc::Grid& grid,
Site& site,
ToolLoopParams& params)
: m_editor(editor)
, m_tool(params.tool)
@ -143,8 +143,8 @@ public:
, m_contiguous(params.contiguous)
, m_snapToGrid(m_docPref.grid.snap())
, m_isSelectingTiles(false)
, m_grid(grid)
, m_gridBounds(grid.origin(), grid.tileSize())
, m_grid(site.grid())
, m_gridBounds(m_grid.origin(), m_grid.tileSize())
, m_button(params.button)
, m_ink(params.ink->clone())
, m_controller(params.controller)
@ -454,11 +454,10 @@ class ToolLoopImpl : public ToolLoopBase {
public:
ToolLoopImpl(Editor* editor,
Site& site,
const doc::Grid& grid,
Context* context,
ToolLoopParams& params,
const bool saveLastPoint)
: ToolLoopBase(editor, site, grid, params)
: ToolLoopBase(editor, site, params)
, m_context(context)
, m_canceled(false)
, m_tx(m_context,
@ -549,6 +548,10 @@ public:
m_maskOrigin = (!m_mask->isEmpty() ? gfx::Point(m_mask->bounds().x-m_celOrigin.x,
m_mask->bounds().y-m_celOrigin.y):
gfx::Point(0, 0));
// Setup the new grid of ExpandCelCanvas which can be displaced to
// match the new temporal cel position (m_celOrigin).
m_grid = m_expandCelCanvas->getGrid();
}
~ToolLoopImpl() {
@ -714,7 +717,6 @@ tools::ToolLoop* create_tool_loop(
const bool selectTiles)
{
Site site = editor->getSite();
doc::Grid grid = site.grid();
ToolLoopParams params;
params.tool = editor->getCurrentEditorTool();
@ -820,7 +822,7 @@ tools::ToolLoop* create_tool_loop(
ASSERT(context->activeDocument() == editor->document());
auto toolLoop = new ToolLoopImpl(
editor, site, grid, context, params, saveLastPoint);
editor, site, context, params, saveLastPoint);
if (selectTiles)
toolLoop->forceSnapToTiles();
@ -859,8 +861,7 @@ tools::ToolLoop* create_tool_loop_for_script(
Site site2(site);
return new ToolLoopImpl(
nullptr, site2, site2.grid(),
context, params, false);
nullptr, site2, context, params, false);
}
catch (const std::exception& ex) {
Console::showException(ex);
@ -885,7 +886,7 @@ public:
ToolLoopParams& params,
Image* image,
const gfx::Point& celOrigin)
: ToolLoopBase(editor, site, site.grid(), params)
: ToolLoopBase(editor, site, params)
, m_image(image)
{
m_celOrigin = celOrigin;

View File

@ -351,7 +351,8 @@ void draw_image_into_new_tilemap_cel(
newTilemap.reset(doc::Image::create(IMAGE_TILEMAP,
tilemapBounds.w,
tilemapBounds.h));
newTilemap->clear(0); // <- This should not necessary
newTilemap->setMaskColor(tile_i_notile);
newTilemap->clear(tile_i_notile);
}
else {
ASSERT(tilemapBounds.w == newTilemap->width());
@ -444,7 +445,8 @@ void modify_tilemap_cel_region(
newTilemapBounds.w,
newTilemapBounds.h));
newTilemap->clear(0); // TODO find the tile with empty content?
newTilemap->setMaskColor(tile_i_notile);
newTilemap->clear(tile_i_notile); // TODO find the tile with empty content?
newTilemap->copy(
cel->image(),
gfx::Clip(oldTilemapBounds.x-newTilemapBounds.x,
@ -471,7 +473,7 @@ void modify_tilemap_cel_region(
const doc::tile_index ti = doc::tile_geti(t);
const doc::ImageRef existenTileImage = tileset->get(ti);
if (tilesetMode == TilesetMode::Auto)
if (tilesetMode == TilesetMode::Auto && t != tile_i_notile)
modifiedTileIndexes[ti] = true;
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
@ -540,38 +542,40 @@ void modify_tilemap_cel_region(
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);
if (t != tile_i_notile) {
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());
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());
gfx::Region tileRgn(tileInCanvasRc);
tileRgn.createIntersection(tileRgn, region);
tileRgn.offset(-tileInCanvasRc.origin());
ImageRef tileOrigImage = tileset->get(ti);
ImageRef tileOrigImage = tileset->get(ti);
gfx::Region diffRgn;
create_region_with_differences(tileOrigImage.get(),
tileImage.get(),
tileRgn.bounds(),
diffRgn);
gfx::Region diffRgn;
create_region_with_differences(tileOrigImage.get(),
tileImage.get(),
tileRgn.bounds(),
diffRgn);
// Keep only the modified region for this specific modification
tileRgn &= diffRgn;
// Keep only the modified region for this specific modification
tileRgn &= diffRgn;
if (!tileRgn.isEmpty()) {
Mod mod;
mod.tileIndex = ti;
mod.tileOrigImage = tileOrigImage;
mod.tileImage = tileImage;
mod.tileRgn = tileRgn;
mods.push_back(mod);
if (!tileRgn.isEmpty()) {
Mod mod;
mod.tileIndex = ti;
mod.tileOrigImage = tileOrigImage;
mod.tileImage = tileImage;
mod.tileRgn = tileRgn;
mods.push_back(mod);
modifiedTileIndexes[ti] = true;
modifiedTileIndexes[ti] = true;
}
}
}
@ -655,12 +659,14 @@ void remove_unused_tiles_from_tileset(
for_each_pixel<TilemapTraits>(
tilemapImage,
[&unusedTiles, &n](const doc::tile_t t) {
const doc::tile_index ti = doc::tile_geti(t);
n = std::max<int>(n, ti+1);
if (ti >= 0 &&
ti < int(unusedTiles.size()) &&
unusedTiles[ti]) {
unusedTiles[ti] = false;
if (t != tile_i_notile) {
const doc::tile_index ti = doc::tile_geti(t);
n = std::max<int>(n, ti+1);
if (ti >= 0 &&
ti < int(unusedTiles.size()) &&
unusedTiles[ti]) {
unusedTiles[ti] = false;
}
}
});
}

View File

@ -5,6 +5,8 @@
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#define EXP_TRACE(...) // TRACEARGS(__VA_ARGS__)
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@ -22,6 +24,7 @@
#include "app/site.h"
#include "app/util/cel_ops.h"
#include "app/util/range_utils.h"
#include "base/debug.h"
#include "doc/algorithm/shrink_bounds.h"
#include "doc/cel.h"
#include "doc/image.h"
@ -33,11 +36,11 @@
#include "doc/tileset.h"
#include "doc/tileset_hash_table.h"
#include "doc/tilesets.h"
#include "gfx/point_io.h"
#include "gfx/rect_io.h"
#include "gfx/size_io.h"
#include "render/render.h"
#define EXP_TRACE(...) // TRACEARGS
namespace {
// We cannot have two ExpandCelCanvas instances at the same time
@ -129,13 +132,28 @@ ExpandCelCanvas::ExpandCelCanvas(
}
if (m_tilemapMode == TilemapMode::Tiles) {
m_bounds = site.grid().canvasToTile(m_bounds);
// Bounds of the canvas in tiles.
m_bounds = m_grid.canvasToTile(m_bounds);
// As the grid origin depends on the current cel position (see
// Site::grid()), and we're going to modify the m_cel position
// temporarily, we need to adjust the grid to the new temporal
// grid origin matching the new m_dstImage position.
auto newCelPosition = m_grid.tileToCanvas(m_bounds.origin());
m_grid.origin(m_grid.origin() - m_cel->position() + newCelPosition);
// The origin of m_bounds must be in canvas position
m_bounds.setOrigin(newCelPosition);
}
// We have to adjust the cel position to match the m_dstImage
// position (the new m_dstImage will be used in RenderEngine to
// draw this cel).
m_cel->setPosition(m_bounds.x, m_bounds.y);
m_cel->setPosition(m_bounds.origin());
EXP_TRACE("ExpandCelCanvas",
"m_cel->bounds()=", m_cel->bounds(),
"m_bounds=", m_bounds,
"m_grid=", m_grid.origin(), m_grid.tileSize());
if (m_celCreated) {
getDestCanvas();
@ -144,6 +162,13 @@ ExpandCelCanvas::ExpandCelCanvas(
if (m_layer && m_layer->isImage())
static_cast<LayerImage*>(m_layer)->addCel(m_cel);
}
else if (m_layer->isTilemap() &&
m_tilemapMode == TilemapMode::Tiles) {
// Calling "getDestCanvas()" we create the m_dstImage
getDestCanvas();
m_dstImage->clear(tile_i_notile);
m_cel->data()->setImage(m_dstImage, m_layer);
}
// If we are in a tilemap, we use m_dstImage to draw pixels (instead
// of the tilemap image).
else if (m_layer->isTilemap() &&
@ -234,8 +259,7 @@ void ExpandCelCanvas::commit()
m_cel->setPosition(m_origCelPos);
#ifdef _DEBUG
if (m_layer->isTilemap() &&
m_tilemapMode == TilemapMode::Pixels) {
if (m_layer->isTilemap()) {
ASSERT(m_cel->image() != m_celImage.get());
}
else {
@ -292,9 +316,10 @@ void ExpandCelCanvas::commit()
// Check that the region to copy or patch is not empty before we
// create the new cmd
else if (!regionToPatch->isEmpty()) {
ASSERT(m_celImage.get() == m_cel->image());
if (m_layer->isBackground()) {
// TODO support for tilemap backgrounds?
ASSERT(m_celImage.get() == m_cel->image());
m_cmds->executeAndAdd(
new cmd::CopyRegion(
m_cel->image(),
@ -302,7 +327,24 @@ void ExpandCelCanvas::commit()
*regionToPatch,
m_bounds.origin()));
}
else if (m_tilemapMode == TilemapMode::Tiles) {
ASSERT(m_celImage.get() != m_cel->image());
m_cel->data()->setImage(m_celImage, m_layer);
gfx::Region regionInCanvas = m_grid.tileToCanvas(*regionToPatch);
EXP_TRACE(" - Tilemap bounds to patch", regionInCanvas.bounds());
m_cmds->executeAndAdd(
new cmd::PatchCel(
m_cel,
m_dstImage.get(),
regionInCanvas,
m_grid.origin()));
}
else {
ASSERT(m_celImage.get() == m_cel->image());
m_cmds->executeAndAdd(
new cmd::PatchCel(
m_cel,
@ -311,6 +353,11 @@ void ExpandCelCanvas::commit()
m_bounds.origin()));
}
}
// Restore the original cel image if needed (e.g. no region to
// patch on a tilemap)
else if (m_celImage.get() != m_cel->image()) {
m_cel->data()->setImage(m_celImage, m_layer);
}
}
else {
ASSERT(false);
@ -349,13 +396,19 @@ Image* ExpandCelCanvas::getSourceCanvas()
ASSERT((m_flags & NeedsSource) == NeedsSource);
if (!m_srcImage) {
m_srcImage.reset(
Image::create(m_tilemapMode == TilemapMode::Tiles ? IMAGE_TILEMAP:
m_sprite->pixelFormat(),
if (m_tilemapMode == TilemapMode::Tiles) {
m_srcImage.reset(Image::create(IMAGE_TILEMAP,
m_bounds.w, m_bounds.h, src_buffer));
m_srcImage->setMaskColor(tile_i_notile);
m_srcImage->clear(tile_i_notile);
}
else {
m_srcImage.reset(
Image::create(m_sprite->pixelFormat(),
m_bounds.w, m_bounds.h, src_buffer));
m_srcImage->setMaskColor(m_sprite->transparentColor());
m_srcImage->clear(m_sprite->transparentColor());
m_srcImage->setMaskColor(m_sprite->transparentColor());
m_srcImage->clear(m_sprite->transparentColor());
}
}
return m_srcImage.get();
}
@ -363,12 +416,16 @@ Image* ExpandCelCanvas::getSourceCanvas()
Image* ExpandCelCanvas::getDestCanvas()
{
if (!m_dstImage) {
m_dstImage.reset(
Image::create(m_tilemapMode == TilemapMode::Tiles ? IMAGE_TILEMAP:
m_sprite->pixelFormat(),
m_bounds.w, m_bounds.h, dst_buffer));
m_dstImage->setMaskColor(m_sprite->transparentColor());
if (m_tilemapMode == TilemapMode::Tiles) {
m_dstImage.reset(Image::create(IMAGE_TILEMAP,
m_bounds.w, m_bounds.h, dst_buffer));
m_dstImage->setMaskColor(tile_i_notile);
}
else {
m_dstImage.reset(Image::create(m_sprite->pixelFormat(),
m_bounds.w, m_bounds.h, dst_buffer));
m_dstImage->setMaskColor(m_sprite->transparentColor());
}
}
return m_dstImage.get();
}
@ -380,16 +437,21 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
getSourceCanvas();
gfx::Region rgnToValidate;
gfx::Point origCelPos;
gfx::Point zeroPos;
if (m_tilemapMode == TilemapMode::Tiles) {
for (const auto& rc : rgn)
rgnToValidate |= gfx::Region(m_grid.canvasToTile(rc));
// Position of the tilemap cel inside the m_dstImage tilemap
origCelPos = m_grid.canvasToTile(m_origCelPos);
rgnToValidate = m_grid.canvasToTile(rgn);
}
else {
origCelPos = m_origCelPos;
rgnToValidate = rgn;
zeroPos = -m_bounds.origin();
}
EXP_TRACE(" ->", rgnToValidate.bounds());
rgnToValidate.offset(-m_bounds.origin());
rgnToValidate.offset(zeroPos);
rgnToValidate.createSubtraction(rgnToValidate, m_validSrcRegion);
rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_srcImage->bounds()));
@ -398,8 +460,8 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
rgnToClear.createSubtraction(
rgnToValidate,
gfx::Region(m_celImage->bounds()
.offset(m_origCelPos)
.offset(-m_bounds.origin())));
.offset(origCelPos)
.offset(zeroPos)));
for (const auto& rc : rgnToClear)
fill_rect(m_srcImage.get(), rc, m_srcImage->maskColor());
@ -419,11 +481,24 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
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),
rc.x+m_bounds.x-origCelPos.x,
rc.y+m_bounds.y-origCelPos.y, rc.w, rc.h),
255, BlendMode::NORMAL);
}
}
else if (m_celImage->pixelFormat() == IMAGE_TILEMAP &&
m_srcImage->pixelFormat() == IMAGE_TILEMAP) {
ASSERT(m_tilemapMode == TilemapMode::Tiles);
// 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-origCelPos.x,
rc.y-origCelPos.y, rc.w, rc.h));
}
}
else {
ASSERT(m_celImage->pixelFormat() != IMAGE_TILEMAP ||
m_tilemapMode == TilemapMode::Tiles);
@ -433,8 +508,8 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
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));
rc.x+m_bounds.x-origCelPos.x,
rc.y+m_bounds.y-origCelPos.y, rc.w, rc.h));
}
}
else {
@ -475,7 +550,8 @@ void ExpandCelCanvas::validateDestCanvas(const gfx::Region& rgn)
}
EXP_TRACE(" ->", rgnToValidate.bounds());
rgnToValidate.offset(-m_bounds.origin());
if (m_tilemapMode != TilemapMode::Tiles)
rgnToValidate.offset(-m_bounds.origin());
rgnToValidate.createSubtraction(rgnToValidate, m_validDstRegion);
rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_dstImage->bounds()));

View File

@ -74,6 +74,7 @@ namespace app {
void copyValidDestToSourceCanvas(const gfx::Region& rgn);
const Cel* getCel() const { return m_cel; }
const doc::Grid& getGrid() const { return m_grid; }
private:
gfx::Rect getTrimDstImageBounds() const;

View File

@ -782,6 +782,8 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
if (w > 0 && h > 0) {
doc::ImageRef image(doc::Image::create(doc::IMAGE_TILEMAP, w, h));
image->setMaskColor(doc::tile_i_notile);
image->clear(doc::tile_i_notile);
read_compressed_image(f(), delegate(), image.get(), header, chunk_end);
cel.reset(new doc::Cel(frame, image));
cel->setPosition(x, y);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2019-2020 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -15,6 +15,7 @@
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "doc/tile.h"
namespace doc {
@ -149,7 +150,8 @@ void Cel::fixupImage()
{
// Change the mask color to the sprite mask color
if (m_layer && image()) {
image()->setMaskColor(m_layer->sprite()->transparentColor());
image()->setMaskColor((image()->pixelFormat() == IMAGE_TILEMAP) ?
tile_i_notile : m_layer->sprite()->transparentColor());
ASSERT(m_data);
m_data->adjustBounds(m_layer);
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2019-2020 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -52,6 +52,15 @@ gfx::Rect Grid::tileToCanvas(const gfx::Rect& tileBounds) const
return gfx::Rect(pt1, pt2);
}
gfx::Region Grid::tileToCanvas(const gfx::Region& tileRgn)
{
gfx::Region canvasRgn;
for (const gfx::Rect& rc : tileRgn) {
canvasRgn |= gfx::Region(tileToCanvas(rc));
}
return canvasRgn;
}
gfx::Point Grid::canvasToTile(const gfx::Point& canvasPoint) const
{
ASSERT(m_tileSize.w > 0);
@ -107,6 +116,16 @@ gfx::Rect Grid::canvasToTile(const gfx::Rect& canvasBounds) const
pt2.y - pt1.y + 1));
}
gfx::Region Grid::canvasToTile(const gfx::Region& canvasRgn)
{
gfx::Region tilesRgn;
for (const gfx::Rect& rc : canvasRgn) {
tilesRgn |= gfx::Region(canvasToTile(rc));
}
return tilesRgn;
}
gfx::Size Grid::tilemapSizeToCanvas(const gfx::Size& tilemapSize) const
{
gfx::Point pt = tileToCanvas(gfx::Point(tilemapSize.w-1,

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2019-2020 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -47,10 +47,12 @@ namespace doc {
// Converts a tile position into a canvas position
gfx::Point tileToCanvas(const gfx::Point& tile) const;
gfx::Rect tileToCanvas(const gfx::Rect& tileBounds) const;
gfx::Region tileToCanvas(const gfx::Region& tileRgn);
// 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::Region canvasToTile(const gfx::Region& canvasRgn);
gfx::Size tilemapSizeToCanvas(const gfx::Size& tilemapSize) const;
gfx::Rect tileBoundsInCanvas(const gfx::Point& tile) const;

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018-2019 Igara Studio S.A.
// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -17,6 +17,7 @@
#include "doc/palette.h"
#include "doc/remap.h"
#include "doc/rgbmap.h"
#include "doc/tile.h"
#include "gfx/region.h"
#include <stdexcept>
@ -426,8 +427,12 @@ void remap_image(Image* image, const Remap& remap)
case IMAGE_TILEMAP:
transform_image<TilemapTraits>(
image, [&remap](color_t c) -> color_t {
ASSERT(remap[c] != Remap::kNoMap);
return remap[c];
if (c == tile_i_notile)
return tile_i_notile;
else {
ASSERT(remap[c] != Remap::kNoMap);
return remap[c];
}
});
break;
}

View File

@ -1285,18 +1285,20 @@ void Render::renderCel(
continue;
const tile_t t = cel_image->getPixel(u, v);
const tile_index i = tile_geti(t);
if (t != tile_i_notile) {
const tile_index i = tile_geti(t);
if (dst_image->pixelFormat() == IMAGE_TILEMAP) {
put_pixel(dst_image, u-area.dst.x, v-area.dst.y, t);
}
else {
const ImageRef tile_image = tileset->get(i);
if (!tile_image)
continue;
if (dst_image->pixelFormat() == IMAGE_TILEMAP) {
put_pixel(dst_image, u-area.dst.x, v-area.dst.y, t);
}
else {
const ImageRef tile_image = tileset->get(i);
if (!tile_image)
continue;
renderImage(dst_image, tile_image.get(), pal, tileBoundsOnCanvas,
area, compositeImage, opacity, blendMode);
renderImage(dst_image, tile_image.get(), pal, tileBoundsOnCanvas,
area, compositeImage, opacity, blendMode);
}
}
}
}