diff --git a/src/app/ui/editor/moving_slice_state.cpp b/src/app/ui/editor/moving_slice_state.cpp index 90c577139..1e7d01a97 100644 --- a/src/app/ui/editor/moving_slice_state.cpp +++ b/src/app/ui/editor/moving_slice_state.cpp @@ -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()); - 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(*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(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(); diff --git a/src/app/ui/editor/moving_slice_state.h b/src/app/ui/editor/moving_slice_state.h index b974207b0..5db5c0503 100644 --- a/src/app/ui/editor/moving_slice_state.h +++ b/src/app/ui/editor/moving_slice_state.h @@ -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; + 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; + + 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(); + 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(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 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 imgs; - // Masks for each of the images in imgs vector - std::vector masks; + std::vector 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(this, mergedImage) + : std::make_shared(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(this, image) + : std::make_shared(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, diff --git a/src/app/util/new_image_from_mask.cpp b/src/app/util/new_image_from_mask.cpp index 417534ea5..975ccdfaf 100644 --- a/src/app/util/new_image_from_mask.cpp +++ b/src/app/util/new_image_from_mask.cpp @@ -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(); }