mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-04 15:40:10 +00:00
Add 9-slice transformation support
This commit is contained in:
parent
ca75a98679
commit
fb74feea21
@ -21,7 +21,6 @@
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/expand_cel_canvas.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "doc/algorithm/rotate.h"
|
||||
#include "doc/blend_internals.h"
|
||||
#include "doc/color.h"
|
||||
@ -90,66 +89,44 @@ MovingSliceState::MovingSliceState(Editor* editor,
|
||||
editor->captureMouse();
|
||||
}
|
||||
|
||||
void MovingSliceState::initializeItemsContent() {
|
||||
for (auto& item : m_items) {
|
||||
// Align slice origin to tiles origin under Tiles mode.
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
auto origin = m_site.grid().tileToCanvas(m_site.grid().canvasToTile(item.newKey.bounds().origin()));
|
||||
auto bounds = gfx::Rect(origin, item.newKey.bounds().size());
|
||||
item.newKey.setBounds(bounds);
|
||||
}
|
||||
// Reserve one ItemContent slot for each selected layer.
|
||||
item.content.reserve(m_selectedLayers.size());
|
||||
|
||||
for (const auto* layer : m_selectedLayers) {
|
||||
Mask mask;
|
||||
ImageRef image = ImageRef();
|
||||
|
||||
mask.add(item.newKey.bounds());
|
||||
if (layer &&
|
||||
layer->isTilemap() &&
|
||||
m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
image.reset(new_tilemap_from_mask(m_site, &mask));
|
||||
}
|
||||
else {
|
||||
image.reset(new_image_from_mask(
|
||||
*layer,
|
||||
m_frame,
|
||||
&mask,
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
}
|
||||
|
||||
item.pushContent(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MovingSliceState::onEnterState(Editor* editor)
|
||||
{
|
||||
if (editor->slicesTransforms() && !m_items.empty()) {
|
||||
for (auto& item : m_items) {
|
||||
// Align slice origin to tiles origin under Tiles mode.
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
auto origin = m_site.grid().tileToCanvas(m_site.grid().canvasToTile(item.newKey.bounds().origin()));
|
||||
auto bounds = gfx::Rect(origin, item.newKey.bounds().size());
|
||||
item.newKey.setBounds(bounds);
|
||||
}
|
||||
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.imgs[i].reset(new_tilemap_from_mask(m_site, item.masks[i].get()));
|
||||
}
|
||||
else {
|
||||
item.imgs[i].reset(new_image_from_mask(
|
||||
*layer,
|
||||
m_frame,
|
||||
item.masks[i].get(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
// TODO: See if part of the code in fromImage can be replaced
|
||||
// refactored to use mask_image (in cel_ops.h)?
|
||||
item.masks[i]->fromImage(item.imgs[i].get(), item.masks[i]->origin());
|
||||
}
|
||||
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
initializeItemsContent();
|
||||
|
||||
// Clear brush preview, as the extra cel will be replaced with the
|
||||
// transformed image.
|
||||
@ -157,7 +134,7 @@ void MovingSliceState::onEnterState(Editor* editor)
|
||||
|
||||
clearSlices();
|
||||
|
||||
drawSliceContents();
|
||||
drawExtraCel();
|
||||
|
||||
// Redraw the editor.
|
||||
editor->invalidate();
|
||||
@ -179,9 +156,10 @@ bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
|
||||
auto* layer = m_selectedLayers[i];
|
||||
m_site.layer(layer);
|
||||
m_site.frame(m_frame);
|
||||
drawSliceContentsByLayer(i);
|
||||
drawExtraCel(i);
|
||||
stampExtraCelImage();
|
||||
}
|
||||
m_site.document()->setExtraCel(ExtraCelRef(nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,41 +204,7 @@ void MovingSliceState::stampExtraCelImage()
|
||||
expand.commit();
|
||||
}
|
||||
|
||||
void MovingSliceState::drawSliceContents()
|
||||
{
|
||||
gfx::Rect bounds;
|
||||
for (auto& item : m_items)
|
||||
bounds |= item.newKey.bounds();
|
||||
|
||||
drawExtraCel(bounds, [this](const gfx::Rect& bounds, Image* dst){
|
||||
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(dst,
|
||||
item.mergedImg.get(),
|
||||
item.mergedMask.get(),
|
||||
gfx::Rect(item.newKey.bounds()).offset(-bounds.origin()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MovingSliceState::drawSliceContentsByLayer(int layerIdx)
|
||||
{
|
||||
gfx::Rect bounds;
|
||||
for (auto& item : m_items)
|
||||
bounds |= item.newKey.bounds();
|
||||
|
||||
drawExtraCel(bounds, [this, layerIdx](const gfx::Rect& bounds, Image* dst){
|
||||
for (auto& item : m_items) {
|
||||
drawImage(dst,
|
||||
item.imgs[layerIdx].get(),
|
||||
item.masks[layerIdx].get(),
|
||||
gfx::Rect(item.newKey.bounds()).offset(-bounds.origin()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MovingSliceState::drawExtraCel(const gfx::Rect& bounds, DrawExtraCelContentFunc drawContent)
|
||||
void MovingSliceState::drawExtraCel(int layerIdx)
|
||||
{
|
||||
int t, opacity = (m_site.layer()->isImage() ?
|
||||
static_cast<LayerImage*>(m_site.layer())->opacity(): 255);
|
||||
@ -270,6 +214,10 @@ void MovingSliceState::drawExtraCel(const gfx::Rect& bounds, DrawExtraCelContent
|
||||
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) {
|
||||
@ -319,13 +267,31 @@ void MovingSliceState::drawExtraCel(const gfx::Rect& bounds, DrawExtraCelContent
|
||||
dst, m_site.layer(), m_site.frame(),
|
||||
gfx::Clip(0, 0, bounds),
|
||||
doc::BlendMode::SRC);
|
||||
|
||||
}
|
||||
|
||||
drawContent(bounds, dst);
|
||||
for (auto& item : m_items) {
|
||||
// Draw the transformed pixels in the extra-cel which is the chunk
|
||||
// of pixels that the user is moving.
|
||||
drawItem(dst, item, bounds.origin(), layerIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MovingSliceState::drawItem(doc::Image* dst,
|
||||
const Item& item,
|
||||
const gfx::Point& itemsBoundsOrigin,
|
||||
int layerIdx)
|
||||
{
|
||||
const ItemContentRef content = (layerIdx >= 0 ? item.content[layerIdx]
|
||||
: item.mergedContent);
|
||||
|
||||
content->forEachPart(
|
||||
[this, dst, itemsBoundsOrigin]
|
||||
(const doc::Image* src, const doc::Mask* mask, const gfx::Rect& bounds) {
|
||||
drawImage(dst, src, mask, gfx::Rect(bounds).offset(-itemsBoundsOrigin));
|
||||
});
|
||||
}
|
||||
|
||||
void MovingSliceState::drawImage(doc::Image* dst,
|
||||
const doc::Image* src,
|
||||
const doc::Mask* mask,
|
||||
@ -347,7 +313,7 @@ void MovingSliceState::drawImage(doc::Image* dst,
|
||||
}
|
||||
else {
|
||||
doc::algorithm::parallelogram(
|
||||
dst, src, mask->bitmap(),
|
||||
dst, src, (mask ? mask->bitmap() : nullptr),
|
||||
bounds.x , bounds.y,
|
||||
bounds.x+bounds.w, bounds.y,
|
||||
bounds.x+bounds.w, bounds.y+bounds.h,
|
||||
@ -459,7 +425,7 @@ bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
|
||||
}
|
||||
|
||||
if (editor->slicesTransforms())
|
||||
drawSliceContents();
|
||||
drawExtraCel();
|
||||
|
||||
// Redraw the editor.
|
||||
editor->invalidate();
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "app/ui/editor/editor_hit.h"
|
||||
#include "app/ui/editor/standby_state.h"
|
||||
#include "app/ui/editor/pixels_movement.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/mask.h"
|
||||
@ -37,38 +38,167 @@ namespace app {
|
||||
bool requireBrushPreview() override { return false; }
|
||||
|
||||
private:
|
||||
using DrawExtraCelContentFunc = std::function<void(const gfx::Rect& bounds, Image* dst)>;
|
||||
struct Item;
|
||||
using ItemContentPartFunc = std::function<
|
||||
void(const doc::Image* src,
|
||||
const doc::Mask* mask,
|
||||
const gfx::Rect& bounds)>;
|
||||
|
||||
class ItemContent {
|
||||
public:
|
||||
ItemContent(const Item *item) : m_item(item) {}
|
||||
virtual ~ItemContent() {};
|
||||
virtual void forEachPart(ItemContentPartFunc fn) = 0;
|
||||
virtual void copy(const Image* src) = 0;
|
||||
|
||||
protected:
|
||||
const Item* m_item = nullptr;
|
||||
};
|
||||
|
||||
using ItemContentRef = std::shared_ptr<ItemContent>;
|
||||
|
||||
class SingleSlice : public ItemContent {
|
||||
public:
|
||||
SingleSlice(const Item *item,
|
||||
const ImageRef& image) : SingleSlice(item, image, item->oldKey.bounds().origin()) {
|
||||
}
|
||||
|
||||
SingleSlice(const Item *item,
|
||||
const ImageRef& image,
|
||||
const gfx::Point& origin) : ItemContent(item)
|
||||
, m_img(image) {
|
||||
m_mask = std::make_shared<Mask>();
|
||||
m_mask->freeze();
|
||||
m_mask->fromImage(m_img.get(), origin);
|
||||
}
|
||||
|
||||
~SingleSlice() {
|
||||
m_mask->unfreeze();
|
||||
}
|
||||
|
||||
void forEachPart(ItemContentPartFunc fn) override {
|
||||
fn(m_img.get(), m_mask.get(), m_item->newKey.bounds());
|
||||
}
|
||||
|
||||
void copy(const Image* src) override {
|
||||
doc::Mask srcMask;
|
||||
srcMask.freeze();
|
||||
srcMask.add(m_item->oldKey.bounds());
|
||||
// TODO: See if part of the code in fromImage can be replaced or
|
||||
// refactored to use mask_image (in cel_ops.h)?
|
||||
srcMask.fromImage(src, srcMask.origin());
|
||||
copy_masked_zones(m_img.get(), src, &srcMask, srcMask.bounds().x, srcMask.bounds().y);
|
||||
|
||||
m_mask->add(srcMask);
|
||||
srcMask.unfreeze();
|
||||
}
|
||||
|
||||
const Image* image() { return m_img.get(); }
|
||||
Mask* mask() { return m_mask.get(); }
|
||||
|
||||
private:
|
||||
// Images containing the parts of each selected layer of the sprite under
|
||||
// the slice bounds that will be transformed when Slice Transform is
|
||||
// enabled
|
||||
ImageRef m_img;
|
||||
// Masks for each of the images in imgs vector
|
||||
MaskRef m_mask;
|
||||
};
|
||||
|
||||
class NineSlice : public ItemContent {
|
||||
public:
|
||||
NineSlice(const Item *item,
|
||||
const ImageRef& image) : ItemContent(item) {
|
||||
|
||||
if (!m_item->oldKey.hasCenter()) return;
|
||||
|
||||
const gfx::Rect totalBounds(m_item->oldKey.bounds().size());
|
||||
gfx::Rect bounds[9];
|
||||
totalBounds.nineSlice(m_item->oldKey.center(), bounds);
|
||||
for (int i=0; i<9; ++i) {
|
||||
if (!bounds[i].isEmpty()) {
|
||||
ImageRef img;
|
||||
img.reset(Image::create(image->pixelFormat(), bounds[i].w, bounds[i].h));
|
||||
img->copy(image.get(), gfx::Clip(0, 0, bounds[i]));
|
||||
m_part[i] = std::make_unique<SingleSlice>(m_item, img, bounds[i].origin());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~NineSlice() {}
|
||||
|
||||
void forEachPart(ItemContentPartFunc fn) override {
|
||||
gfx::Rect bounds[9];
|
||||
m_item->newKey.bounds().nineSlice(m_item->newKey.center(), bounds);
|
||||
for (int i=0; i<9; ++i) {
|
||||
if (m_part[i])
|
||||
fn(m_part[i]->image(), m_part[i]->mask(), bounds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void copy(const Image* src) override {
|
||||
if (!m_item->oldKey.hasCenter()) return;
|
||||
|
||||
const gfx::Rect totalBounds(m_item->oldKey.bounds().size());
|
||||
gfx::Rect bounds[9];
|
||||
totalBounds.nineSlice(m_item->oldKey.center(), bounds);
|
||||
for (int i=0; i<9; ++i) {
|
||||
if (!bounds[i].isEmpty()) {
|
||||
ImageRef img;
|
||||
img.reset(Image::create(src->pixelFormat(), bounds[i].w, bounds[i].h));
|
||||
img->copy(src, gfx::Clip(0, 0, bounds[i]));
|
||||
m_part[i]->copy(img.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SingleSlice> m_part[9] = {nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr};
|
||||
};
|
||||
|
||||
|
||||
struct Item {
|
||||
doc::Slice* slice;
|
||||
doc::SliceKey oldKey;
|
||||
doc::SliceKey newKey;
|
||||
// Images containing the parts of each selected layer of the sprite under
|
||||
// Vector of each selected layer's part 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;
|
||||
std::vector<ItemContentRef> content;
|
||||
ItemContentRef mergedContent;
|
||||
|
||||
// Image containing the result of merging all the images in the imgs
|
||||
// vector
|
||||
ImageRef mergedImg = nullptr;
|
||||
MaskRef mergedMask = nullptr;
|
||||
void pushContent(const ImageRef& image) {
|
||||
if (content.empty()) {
|
||||
const gfx::Rect& srcBounds = image->bounds();
|
||||
ImageRef mergedImage;
|
||||
mergedImage.reset(Image::create(image->pixelFormat(), srcBounds.w, srcBounds.h));
|
||||
mergedImage->clear(image->maskColor());
|
||||
mergedContent = (this->oldKey.hasCenter() ? (ItemContentRef)std::make_shared<NineSlice>(this, mergedImage)
|
||||
: std::make_shared<SingleSlice>(this, mergedImage));
|
||||
}
|
||||
|
||||
~Item() {
|
||||
if (!masks.empty() && mergedMask != masks[0])
|
||||
mergedMask->unfreeze();
|
||||
for (auto& m : masks)
|
||||
m->unfreeze();
|
||||
mergedContent->copy(image.get());
|
||||
|
||||
ItemContentRef ssc = (this->oldKey.hasCenter() ? (ItemContentRef)std::make_shared<NineSlice>(this, image)
|
||||
: std::make_shared<SingleSlice>(this, image));
|
||||
content.push_back(ssc);
|
||||
}
|
||||
};
|
||||
|
||||
// Initializes the content of the Items. So each item will contain the
|
||||
// part of the cel's layers within the corresponding slice.
|
||||
void initializeItemsContent();
|
||||
|
||||
Item getItemForSlice(doc::Slice* slice);
|
||||
gfx::Rect selectedSlicesBounds() const;
|
||||
|
||||
void drawSliceContents();
|
||||
void drawSliceContentsByLayer(int layerIdx);
|
||||
void drawExtraCel(const gfx::Rect& bounds, DrawExtraCelContentFunc drawContent);
|
||||
void drawExtraCel(int layerIdx = -1);
|
||||
void drawItem(doc::Image* dst,
|
||||
const Item& item,
|
||||
const gfx::Point& itemsBoundsOrigin,
|
||||
int layerIdx);
|
||||
void drawImage(doc::Image* dst,
|
||||
const doc::Image* src,
|
||||
const doc::Mask* mask,
|
||||
|
@ -127,13 +127,16 @@ doc::Image* new_image_from_mask(const Layer& layer,
|
||||
src = dst.get();
|
||||
}
|
||||
else {
|
||||
src = cel->image();
|
||||
x = cel->x();
|
||||
y = cel->y();
|
||||
if (cel) {
|
||||
src = cel->image();
|
||||
x = cel->x();
|
||||
y = cel->y();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the masked zones
|
||||
copy_masked_zones(dst.get(), src, srcMask, x, y);
|
||||
if (src)
|
||||
// Copy the masked zones
|
||||
copy_masked_zones(dst.get(), src, srcMask, x, y);
|
||||
|
||||
return dst.release();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user