mirror of https://github.com/aseprite/aseprite.git
Try specific logic to transform slices contents
Instead of trying to reuse the PixelsMovement class we create new commands and logic to handle the slices content transformation
This commit is contained in:
parent
159b5efa29
commit
22a4344b14
|
@ -506,6 +506,7 @@ target_sources(app-lib PRIVATE
|
|||
cmd/clear_image.cpp
|
||||
cmd/clear_mask.cpp
|
||||
cmd/clear_rect.cpp
|
||||
cmd/clear_slices.cpp
|
||||
cmd/configure_background.cpp
|
||||
cmd/convert_color_profile.cpp
|
||||
cmd/copy_cel.cpp
|
||||
|
@ -576,6 +577,7 @@ target_sources(app-lib PRIVATE
|
|||
cmd/set_user_data_properties.cpp
|
||||
cmd/set_user_data_property.cpp
|
||||
cmd/shift_masked_cel.cpp
|
||||
cmd/stamp_in_cel.cpp
|
||||
cmd/trim_cel.cpp
|
||||
cmd/unlink_cel.cpp
|
||||
cmd/with_cel.cpp
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/cmd/clear_slices.h"
|
||||
|
||||
#include "app/doc.h"
|
||||
#include "doc/algorithm/fill_selection.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_list.h"
|
||||
#include "doc/primitives.h"
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
ClearSlices::ClearSlices(const LayerList& layers, frame_t frame, const std::vector<SliceKey>& slicesKeys)
|
||||
{
|
||||
//Doc* doc = static_cast<Doc*>(cel->document());
|
||||
|
||||
if (layers.empty())
|
||||
return;
|
||||
|
||||
Doc* doc = static_cast<Doc*>((*layers.begin())->sprite()->document());
|
||||
|
||||
for (const auto& sk : slicesKeys) {
|
||||
m_mask.add(sk.bounds());
|
||||
}
|
||||
const gfx::Rect maskBounds = m_mask.bounds();
|
||||
|
||||
// gfx::Rect maskBounds;
|
||||
// if (image->pixelFormat() == IMAGE_TILEMAP) {
|
||||
// auto grid = cel->grid();
|
||||
// imageBounds = gfx::Rect(grid.canvasToTile(cel->position()),
|
||||
// cel->image()->size());
|
||||
// maskBounds = grid.canvasToTile(mask->bounds());
|
||||
// m_bgcolor = doc::notile; // TODO configurable empty tile
|
||||
// }
|
||||
// else {
|
||||
for (auto* layer : layers) {
|
||||
Cel* cel = layer->cel(frame);
|
||||
const gfx::Rect imageBounds = cel->bounds();
|
||||
gfx::Rect cropBounds = (imageBounds & maskBounds);
|
||||
if (cropBounds.isEmpty())
|
||||
continue;
|
||||
|
||||
cropBounds.offset(-imageBounds.origin());
|
||||
|
||||
Image* image = cel->image();
|
||||
assert(image);
|
||||
if (!image)
|
||||
continue;
|
||||
|
||||
SlicesContent sc;
|
||||
sc.cel = cel;
|
||||
sc.cropPos = cropBounds.origin();
|
||||
sc.bgcolor = doc->bgColor(layer);
|
||||
sc.copy.reset(crop_image(image, cropBounds, sc.bgcolor));
|
||||
|
||||
m_slicesContents.push_back(sc);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearSlices::onExecute()
|
||||
{
|
||||
m_seq.execute(context());
|
||||
clear();
|
||||
}
|
||||
|
||||
void ClearSlices::onUndo()
|
||||
{
|
||||
restore();
|
||||
m_seq.undo();
|
||||
}
|
||||
|
||||
void ClearSlices::onRedo()
|
||||
{
|
||||
m_seq.redo();
|
||||
clear();
|
||||
}
|
||||
|
||||
void ClearSlices::clear()
|
||||
{
|
||||
for (auto& sc : m_slicesContents) {
|
||||
if (!sc.copy)
|
||||
continue;
|
||||
|
||||
Grid grid = sc.cel->grid();
|
||||
doc::algorithm::fill_selection(
|
||||
sc.cel->image(),
|
||||
sc.cel->bounds(),
|
||||
&m_mask,
|
||||
sc.bgcolor,
|
||||
(sc.cel->image()->isTilemap() ? &grid: nullptr));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ClearSlices::restore()
|
||||
{
|
||||
for (auto& sc : m_slicesContents) {
|
||||
if (!sc.copy)
|
||||
continue;
|
||||
|
||||
copy_image(sc.cel->image(),
|
||||
sc.copy.get(),
|
||||
sc.cropPos.x,
|
||||
sc.cropPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
|
@ -0,0 +1,65 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_CMD_CLEAR_SLICES_H_INCLUDED
|
||||
#define APP_CMD_CLEAR_SLICES_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/layer_list.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/slice.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
using namespace doc;
|
||||
|
||||
class ClearSlices : public Cmd {
|
||||
public:
|
||||
ClearSlices(const LayerList& layers, frame_t frame, const std::vector<SliceKey>& slicesKeys);
|
||||
|
||||
protected:
|
||||
void onExecute() override;
|
||||
void onUndo() override;
|
||||
void onRedo() override;
|
||||
size_t onMemSize() const override {
|
||||
size_t sliceContentsSize = 0;
|
||||
for (const auto& sc : m_slicesContents) {
|
||||
sliceContentsSize += sc.memSize();
|
||||
}
|
||||
return sizeof(*this) + m_seq.memSize() + sliceContentsSize;
|
||||
}
|
||||
|
||||
private:
|
||||
struct SlicesContent {
|
||||
Cel* cel = nullptr;
|
||||
// Image having a copy of the content of each selected slice.
|
||||
ImageRef copy = nullptr;
|
||||
gfx::Point cropPos;
|
||||
color_t bgcolor;
|
||||
size_t memSize() const {
|
||||
return sizeof(*this) + (copy ? copy->getMemSize(): 0);
|
||||
}
|
||||
};
|
||||
|
||||
void clear();
|
||||
void restore();
|
||||
|
||||
Mask m_mask;
|
||||
CmdSequence m_seq;
|
||||
// Slices content for each selected layer's cel
|
||||
std::vector<SlicesContent> m_slicesContents;
|
||||
};
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
||||
|
||||
#endif
|
|
@ -0,0 +1,170 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/cmd/stamp_in_cel.h"
|
||||
|
||||
#include "app/cmd/copy_region.h"
|
||||
#include "app/cmd/crop_cel.h"
|
||||
#include "app/cmd/trim_cel.h"
|
||||
#include "app/cmd/with_image.h"
|
||||
#include "app/util/buffer_region.h"
|
||||
#include "base/debug.h"
|
||||
#include "doc/algorithm/rotate.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/mask.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/region.h"
|
||||
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
class StampImage : public Cmd
|
||||
, public WithImage {
|
||||
|
||||
public:
|
||||
StampImage(doc::Image* dst,
|
||||
const doc::ImageRef& src,
|
||||
const doc::MaskRef& mask,
|
||||
const gfx::Rect& stampBounds) : WithImage(dst)
|
||||
, m_src(src)
|
||||
, m_mask(mask)
|
||||
, m_stampBounds(stampBounds) {
|
||||
ASSERT(!stampBounds.isEmpty());
|
||||
ASSERT(src);
|
||||
ASSERT(mask);
|
||||
|
||||
|
||||
gfx::Rect rc = stampBounds;
|
||||
gfx::Clip clip(
|
||||
rc.x, rc.y,
|
||||
0, 0, rc.w, rc.h);
|
||||
if (clip.clip(
|
||||
dst->width(), dst->height(),
|
||||
rc.w, rc.h)) {
|
||||
// Create region to save/swap later
|
||||
m_region = gfx::Region(stampBounds);
|
||||
m_region &= gfx::Region(clip.dstBounds());
|
||||
}
|
||||
|
||||
save_image_region_in_buffer(m_region, dst, gfx::Point(0,0), m_buffer);
|
||||
}
|
||||
|
||||
protected:
|
||||
void onExecute() override {
|
||||
ASSERT(image());
|
||||
|
||||
gfx::Rect rc = m_stampBounds;
|
||||
doc::algorithm::parallelogram(
|
||||
image(), m_src.get(), m_mask->bitmap(),
|
||||
rc.x , rc.y,
|
||||
rc.x+rc.w , rc.y,
|
||||
rc.x+rc.w , rc.y+rc.h,
|
||||
rc.x , rc.y+rc.h
|
||||
);
|
||||
|
||||
image()->incrementVersion();
|
||||
}
|
||||
|
||||
void onUndo() override {
|
||||
swap();
|
||||
}
|
||||
|
||||
void onRedo() override {
|
||||
swap();
|
||||
}
|
||||
|
||||
void swap()
|
||||
{
|
||||
Image* image = this->image();
|
||||
ASSERT(image);
|
||||
|
||||
swap_image_region_with_buffer(m_region, image, m_buffer);
|
||||
image->incrementVersion();
|
||||
|
||||
//rehash();
|
||||
}
|
||||
|
||||
size_t onMemSize() const override {
|
||||
return sizeof(*this) + m_buffer.size();
|
||||
}
|
||||
|
||||
private:
|
||||
const doc::ImageRef& m_src = nullptr;
|
||||
const doc::MaskRef& m_mask = nullptr;
|
||||
const gfx::Rect m_stampBounds;
|
||||
gfx::Region m_region;
|
||||
base::buffer m_buffer;
|
||||
};
|
||||
|
||||
|
||||
StampInCel::StampInCel(doc::Cel* dstCel,
|
||||
const doc::ImageRef& image,
|
||||
const doc::MaskRef& mask,
|
||||
const gfx::Rect& stampBounds)
|
||||
: WithCel(dstCel)
|
||||
, m_image(image)
|
||||
, m_mask(mask)
|
||||
, m_stampBounds(stampBounds)
|
||||
{
|
||||
ASSERT(image.get());
|
||||
ASSERT(mask.get());
|
||||
ASSERT(!stampBounds.isEmpty());
|
||||
}
|
||||
|
||||
void StampInCel::onExecute()
|
||||
{
|
||||
Cel* cel = this->cel();
|
||||
|
||||
gfx::Rect newBounds;
|
||||
gfx::Region regionInTiles;
|
||||
doc::Grid grid;
|
||||
if (cel->image()->pixelFormat() == IMAGE_TILEMAP) {
|
||||
//newBounds = cel->bounds() | m_region.bounds();
|
||||
//auto tileset = static_cast<LayerTilemap*>(cel->layer())->tileset();
|
||||
//grid = tileset->grid();
|
||||
//grid.origin(m_pos);
|
||||
//regionInTiles = grid.canvasToTile(m_region);
|
||||
}
|
||||
else {
|
||||
newBounds = cel->bounds() | m_stampBounds;
|
||||
}
|
||||
|
||||
if (cel->bounds() != newBounds)
|
||||
executeAndAdd(new CropCel(cel, newBounds));
|
||||
|
||||
if (cel->image()->pixelFormat() == IMAGE_TILEMAP) {
|
||||
/*
|
||||
executeAndAdd(
|
||||
new CopyRegion(cel->image(),
|
||||
m_patch,
|
||||
regionInTiles,
|
||||
-grid.canvasToTile(cel->position())));
|
||||
*/
|
||||
}
|
||||
else {
|
||||
executeAndAdd(
|
||||
new StampImage(cel->image(),
|
||||
m_image,
|
||||
m_mask,
|
||||
m_stampBounds.offset(-cel->position())));
|
||||
|
||||
}
|
||||
|
||||
executeAndAdd(new TrimCel(cel));
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
|
@ -0,0 +1,46 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_CMD_STAMP_IN_CEL_H_INCLUDED
|
||||
#define APP_CMD_STAMP_IN_CEL_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd/with_cel.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "doc/mask.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
class Image;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
class StampInCel : public CmdSequence
|
||||
, public WithCel {
|
||||
public:
|
||||
// Stamps image into dstCel using the specified mask. The image is
|
||||
// positioned and scaled according to the stampBounds rectangle.
|
||||
StampInCel(doc::Cel* dstCel,
|
||||
const doc::ImageRef& image,
|
||||
const doc::MaskRef& mask,
|
||||
const gfx::Rect& stampBounds);
|
||||
|
||||
protected:
|
||||
void onExecute() override;
|
||||
|
||||
private:
|
||||
const doc::ImageRef m_image;
|
||||
const doc::MaskRef m_mask;
|
||||
gfx::Rect m_stampBounds;
|
||||
};
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
||||
|
||||
#endif
|
|
@ -9,23 +9,37 @@
|
|||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/ui/editor/moving_slice_state.h"
|
||||
|
||||
#include "app/cmd/stamp_in_cel.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "app/cmd_transaction.h"
|
||||
#include "app/cmd/set_slice_key.h"
|
||||
#include "app/cmd/clear_slices.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/editor/moving_slice_state.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/algorithm/rotate.h"
|
||||
#include "doc/blend_internals.h"
|
||||
#include "doc/color.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/algorithm/fill_selection.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/rect.h"
|
||||
#include "gfx/region_skia.h"
|
||||
#include "ui/message.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
|
||||
|
@ -38,6 +52,10 @@ MovingSliceState::MovingSliceState(Editor* editor,
|
|||
: m_frame(editor->frame())
|
||||
, m_hit(hit)
|
||||
, m_items(std::max<std::size_t>(1, selectedSlices.size()))
|
||||
//, m_reader(UIContext::instance())
|
||||
, m_tx(Tx::DontLockDoc, UIContext::instance(),
|
||||
UIContext::instance()->activeDocument(),
|
||||
(editor->slicesTransforms() ? "Slices Transformation" : "Slice Movement"))
|
||||
{
|
||||
m_mouseStart = editor->screenToEditor(msg->position());
|
||||
|
||||
|
@ -52,84 +70,118 @@ MovingSliceState::MovingSliceState(Editor* editor,
|
|||
}
|
||||
}
|
||||
|
||||
editor->getSite(&m_site);
|
||||
|
||||
m_selectedLayers = m_site.range().selectedLayers().toAllLayersList();
|
||||
if (m_selectedLayers.empty()) {
|
||||
m_selectedLayers.push_back(m_site.layer());
|
||||
}
|
||||
|
||||
editor->captureMouse();
|
||||
}
|
||||
|
||||
void MovingSliceState::onEnterState(Editor* editor)
|
||||
{
|
||||
if (editor->slicesTransforms() && !m_items.empty()) {
|
||||
auto site = Site();
|
||||
editor->getSite(&site);
|
||||
ImageRef tmpImage;
|
||||
for (auto& item : m_items) {
|
||||
item.imgs.reserve(m_selectedLayers.size());
|
||||
item.masks.reserve(m_selectedLayers.size());
|
||||
int i = 0;
|
||||
for (const auto* layer : m_selectedLayers) {
|
||||
item.masks.push_back(std::make_shared<Mask>());
|
||||
item.imgs.push_back(ImageRef());
|
||||
item.masks[i]->add(item.newKey.bounds());
|
||||
item.masks[i]->freeze();
|
||||
if (layer &&
|
||||
layer->isTilemap() &&
|
||||
m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
//item.img.reset(new_tilemap_from_mask(m_site, item.mask.get()));
|
||||
}
|
||||
else {
|
||||
item.imgs[i].reset(new_image_from_mask(
|
||||
*layer,
|
||||
m_frame,
|
||||
item.masks[i].get(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
item.masks[i]->fromImage(item.imgs[i].get(), item.masks[i]->origin());
|
||||
}
|
||||
|
||||
Mask newMask;
|
||||
for (const auto& item : m_items) {
|
||||
newMask.add(item.newKey.bounds());
|
||||
}
|
||||
|
||||
if (site.layer() &&
|
||||
site.layer()->isTilemap() &&
|
||||
site.tilemapMode() == TilemapMode::Tiles) {
|
||||
tmpImage.reset(new_tilemap_from_mask(site, &newMask));
|
||||
}
|
||||
else {
|
||||
tmpImage.reset(new_image_from_mask(site, &newMask,
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
}
|
||||
|
||||
ASSERT(tmpImage);
|
||||
if (!tmpImage) {
|
||||
editor->releaseMouse();
|
||||
// We've received a bug report with this case, we're not sure
|
||||
// yet how to reproduce it. Probably new_tilemap_from_mask() can
|
||||
// return nullptr (e.g. when site.cel() is nullptr?)
|
||||
editor->backToPreviousState();
|
||||
return;
|
||||
// If there is just one layer selected, we can use the same image as the
|
||||
// mergedImg.
|
||||
if (m_selectedLayers.size() == 1) {
|
||||
item.mergedImg = item.imgs[0];
|
||||
item.mergedMask = item.masks[0];
|
||||
}
|
||||
else {
|
||||
if (i == 0) {
|
||||
const gfx::Rect& srcBounds = item.imgs[i]->bounds();
|
||||
item.mergedImg.reset(Image::create(layer->sprite()->pixelFormat(), srcBounds.w, srcBounds.h));
|
||||
item.mergedImg->clear(layer->sprite()->transparentColor());
|
||||
item.mergedMask = std::make_shared<Mask>(*item.masks[i].get());
|
||||
item.mergedMask->freeze();
|
||||
}
|
||||
else {
|
||||
item.mergedMask->add(*item.masks[i].get());
|
||||
}
|
||||
copy_masked_zones(item.mergedImg.get(),
|
||||
item.imgs[i].get(),
|
||||
item.masks[i].get(),
|
||||
item.masks[i]->bounds().x,
|
||||
item.masks[i]->bounds().y);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear brush preview, as the extra cel will be replaced with the
|
||||
// transformed image.
|
||||
editor->brushPreview().hide();
|
||||
|
||||
m_pixelsMovement = PixelsMovementPtr(
|
||||
new PixelsMovement(UIContext::instance(),
|
||||
site,
|
||||
tmpImage.get(),
|
||||
&newMask,
|
||||
"Transformation"));
|
||||
clearSlices();
|
||||
|
||||
const gfx::Rect totalBounds = selectedSlicesBounds();
|
||||
if (m_hit.border() == (CENTER | MIDDLE)) {
|
||||
m_pixelsMovement->catchImage(gfx::PointF(totalBounds.origin()),
|
||||
HandleType::MovePixelsHandle);
|
||||
if (editor->slicesTransforms()) {
|
||||
drawExtraCel(editor);
|
||||
|
||||
// Redraw the editor.
|
||||
editor->invalidate();
|
||||
}
|
||||
m_pixelsMovement->cutMask();
|
||||
}
|
||||
}
|
||||
|
||||
EditorState::LeaveAction MovingSliceState::onLeaveState(Editor *editor, EditorState *newState)
|
||||
{
|
||||
editor->document()->resetTransformation();
|
||||
//editor->document()->resetTransformation();
|
||||
|
||||
return StandbyState::onLeaveState(editor, newState);
|
||||
}
|
||||
|
||||
bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
|
||||
{
|
||||
if (m_pixelsMovement) {
|
||||
m_pixelsMovement->dropImage();
|
||||
m_pixelsMovement.reset();
|
||||
}
|
||||
|
||||
{
|
||||
ContextWriter writer(UIContext::instance(), 1000);
|
||||
Tx tx(writer, "Slice Movement", ModifyDocument);
|
||||
|
||||
CmdTransaction* cmds = m_tx;
|
||||
for (const auto& item : m_items) {
|
||||
item.slice->insert(m_frame, item.oldKey);
|
||||
tx(new cmd::SetSliceKey(item.slice, m_frame, item.newKey));
|
||||
cmds->addAndExecute(writer.context(),
|
||||
new cmd::SetSliceKey(item.slice, m_frame, item.newKey));
|
||||
|
||||
if (editor->slicesTransforms()) {
|
||||
for (int i=0; i<m_selectedLayers.size(); ++i) {
|
||||
auto* layer = m_selectedLayers[i];
|
||||
auto* cel = layer->cel(m_frame);
|
||||
cmds->addAndExecute(writer.context(),
|
||||
new cmd::StampInCel(
|
||||
cel,
|
||||
item.imgs[i],
|
||||
item.masks[i],
|
||||
item.newKey.bounds()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
m_tx.commit();
|
||||
}
|
||||
|
||||
editor->backToPreviousState();
|
||||
|
@ -138,6 +190,112 @@ bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
|
|||
return true;
|
||||
}
|
||||
|
||||
void MovingSliceState::drawExtraCel(Editor* editor)
|
||||
{
|
||||
int t, opacity = (m_site.layer()->isImage() ?
|
||||
static_cast<LayerImage*>(m_site.layer())->opacity(): 255);
|
||||
Cel* cel = m_site.cel();
|
||||
if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t);
|
||||
|
||||
if (!m_extraCel)
|
||||
m_extraCel.reset(new ExtraCel);
|
||||
|
||||
gfx::Rect bounds;
|
||||
for (auto& item : m_items)
|
||||
bounds |= item.newKey.bounds();
|
||||
|
||||
|
||||
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_site.document()->sprite(),
|
||||
bounds,
|
||||
extraCelSize,
|
||||
m_site.frame(),
|
||||
opacity);
|
||||
m_extraCel->setType(render::ExtraType::PATCH);
|
||||
m_extraCel->setBlendMode(m_site.layer()->isImage() ?
|
||||
static_cast<LayerImage*>(m_site.layer())->blendMode():
|
||||
doc::BlendMode::NORMAL);
|
||||
}
|
||||
|
||||
m_site.document()->setExtraCel(m_extraCel);
|
||||
|
||||
if (m_extraCel->image()) {
|
||||
Image* dst = m_extraCel->image();
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
dst->setMaskColor(doc::notile);
|
||||
dst->clear(dst->maskColor());
|
||||
/*
|
||||
TODO: Fix this when the TilemapMode::Pixels mode works
|
||||
if (m_site.cel()) {
|
||||
doc::Grid grid = m_site.grid();
|
||||
dst->copy(m_site.cel()->image(),
|
||||
gfx::Clip(0, 0, grid.canvasToTile(bounds)));
|
||||
//dst->copy(item.img.get(),
|
||||
// gfx::Clip(0, 0, grid.canvasToTile(bounds)));
|
||||
}
|
||||
*/
|
||||
}
|
||||
else {
|
||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
||||
dst->clear(dst->maskColor());
|
||||
|
||||
render::Render render;
|
||||
render.renderLayer(
|
||||
dst, m_site.layer(), m_site.frame(),
|
||||
gfx::Clip(0, 0, bounds),
|
||||
doc::BlendMode::SRC);
|
||||
|
||||
}
|
||||
|
||||
for (auto& item : m_items) {
|
||||
// Draw the transformed pixels in the extra-cel which is the chunk
|
||||
// of pixels that the user is moving.
|
||||
drawImage(item, dst, gfx::PointF(bounds.origin()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MovingSliceState::drawImage(const Item& item, doc::Image* dst, const gfx::PointF& pt)
|
||||
{
|
||||
ASSERT(dst);
|
||||
|
||||
if (!item.mergedImg.get()) return;
|
||||
|
||||
gfx::Rect bounds = item.newKey.bounds();
|
||||
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
|
||||
/* TODO: Finish this when TilemapMode::Pixels works
|
||||
drawTransformedTilemap(
|
||||
transformation,
|
||||
dst, m_originalImage.get(),
|
||||
m_initialMask.get());
|
||||
*/
|
||||
}
|
||||
else {
|
||||
doc::algorithm::parallelogram(
|
||||
dst, item.mergedImg.get(), item.mergedMask->bitmap(),
|
||||
bounds.x-pt.x , bounds.y-pt.y,
|
||||
bounds.x+bounds.w-pt.x, bounds.y-pt.y,
|
||||
bounds.x+bounds.w-pt.x, bounds.y+bounds.h-pt.y,
|
||||
bounds.x-pt.x , bounds.y+bounds.h-pt.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
|
||||
{
|
||||
gfx::Point newCursorPos = editor->screenToEditor(msg->position());
|
||||
|
@ -159,12 +317,6 @@ bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
|
|||
if (m_hit.border() == (CENTER | MIDDLE)) {
|
||||
rc.x += delta.x;
|
||||
rc.y += delta.y;
|
||||
|
||||
if (m_pixelsMovement) {
|
||||
m_pixelsMovement->moveImage(gfx::PointF(totalBounds.origin()+delta),
|
||||
PixelsMovement::MoveModifier::NormalMovement);
|
||||
m_pixelsMovement->dropImageTemporarily();
|
||||
}
|
||||
}
|
||||
// Move/resize 9-slices center
|
||||
else if (m_hit.type() == EditorHit::SliceCenter) {
|
||||
|
@ -236,6 +388,9 @@ bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
|
|||
item.slice->insert(m_frame, key);
|
||||
}
|
||||
|
||||
if (editor->slicesTransforms())
|
||||
drawExtraCel(editor);
|
||||
|
||||
// Redraw the editor.
|
||||
editor->invalidate();
|
||||
|
||||
|
@ -295,4 +450,56 @@ gfx::Rect MovingSliceState::selectedSlicesBounds() const
|
|||
return bounds;
|
||||
}
|
||||
|
||||
void MovingSliceState::clearSlices()
|
||||
{
|
||||
ContextWriter writer(UIContext::instance(), 1000);
|
||||
if (writer.cel()) {
|
||||
std::vector<SliceKey> slicesKeys;
|
||||
slicesKeys.reserve(m_items.size());
|
||||
for (auto& item : m_items) {
|
||||
slicesKeys.push_back(item.newKey);
|
||||
}
|
||||
|
||||
// TODO: Add tilemap and tileset case.
|
||||
auto tilemapMode = m_site.tilemapMode();
|
||||
auto tilesetMode = m_site.tilesetMode();
|
||||
|
||||
CmdTransaction* cmds = m_tx;
|
||||
//if (cel->layer()->isTilemap() && tilemapMode == TilemapMode::Pixels) {
|
||||
/*
|
||||
Doc* doc = static_cast<Doc*>(cel->document());
|
||||
|
||||
// Simple case (there is no visible selection, so we remove the
|
||||
// whole cel)
|
||||
if (!doc->isMaskVisible()) {
|
||||
cmds->executeAndAdd(new cmd::ClearCel(cel));
|
||||
return;
|
||||
}
|
||||
|
||||
color_t bgcolor = doc->bgColor(cel->layer());
|
||||
doc::Mask* mask = doc->mask();
|
||||
|
||||
modify_tilemap_cel_region(
|
||||
cmds, cel, nullptr,
|
||||
gfx::Region(doc->mask()->bounds()),
|
||||
tilesetMode,
|
||||
[bgcolor, mask](const doc::ImageRef& origTile,
|
||||
const gfx::Rect& tileBoundsInCanvas) -> doc::ImageRef {
|
||||
doc::ImageRef modified(doc::Image::createCopy(origTile.get()));
|
||||
doc::algorithm::fill_selection(
|
||||
modified.get(),
|
||||
tileBoundsInCanvas,
|
||||
mask,
|
||||
bgcolor,
|
||||
nullptr);
|
||||
return modified;
|
||||
});
|
||||
*/
|
||||
//}
|
||||
//else {
|
||||
cmds->executeAndAdd(new cmd::ClearSlices(m_selectedLayers, m_frame, slicesKeys));
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#include "app/ui/editor/standby_state.h"
|
||||
#include "app/ui/editor/pixels_movement.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/selected_layers.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "doc/slice.h"
|
||||
|
||||
|
@ -39,17 +42,42 @@ namespace app {
|
|||
doc::Slice* slice;
|
||||
doc::SliceKey oldKey;
|
||||
doc::SliceKey newKey;
|
||||
// Images containing the parts of each selected layer of the sprite under
|
||||
// the slice bounds that will be transformed when Slice Transform is
|
||||
// enabled
|
||||
std::vector<ImageRef> imgs;
|
||||
// Masks for each of the images in imgs vector
|
||||
std::vector<MaskRef> masks;
|
||||
|
||||
// Image containing the result of merging all the images in the imgs
|
||||
// vector
|
||||
ImageRef mergedImg = nullptr;
|
||||
MaskRef mergedMask = nullptr;
|
||||
|
||||
~Item() {
|
||||
if (!masks.empty() && mergedMask != masks[0])
|
||||
mergedMask->unfreeze();
|
||||
for (auto& m : masks)
|
||||
m->unfreeze();
|
||||
}
|
||||
};
|
||||
|
||||
Item getItemForSlice(doc::Slice* slice);
|
||||
gfx::Rect selectedSlicesBounds() const;
|
||||
|
||||
void drawExtraCel(Editor* editor);
|
||||
void drawImage(const Item& item, doc::Image* dst, const gfx::PointF& pt);
|
||||
|
||||
void clearSlices();
|
||||
|
||||
doc::frame_t m_frame;
|
||||
EditorHit m_hit;
|
||||
gfx::Point m_mouseStart;
|
||||
std::vector<Item> m_items;
|
||||
// Helper member to move/translate the pixels under the slices.
|
||||
PixelsMovementPtr m_pixelsMovement = nullptr;
|
||||
LayerList m_selectedLayers;
|
||||
Site m_site;
|
||||
ExtraCelRef m_extraCel = nullptr;
|
||||
Tx m_tx;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
|
|
@ -119,6 +119,8 @@ namespace doc {
|
|||
Mask& operator=(const Mask& mask);
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Mask> MaskRef;
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue