Add support to copy/cut/paste/transform tilemaps

This commit is contained in:
David Capello 2020-10-02 19:03:53 -03:00
parent edebb57f66
commit 120fb4dca4
14 changed files with 338 additions and 96 deletions

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -15,7 +16,6 @@
#include "app/tools/tool_box.h" #include "app/tools/tool_box.h"
#include "app/ui/context_bar.h" #include "app/ui/context_bar.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "app/util/new_image_from_mask.h"
namespace app { namespace app {

View File

@ -86,7 +86,7 @@ RectF Transformation::transformedBounds() const
// Create a union of all corners // Create a union of all corners
RectF bounds; RectF bounds;
for (int i=0; i<Corners::NUM_OF_CORNERS; ++i) for (int i=0; i<Corners::NUM_OF_CORNERS; ++i)
bounds = bounds.createUnion(RectF(corners[i].x, corners[i].y, 1, 1)); bounds = bounds.createUnion(RectF(corners[i].x, corners[i].y, 0.001, 0.001));
return bounds; return bounds;
} }

View File

@ -59,7 +59,7 @@ public:
gfx::RectF bounds() const { gfx::RectF bounds() const {
gfx::RectF bounds; gfx::RectF bounds;
for (int i=0; i<Corners::NUM_OF_CORNERS; ++i) for (int i=0; i<Corners::NUM_OF_CORNERS; ++i)
bounds |= gfx::RectF(m_corners[i].x, m_corners[i].y, 1, 1); bounds |= gfx::RectF(m_corners[i].x, m_corners[i].y, 0.001, 0.001);
return bounds; return bounds;
} }

View File

@ -498,13 +498,21 @@ bool DocView::onCanCopy(Context* ctx)
bool DocView::onCanPaste(Context* ctx) bool DocView::onCanPaste(Context* ctx)
{ {
return if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
(ctx->clipboard()->format() == ClipboardFormat::Image
&& ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible | ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable | ContextFlags::ActiveLayerIsEditable |
ContextFlags::ActiveLayerIsImage) ContextFlags::ActiveLayerIsImage)
&& !ctx->checkFlags(ContextFlags::ActiveLayerIsReference)); && !ctx->checkFlags(ContextFlags::ActiveLayerIsReference)) {
auto format = ctx->clipboard()->format();
if (format == ClipboardFormat::Image) {
return true;
}
else if (format == ClipboardFormat::Tilemap &&
ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap)) {
return true;
}
}
return false;
} }
bool DocView::onCanClear(Context* ctx) bool DocView::onCanClear(Context* ctx)
@ -546,7 +554,8 @@ bool DocView::onCopy(Context* ctx)
bool DocView::onPaste(Context* ctx) bool DocView::onPaste(Context* ctx)
{ {
auto clipboard = ctx->clipboard(); auto clipboard = ctx->clipboard();
if (clipboard->format() == ClipboardFormat::Image) { if (clipboard->format() == ClipboardFormat::Image ||
clipboard->format() == ClipboardFormat::Tilemap) {
clipboard->paste(ctx, true); clipboard->paste(ctx, true);
return true; return true;
} }

View File

@ -27,6 +27,7 @@
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/snap_to_grid.h"
#include "app/tools/active_tool.h" #include "app/tools/active_tool.h"
#include "app/tools/controller.h" #include "app/tools/controller.h"
#include "app/tools/ink.h" #include "app/tools/ink.h"
@ -2498,6 +2499,19 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
} }
} }
Site site = getSite();
// Snap to grid a pasted tilemap
// TODO should we move this to PixelsMovement or MovingPixelsState?
if (site.tilemapMode() == TilemapMode::Tiles) {
gfx::Rect gridBounds = site.gridBounds();
gfx::Point pt = snap_to_grid(gridBounds,
gfx::Point(x, y),
PreferSnapTo::ClosestGridVertex);
x = pt.x;
y = pt.y;
}
// Clear brush preview, as the extra cel will be replaced with the // Clear brush preview, as the extra cel will be replaced with the
// pasted image. // pasted image.
m_brushPreview.hide(); m_brushPreview.hide();
@ -2506,7 +2520,7 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
mask2.setOrigin(x, y); mask2.setOrigin(x, y);
PixelsMovementPtr pixelsMovement( PixelsMovementPtr pixelsMovement(
new PixelsMovement(UIContext::instance(), getSite(), new PixelsMovement(UIContext::instance(), site,
image, &mask2, "Paste")); image, &mask2, "Paste"));
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle))); setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));

