mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-11 09:40:42 +00:00
Add support to copy/cut/paste/transform tilemaps
This commit is contained in:
parent
edebb57f66
commit
120fb4dca4
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -15,7 +16,6 @@
|
||||
#include "app/tools/tool_box.h"
|
||||
#include "app/ui/context_bar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
|
@ -86,7 +86,7 @@ RectF Transformation::transformedBounds() const
|
||||
// Create a union of all corners
|
||||
RectF bounds;
|
||||
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;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
gfx::RectF bounds() const {
|
||||
gfx::RectF bounds;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -498,13 +498,21 @@ bool DocView::onCanCopy(Context* ctx)
|
||||
|
||||
bool DocView::onCanPaste(Context* ctx)
|
||||
{
|
||||
return
|
||||
(ctx->clipboard()->format() == ClipboardFormat::Image
|
||||
&& ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::ActiveLayerIsVisible |
|
||||
ContextFlags::ActiveLayerIsEditable |
|
||||
ContextFlags::ActiveLayerIsImage)
|
||||
&& !ctx->checkFlags(ContextFlags::ActiveLayerIsReference));
|
||||
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::ActiveLayerIsVisible |
|
||||
ContextFlags::ActiveLayerIsEditable |
|
||||
ContextFlags::ActiveLayerIsImage)
|
||||
&& !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)
|
||||
@ -546,7 +554,8 @@ bool DocView::onCopy(Context* ctx)
|
||||
bool DocView::onPaste(Context* ctx)
|
||||
{
|
||||
auto clipboard = ctx->clipboard();
|
||||
if (clipboard->format() == ClipboardFormat::Image) {
|
||||
if (clipboard->format() == ClipboardFormat::Image ||
|
||||
clipboard->format() == ClipboardFormat::Tilemap) {
|
||||
clipboard->paste(ctx, true);
|
||||
return true;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/modules/palettes.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/snap_to_grid.h"
|
||||
#include "app/tools/active_tool.h"
|
||||
#include "app/tools/controller.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
|
||||
// pasted image.
|
||||
m_brushPreview.hide();
|
||||
@ -2506,7 +2520,7 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
|
||||
mask2.setOrigin(x, y);
|
||||
|
||||
PixelsMovementPtr pixelsMovement(
|
||||
new PixelsMovement(UIContext::instance(), getSite(),
|
||||
new PixelsMovement(UIContext::instance(), site,
|
||||
image, &mask2, "Paste"));
|
||||
|
||||
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
|
||||
|
@ -388,7 +388,8 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
|
||||
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.
|
||||
gfx::Rect gridBounds = m_site.gridBounds();
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
// keep a rectangle with negative width or height (to know that
|
||||
// it was flipped).
|
||||
@ -504,6 +512,11 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
case RotateNEHandle:
|
||||
case RotateSWHandle:
|
||||
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)(+pos.x - abs_pivot.x)) -
|
||||
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 SkewWHandle:
|
||||
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
|
||||
// ------>
|
||||
//
|
||||
@ -796,21 +815,22 @@ void PixelsMovement::stampExtraCelImage()
|
||||
TiledMode::NONE, m_tx,
|
||||
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
|
||||
gfx::Rect modifiedRect(
|
||||
cel->x(),
|
||||
cel->y(),
|
||||
image->width(),
|
||||
image->height());
|
||||
expand.validateDestCanvas(
|
||||
gfx::Region(gfx::Rect(cel->position(), canvasImageSize)));
|
||||
|
||||
gfx::Region modifiedRegion(modifiedRect);
|
||||
expand.validateDestCanvas(modifiedRegion);
|
||||
|
||||
expand.getDestCanvas()->copy(
|
||||
image, gfx::Clip(
|
||||
cel->x()-expand.getCel()->x(),
|
||||
cel->y()-expand.getCel()->y(),
|
||||
image->bounds()));
|
||||
expand.getDestCanvas()->copy(image, gfx::Clip(dstPt, image->bounds()));
|
||||
|
||||
expand.commit();
|
||||
}
|
||||
@ -953,13 +973,23 @@ void PixelsMovement::redrawExtraImage(Transformation* transformation)
|
||||
m_extraCel.reset(new ExtraCel);
|
||||
|
||||
gfx::Rect bounds = transformation->transformedBounds();
|
||||
|
||||
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_site.tilemapMode(),
|
||||
m_document->sprite(),
|
||||
bounds,
|
||||
(m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size():
|
||||
bounds.size()),
|
||||
extraCelSize,
|
||||
m_site.frame(),
|
||||
opacity);
|
||||
m_extraCel->setType(render::ExtraType::PATCH);
|
||||
@ -995,36 +1025,47 @@ void PixelsMovement::drawImage(
|
||||
auto corners = transformation.transformedCorners();
|
||||
gfx::Rect bounds = corners.bounds();
|
||||
|
||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
||||
dst->clear(dst->maskColor());
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
dst->setMaskColor(doc::tile_i_notile);
|
||||
dst->clear(dst->maskColor());
|
||||
|
||||
if (renderOriginalLayer) {
|
||||
render::Render render;
|
||||
render.renderLayer(
|
||||
dst, m_site.layer(), m_site.frame(),
|
||||
gfx::Clip(bounds.x-pt.x, bounds.y-pt.y, bounds),
|
||||
BlendMode::SRC);
|
||||
drawTransformedTilemap(
|
||||
transformation,
|
||||
dst, m_originalImage.get(),
|
||||
m_initialMask.get(), corners, pt);
|
||||
}
|
||||
else {
|
||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
||||
dst->clear(dst->maskColor());
|
||||
|
||||
color_t maskColor = m_maskColor;
|
||||
if (renderOriginalLayer) {
|
||||
render::Render render;
|
||||
render.renderLayer(
|
||||
dst, m_site.layer(), m_site.frame(),
|
||||
gfx::Clip(bounds.x-pt.x, bounds.y-pt.y, bounds),
|
||||
BlendMode::SRC);
|
||||
}
|
||||
|
||||
// In case that Opaque option is enabled, or if we are drawing the
|
||||
// image for the clipboard (renderOriginalLayer is false), we use a
|
||||
// dummy mask color to call drawParallelogram(). In this way all
|
||||
// pixels will be opaqued (all colors are copied)
|
||||
if (m_opaque ||
|
||||
!renderOriginalLayer) {
|
||||
if (m_originalImage->pixelFormat() == IMAGE_INDEXED)
|
||||
maskColor = -1;
|
||||
else
|
||||
maskColor = 0;
|
||||
color_t maskColor = m_maskColor;
|
||||
|
||||
// In case that Opaque option is enabled, or if we are drawing the
|
||||
// image for the clipboard (renderOriginalLayer is false), we use a
|
||||
// dummy mask color to call drawParallelogram(). In this way all
|
||||
// pixels will be opaqued (all colors are copied)
|
||||
if (m_opaque ||
|
||||
!renderOriginalLayer) {
|
||||
if (m_originalImage->pixelFormat() == IMAGE_INDEXED)
|
||||
maskColor = -1;
|
||||
else
|
||||
maskColor = 0;
|
||||
}
|
||||
m_originalImage->setMaskColor(maskColor);
|
||||
|
||||
drawParallelogram(
|
||||
transformation,
|
||||
dst, m_originalImage.get(),
|
||||
m_initialMask.get(), corners, pt);
|
||||
}
|
||||
m_originalImage->setMaskColor(maskColor);
|
||||
|
||||
drawParallelogram(
|
||||
transformation,
|
||||
dst, m_originalImage.get(),
|
||||
m_initialMask.get(), corners, pt);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
set_pivot_from_preferences(m_currentData);
|
||||
|
@ -148,6 +148,11 @@ namespace app {
|
||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||
const Transformation::Corners& corners,
|
||||
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 hideDocumentMask();
|
||||
|
||||
|
@ -753,12 +753,22 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT
|
||||
editor->brushPreview().hide();
|
||||
|
||||
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
|
||||
std::unique_ptr<Image> tmpImage(new_image_from_mask(editor->getSite(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
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()));
|
||||
}
|
||||
|
||||
PixelsMovementPtr pixelsMovement(
|
||||
new PixelsMovement(UIContext::instance(),
|
||||
editor->getSite(),
|
||||
site,
|
||||
tmpImage.get(),
|
||||
document->mask(),
|
||||
"Transformation"));
|
||||
|
@ -209,49 +209,85 @@ bool Clipboard::getClipboardText(std::string& text)
|
||||
void Clipboard::setData(Image* image,
|
||||
Mask* mask,
|
||||
Palette* palette,
|
||||
bool set_system_clipboard,
|
||||
Tileset* tileset,
|
||||
bool set_native_clipboard,
|
||||
bool image_source_is_transparent)
|
||||
{
|
||||
const bool isTilemap = (image && image->isTilemap());
|
||||
|
||||
m_data->clear();
|
||||
m_data->palette.reset(palette);
|
||||
m_data->image.reset(image);
|
||||
m_data->tileset.reset(tileset);
|
||||
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_system_clipboard) {
|
||||
color_t oldMask;
|
||||
if (image) {
|
||||
oldMask = image->maskColor();
|
||||
if (!image_source_is_transparent)
|
||||
image->setMaskColor(-1);
|
||||
if (set_native_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;
|
||||
if (image) {
|
||||
oldMask = image->maskColor();
|
||||
if (!image_source_is_transparent)
|
||||
image->setMaskColor(-1);
|
||||
}
|
||||
|
||||
if (use_native_clipboard())
|
||||
setNativeBitmap(image, mask, palette);
|
||||
if (use_native_clipboard())
|
||||
setNativeBitmap(image, mask, palette);
|
||||
|
||||
if (image && !image_source_is_transparent)
|
||||
image->setMaskColor(oldMask);
|
||||
if (image && !image_source_is_transparent)
|
||||
image->setMaskColor(oldMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
||||
{
|
||||
const Doc* document = static_cast<const Doc*>(site.document());
|
||||
ASSERT(document);
|
||||
ASSERT(site.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,
|
||||
Preferences::instance().experimental.newBlend(),
|
||||
merged);
|
||||
if (!image)
|
||||
return false;
|
||||
|
||||
const Palette* pal = document->sprite()->palette(site.frame());
|
||||
setData(
|
||||
image,
|
||||
(mask ? new Mask(*mask): nullptr),
|
||||
(pal ? new Palette(*pal): nullptr),
|
||||
true,
|
||||
nullptr,
|
||||
true, // set native clipboard
|
||||
site.layer() && !site.layer()->isBackground());
|
||||
|
||||
return true;
|
||||
@ -260,10 +296,12 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
||||
ClipboardFormat Clipboard::format() const
|
||||
{
|
||||
// Check if the native clipboard has an image
|
||||
if (use_native_clipboard() && hasNativeBitmap())
|
||||
if (use_native_clipboard() && hasNativeBitmap()) {
|
||||
return ClipboardFormat::Image;
|
||||
else
|
||||
}
|
||||
else {
|
||||
return m_data->format();
|
||||
}
|
||||
}
|
||||
|
||||
void Clipboard::getDocumentRangeInfo(Doc** document, DocRange* range)
|
||||
@ -375,16 +413,20 @@ void Clipboard::copyRange(const ContextReader& reader, const DocRange& range)
|
||||
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(
|
||||
Image::createCopy(image),
|
||||
(mask ? new Mask(*mask): nullptr),
|
||||
(pal ? new Palette(*pal): nullptr),
|
||||
nullptr,
|
||||
true, false);
|
||||
}
|
||||
|
||||
void Clipboard::copyPalette(const Palette* palette, const doc::PalettePicks& picks)
|
||||
void Clipboard::copyPalette(const Palette* palette,
|
||||
const PalettePicks& picks)
|
||||
{
|
||||
if (!picks.picks())
|
||||
return; // Do nothing case
|
||||
@ -392,11 +434,14 @@ void Clipboard::copyPalette(const Palette* palette, const doc::PalettePicks& pic
|
||||
setData(nullptr,
|
||||
nullptr,
|
||||
new Palette(*palette),
|
||||
true, false);
|
||||
nullptr,
|
||||
true, // set native clipboard
|
||||
false);
|
||||
m_data->picks = picks;
|
||||
}
|
||||
|
||||
void Clipboard::paste(Context* ctx, const bool interactive)
|
||||
void Clipboard::paste(Context* ctx,
|
||||
const bool interactive)
|
||||
{
|
||||
Site site = ctx->activeSite();
|
||||
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')
|
||||
ImageRef src_image;
|
||||
if (m_data->image->pixelFormat() == dstSpr->pixelFormat() &&
|
||||
// Indexed images can be copied directly only if both images
|
||||
// have the same palette.
|
||||
(m_data->image->pixelFormat() != IMAGE_INDEXED ||
|
||||
m_data->palette->countDiff(dst_palette, NULL, NULL) == 0)) {
|
||||
if (// Copy image of the same pixel format
|
||||
(m_data->image->pixelFormat() == dstSpr->pixelFormat() &&
|
||||
// Indexed images can be copied directly only if both images
|
||||
// have the same palette.
|
||||
(m_data->image->pixelFormat() != IMAGE_INDEXED ||
|
||||
m_data->palette->countDiff(dst_palette, NULL, NULL) == 0))) {
|
||||
src_image = m_data->image;
|
||||
}
|
||||
else {
|
||||
@ -483,6 +529,21 @@ void Clipboard::paste(Context* ctx, const bool interactive)
|
||||
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: {
|
||||
DocRange srcRange = m_data->range.range();
|
||||
Doc* srcDoc = m_data->range.document();
|
||||
@ -683,14 +744,18 @@ ImageRef Clipboard::getImage(Palette* palette)
|
||||
Image* native_image = nullptr;
|
||||
Mask* native_mask = nullptr;
|
||||
Palette* native_palette = nullptr;
|
||||
Tileset* native_tileset = nullptr;
|
||||
getNativeBitmap(&native_image,
|
||||
&native_mask,
|
||||
&native_palette);
|
||||
if (native_image)
|
||||
&native_palette,
|
||||
&native_tileset);
|
||||
if (native_image) {
|
||||
setData(native_image,
|
||||
native_mask,
|
||||
native_palette,
|
||||
native_tileset,
|
||||
false, false);
|
||||
}
|
||||
}
|
||||
if (m_data->palette && palette)
|
||||
m_data->palette->copyColorsTo(palette);
|
||||
|
@ -23,6 +23,7 @@ namespace doc {
|
||||
class Mask;
|
||||
class Palette;
|
||||
class PalettePicks;
|
||||
class Tileset;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
@ -88,7 +89,8 @@ namespace app {
|
||||
void setData(doc::Image* image,
|
||||
doc::Mask* mask,
|
||||
doc::Palette* palette,
|
||||
bool set_system_clipboard,
|
||||
doc::Tileset* tileset,
|
||||
bool set_native_clipboard,
|
||||
bool image_source_is_transparent);
|
||||
bool copyFromDocument(const Site& site, bool merged = false);
|
||||
|
||||
@ -97,10 +99,12 @@ namespace app {
|
||||
bool hasNativeBitmap() const;
|
||||
bool setNativeBitmap(const doc::Image* image,
|
||||
const doc::Mask* mask,
|
||||
const doc::Palette* palette);
|
||||
const doc::Palette* palette,
|
||||
const doc::Tileset* tileset = nullptr);
|
||||
bool getNativeBitmap(doc::Image** image,
|
||||
doc::Mask** mask,
|
||||
doc::Palette** palette);
|
||||
doc::Palette** palette,
|
||||
doc::Tileset** tileset);
|
||||
bool getNativeBitmapSize(gfx::Size* size);
|
||||
|
||||
struct Data;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "doc/image_io.h"
|
||||
#include "doc/mask_io.h"
|
||||
#include "doc/palette_io.h"
|
||||
#include "doc/tileset_io.h"
|
||||
#include "gfx/size.h"
|
||||
#include "os/display.h"
|
||||
#include "os/system.h"
|
||||
@ -66,7 +67,8 @@ bool Clipboard::hasNativeBitmap() const
|
||||
|
||||
bool Clipboard::setNativeBitmap(const doc::Image* image,
|
||||
const doc::Mask* mask,
|
||||
const doc::Palette* palette)
|
||||
const doc::Palette* palette,
|
||||
const doc::Tileset* tileset)
|
||||
{
|
||||
clip::lock l(native_display_handle());
|
||||
if (!l.locked())
|
||||
@ -83,10 +85,12 @@ bool Clipboard::setNativeBitmap(const doc::Image* image,
|
||||
write32(os,
|
||||
(image ? 1: 0) |
|
||||
(mask ? 2: 0) |
|
||||
(palette ? 4: 0));
|
||||
(palette ? 4: 0) |
|
||||
(tileset ? 8: 0));
|
||||
if (image) doc::write_image(os, image);
|
||||
if (mask) doc::write_mask(os, mask);
|
||||
if (palette) doc::write_palette(os, palette);
|
||||
if (tileset) doc::write_tileset(os, tileset);
|
||||
|
||||
if (os.good()) {
|
||||
size_t size = (size_t)os.tellp();
|
||||
@ -165,11 +169,13 @@ bool Clipboard::setNativeBitmap(const doc::Image* image,
|
||||
|
||||
bool Clipboard::getNativeBitmap(doc::Image** image,
|
||||
doc::Mask** mask,
|
||||
doc::Palette** palette)
|
||||
doc::Palette** palette,
|
||||
doc::Tileset** tileset)
|
||||
{
|
||||
*image = nullptr;
|
||||
*mask = nullptr;
|
||||
*palette = nullptr;
|
||||
*tileset = nullptr;
|
||||
|
||||
clip::lock l(native_display_handle());
|
||||
if (!l.locked())
|
||||
@ -189,6 +195,7 @@ bool Clipboard::getNativeBitmap(doc::Image** image,
|
||||
if (bits & 1) *image = doc::read_image(is, false);
|
||||
if (bits & 2) *mask = doc::read_mask(is);
|
||||
if (bits & 4) *palette = doc::read_palette(is);
|
||||
if (bits & 8) *tileset = doc::read_tileset(is, nullptr);
|
||||
if (image)
|
||||
return true;
|
||||
}
|
||||
|
@ -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
|
||||
@ -116,4 +116,55 @@ doc::Image* new_image_from_mask(const Site& site,
|
||||
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
|
||||
|
@ -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
|
||||
@ -23,6 +23,8 @@ namespace app {
|
||||
const doc::Mask* mask,
|
||||
const bool newBlend,
|
||||
bool merged = false);
|
||||
doc::Image* new_tilemap_from_mask(const Site& site,
|
||||
const doc::Mask* mask);
|
||||
|
||||
} // namespace app
|
||||
|
||||
|
@ -27,7 +27,11 @@ Tileset::Tileset(Sprite* sprite,
|
||||
, m_grid(grid)
|
||||
, 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) {
|
||||
m_tiles[ti] = makeEmptyTile();
|
||||
m_hash[m_tiles[ti]] = ti;
|
||||
|
Loading…
x
Reference in New Issue
Block a user