diff --git a/src/app/color_utils.cpp b/src/app/color_utils.cpp index 5d3bd3e51..3fd623e11 100644 --- a/src/app/color_utils.cpp +++ b/src/app/color_utils.cpp @@ -100,6 +100,9 @@ doc::color_t color_utils::color_for_image(const app::Color& color, PixelFormat f case IMAGE_INDEXED: c = color.getIndex(); break; + case IMAGE_TILEMAP: + c = color.getIndex(); // TODO Add app::Color::getTile() ? + break; } return c; @@ -122,6 +125,9 @@ doc::color_t color_utils::color_for_image_without_alpha(const app::Color& color, case IMAGE_INDEXED: c = color.getIndex(); break; + case IMAGE_TILEMAP: + c = color.getIndex(); // TODO Add app::Color::getTile() ? + break; } return c; @@ -165,6 +171,9 @@ doc::color_t color_utils::color_for_target_mask(const app::Color& color, const C c = get_current_palette()->findBestfit(r, g, b, a, mask); } break; + case IMAGE_TILEMAP: + c = color.getIndex(); // TODO Add app::Color::getTile() ? + break; } } diff --git a/src/app/commands/cmd_toggle_tiles_mode.cpp b/src/app/commands/cmd_toggle_tiles_mode.cpp index a5721c2d2..c1f79bd15 100644 --- a/src/app/commands/cmd_toggle_tiles_mode.cpp +++ b/src/app/commands/cmd_toggle_tiles_mode.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (c) 2019-2020 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -25,12 +25,15 @@ public: protected: bool onChecked(Context* context) override { auto colorBar = ColorBar::instance(); - return colorBar->inTilesMode(); + return (colorBar->tilemapMode() == TilemapMode::Tiles); } void onExecute(Context* context) override { auto colorBar = ColorBar::instance(); - colorBar->setTilesMode(!colorBar->inTilesMode()); + colorBar->setTilemapMode( + colorBar->tilemapMode() == TilemapMode::Pixels ? + TilemapMode::Tiles: + TilemapMode::Pixels); } }; diff --git a/src/app/extra_cel.cpp b/src/app/extra_cel.cpp index 9892e91b7..c6cc0f8a2 100644 --- a/src/app/extra_cel.cpp +++ b/src/app/extra_cel.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of @@ -20,21 +21,29 @@ ExtraCel::ExtraCel() { } -void ExtraCel::create(doc::Sprite* sprite, +void ExtraCel::create(const TilemapMode tilemapMode, + doc::Sprite* sprite, const gfx::Rect& bounds, - doc::frame_t frame, - int opacity) + const gfx::Size& imageSize, + const doc::frame_t frame, + const int opacity) { ASSERT(sprite); + doc::PixelFormat pixelFormat; + if (tilemapMode == TilemapMode::Tiles) + pixelFormat = doc::IMAGE_TILEMAP; + else + pixelFormat = sprite->pixelFormat(); + if (!m_image || - m_image->pixelFormat() != sprite->pixelFormat() || - m_image->width() != bounds.w || - m_image->height() != bounds.h) { + m_image->pixelFormat() != pixelFormat || + m_image->width() != imageSize.w || + m_image->height() != imageSize.h) { if (!m_imageBuffer) m_imageBuffer.reset(new doc::ImageBuffer(1)); - doc::Image* newImage = doc::Image::create(sprite->pixelFormat(), - bounds.w, bounds.h, + doc::Image* newImage = doc::Image::create(pixelFormat, + imageSize.w, imageSize.h, m_imageBuffer); m_image.reset(newImage); } diff --git a/src/app/extra_cel.h b/src/app/extra_cel.h index d12fb261a..c30b6cfed 100644 --- a/src/app/extra_cel.h +++ b/src/app/extra_cel.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -9,6 +9,7 @@ #define APP_EXTRA_CEL_H_INCLUDED #pragma once +#include "app/tilemap_mode.h" #include "base/disable_copying.h" #include "doc/blend_mode.h" #include "doc/cel.h" @@ -30,7 +31,12 @@ namespace app { public: ExtraCel(); - void create(doc::Sprite* sprite, const gfx::Rect& bounds, doc::frame_t frame, int opacity); + void create(const TilemapMode tilemapMode, + doc::Sprite* sprite, + const gfx::Rect& bounds, + const gfx::Size& imageSize, + const doc::frame_t frame, + const int opacity); render::ExtraType type() const { return m_type; } void setType(render::ExtraType type) { m_type = type; } diff --git a/src/app/site.cpp b/src/app/site.cpp index 744d01380..81f4b49f9 100644 --- a/src/app/site.cpp +++ b/src/app/site.cpp @@ -35,15 +35,7 @@ RgbMap* Site::rgbMap() const return (m_sprite ? m_sprite->rgbMap(m_frame): nullptr); } -const Cel* Site::cel() const -{ - if (m_layer) - return m_layer->cel(m_frame); - else - return nullptr; -} - -Cel* Site::cel() +Cel* Site::cel() const { if (m_layer) return m_layer->cel(m_frame); diff --git a/src/app/site.h b/src/app/site.h index 842655644..db741279e 100644 --- a/src/app/site.h +++ b/src/app/site.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -10,6 +10,7 @@ #pragma once #include "app/doc_range.h" +#include "app/tilemap_mode.h" #include "app/tileset_mode.h" #include "doc/frame.h" #include "doc/palette_picks.h" @@ -52,6 +53,7 @@ namespace app { , m_sprite(nullptr) , m_layer(nullptr) , m_frame(0) + , m_tilemapMode(TilemapMode::Pixels) , m_tilesetMode(TilesetMode::Manual) { } const Focus focus() const { return m_focus; } @@ -62,16 +64,11 @@ namespace app { bool inColorBar() const { return m_focus == InColorBar; } bool inTimeline() const { return (inLayers() || inFrames() || inCels()); } - const Doc* document() const { return m_document; } - const doc::Sprite* sprite() const { return m_sprite; } - const doc::Layer* layer() const { return m_layer; } + Doc* document() const { return m_document; } + doc::Sprite* sprite() const { return m_sprite; } + doc::Layer* layer() const { return m_layer; } doc::frame_t frame() const { return m_frame; } - const doc::Cel* cel() const; - - Doc* document() { return m_document; } - doc::Sprite* sprite() { return m_sprite; } - doc::Layer* layer() { return m_layer; } - doc::Cel* cel(); + doc::Cel* cel() const; const DocRange& range() const { return m_range; } void focus(Focus focus) { m_focus = focus; } @@ -112,8 +109,10 @@ namespace app { doc::Grid grid() const; gfx::Rect gridBounds() const; - void tilesetMode(const TilesetMode& mode) { m_tilesetMode = mode; } - const TilesetMode& tilesetMode() const { return m_tilesetMode; } + void tilemapMode(const TilemapMode mode) { m_tilemapMode = mode; } + void tilesetMode(const TilesetMode mode) { m_tilesetMode = mode; } + TilemapMode tilemapMode() const { return m_tilemapMode; } + TilesetMode tilesetMode() const { return m_tilesetMode; } private: Focus m_focus; @@ -125,6 +124,7 @@ namespace app { doc::PalettePicks m_selectedColors; doc::PalettePicks m_selectedTiles; doc::SelectedObjects m_selectedSlices; + TilemapMode m_tilemapMode; TilesetMode m_tilesetMode; }; diff --git a/src/app/tilemap_mode.h b/src/app/tilemap_mode.h new file mode 100644 index 000000000..d91ddc888 --- /dev/null +++ b/src/app/tilemap_mode.h @@ -0,0 +1,21 @@ +// Aseprite +// Copyright (c) 2020 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_TILEMAP_MODE_H_INCLUDED +#define APP_TILEMAP_MODE_H_INCLUDED +#pragma once + +namespace app { + + // Should we edit the pixels or the tiles of the tilemap? + enum class TilemapMode { + Pixels, // Edit tile pixels + Tiles, // Edit tiles + }; + +} // namespace app + +#endif diff --git a/src/app/tileset_mode.h b/src/app/tileset_mode.h index 452def6ca..ea2cd78c1 100644 --- a/src/app/tileset_mode.h +++ b/src/app/tileset_mode.h @@ -1,11 +1,11 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. -#ifndef APP_TILES_MODE_H_INCLUDED -#define APP_TILES_MODE_H_INCLUDED +#ifndef APP_TILESET_MODE_H_INCLUDED +#define APP_TILESET_MODE_H_INCLUDED #pragma once namespace app { diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h index cd8a32733..b40f5176c 100644 --- a/src/app/tools/inks.h +++ b/src/app/tools/inks.h @@ -158,7 +158,12 @@ public: break; } case Copy: - setProc(get_ink_proc(loop)); + if (loop->getDstImage()->pixelFormat() == IMAGE_TILEMAP) { + setProc(new CopyInkProcessing(loop)); + } + else { + setProc(get_ink_proc(loop)); + } break; case LockAlpha: setProc(get_ink_proc(loop)); diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h index 190a1b58d..452dc75ab 100644 --- a/src/app/tools/point_shapes.h +++ b/src/app/tools/point_shapes.h @@ -38,6 +38,24 @@ public: } }; +class TilePointShape : public PointShape { +public: + bool isPixel() override { return true; } + + void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override { + const doc::Grid& grid = loop->getGrid(); + gfx::Point newPos = grid.canvasToTile(pt.toPoint()); + + loop->getInk()->prepareForPointShape(loop, true, newPos.x, newPos.y); + doInkHline(newPos.x, newPos.y, newPos.x, loop); + } + + void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override { + const doc::Grid& grid = loop->getGrid(); + area = grid.alignBounds(Rect(x, y, 1, 1)); + } +}; + class BrushPointShape : public PointShape { bool m_firstPoint; Brush* m_lastBrush; diff --git a/src/app/tools/tool_box.cpp b/src/app/tools/tool_box.cpp index ac5f8b9a6..0412ec693 100644 --- a/src/app/tools/tool_box.cpp +++ b/src/app/tools/tool_box.cpp @@ -92,6 +92,7 @@ const char* WellKnownIntertwiners::AsPixelPerfect = "as_pixel_perfect"; const char* WellKnownPointShapes::None = "none"; const char* WellKnownPointShapes::Pixel = "pixel"; +const char* WellKnownPointShapes::Tile = "tile"; const char* WellKnownPointShapes::Brush = "brush"; const char* WellKnownPointShapes::FloodFill = "floodfill"; const char* WellKnownPointShapes::Spray = "spray"; @@ -143,6 +144,7 @@ ToolBox::ToolBox() m_pointshapers[WellKnownPointShapes::None] = new NonePointShape(); m_pointshapers[WellKnownPointShapes::Pixel] = new PixelPointShape(); + m_pointshapers[WellKnownPointShapes::Tile] = new TilePointShape(); m_pointshapers[WellKnownPointShapes::Brush] = new BrushPointShape(); m_pointshapers[WellKnownPointShapes::FloodFill] = new FloodFillPointShape(); m_pointshapers[WellKnownPointShapes::Spray] = new SprayPointShape(); diff --git a/src/app/tools/tool_box.h b/src/app/tools/tool_box.h index 0514c73fa..7f2b48fd6 100644 --- a/src/app/tools/tool_box.h +++ b/src/app/tools/tool_box.h @@ -78,6 +78,7 @@ namespace app { namespace WellKnownPointShapes { extern const char* None; extern const char* Pixel; + extern const char* Tile; extern const char* Brush; extern const char* FloodFill; extern const char* Spray; diff --git a/src/app/tools/tool_loop.h b/src/app/tools/tool_loop.h index 6571998b7..4db301257 100644 --- a/src/app/tools/tool_loop.h +++ b/src/app/tools/tool_loop.h @@ -16,6 +16,7 @@ #include "doc/brush.h" #include "doc/color.h" #include "doc/frame.h" +#include "doc/grid.h" #include "filters/tiled_mode.h" #include "gfx/point.h" #include "gfx/rect.h" @@ -182,6 +183,7 @@ namespace app { virtual bool getSnapToGrid() = 0; virtual bool isSelectingTiles() = 0; virtual bool getStopAtGrid() = 0; // For floodfill-like tools + virtual const doc::Grid& getGrid() const = 0; virtual gfx::Rect getGridBounds() = 0; virtual bool isPixelConnectivityEightConnected() = 0; diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp index be691d699..ffdb0db51 100644 --- a/src/app/ui/color_bar.cpp +++ b/src/app/ui/color_bar.cpp @@ -170,7 +170,7 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager) , m_ascending(true) , m_lastButton(kButtonLeft) , m_editMode(false) - , m_tilesMode(false) + , m_tilemapMode(TilemapMode::Pixels) , m_tilesetMode(TilesetMode::Auto) , m_redrawTimer(250, this) , m_redrawAll(false) @@ -388,6 +388,16 @@ void ColorBar::setBgColor(const app::Color& color) onColorButtonChange(color); } +doc::tile_index ColorBar::getFgTile() const +{ + return m_fgColor.getColor().getIndex(); // TODO +} + +doc::tile_index ColorBar::getBgTile() const +{ + return m_bgColor.getColor().getIndex(); // TODO +} + PaletteView* ColorBar::getPaletteView() { return &m_paletteView; @@ -485,28 +495,31 @@ void ColorBar::setEditMode(bool state) m_paletteView.deselect(); } -bool ColorBar::inTilesMode() const +TilemapMode ColorBar::tilemapMode() const { return - (m_tilesMode && - m_tilesHBox.isVisible() && + (m_tilesHBox.isVisible() && m_lastDocument && - m_lastDocument->sprite()); + m_lastDocument->sprite()) ? m_tilemapMode: + TilemapMode::Pixels; } -void ColorBar::setTilesMode(bool state) +void ColorBar::setTilemapMode(const TilemapMode mode) { const Site site = UIContext::instance()->activeSite(); const bool isTilemap = (site.layer() && site.layer()->isTilemap()); + const bool editTiles = (mode == TilemapMode::Tiles); SkinTheme* theme = static_cast(this->theme()); ButtonSet::Item* item = m_tilesButton.getItem(0); - m_tilesMode = state; - item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone); + m_tilemapMode = mode; + item->setHotColor(editTiles ? + theme->colors.editPalFace(): + gfx::ColorNone); item->setMono(true); - if (state && isTilemap) { + if (editTiles && isTilemap) { manager()->freeWidget(&m_paletteView); m_scrollablePalView.setVisible(false); m_scrollableTilesView.setVisible(true); @@ -575,8 +588,8 @@ void ColorBar::onActiveSiteChange(const Site& site) } if (!isTilemap) { m_lastTilesetId = doc::NullId; - if (m_tilesMode) - setTilesMode(false); + if (m_tilemapMode == TilemapMode::Tiles) + setTilemapMode(TilemapMode::Pixels); } } @@ -679,7 +692,9 @@ void ColorBar::onPaletteButtonClick() void ColorBar::onTilesButtonClick() { m_tilesButton.deselectItems(); - setTilesMode(!inTilesMode()); + setTilemapMode( + (tilemapMode() == TilemapMode::Pixels ? TilemapMode::Tiles: + TilemapMode::Pixels)); } void ColorBar::onTilesetModeButtonClick() @@ -885,7 +900,7 @@ void ColorBar::setTransparentIndex(int index) void ColorBar::onPaletteViewChangeSize(int boxsize) { - if (inTilesMode()) + if (tilemapMode() == TilemapMode::Tiles) Preferences::instance().colorBar.tilesBoxSize(boxsize); else Preferences::instance().colorBar.boxSize(boxsize); @@ -1037,6 +1052,14 @@ void ColorBar::onTilesViewDragAndDrop(doc::Tileset* tileset, void ColorBar::onTilesViewIndexChange(int index, ui::MouseButton button) { // TODO show tools to stamp/draw/pick tiles + + if (button == kButtonRight) + setBgColor(app::Color::fromIndex(index)); + else if (button == kButtonLeft) + setFgColor(app::Color::fromIndex(index)); + else if (button == kButtonMiddle) { + // TODO ? + } } void ColorBar::onFgColorChangeFromPreferences() @@ -1323,7 +1346,7 @@ void ColorBar::onNewInputPriority(InputChainElement* element, return; if (element != this) { - if (m_tilesMode) + if (m_tilemapMode == TilemapMode::Tiles) m_tilesView.deselect(); else m_paletteView.deselect(); @@ -1332,7 +1355,7 @@ void ColorBar::onNewInputPriority(InputChainElement* element, bool ColorBar::onCanCut(Context* ctx) { - if (m_tilesMode) + if (m_tilemapMode == TilemapMode::Tiles) return (m_tilesView.getSelectedEntriesCount() > 0); else return (m_paletteView.getSelectedEntriesCount() > 0); @@ -1345,7 +1368,7 @@ bool ColorBar::onCanCopy(Context* ctx) bool ColorBar::onCanPaste(Context* ctx) { - if (m_tilesMode) + if (m_tilemapMode == TilemapMode::Tiles) return (clipboard::get_current_format() == clipboard::ClipboardTiles); else return (clipboard::get_current_format() == clipboard::ClipboardPaletteEntries); @@ -1358,7 +1381,7 @@ bool ColorBar::onCanClear(Context* ctx) bool ColorBar::onCut(Context* ctx) { - if (m_tilesMode) { + if (m_tilemapMode == TilemapMode::Tiles) { m_tilesView.cutToClipboard(); showRemapTiles(); } @@ -1369,7 +1392,7 @@ bool ColorBar::onCut(Context* ctx) bool ColorBar::onCopy(Context* ctx) { - if (m_tilesMode) + if (m_tilemapMode == TilemapMode::Tiles) m_tilesView.copyToClipboard(); else m_paletteView.copyToClipboard(); @@ -1378,7 +1401,7 @@ bool ColorBar::onCopy(Context* ctx) bool ColorBar::onPaste(Context* ctx) { - if (m_tilesMode) { + if (m_tilemapMode == TilemapMode::Tiles) { m_tilesView.pasteFromClipboard(); showRemapTiles(); } @@ -1389,7 +1412,7 @@ bool ColorBar::onPaste(Context* ctx) bool ColorBar::onClear(Context* ctx) { - if (m_tilesMode) { + if (m_tilemapMode == TilemapMode::Tiles) { m_tilesView.clearSelection(); showRemapTiles(); } diff --git a/src/app/ui/color_bar.h b/src/app/ui/color_bar.h index 546bb5933..15175a5c3 100644 --- a/src/app/ui/color_bar.h +++ b/src/app/ui/color_bar.h @@ -13,6 +13,7 @@ #include "app/context_observer.h" #include "app/doc_observer.h" #include "app/docs_observer.h" +#include "app/tilemap_mode.h" #include "app/tileset_mode.h" #include "app/ui/button_set.h" #include "app/ui/color_button.h" @@ -71,6 +72,9 @@ namespace app { void setFgColor(const app::Color& color); void setBgColor(const app::Color& color); + doc::tile_index getFgTile() const; + doc::tile_index getBgTile() const; + PaletteView* getPaletteView(); ColorSelector getColorSelector() const; @@ -81,8 +85,8 @@ namespace app { bool inEditMode() const; void setEditMode(bool state); - bool inTilesMode() const; - void setTilesMode(bool state); + TilemapMode tilemapMode() const; + void setTilemapMode(const TilemapMode mode); TilesetMode tilesetMode() const; void setTilesetMode(const TilesetMode mode); @@ -234,7 +238,7 @@ namespace app { bool m_editMode; // True if we should be putting/setting tiles. - bool m_tilesMode; + TilemapMode m_tilemapMode; TilesetMode m_tilesetMode; // Timer to redraw editors after a palette change. diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp index 51f493694..f67ca6029 100644 --- a/src/app/ui/editor/brush_preview.cpp +++ b/src/app/ui/editor/brush_preview.cpp @@ -22,6 +22,7 @@ #include "app/tools/point_shape.h" #include "app/tools/tool.h" #include "app/tools/tool_loop.h" +#include "app/ui/color_bar.h" #include "app/ui/context_bar.h" #include "app/ui/editor/editor.h" #include "app/ui/editor/tool_loop_impl.h" @@ -34,8 +35,8 @@ #include "doc/image_impl.h" #include "doc/layer.h" #include "doc/primitives.h" -#include "render/render.h" #include "os/display.h" +#include "render/render.h" #include "ui/manager.h" #include "ui/system.h" @@ -201,7 +202,12 @@ void BrushPreview::show(const gfx::Point& screenPos) // Draw pixel/brush preview if (showPreview) { - gfx::Rect origBrushBounds = (isFloodfill ? gfx::Rect(0, 0, 1, 1): brush->bounds()); + Site site = m_editor->getSite(); + + // TODO add support for "tile-brushes" + gfx::Rect origBrushBounds = + (isFloodfill || site.tilemapMode() == TilemapMode::Tiles ? gfx::Rect(0, 0, 1, 1): + brush->bounds()); gfx::Rect brushBounds = origBrushBounds; brushBounds.offset(spritePos); gfx::Rect extraCelBounds = brushBounds; @@ -226,8 +232,17 @@ void BrushPreview::show(const gfx::Point& screenPos) } } + gfx::Rect extraCelBoundsInCanvas; + if (site.tilemapMode() == TilemapMode::Tiles) { + ASSERT(layer->isTilemap()); + extraCelBounds.setOrigin(site.grid().canvasToTile(extraCelBounds.origin())); + extraCelBoundsInCanvas = site.grid().tileToCanvas(extraCelBounds); + } + else { + extraCelBoundsInCanvas = extraCelBounds; + } + // Create the extra cel to show the brush preview - Site site = m_editor->getSite(); Cel* cel = site.cel(); int t, opacity = 255; @@ -236,7 +251,14 @@ void BrushPreview::show(const gfx::Point& screenPos) if (!m_extraCel) m_extraCel.reset(new ExtraCel); - m_extraCel->create(document->sprite(), extraCelBounds, site.frame(), opacity); + + m_extraCel->create( + site.tilemapMode(), + document->sprite(), + extraCelBoundsInCanvas, + extraCelBounds.size(), + site.frame(), + opacity); m_extraCel->setType(render::ExtraType::NONE); m_extraCel->setBlendMode( (layer ? static_cast(layer)->blendMode(): @@ -252,7 +274,7 @@ void BrushPreview::show(const gfx::Point& screenPos) if (layer) { render::Render().renderLayer( extraImage, layer, site.frame(), - gfx::Clip(0, 0, extraCelBounds), + gfx::Clip(0, 0, extraCelBoundsInCanvas), BlendMode::SRC); // This extra cel is a patch for the current layer/frame @@ -279,7 +301,7 @@ void BrushPreview::show(const gfx::Point& screenPos) } document->notifySpritePixelsModified( - sprite, gfx::Region(m_lastBounds = extraCelBounds), + sprite, gfx::Region(m_lastBounds = extraCelBoundsInCanvas), m_lastFrame = site.frame()); m_withRealPreview = true; diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index c7c4dfbd4..512adeede 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -410,12 +410,16 @@ void Editor::getSite(Site* site) const } if (m_layer && m_layer->isTilemap()) { - TilesetMode mode = site->tilesetMode(); + TilemapMode tilemapMode = site->tilemapMode(); + TilesetMode tilesetMode = site->tilesetMode(); const ColorBar* colorbar = ColorBar::instance(); ASSERT(colorbar); - if (colorbar) - mode = colorbar->tilesetMode(); - site->tilesetMode(mode); + if (colorbar) { + tilemapMode = colorbar->tilemapMode(); + tilesetMode = colorbar->tilesetMode(); + } + site->tilemapMode(tilemapMode); + site->tilesetMode(tilesetMode); } } diff --git a/src/app/ui/editor/pixels_movement.cpp b/src/app/ui/editor/pixels_movement.cpp index 89078743c..b7f53a827 100644 --- a/src/app/ui/editor/pixels_movement.cpp +++ b/src/app/ui/editor/pixels_movement.cpp @@ -785,7 +785,14 @@ void PixelsMovement::redrawExtraImage(Transformation* transformation) if (!m_extraCel) m_extraCel.reset(new ExtraCel); - m_extraCel->create(m_document->sprite(), bounds, m_site.frame(), opacity); + m_extraCel->create( + m_site.tilemapMode(), + m_document->sprite(), + bounds, + (m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size(): + bounds.size()), + m_site.frame(), + opacity); m_extraCel->setType(render::ExtraType::PATCH); m_extraCel->setBlendMode(m_site.layer()->isImage() ? static_cast(m_site.layer())->blendMode(): diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 041851beb..ff27acb4f 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -103,6 +103,7 @@ protected: bool m_contiguous; bool m_snapToGrid; bool m_isSelectingTiles; + doc::Grid m_grid; gfx::Rect m_gridBounds; gfx::Point m_celOrigin; gfx::Point m_speed; @@ -123,7 +124,8 @@ protected: tools::DynamicsOptions m_dynamics; public: - ToolLoopBase(Editor* editor, Site site, + ToolLoopBase(Editor* editor, + const Site& site, ToolLoopParams& params) : m_editor(editor) , m_tool(params.tool) @@ -142,7 +144,8 @@ public: , m_contiguous(params.contiguous) , m_snapToGrid(m_docPref.grid.snap()) , m_isSelectingTiles(false) - , m_gridBounds(params.gridBounds) + , m_grid(site.grid()) + , m_gridBounds(site.gridBounds()) , m_button(params.button) , m_ink(params.ink->clone()) , m_controller(params.controller) @@ -150,7 +153,9 @@ public: , m_intertwine(m_tool->getIntertwine(m_button)) , m_tracePolicy(m_tool->getTracePolicy(m_button)) , m_symmetry(nullptr) - , m_colorTarget(m_layer ? ColorTarget(m_layer): + , m_colorTarget(site.tilemapMode() == TilemapMode::Tiles ? ColorTarget(ColorTarget::BackgroundLayer, + IMAGE_TILEMAP, 0): + m_layer ? ColorTarget(m_layer): ColorTarget(ColorTarget::BackgroundLayer, m_sprite->pixelFormat(), m_sprite->transparentColor())) @@ -163,6 +168,11 @@ public: ASSERT(m_ink); ASSERT(m_controller); + if (site.tilemapMode() == TilemapMode::Tiles) { + m_pointShape = App::instance()->toolBox()->getPointShapeById( + tools::WellKnownPointShapes::Tile); + } + #ifdef ENABLE_UI // TODO add dynamics support when UI is not enabled if (m_controller->isFreehand() && !m_pointShape->isFloodFill() && @@ -309,6 +319,7 @@ public: == app::gen::PixelConnectivity::EIGHT_CONNECTED); } + const doc::Grid& getGrid() const override { return m_grid; } gfx::Rect getGridBounds() override { return m_gridBounds; } gfx::Point getCelOrigin() override { return m_celOrigin; } void setSpeed(const gfx::Point& speed) override { m_speed = speed; } @@ -432,7 +443,7 @@ class ToolLoopImpl : public ToolLoopBase { public: ToolLoopImpl(Editor* editor, - Site site, + const Site& site, Context* context, ToolLoopParams& params, const bool saveLastPoint) @@ -691,9 +702,18 @@ tools::ToolLoop* create_tool_loop( const bool convertLineToFreehand, const bool selectTiles) { + Site site = editor->getSite(); + ToolLoopParams params; params.tool = editor->getCurrentEditorTool(); params.ink = editor->getCurrentEditorInk(); + + // TODO add inks for tilemaps + if (site.tilemapMode() == TilemapMode::Tiles) { + if (!params.ink->isSelection()) + params.ink = App::instance()->toolBox()->getInkById(tools::WellKnownInks::PaintCopy); + } + if (!params.tool || !params.ink) return nullptr; @@ -702,14 +722,6 @@ tools::ToolLoop* create_tool_loop( params.ink = params.tool->getInk(button == tools::Pointer::Left ? 0: 1); } - Site site = editor->getSite(); - - // Get grid bounds from the original site (as we call - // site.layer(nullptr) in certain cases, we need to know if the - // active layer is a tilemap, and in that case the grid bounds can - // be different than the sprite grid bounds). - params.gridBounds = site.gridBounds(); - // For selection tools, we can use any layer (even without layers at // all), so we specify a nullptr here as the active layer. This is // used as a special case by the render::Render class to show the @@ -750,11 +762,18 @@ tools::ToolLoop* create_tool_loop( // Get fg/bg colors ColorBar* colorbar = ColorBar::instance(); - params.fg = colorbar->getFgColor(); - params.bg = colorbar->getBgColor(); + if (site.tilemapMode() == TilemapMode::Tiles) { + params.fg = app::Color::fromIndex(colorbar->getFgTile()); // TODO Color::fromTileIndex? + params.bg = app::Color::fromIndex(colorbar->getBgTile()); + } + else { + params.fg = colorbar->getFgColor(); + params.bg = colorbar->getBgColor(); + } - if (!params.fg.isValid() || - !params.bg.isValid()) { + if (site.tilemapMode() == TilemapMode::Pixels && + (!params.fg.isValid() || + !params.bg.isValid())) { if (Preferences::instance().colorBar.showInvalidFgBgColorAlert()) { OptionalAlert::show( Preferences::instance().colorBar.showInvalidFgBgColorAlert, @@ -817,9 +836,6 @@ tools::ToolLoop* create_tool_loop_for_script( if (!site.layer()) return nullptr; - // TODO should gridBounds be specified by the caller? - params.gridBounds = site.gridBounds(); - try { // If we don't have the UI available, we reset the tools // preferences, so scripts that are executed in batch mode have a @@ -849,10 +865,11 @@ class PreviewToolLoopImpl : public ToolLoopBase { public: PreviewToolLoopImpl( Editor* editor, + const Site& site, ToolLoopParams& params, Image* image, const gfx::Point& celOrigin) - : ToolLoopBase(editor, editor->getSite(), params) + : ToolLoopBase(editor, site, params) , m_image(image) { m_celOrigin = celOrigin; @@ -906,9 +923,19 @@ tools::ToolLoop* create_tool_loop_preview( Image* image, const gfx::Point& celOrigin) { + Site site = editor->getSite(); + ToolLoopParams params; params.tool = editor->getCurrentEditorTool(); params.ink = editor->getCurrentEditorInk(); + + // TODO add inks for tilemaps + if (site.tilemapMode() == TilemapMode::Tiles && + image->pixelFormat() == IMAGE_TILEMAP) { + if (!params.ink->isSelection()) + params.ink = App::instance()->toolBox()->getInkById(tools::WellKnownInks::PaintCopy); + } + if (!params.tool || !params.ink) return nullptr; @@ -922,8 +949,16 @@ tools::ToolLoop* create_tool_loop_preview( // Get fg/bg colors ColorBar* colorbar = ColorBar::instance(); - params.fg = colorbar->getFgColor(); - params.bg = colorbar->getBgColor(); + if (site.tilemapMode() == TilemapMode::Tiles) { + params.fg = app::Color::fromIndex(colorbar->getFgTile()); // TODO Color::fromTileIndex? + params.bg = app::Color::fromIndex(colorbar->getBgTile()); + if (!params.fg.isValid() || !params.bg.isValid()) + return nullptr; + } + else { + params.fg = colorbar->getFgColor(); + params.bg = colorbar->getBgColor(); + } if (!params.fg.isValid() || !params.bg.isValid()) return nullptr; @@ -931,14 +966,13 @@ tools::ToolLoop* create_tool_loop_preview( params.brush = brush; params.button = tools::ToolLoop::Left; params.controller = params.tool->getController(params.button); - params.gridBounds = editor->getSite().gridBounds(); // Create the new tool loop try { fill_toolloop_params_from_tool_preferences(params); return new PreviewToolLoopImpl( - editor, params, image, celOrigin); + editor, site, params, image, celOrigin); } catch (const std::exception& e) { LOG(ERROR, e.what()); diff --git a/src/app/ui/editor/tool_loop_impl.h b/src/app/ui/editor/tool_loop_impl.h index 344076085..9af848257 100644 --- a/src/app/ui/editor/tool_loop_impl.h +++ b/src/app/ui/editor/tool_loop_impl.h @@ -46,8 +46,6 @@ namespace app { int tolerance = 0; bool contiguous = true; tools::FreehandAlgorithm freehandAlgorithm = tools::FreehandAlgorithm::DEFAULT; - - gfx::Rect gridBounds; }; ////////////////////////////////////////////////////////////////////// diff --git a/src/app/util/expand_cel_canvas.cpp b/src/app/util/expand_cel_canvas.cpp index f884ffdd5..de063c967 100644 --- a/src/app/util/expand_cel_canvas.cpp +++ b/src/app/util/expand_cel_canvas.cpp @@ -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 @@ -86,6 +86,8 @@ ExpandCelCanvas::ExpandCelCanvas( , m_closed(false) , m_committed(false) , m_cmds(cmds) + , m_grid(site.grid()) + , m_tilemapMode(site.tilemapMode()) , m_tilesetMode(site.tilesetMode()) { if (m_layer && m_layer->isTilemap()) { @@ -126,6 +128,10 @@ ExpandCelCanvas::ExpandCelCanvas( m_bounds = spriteBounds; } + if (m_tilemapMode == TilemapMode::Tiles) { + m_bounds = site.grid().canvasToTile(m_bounds); + } + // 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). @@ -140,7 +146,8 @@ ExpandCelCanvas::ExpandCelCanvas( } // If we are in a tilemap, we use m_dstImage to draw pixels (instead // of the tilemap image). - else if (m_layer->isTilemap()) { + else if (m_layer->isTilemap() && + m_tilemapMode == TilemapMode::Pixels) { // Calling "getDestCanvas()" we create the m_dstImage getDestCanvas(); m_cel->data()->setImage(m_dstImage, m_layer); @@ -191,7 +198,8 @@ void ExpandCelCanvas::commit() gfx::Rect trimBounds = getTrimDstImageBounds(); if (!trimBounds.isEmpty()) { // Convert the image to tiles - if (m_layer->isTilemap()) { + if (m_layer->isTilemap() && + m_tilemapMode == TilemapMode::Pixels) { doc::ImageRef newTilemap; draw_image_into_new_tilemap_cel( m_cmds, static_cast(m_layer), m_cel, @@ -226,7 +234,8 @@ void ExpandCelCanvas::commit() m_cel->setPosition(m_origCelPos); #ifdef _DEBUG - if (m_layer->isTilemap()) { + if (m_layer->isTilemap() && + m_tilemapMode == TilemapMode::Pixels) { ASSERT(m_cel->image() != m_celImage.get()); } else { @@ -253,7 +262,8 @@ void ExpandCelCanvas::commit() EXP_TRACE(" - regionToPatch", regionToPatch->bounds()); // Convert the image to tiles again - if (m_layer->isTilemap()) { + if (m_layer->isTilemap() && + m_tilemapMode == TilemapMode::Pixels) { ASSERT(m_celImage.get() != m_cel->image()); ASSERT(m_celImage->pixelFormat() == IMAGE_TILEMAP); @@ -326,7 +336,6 @@ void ExpandCelCanvas::rollback() } // 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()); @@ -340,8 +349,10 @@ Image* ExpandCelCanvas::getSourceCanvas() ASSERT((m_flags & NeedsSource) == NeedsSource); if (!m_srcImage) { - m_srcImage.reset(Image::create(m_sprite->pixelFormat(), - m_bounds.w, m_bounds.h, src_buffer)); + m_srcImage.reset( + Image::create(m_tilemapMode == TilemapMode::Tiles ? IMAGE_TILEMAP: + m_sprite->pixelFormat(), + m_bounds.w, m_bounds.h, src_buffer)); m_srcImage->setMaskColor(m_sprite->transparentColor()); } @@ -351,8 +362,10 @@ Image* ExpandCelCanvas::getSourceCanvas() Image* ExpandCelCanvas::getDestCanvas() { if (!m_dstImage) { - m_dstImage.reset(Image::create(m_sprite->pixelFormat(), - m_bounds.w, m_bounds.h, dst_buffer)); + 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()); } @@ -365,7 +378,16 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn) getSourceCanvas(); - gfx::Region rgnToValidate(rgn); + gfx::Region rgnToValidate; + if (m_tilemapMode == TilemapMode::Tiles) { + for (const auto& rc : rgn) + rgnToValidate |= gfx::Region(m_grid.canvasToTile(rc)); + } + else { + rgnToValidate = rgn; + } + EXP_TRACE(" ->", rgnToValidate.bounds()); + rgnToValidate.offset(-m_bounds.origin()); rgnToValidate.createSubtraction(rgnToValidate, m_validSrcRegion); rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_srcImage->bounds())); @@ -380,7 +402,10 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn) for (const auto& rc : rgnToClear) fill_rect(m_srcImage.get(), rc, m_srcImage->maskColor()); - if (m_celImage->pixelFormat() == IMAGE_TILEMAP) { + if (m_celImage->pixelFormat() == IMAGE_TILEMAP && + m_srcImage->pixelFormat() != IMAGE_TILEMAP) { + ASSERT(m_tilemapMode == TilemapMode::Pixels); + // For tilemaps, we can use the Render class to render visible // tiles in the rgnToValidate of this cel. render::Render subRender; @@ -399,6 +424,9 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn) } } else { + ASSERT(m_celImage->pixelFormat() != IMAGE_TILEMAP || + m_tilemapMode == TilemapMode::Tiles); + // We can copy the cel image directly for (const auto& rc : rgnToValidate) m_srcImage->copy( @@ -436,7 +464,16 @@ void ExpandCelCanvas::validateDestCanvas(const gfx::Region& rgn) getDestCanvas(); // Create m_dstImage - gfx::Region rgnToValidate(rgn); + gfx::Region rgnToValidate; + if (m_tilemapMode == TilemapMode::Tiles) { + for (const auto& rc : rgn) + rgnToValidate |= gfx::Region(m_grid.canvasToTile(rc)); + } + else { + rgnToValidate = rgn; + } + EXP_TRACE(" ->", rgnToValidate.bounds()); + rgnToValidate.offset(-m_bounds.origin()); rgnToValidate.createSubtraction(rgnToValidate, m_validDstRegion); rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_dstImage->bounds())); diff --git a/src/app/util/expand_cel_canvas.h b/src/app/util/expand_cel_canvas.h index c88e3192b..d772f7397 100644 --- a/src/app/util/expand_cel_canvas.h +++ b/src/app/util/expand_cel_canvas.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -9,8 +9,10 @@ #define APP_UTIL_EXPAND_CEL_CANVAS_H_INCLUDED #pragma once +#include "app/tilemap_mode.h" #include "app/tileset_mode.h" #include "doc/frame.h" +#include "doc/grid.h" #include "doc/image_ref.h" #include "filters/tiled_mode.h" #include "gfx/point.h" @@ -100,6 +102,8 @@ namespace app { // reduce the patched region because both images will be the same. bool m_canCompareSrcVsDst; + doc::Grid m_grid; + TilemapMode m_tilemapMode; TilesetMode m_tilesetMode; }; diff --git a/src/doc/algorithm/shrink_bounds.cpp b/src/doc/algorithm/shrink_bounds.cpp index 32d8e1975..20938def1 100644 --- a/src/doc/algorithm/shrink_bounds.cpp +++ b/src/doc/algorithm/shrink_bounds.cpp @@ -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. @@ -399,7 +399,7 @@ bool shrink_bounds2(const Image* a, case IMAGE_GRAYSCALE: return shrink_bounds_templ2(a, b, bounds); case IMAGE_INDEXED: return shrink_bounds_templ2(a, b, bounds); case IMAGE_BITMAP: return shrink_bounds_templ2(a, b, bounds); - // case IMAGE_TILEMAP: return shrink_bounds_templ2(a, b, bounds); + case IMAGE_TILEMAP: return shrink_bounds_templ2(a, b, bounds); } ASSERT(false); return false; diff --git a/src/render/render.cpp b/src/render/render.cpp index 3e712fb04..2f12e1444 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -677,7 +677,8 @@ void Render::renderLayer( CompositeImageFunc compositeImage = getImageComposition( - dstImage->pixelFormat(), + (dstImage->pixelFormat() != IMAGE_TILEMAP ? dstImage->pixelFormat(): + m_sprite->pixelFormat()), m_sprite->pixelFormat(), layer); if (!compositeImage) return; @@ -757,7 +758,10 @@ void Render::renderSprite( // Overlay preview image if (m_previewImage && m_selectedLayer == nullptr && - m_selectedFrame == frame) { + m_selectedFrame == frame + // TODO allow previewImage for tilemaps? + && m_previewImage->pixelFormat() == m_sprite->pixelFormat() + ) { renderImage( dstImage, m_previewImage, @@ -768,7 +772,8 @@ void Render::renderSprite( area, getImageComposition( dstImage->pixelFormat(), - m_previewImage->pixelFormat(), sprite->root()), + m_previewImage->pixelFormat(), + sprite->root()), 255, m_previewBlendMode); } @@ -1033,11 +1038,7 @@ void Render::renderLayer( } if (drawExtra) { - extraArea = gfx::Rect( - m_extraCel->x(), - m_extraCel->y(), - m_extraImage->width(), - m_extraImage->height()); + extraArea = m_extraCel->bounds(); extraArea = m_proj.apply(extraArea); if (m_proj.scaleX() < 1.0) extraArea.w--; if (m_proj.scaleY() < 1.0) extraArea.h--; @@ -1178,14 +1179,15 @@ void Render::renderLayer( if (drawExtra && m_extraType != ExtraType::NONE) { if (m_extraCel->opacity() > 0) { renderCel( - image, m_extraImage, - nullptr, // Without layer + image, + m_sprite, + m_extraImage, + m_currentLayer, // Current layer (useful to use get the tileset if extra cel is a tilemap) m_sprite->palette(frame), m_extraCel->bounds(), gfx::Clip(area.dst.x+extraArea.x-area.src.x, area.dst.y+extraArea.y-area.src.y, extraArea), - compositeImage, m_extraCel->opacity(), m_extraBlendMode); } @@ -1285,12 +1287,17 @@ void Render::renderCel( const tile_t t = cel_image->getPixel(u, v); const tile_index i = tile_geti(t); - 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); + } } } } @@ -1374,8 +1381,16 @@ CompositeImageFunc Render::getImageComposition( case IMAGE_INDEXED: return get_fastest_composition_path(m_proj, finegrain); } break; + + case IMAGE_TILEMAP: + switch (dstFormat) { + case IMAGE_TILEMAP: + return get_fastest_composition_path(m_proj, finegrain); + } + break; } + TRACE_RENDER_CEL("Render::getImageComposition srcFormat", srcFormat, "dstFormat", dstFormat); ASSERT(false && "Invalid pixel formats"); return nullptr; }