mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-03 13:32:27 +00:00
Add support to copy/cut/paste/transform tilemaps
This commit is contained in:
parent
edebb57f66
commit
120fb4dca4
@ -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 {
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
ContextFlags::ActiveLayerIsVisible |
|
||||||
&& ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
ContextFlags::ActiveLayerIsEditable |
|
||||||
ContextFlags::ActiveLayerIsVisible |
|
ContextFlags::ActiveLayerIsImage)
|
||||||
ContextFlags::ActiveLayerIsEditable |
|
&& !ctx->checkFlags(ContextFlags::ActiveLayerIsReference)) {
|
||||||
ContextFlags::ActiveLayerIsImage)
|
auto format = ctx->clipboard()->format();
|
||||||
&& !ctx->checkFlags(ContextFlags::ActiveLayerIsReference));
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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)));
|
||||||
|
@ -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,36 +1025,47 @@ void PixelsMovement::drawImage(
|
|||||||
auto corners = transformation.transformedCorners();
|
auto corners = transformation.transformedCorners();
|
||||||
gfx::Rect bounds = corners.bounds();
|
gfx::Rect bounds = corners.bounds();
|
||||||
|
|
||||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||||
dst->clear(dst->maskColor());
|
dst->setMaskColor(doc::tile_i_notile);
|
||||||
|
dst->clear(dst->maskColor());
|
||||||
|
|
||||||
if (renderOriginalLayer) {
|
drawTransformedTilemap(
|
||||||
render::Render render;
|
transformation,
|
||||||
render.renderLayer(
|
dst, m_originalImage.get(),
|
||||||
dst, m_site.layer(), m_site.frame(),
|
m_initialMask.get(), corners, pt);
|
||||||
gfx::Clip(bounds.x-pt.x, bounds.y-pt.y, bounds),
|
|
||||||
BlendMode::SRC);
|
|
||||||
}
|
}
|
||||||
|
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
|
color_t maskColor = m_maskColor;
|
||||||
// image for the clipboard (renderOriginalLayer is false), we use a
|
|
||||||
// dummy mask color to call drawParallelogram(). In this way all
|
// In case that Opaque option is enabled, or if we are drawing the
|
||||||
// pixels will be opaqued (all colors are copied)
|
// image for the clipboard (renderOriginalLayer is false), we use a
|
||||||
if (m_opaque ||
|
// dummy mask color to call drawParallelogram(). In this way all
|
||||||
!renderOriginalLayer) {
|
// pixels will be opaqued (all colors are copied)
|
||||||
if (m_originalImage->pixelFormat() == IMAGE_INDEXED)
|
if (m_opaque ||
|
||||||
maskColor = -1;
|
!renderOriginalLayer) {
|
||||||
else
|
if (m_originalImage->pixelFormat() == IMAGE_INDEXED)
|
||||||
maskColor = 0;
|
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)
|
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);
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
Preferences::instance().experimental.newBlend()));
|
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(
|
PixelsMovementPtr pixelsMovement(
|
||||||
new PixelsMovement(UIContext::instance(),
|
new PixelsMovement(UIContext::instance(),
|
||||||
editor->getSite(),
|
site,
|
||||||
tmpImage.get(),
|
tmpImage.get(),
|
||||||
document->mask(),
|
document->mask(),
|
||||||
"Transformation"));
|
"Transformation"));
|
||||||
|
@ -209,49 +209,85 @@ 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
|
||||||
color_t oldMask;
|
if (isTilemap) {
|
||||||
if (image) {
|
ASSERT(tileset);
|
||||||
oldMask = image->maskColor();
|
setNativeBitmap(image, mask, palette, tileset);
|
||||||
if (!image_source_is_transparent)
|
|
||||||
image->setMaskColor(-1);
|
|
||||||
}
|
}
|
||||||
|
// 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())
|
if (use_native_clipboard())
|
||||||
setNativeBitmap(image, mask, palette);
|
setNativeBitmap(image, mask, palette);
|
||||||
|
|
||||||
if (image && !image_source_is_transparent)
|
if (image && !image_source_is_transparent)
|
||||||
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,10 +296,12 @@ 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
|
||||||
// Indexed images can be copied directly only if both images
|
(m_data->image->pixelFormat() == dstSpr->pixelFormat() &&
|
||||||
// have the same palette.
|
// Indexed images can be copied directly only if both images
|
||||||
(m_data->image->pixelFormat() != IMAGE_INDEXED ||
|
// have the same palette.
|
||||||
m_data->palette->countDiff(dst_palette, NULL, NULL) == 0)) {
|
(m_data->image->pixelFormat() != IMAGE_INDEXED ||
|
||||||
|
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,14 +744,18 @@ 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);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user