View File

@ -388,7 +388,8 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
bounds.offset(dx, dy); bounds.offset(dx, dy);
if ((moveModifier & SnapToGridMovement) == SnapToGridMovement) { if ((m_site.tilemapMode() == TilemapMode::Tiles) ||
(moveModifier & SnapToGridMovement) == SnapToGridMovement) {
// Snap the x1,y1 point to the grid. // Snap the x1,y1 point to the grid.
gfx::Rect gridBounds = m_site.gridBounds(); gfx::Rect gridBounds = m_site.gridBounds();
gfx::PointF gridOffset( gfx::PointF gridOffset(
@ -487,6 +488,13 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
b.y = ((b.y-pivot.y)*(1.0+dy/h) + pivot.y); b.y = ((b.y-pivot.y)*(1.0+dy/h) + pivot.y);
} }
// Snap to grid when resizing tilemaps
if (m_site.tilemapMode() == TilemapMode::Tiles) {
gfx::Rect gridBounds = m_site.gridBounds();
a = gfx::PointF(snap_to_grid(gridBounds, gfx::Point(a), PreferSnapTo::BoxOrigin));
b = gfx::PointF(snap_to_grid(gridBounds, gfx::Point(b), PreferSnapTo::BoxOrigin));
}
// Do not use "gfx::Rect(a, b)" here because if a > b we want to // Do not use "gfx::Rect(a, b)" here because if a > b we want to
// keep a rectangle with negative width or height (to know that // keep a rectangle with negative width or height (to know that
// it was flipped). // it was flipped).
@ -504,6 +512,11 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
case RotateNEHandle: case RotateNEHandle:
case RotateSWHandle: case RotateSWHandle:
case RotateSEHandle: { case RotateSEHandle: {
// Cannot rotate tiles
// TODO add support to rotate tiles in straight angles (changing tile flags)
if (m_site.tilemapMode() == TilemapMode::Tiles)
break;
double da = (std::atan2((double)(-pos.y + abs_pivot.y), double da = (std::atan2((double)(-pos.y + abs_pivot.y),
(double)(+pos.x - abs_pivot.x)) - (double)(+pos.x - abs_pivot.x)) -
std::atan2((double)(-m_catchPos.y + abs_initial_pivot.y), std::atan2((double)(-m_catchPos.y + abs_initial_pivot.y),
@ -541,6 +554,12 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
case SkewSHandle: case SkewSHandle:
case SkewWHandle: case SkewWHandle:
case SkewEHandle: { case SkewEHandle: {
// Cannot skew tiles
// TODO could we support to skew tiles if we have the set of tiles (e.g. diagonals)?
// maybe too complex to implement in UI terms
if (m_site.tilemapMode() == TilemapMode::Tiles)
break;
// u // u
// ------> // ------>
// //
@ -796,21 +815,22 @@ void PixelsMovement::stampExtraCelImage()
TiledMode::NONE, m_tx, TiledMode::NONE, m_tx,
ExpandCelCanvas::None); ExpandCelCanvas::None);
gfx::Point dstPt;
gfx::Size canvasImageSize = image->size();
if (m_site.tilemapMode() == TilemapMode::Tiles) {
doc::Grid grid = m_site.grid();
dstPt = grid.canvasToTile(cel->position());
canvasImageSize = grid.tileToCanvas(gfx::Rect(dstPt, canvasImageSize)).size();
}
else {
dstPt = cel->position() - expand.getCel()->position();
}
// We cannot use cel->bounds() because cel->image() is nullptr // We cannot use cel->bounds() because cel->image() is nullptr
gfx::Rect modifiedRect( expand.validateDestCanvas(
cel->x(), gfx::Region(gfx::Rect(cel->position(), canvasImageSize)));
cel->y(),
image->width(),
image->height());
gfx::Region modifiedRegion(modifiedRect); expand.getDestCanvas()->copy(image, gfx::Clip(dstPt, image->bounds()));
expand.validateDestCanvas(modifiedRegion);
expand.getDestCanvas()->copy(
image, gfx::Clip(
cel->x()-expand.getCel()->x(),
cel->y()-expand.getCel()->y(),
image->bounds()));
expand.commit(); expand.commit();
} }
@ -953,13 +973,23 @@ void PixelsMovement::redrawExtraImage(Transformation* transformation)
m_extraCel.reset(new ExtraCel); m_extraCel.reset(new ExtraCel);
gfx::Rect bounds = transformation->transformedBounds(); gfx::Rect bounds = transformation->transformedBounds();
if (!bounds.isEmpty()) { if (!bounds.isEmpty()) {
gfx::Size extraCelSize;
if (m_site.tilemapMode() == TilemapMode::Tiles) {
// Transforming tiles
extraCelSize = m_site.grid().canvasToTile(bounds).size();
}
else {
// Transforming pixels
extraCelSize = bounds.size();
}
m_extraCel->create( m_extraCel->create(
m_site.tilemapMode(), m_site.tilemapMode(),
m_document->sprite(), m_document->sprite(),
bounds, bounds,
(m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size(): extraCelSize,
bounds.size()),
m_site.frame(), m_site.frame(),
opacity); opacity);
m_extraCel->setType(render::ExtraType::PATCH); m_extraCel->setType(render::ExtraType::PATCH);
@ -995,6 +1025,16 @@ void PixelsMovement::drawImage(
auto corners = transformation.transformedCorners(); auto corners = transformation.transformedCorners();
gfx::Rect bounds = corners.bounds(); gfx::Rect bounds = corners.bounds();
if (m_site.tilemapMode() == TilemapMode::Tiles) {
dst->setMaskColor(doc::tile_i_notile);
dst->clear(dst->maskColor());
drawTransformedTilemap(
transformation,
dst, m_originalImage.get(),
m_initialMask.get(), corners, pt);
}
else {
dst->setMaskColor(m_site.sprite()->transparentColor()); dst->setMaskColor(m_site.sprite()->transparentColor());
dst->clear(dst->maskColor()); dst->clear(dst->maskColor());
@ -1026,6 +1066,7 @@ void PixelsMovement::drawImage(
dst, m_originalImage.get(), dst, m_originalImage.get(),
m_initialMask.get(), corners, pt); m_initialMask.get(), corners, pt);
} }
}
void PixelsMovement::drawMask(doc::Mask* mask, bool shrink) void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
{ {
@ -1119,6 +1160,36 @@ retry:; // In case that we don't have enough memory for RotSprite
} }
} }
void PixelsMovement::drawTransformedTilemap(
const Transformation& transformation,
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
const Transformation::Corners& corners,
const gfx::PointF& leftTop)
{
const int boxw = std::max(1, src->width()-2);
const int boxh = std::max(1, src->height()-2);
// Function to copy a whole row of tiles (h=number of tiles in Y axis)
auto draw_row =
[dst, src, boxw](int y, int v, int h) {
dst->copy(src, gfx::Clip(0, y, 0, v, 1, h));
if (boxw) {
const int u = std::min(1, src->width()-1);
for (int x=1; x<dst->width()-1; x+=boxw)
dst->copy(src, gfx::Clip(x, y, u, v, boxw, h));
}
dst->copy(src, gfx::Clip(dst->width()-1, y, src->width()-1, v, 1, h));
};
draw_row(0, 0, 1);
if (boxh) {
const int v = std::min(1, src->height()-1);
for (int y=1; y<dst->height()-1; y+=boxh)
draw_row(y, v, boxh);
}
draw_row(dst->height()-1, src->height()-1, 1);
}
void PixelsMovement::onPivotChange() void PixelsMovement::onPivotChange()
{ {
set_pivot_from_preferences(m_currentData); set_pivot_from_preferences(m_currentData);

View File

@ -148,6 +148,11 @@ namespace app {
doc::Image* dst, const doc::Image* src, const doc::Mask* mask, doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
const Transformation::Corners& corners, const Transformation::Corners& corners,
const gfx::PointF& leftTop); const gfx::PointF& leftTop);
void drawTransformedTilemap(
const Transformation& transformation,
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
const Transformation::Corners& corners,
const gfx::PointF& leftTop);
void updateDocumentMask(); void updateDocumentMask();
void hideDocumentMask(); void hideDocumentMask();

View File

@ -753,12 +753,22 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT
editor->brushPreview().hide(); editor->brushPreview().hide();
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate(); EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
std::unique_ptr<Image> tmpImage(new_image_from_mask(editor->getSite(), Site site = editor->getSite();
ImageRef tmpImage;
if (site.layer() &&
site.layer()->isTilemap() &&
site.tilemapMode() == TilemapMode::Tiles) {
tmpImage.reset(new_tilemap_from_mask(site, site.document()->mask()));
}
else {
tmpImage.reset(new_image_from_mask(site,
Preferences::instance().experimental.newBlend())); Preferences::instance().experimental.newBlend()));
}
PixelsMovementPtr pixelsMovement( PixelsMovementPtr pixelsMovement(
new PixelsMovement(UIContext::instance(), new PixelsMovement(UIContext::instance(),
editor->getSite(), site,
tmpImage.get(), tmpImage.get(),
document->mask(), document->mask(),
"Transformation")); "Transformation"));

View File

@ -209,16 +209,29 @@ bool Clipboard::getClipboardText(std::string& text)
void Clipboard::setData(Image* image, void Clipboard::setData(Image* image,
Mask* mask, Mask* mask,
Palette* palette, Palette* palette,
bool set_system_clipboard, Tileset* tileset,
bool set_native_clipboard,
bool image_source_is_transparent) bool image_source_is_transparent)
{ {
const bool isTilemap = (image && image->isTilemap());
m_data->clear(); m_data->clear();
m_data->palette.reset(palette); m_data->palette.reset(palette);
m_data->image.reset(image); m_data->tileset.reset(tileset);
m_data->mask.reset(mask); m_data->mask.reset(mask);
if (isTilemap)
m_data->tilemap.reset(image);
else
m_data->image.reset(image);
// Copy image to the native clipboard if (set_native_clipboard) {
if (set_system_clipboard) { // Copy tilemap to the native clipboard
if (isTilemap) {
ASSERT(tileset);
setNativeBitmap(image, mask, palette, tileset);
}
// Copy non-tilemap images to the native clipboard
else {
color_t oldMask; color_t oldMask;
if (image) { if (image) {
oldMask = image->maskColor(); oldMask = image->maskColor();
@ -233,25 +246,48 @@ void Clipboard::setData(Image* image,
image->setMaskColor(oldMask); image->setMaskColor(oldMask);
} }
} }
}
bool Clipboard::copyFromDocument(const Site& site, bool merged) bool Clipboard::copyFromDocument(const Site& site, bool merged)
{ {
const Doc* document = static_cast<const Doc*>(site.document()); ASSERT(site.document());
ASSERT(document); const Doc* doc = static_cast<const Doc*>(site.document());
const Mask* mask = doc->mask();
const Palette* pal = doc->sprite()->palette(site.frame());
if (!merged &&
site.layer() &&
site.layer()->isTilemap() &&
site.tilemapMode() == TilemapMode::Tiles) {
const Tileset* ts = static_cast<LayerTilemap*>(site.layer())->tileset();
Image* image = new_tilemap_from_mask(site, mask);
if (!image)
return false;
setData(
image,
(mask ? new Mask(*mask): nullptr),
(pal ? new Palette(*pal): nullptr),
new Tileset(*ts),
true, // set native clipboard
site.layer() && !site.layer()->isBackground());
return true;
}
const Mask* mask = document->mask();
Image* image = new_image_from_mask(site, mask, Image* image = new_image_from_mask(site, mask,
Preferences::instance().experimental.newBlend(), Preferences::instance().experimental.newBlend(),
merged); merged);
if (!image) if (!image)
return false; return false;
const Palette* pal = document->sprite()->palette(site.frame());
setData( setData(
image, image,
(mask ? new Mask(*mask): nullptr), (mask ? new Mask(*mask): nullptr),
(pal ? new Palette(*pal): nullptr), (pal ? new Palette(*pal): nullptr),
true, nullptr,
true, // set native clipboard
site.layer() && !site.layer()->isBackground()); site.layer() && !site.layer()->isBackground());
return true; return true;
@ -260,11 +296,13 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
ClipboardFormat Clipboard::format() const ClipboardFormat Clipboard::format() const
{ {
// Check if the native clipboard has an image // Check if the native clipboard has an image
if (use_native_clipboard() && hasNativeBitmap()) if (use_native_clipboard() && hasNativeBitmap()) {
return ClipboardFormat::Image; return ClipboardFormat::Image;
else }
else {
return m_data->format(); return m_data->format();
} }
}
void Clipboard::getDocumentRangeInfo(Doc** document, DocRange* range) void Clipboard::getDocumentRangeInfo(Doc** document, DocRange* range)
{ {
@ -375,16 +413,20 @@ void Clipboard::copyRange(const ContextReader& reader, const DocRange& range)
App::instance()->timeline()->activateClipboardRange(); App::instance()->timeline()->activateClipboardRange();
} }
void Clipboard::copyImage(const Image* image, const Mask* mask, const Palette* pal) void Clipboard::copyImage(const Image* image,
const Mask* mask,
const Palette* pal)
{ {
setData( setData(
Image::createCopy(image), Image::createCopy(image),
(mask ? new Mask(*mask): nullptr), (mask ? new Mask(*mask): nullptr),
(pal ? new Palette(*pal): nullptr), (pal ? new Palette(*pal): nullptr),
nullptr,
true, false); true, false);
} }
void Clipboard::copyPalette(const Palette* palette, const doc::PalettePicks& picks) void Clipboard::copyPalette(const Palette* palette,
const PalettePicks& picks)
{ {
if (!picks.picks()) if (!picks.picks())
return; // Do nothing case return; // Do nothing case
@ -392,11 +434,14 @@ void Clipboard::copyPalette(const Palette* palette, const doc::PalettePicks& pic
setData(nullptr, setData(nullptr,
nullptr, nullptr,
new Palette(*palette), new Palette(*palette),
true, false); nullptr,
true, // set native clipboard
false);
m_data->picks = picks; m_data->picks = picks;
} }
void Clipboard::paste(Context* ctx, const bool interactive) void Clipboard::paste(Context* ctx,
const bool interactive)
{ {
Site site = ctx->activeSite(); Site site = ctx->activeSite();
Doc* dstDoc = site.document(); Doc* dstDoc = site.document();
@ -420,11 +465,12 @@ void Clipboard::paste(Context* ctx, const bool interactive)
// Source image (clipboard or a converted copy to the destination 'imgtype') // Source image (clipboard or a converted copy to the destination 'imgtype')
ImageRef src_image; ImageRef src_image;
if (m_data->image->pixelFormat() == dstSpr->pixelFormat() && if (// Copy image of the same pixel format
(m_data->image->pixelFormat() == dstSpr->pixelFormat() &&
// Indexed images can be copied directly only if both images // Indexed images can be copied directly only if both images
// have the same palette. // have the same palette.
(m_data->image->pixelFormat() != IMAGE_INDEXED || (m_data->image->pixelFormat() != IMAGE_INDEXED ||
m_data->palette->countDiff(dst_palette, NULL, NULL) == 0)) { m_data->palette->countDiff(dst_palette, NULL, NULL) == 0))) {
src_image = m_data->image; src_image = m_data->image;
} }
else { else {
@ -483,6 +529,21 @@ void Clipboard::paste(Context* ctx, const bool interactive)
break; break;
} }
case ClipboardFormat::Tilemap: {
if (current_editor && interactive) {
// TODO match both tilesets?
// TODO add post-command parameters (issue #2324)
// Change to MovingTilemapState
current_editor->pasteImage(m_data->tilemap.get(),
m_data->mask.get());
}
else {
// TODO non-interactive version (for scripts)
}
break;
}
case ClipboardFormat::DocRange: { case ClipboardFormat::DocRange: {
DocRange srcRange = m_data->range.range(); DocRange srcRange = m_data->range.range();
Doc* srcDoc = m_data->range.document(); Doc* srcDoc = m_data->range.document();
@ -683,15 +744,19 @@ ImageRef Clipboard::getImage(Palette* palette)
Image* native_image = nullptr; Image* native_image = nullptr;
Mask* native_mask = nullptr; Mask* native_mask = nullptr;
Palette* native_palette = nullptr; Palette* native_palette = nullptr;
Tileset* native_tileset = nullptr;
getNativeBitmap(&native_image, getNativeBitmap(&native_image,
&native_mask, &native_mask,
&native_palette); &native_palette,
if (native_image) &native_tileset);
if (native_image) {
setData(native_image, setData(native_image,
native_mask, native_mask,
native_palette, native_palette,
native_tileset,
false, false); false, false);
} }
}
if (m_data->palette && palette) if (m_data->palette && palette)
m_data->palette->copyColorsTo(palette); m_data->palette->copyColorsTo(palette);
return m_data->image; return m_data->image;

View File

@ -23,6 +23,7 @@ namespace doc {
class Mask; class Mask;
class Palette; class Palette;
class PalettePicks; class PalettePicks;
class Tileset;
} }
namespace app { namespace app {
@ -88,7 +89,8 @@ namespace app {
void setData(doc::Image* image, void setData(doc::Image* image,
doc::Mask* mask, doc::Mask* mask,
doc::Palette* palette, doc::Palette* palette,
bool set_system_clipboard, doc::Tileset* tileset,
bool set_native_clipboard,
bool image_source_is_transparent); bool image_source_is_transparent);
bool copyFromDocument(const Site& site, bool merged = false); bool copyFromDocument(const Site& site, bool merged = false);
@ -97,10 +99,12 @@ namespace app {
bool hasNativeBitmap() const; bool hasNativeBitmap() const;
bool setNativeBitmap(const doc::Image* image, bool setNativeBitmap(const doc::Image* image,
const doc::Mask* mask, const doc::Mask* mask,
const doc::Palette* palette); const doc::Palette* palette,
const doc::Tileset* tileset = nullptr);
bool getNativeBitmap(doc::Image** image, bool getNativeBitmap(doc::Image** image,
doc::Mask** mask, doc::Mask** mask,
doc::Palette** palette); doc::Palette** palette,
doc::Tileset** tileset);
bool getNativeBitmapSize(gfx::Size* size); bool getNativeBitmapSize(gfx::Size* size);
struct Data; struct Data;

View File

@ -20,6 +20,7 @@
#include "doc/image_io.h" #include "doc/image_io.h"
#include "doc/mask_io.h" #include "doc/mask_io.h"
#include "doc/palette_io.h" #include "doc/palette_io.h"
#include "doc/tileset_io.h"
#include "gfx/size.h" #include "gfx/size.h"
#include "os/display.h" #include "os/display.h"
#include "os/system.h" #include "os/system.h"
@ -66,7 +67,8 @@ bool Clipboard::hasNativeBitmap() const
bool Clipboard::setNativeBitmap(const doc::Image* image, bool Clipboard::setNativeBitmap(const doc::Image* image,
const doc::Mask* mask, const doc::Mask* mask,
const doc::Palette* palette) const doc::Palette* palette,
const doc::Tileset* tileset)
{ {
clip::lock l(native_display_handle()); clip::lock l(native_display_handle());
if (!l.locked()) if (!l.locked())
@ -83,10 +85,12 @@ bool Clipboard::setNativeBitmap(const doc::Image* image,
write32(os, write32(os,
(image ? 1: 0) | (image ? 1: 0) |
(mask ? 2: 0) | (mask ? 2: 0) |
(palette ? 4: 0)); (palette ? 4: 0) |
(tileset ? 8: 0));
if (image) doc::write_image(os, image); if (image) doc::write_image(os, image);
if (mask) doc::write_mask(os, mask); if (mask) doc::write_mask(os, mask);
if (palette) doc::write_palette(os, palette); if (palette) doc::write_palette(os, palette);
if (tileset) doc::write_tileset(os, tileset);
if (os.good()) { if (os.good()) {
size_t size = (size_t)os.tellp(); size_t size = (size_t)os.tellp();
@ -165,11 +169,13 @@ bool Clipboard::setNativeBitmap(const doc::Image* image,
bool Clipboard::getNativeBitmap(doc::Image** image, bool Clipboard::getNativeBitmap(doc::Image** image,
doc::Mask** mask, doc::Mask** mask,
doc::Palette** palette) doc::Palette** palette,
doc::Tileset** tileset)
{ {
*image = nullptr; *image = nullptr;
*mask = nullptr; *mask = nullptr;
*palette = nullptr; *palette = nullptr;
*tileset = nullptr;
clip::lock l(native_display_handle()); clip::lock l(native_display_handle());
if (!l.locked()) if (!l.locked())
@ -189,6 +195,7 @@ bool Clipboard::getNativeBitmap(doc::Image** image,
if (bits & 1) *image = doc::read_image(is, false); if (bits & 1) *image = doc::read_image(is, false);
if (bits & 2) *mask = doc::read_mask(is); if (bits & 2) *mask = doc::read_mask(is);
if (bits & 4) *palette = doc::read_palette(is); if (bits & 4) *palette = doc::read_palette(is);
if (bits & 8) *tileset = doc::read_tileset(is, nullptr);
if (image) if (image)
return true; return true;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -116,4 +116,55 @@ doc::Image* new_image_from_mask(const Site& site,
return dst.release(); return dst.release();
} }
doc::Image* new_tilemap_from_mask(const Site& site,
const doc::Mask* srcMask)
{
const Sprite* srcSprite = site.sprite();
ASSERT(srcSprite);
ASSERT(srcMask);
const doc::Grid grid = site.grid();
const Image* srcMaskBitmap = srcMask->bitmap();
const gfx::Rect& srcBounds = srcMask->bounds();
const gfx::Rect srcTilesBounds = grid.canvasToTile(srcBounds);
ASSERT(srcMaskBitmap);
ASSERT(!srcTilesBounds.isEmpty());
ASSERT(site.layer()->isTilemap());
std::unique_ptr<Image> dst(Image::create(IMAGE_TILEMAP,
srcTilesBounds.w,
srcTilesBounds.h));
if (!dst)
return nullptr;
// Clear the new tilemap
clear_image(dst.get(), dst->maskColor());
if (auto cel = site.cel()) {
// Just copy tilemap data
dst->copy(cel->image(), gfx::Clip(0, 0, srcTilesBounds));
}
// Copy the masked zones
if (srcMaskBitmap) {
// Copy active layer with mask
const LockImageBits<BitmapTraits> maskBits(srcMaskBitmap, gfx::Rect(0, 0, srcBounds.w, srcBounds.h));
LockImageBits<BitmapTraits>::const_iterator mask_it = maskBits.begin();
for (int v=0; v<srcBounds.h; ++v) {
for (int u=0; u<srcBounds.w; ++u, ++mask_it) {
ASSERT(mask_it != maskBits.end());
if (!*mask_it) {
gfx::Point pt = grid.canvasToTile(gfx::Point(srcBounds.x+u, srcBounds.y+v));
if (dst->bounds().contains(pt))
dst->putPixel(pt.x, pt.y, dst->maskColor());
}
}
}
}
return dst.release();
}
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -23,6 +23,8 @@ namespace app {
const doc::Mask* mask, const doc::Mask* mask,
const bool newBlend, const bool newBlend,
bool merged = false); bool merged = false);
doc::Image* new_tilemap_from_mask(const Site& site,
const doc::Mask* mask);
} // namespace app } // namespace app

View File

@ -27,7 +27,11 @@ Tileset::Tileset(Sprite* sprite,
, m_grid(grid) , m_grid(grid)
, m_tiles(ntiles) , m_tiles(ntiles)
{ {
ASSERT(sprite); // TODO at the moment retrieving a tileset from the clipboard use no
// sprite, but in the future we should save a whole sprite in the
// clipboard
//ASSERT(sprite);
for (tile_index ti=0; ti<ntiles; ++ti) { for (tile_index ti=0; ti<ntiles; ++ti) {
m_tiles[ti] = makeEmptyTile(); m_tiles[ti] = makeEmptyTile();
m_hash[m_tiles[ti]] = ti; m_hash[m_tiles[ti]] = ti;