From 7cf546c826304e6cbac017e8d902969cd2f4c7d0 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 4 Oct 2016 19:55:30 -0300 Subject: [PATCH 01/29] Add menu option to insert a reference layer --- data/gui.xml | 4 ++ src/app/commands/cmd_new_layer.cpp | 107 ++++++++++++++++++++--------- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/data/gui.xml b/data/gui.xml index 762da3ef9d..d4e5af0621 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -679,6 +679,10 @@ + + + + diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp index 0d29e85588..637210a71b 100644 --- a/src/app/commands/cmd_new_layer.cpp +++ b/src/app/commands/cmd_new_layer.cpp @@ -35,6 +35,9 @@ using namespace ui; class NewLayerCommand : public Command { public: + enum class Type { Layer, Group, ReferenceLayer }; + enum class Place { AfterActiveLayer, Top }; + NewLayerCommand(); Command* clone() const override { return new NewLayerCommand(*this); } @@ -48,10 +51,10 @@ class NewLayerCommand : public Command { int getMaxLayerNum(const Layer* layer) const; const char* layerPrefix() const; - bool m_ask; - bool m_top; - bool m_group; std::string m_name; + Type m_type; + Place m_place; + bool m_ask; }; NewLayerCommand::NewLayerCommand() @@ -59,18 +62,25 @@ NewLayerCommand::NewLayerCommand() "New Layer", CmdRecordableFlag) { - m_ask = false; - m_top = false; - m_group = false; m_name = ""; + m_type = Type::Layer; + m_place = Place::AfterActiveLayer; + m_ask = false; } void NewLayerCommand::onLoadParams(const Params& params) { - m_ask = (params.get("ask") == "true"); - m_top = (params.get("top") == "true"); - m_group = (params.get("group") == "true"); m_name = params.get("name"); + m_type = Type::Layer; + if (params.get("group") == "true") + m_type = Type::Group; + else if (params.get("reference") == "true") + m_type = Type::ReferenceLayer; + + m_ask = (params.get("ask") == "true"); + m_place = Place::AfterActiveLayer; + if (params.get("top") == "true") + m_place = Place::Top; } bool NewLayerCommand::onEnabled(Context* context) @@ -111,7 +121,7 @@ void NewLayerCommand::onExecute(Context* context) if (activeLayer) { if (activeLayer->isGroup() && activeLayer->isExpanded() && - !m_group) { + m_type != Type::Group) { parent = static_cast(activeLayer); activeLayer = nullptr; } @@ -126,43 +136,67 @@ void NewLayerCommand::onExecute(Context* context) writer.context(), std::string("New ") + layerPrefix()); DocumentApi api = document->getApi(transaction); + bool afterBackground = false; + + switch (m_type) { + case Type::Layer: + layer = api.newLayer(parent, name); + break; + case Type::Group: + layer = api.newGroup(parent, name); + break; + case Type::ReferenceLayer: + layer = api.newLayer(parent, name); + afterBackground = true; + break; + } - if (m_group) - layer = api.newGroup(parent, name); - else - layer = api.newLayer(parent, name); + ASSERT(layer); + if (!layer) + return; ASSERT(layer->parent()); - // If "top" parameter is false, create the layer above the active - // one. - if (activeLayer && !m_top) { + // Put new layer as an overlay of the background or in the first + // layer in case the sprite is transparent. + if (afterBackground) { + Layer* first = sprite->root()->firstLayer(); + if (first) { + if (first->isBackground()) + api.restackLayerAfter(layer, sprite->root(), first); + else + api.restackLayerBefore(layer, sprite->root(), first); + } + } + // Move the layer above the active one. + else if (activeLayer && m_place == Place::AfterActiveLayer) { api.restackLayerAfter(layer, activeLayer->parent(), activeLayer); + } - // Put all selected layers inside the group - if (m_group && writer.site()->inTimeline()) { - LayerGroup* commonParent = nullptr; - layer_t sameParents = 0; - for (Layer* l : selLayers) { - if (!commonParent || - commonParent == l->parent()) { - commonParent = l->parent(); - ++sameParents; - } + // Put all selected layers inside the group + if (m_type == Type::Group && writer.site()->inTimeline()) { + LayerGroup* commonParent = nullptr; + layer_t sameParents = 0; + for (Layer* l : selLayers) { + if (!commonParent || + commonParent == l->parent()) { + commonParent = l->parent(); + ++sameParents; } + } - if (sameParents == selLayers.size()) { - for (Layer* newChild : selLayers.toLayerList()) { - transaction.execute( - new cmd::MoveLayer(newChild, layer, - static_cast(layer)->lastLayer())); - } + if (sameParents == selLayers.size()) { + for (Layer* newChild : selLayers.toLayerList()) { + transaction.execute( + new cmd::MoveLayer(newChild, layer, + static_cast(layer)->lastLayer())); } } } + transaction.commit(); } update_screen_for_document(document); @@ -206,7 +240,12 @@ int NewLayerCommand::getMaxLayerNum(const Layer* layer) const const char* NewLayerCommand::layerPrefix() const { - return (m_group ? "Group": "Layer"); + switch (m_type) { + case Type::Layer: return "Layer"; + case Type::Group: return "Group"; + case Type::ReferenceLayer: return "Reference Layer"; + } + return "Unknown"; } Command* CommandFactory::createNewLayerCommand() From 99f37ef2421afd59238316c18c4bd2f68b39ccbd Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 4 Oct 2016 20:06:24 -0300 Subject: [PATCH 02/29] Add LayerFlags::Reference flag to identify a reference layers With this flag we can show reference layers in a special way on the Timeline. --- src/app/commands/cmd_new_layer.cpp | 2 ++ src/app/ui/timeline.cpp | 10 ++++++++++ src/doc/layer.h | 3 +++ 3 files changed, 15 insertions(+) diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp index 637210a71b..c258e5545b 100644 --- a/src/app/commands/cmd_new_layer.cpp +++ b/src/app/commands/cmd_new_layer.cpp @@ -147,6 +147,8 @@ void NewLayerCommand::onExecute(Context* context) break; case Type::ReferenceLayer: layer = api.newLayer(parent, name); + if (layer) + layer->setReference(true); afterBackground = true; break; } diff --git a/src/app/ui/timeline.cpp b/src/app/ui/timeline.cpp index df65a9e7b5..e09ee50fcb 100644 --- a/src/app/ui/timeline.cpp +++ b/src/app/ui/timeline.cpp @@ -1678,6 +1678,16 @@ void Timeline::drawLayer(ui::Graphics* g, int layerIdx) bounds.y+bounds.h-2*s, font()->textLength(layer->name().c_str()), s)); } + else if (layer->isReference()) { + int s = ui::guiscale(); + g->fillRect( + is_active ? + skinTheme()->colors.timelineClickedText(): + skinTheme()->colors.timelineNormalText(), + gfx::Rect(bounds.x+4*s, + bounds.y+bounds.h/2, + font()->textLength(layer->name().c_str()), s)); + } // If this layer wasn't clicked but there are another layer clicked, // we have to draw some indicators to show that the user can move diff --git a/src/doc/layer.h b/src/doc/layer.h index 72db1079c7..483743f62f 100644 --- a/src/doc/layer.h +++ b/src/doc/layer.h @@ -37,6 +37,7 @@ namespace doc { Background = 8, // Stack order cannot be changed Continuous = 16, // Prefer to link cels when the user copy them Collapsed = 32, // Prefer to show this group layer collapsed + Reference = 64, // Is a reference layer BackgroundLayerFlags = LockMove | Background, }; @@ -76,6 +77,7 @@ namespace doc { bool isContinuous() const { return hasFlags(LayerFlags::Continuous); } bool isCollapsed() const { return hasFlags(LayerFlags::Collapsed); } bool isExpanded() const { return !hasFlags(LayerFlags::Collapsed); } + bool isReference() const { return hasFlags(LayerFlags::Reference); } bool isVisibleHierarchy() const; bool isEditableHierarchy() const; @@ -86,6 +88,7 @@ namespace doc { void setMovable (bool state) { switchFlags(LayerFlags::LockMove, !state); } void setContinuous(bool state) { switchFlags(LayerFlags::Continuous, state); } void setCollapsed (bool state) { switchFlags(LayerFlags::Collapsed, state); } + void setReference (bool state) { switchFlags(LayerFlags::Reference, state); } LayerFlags flags() const { return m_flags; From 512ccb5dd063ce1196775f6c753dc84c88dde221 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 4 Oct 2016 21:17:17 -0300 Subject: [PATCH 03/29] Add bounds (width/height) to doc::Cel/CelData --- src/doc/cel.cpp | 17 +++++------------ src/doc/cel.h | 5 +++-- src/doc/cel_data.cpp | 8 ++++++-- src/doc/cel_data.h | 15 +++++++-------- src/doc/cel_data_io.cpp | 10 +++++++--- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/doc/cel.cpp b/src/doc/cel.cpp index 94c026be3b..ed44dbf346 100644 --- a/src/doc/cel.cpp +++ b/src/doc/cel.cpp @@ -72,6 +72,11 @@ void Cel::setPosition(const gfx::Point& pos) m_data->setPosition(pos); } +void Cel::setBounds(const gfx::Rect& bounds) +{ + m_data->setBounds(bounds); +} + void Cel::setOpacity(int opacity) { m_data->setOpacity(opacity); @@ -126,18 +131,6 @@ std::size_t Cel::links() const return links; } -gfx::Rect Cel::bounds() const -{ - Image* image = this->image(); - ASSERT(image); - if (image) - return gfx::Rect( - position().x, position().y, - image->width(), image->height()); - else - return gfx::Rect(); -} - void Cel::setParentLayer(LayerImage* layer) { m_layer = layer; diff --git a/src/doc/cel.h b/src/doc/cel.h index 4d2ad86b10..aacd1c6887 100644 --- a/src/doc/cel.h +++ b/src/doc/cel.h @@ -33,7 +33,8 @@ namespace doc { frame_t frame() const { return m_frame; } int x() const { return m_data->position().x; } int y() const { return m_data->position().y; } - const gfx::Point& position() const { return m_data->position(); } + gfx::Point position() const { return m_data->position(); } + const gfx::Rect& bounds() const { return m_data->bounds(); } int opacity() const { return m_data->opacity(); } LayerImage* layer() const { return m_layer; } @@ -45,7 +46,6 @@ namespace doc { Sprite* sprite() const; Cel* link() const; std::size_t links() const; - gfx::Rect bounds() const; // You should change the frame only if the cel isn't member of a // layer. If the cel is already in a layer, you should use @@ -54,6 +54,7 @@ namespace doc { void setDataRef(const CelDataRef& celData); void setPosition(int x, int y); void setPosition(const gfx::Point& pos); + void setBounds(const gfx::Rect& bounds); void setOpacity(int opacity); virtual int getMemSize() const override { diff --git a/src/doc/cel_data.cpp b/src/doc/cel_data.cpp index 6ebff93569..0bddecbbbb 100644 --- a/src/doc/cel_data.cpp +++ b/src/doc/cel_data.cpp @@ -20,7 +20,9 @@ namespace doc { CelData::CelData(const ImageRef& image) : WithUserData(ObjectType::CelData) , m_image(image) - , m_position(0, 0) + , m_bounds(0, 0, + image ? image->width(): 0, + image ? image->height(): 0) , m_opacity(255) { } @@ -28,7 +30,7 @@ CelData::CelData(const ImageRef& image) CelData::CelData(const CelData& celData) : WithUserData(ObjectType::CelData) , m_image(celData.m_image) - , m_position(celData.m_position) + , m_bounds(celData.m_bounds) , m_opacity(celData.m_opacity) { } @@ -38,6 +40,8 @@ void CelData::setImage(const ImageRef& image) ASSERT(image.get()); m_image = image; + m_bounds.w = image->width(); + m_bounds.h = image->height(); } } // namespace doc diff --git a/src/doc/cel_data.h b/src/doc/cel_data.h index 2b777aaeb5..3fc2fbf6a9 100644 --- a/src/doc/cel_data.h +++ b/src/doc/cel_data.h @@ -12,6 +12,7 @@ #include "doc/image_ref.h" #include "doc/object.h" #include "doc/with_user_data.h" +#include "gfx/rect.h" namespace doc { @@ -20,17 +21,15 @@ namespace doc { CelData(const ImageRef& image); CelData(const CelData& celData); - const gfx::Point& position() const { return m_position; } + gfx::Point position() const { return m_bounds.origin(); } + const gfx::Rect& bounds() const { return m_bounds; } int opacity() const { return m_opacity; } Image* image() const { return const_cast(m_image.get()); }; ImageRef imageRef() const { return m_image; } void setImage(const ImageRef& image); - void setPosition(int x, int y) { - m_position.x = x; - m_position.y = y; - } - void setPosition(const gfx::Point& pos) { m_position = pos; } + void setPosition(const gfx::Point& pos) { m_bounds.setOrigin(pos); } + void setBounds(const gfx::Rect& bounds) { m_bounds = bounds; } void setOpacity(int opacity) { m_opacity = opacity; } virtual int getMemSize() const override { @@ -40,8 +39,8 @@ namespace doc { private: ImageRef m_image; - gfx::Point m_position; // X/Y screen position - int m_opacity; // Opacity level + gfx::Rect m_bounds; + int m_opacity; }; typedef base::SharedPtr CelDataRef; diff --git a/src/doc/cel_data_io.cpp b/src/doc/cel_data_io.cpp index 7ea2a9a892..dbf5746b25 100644 --- a/src/doc/cel_data_io.cpp +++ b/src/doc/cel_data_io.cpp @@ -26,8 +26,10 @@ using namespace base::serialization::little_endian; void write_celdata(std::ostream& os, const CelData* celdata) { write32(os, celdata->id()); - write32(os, (int16_t)celdata->position().x); - write32(os, (int16_t)celdata->position().y); + write32(os, celdata->bounds().x); + write32(os, celdata->bounds().y); + write32(os, celdata->bounds().w); + write32(os, celdata->bounds().h); write8(os, celdata->opacity()); write32(os, celdata->image()->id()); write_user_data(os, celdata->userData()); @@ -38,6 +40,8 @@ CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId) ObjectId id = read32(is); int x = read32(is); int y = read32(is); + int w = read32(is); + int h = read32(is); int opacity = read8(is); ObjectId imageId = read32(is); UserData userData = read_user_data(is); @@ -47,7 +51,7 @@ CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId) return nullptr; base::UniquePtr celdata(new CelData(image)); - celdata->setPosition(x, y); + celdata->setBounds(gfx::Rect(x, y, w, h)); celdata->setOpacity(opacity); celdata->setUserData(userData); if (setId) From 4a7d601d5d8ac616426cb97ede31866e42f78f5c Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 5 Oct 2016 21:37:53 -0300 Subject: [PATCH 04/29] Add composite_image_general() as a generic (slow) image composer --- src/render/render.cpp | 57 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/render/render.cpp b/src/render/render.cpp index f31dca928b..bab761567e 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -131,6 +131,8 @@ class BlenderHelper { } }; +#if 0 + template void composite_image_without_scale( Image* dst, @@ -439,9 +441,59 @@ void composite_image_scale_down_non_square_pixel_ratio( } } +#else + +template +void composite_image_general( + Image* dst, const Image* src, const Palette* pal, + const gfx::Clip& _area, + const int opacity, + const BlendMode blendMode, + const Projection& proj) +{ + ASSERT(dst); + ASSERT(src); + ASSERT(DstTraits::pixel_format == dst->pixelFormat()); + ASSERT(SrcTraits::pixel_format == src->pixelFormat()); + + gfx::Clip area = _area; + if (!area.clip(dst->width(), dst->height(), + proj.applyX(src->width()), + proj.applyY(src->height()))) + return; + + BlenderHelper blender(src, pal, blendMode); + + gfx::Rect dstBounds = area.dstBounds(); + gfx::Rect srcBounds = area.srcBounds(); + + for (int y=0; y= 0 && srcX < src->width() && + srcY >= 0 && srcY < src->height() && + dstX >= 0 && dstX < dst->width() && + dstY >= 0 && dstY < dst->height()) { + put_pixel_fast( + dst, dstX, dstY, + blender(get_pixel_fast(dst, dstX, dstY), + get_pixel_fast(src, srcX, srcY), + opacity)); + } + } + } +} + +#endif + template CompositeImageFunc get_image_composition_impl(const Projection& proj) { +#if 0 if (proj.applyX(1) == 1 && proj.applyY(1) == 1) { return composite_image_without_scale; } @@ -456,6 +508,9 @@ CompositeImageFunc get_image_composition_impl(const Projection& proj) else { return composite_image_scale_down; } +#else + return composite_image_general; +#endif } CompositeImageFunc get_image_composition(const PixelFormat dstFormat, @@ -806,8 +861,6 @@ void Render::renderBackground( } // Tile size - if (tile_w < m_proj.applyX(1)) tile_w = m_proj.applyX(1); - if (tile_h < m_proj.applyY(1)) tile_h = m_proj.applyY(1); if (tile_w < 1) tile_w = 1; if (tile_h < 1) tile_h = 1; From 080e7e3f683b9f50e32d0ee20b9d31b1a4c60387 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 10 Oct 2016 23:42:47 -0300 Subject: [PATCH 05/29] Add subpixel bounds for reference layer cels Now we can move a reference layer cel in a X,Y position with floating point coordinates (i.e. in a subpixel position). --- src/app/CMakeLists.txt | 1 + src/app/cmd/set_cel_bounds.cpp | 51 ++++++++++++++ src/app/cmd/set_cel_bounds.h | 40 +++++++++++ src/app/extra_cel.cpp | 4 +- src/app/ui/editor/editor.cpp | 11 +++ src/app/ui/editor/editor.h | 1 + src/app/ui/editor/moving_cel_state.cpp | 83 +++++++++++++++------- src/app/ui/editor/moving_cel_state.h | 10 +-- src/doc/cel.cpp | 7 +- src/doc/cel.h | 2 + src/doc/cel_data.cpp | 14 +++- src/doc/cel_data.h | 31 +++++++- src/gfx/CMakeLists.txt | 1 - src/gfx/clip.cpp | 64 ----------------- src/gfx/clip.h | 84 +++++++++++++++++----- src/gfx/fwd.h | 2 + src/render/CMakeLists.txt | 2 +- src/render/projection.h | 16 +++++ src/render/render.cpp | 98 +++++++++++++------------- src/render/render.h | 15 ++-- src/she/surface.h | 4 -- 21 files changed, 355 insertions(+), 186 deletions(-) create mode 100644 src/app/cmd/set_cel_bounds.cpp create mode 100644 src/app/cmd/set_cel_bounds.h delete mode 100644 src/gfx/clip.cpp diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index d5653abd40..1ca0ea29f3 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -134,6 +134,7 @@ add_library(app-lib cmd/remove_palette.cpp cmd/replace_image.cpp cmd/reselect_mask.cpp + cmd/set_cel_bounds.cpp cmd/set_cel_data.cpp cmd/set_cel_frame.cpp cmd/set_cel_opacity.cpp diff --git a/src/app/cmd/set_cel_bounds.cpp b/src/app/cmd/set_cel_bounds.cpp new file mode 100644 index 0000000000..dac4c3e994 --- /dev/null +++ b/src/app/cmd/set_cel_bounds.cpp @@ -0,0 +1,51 @@ +// Aseprite +// 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/set_cel_bounds.h" + +#include "app/document.h" +#include "doc/cel.h" +#include "doc/document_event.h" + +namespace app { +namespace cmd { + +using namespace doc; + +SetCelBoundsF::SetCelBoundsF(Cel* cel, const gfx::RectF& bounds) + : WithCel(cel) + , m_oldBounds(cel->boundsF()) + , m_newBounds(bounds) +{ +} + +void SetCelBoundsF::onExecute() +{ + cel()->setBoundsF(m_newBounds); + cel()->incrementVersion(); +} + +void SetCelBoundsF::onUndo() +{ + cel()->setBoundsF(m_oldBounds); + cel()->incrementVersion(); +} + +void SetCelBoundsF::onFireNotifications() +{ + Cel* cel = this->cel(); + DocumentEvent ev(cel->document()); + ev.sprite(cel->sprite()); + ev.cel(cel); + cel->document()->notify_observers(&DocumentObserver::onCelPositionChanged, ev); +} + +} // namespace cmd +} // namespace app diff --git a/src/app/cmd/set_cel_bounds.h b/src/app/cmd/set_cel_bounds.h new file mode 100644 index 0000000000..be467ebfc7 --- /dev/null +++ b/src/app/cmd/set_cel_bounds.h @@ -0,0 +1,40 @@ +// Aseprite +// Copyright (C) 2016 David Capello +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_CMD_SET_CEL_BOUNDS_H_INCLUDED +#define APP_CMD_SET_CEL_BOUNDS_H_INCLUDED +#pragma once + +#include "app/cmd.h" +#include "app/cmd/with_cel.h" +#include "gfx/rect.h" + +namespace app { +namespace cmd { + using namespace doc; + + class SetCelBoundsF : public Cmd + , public WithCel { + public: + SetCelBoundsF(Cel* cel, const gfx::RectF& bounds); + + protected: + void onExecute() override; + void onUndo() override; + void onFireNotifications() override; + size_t onMemSize() const override { + return sizeof(*this); + } + + private: + gfx::RectF m_oldBounds; + gfx::RectF m_newBounds; + }; + +} // namespace cmd +} // namespace app + +#endif diff --git a/src/app/extra_cel.cpp b/src/app/extra_cel.cpp index 28e8958551..9892e91b7a 100644 --- a/src/app/extra_cel.cpp +++ b/src/app/extra_cel.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -44,7 +44,7 @@ void ExtraCel::create(doc::Sprite* sprite, m_cel.reset(new doc::Cel(doc::frame_t(0), doc::ImageRef(nullptr))); } - m_cel->setPosition(bounds.origin()); + m_cel->setBounds(bounds); m_cel->setOpacity(opacity); m_cel->setFrame(frame); } diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index aadc40da50..8025e434ef 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -1028,6 +1028,17 @@ gfx::Point Editor::screenToEditor(const gfx::Point& pt) m_proj.removeY(pt.y - vp.y + scroll.y - m_padding.y)); } +gfx::PointF Editor::screenToEditorF(const gfx::Point& pt) +{ + View* view = View::getView(this); + Rect vp = view->viewportBounds(); + Point scroll = view->viewScroll(); + + return gfx::PointF( + m_proj.removeX(pt.x - vp.x + scroll.x - m_padding.x), + m_proj.removeY(pt.y - vp.y + scroll.y - m_padding.y)); +} + Point Editor::editorToScreen(const gfx::Point& pt) { View* view = View::getView(this); diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 2fc9068cc2..49706c9c1d 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -150,6 +150,7 @@ namespace app { void flashCurrentLayer(); gfx::Point screenToEditor(const gfx::Point& pt); + gfx::PointF screenToEditorF(const gfx::Point& pt); gfx::Point editorToScreen(const gfx::Point& pt); gfx::Rect screenToEditor(const gfx::Rect& rc); gfx::Rect editorToScreen(const gfx::Rect& rc); diff --git a/src/app/ui/editor/moving_cel_state.cpp b/src/app/ui/editor/moving_cel_state.cpp index c11bfb61da..234a97c534 100644 --- a/src/app/ui/editor/moving_cel_state.cpp +++ b/src/app/ui/editor/moving_cel_state.cpp @@ -11,16 +11,17 @@ #include "app/ui/editor/moving_cel_state.h" #include "app/app.h" +#include "app/cmd/set_cel_bounds.h" #include "app/context_access.h" #include "app/document_api.h" #include "app/document_range.h" +#include "app/transaction.h" #include "app/ui/editor/editor.h" #include "app/ui/editor/editor_customization_delegate.h" #include "app/ui/main_window.h" #include "app/ui/status_bar.h" #include "app/ui/timeline.h" #include "app/ui_context.h" -#include "app/transaction.h" #include "app/util/range_utils.h" #include "doc/cel.h" #include "doc/layer.h" @@ -35,6 +36,7 @@ using namespace ui; MovingCelState::MovingCelState(Editor* editor, MouseMessage* msg) : m_reader(UIContext::instance(), 500) , m_canceled(false) + , m_hasReference(false) { ContextWriter writer(m_reader); Document* document = editor->document(); @@ -55,12 +57,18 @@ MovingCelState::MovingCelState(Editor* editor, MouseMessage* msg) if (layer && layer->isMovable() && !layer->isBackground()) { m_celList.push_back(cel); - m_celStarts.push_back(cel->position()); + + if (cel->layer()->isReference()) { + m_celStarts.push_back(cel->boundsF()); + m_hasReference = true; + } + else + m_celStarts.push_back(cel->bounds()); } } - m_cursorStart = editor->screenToEditor(msg->position()); - m_celOffset = gfx::Point(0, 0); + m_cursorStart = editor->screenToEditorF(msg->position()); + m_celOffset = gfx::PointF(0, 0); editor->captureMouse(); // Hide the mask (temporarily, until mouse-up event) @@ -71,23 +79,23 @@ MovingCelState::MovingCelState(Editor* editor, MouseMessage* msg) } } -MovingCelState::~MovingCelState() -{ -} - bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg) { Document* document = editor->document(); // Here we put back the cel into its original coordinate (so we can // add an undoer before). - if (m_celOffset != gfx::Point(0, 0)) { + if ((m_hasReference && m_celOffset != gfx::PointF(0, 0)) || + (!m_hasReference && gfx::Point(m_celOffset) != gfx::Point(0, 0))) { // Put the cels in the original position. for (size_t i=0; isetPosition(celStart); + if (cel->layer()->isReference()) + cel->setBoundsF(celStart); + else + cel->setBounds(gfx::Rect(celStart)); } // If the user didn't cancel the operation... @@ -98,9 +106,18 @@ bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg) // And now we move the cel (or all selected range) to the new position. for (Cel* cel : m_celList) { - api.setCelPosition(writer.sprite(), cel, - cel->x() + m_celOffset.x, - cel->y() + m_celOffset.y); + // Change reference layer with subpixel precision + if (cel->layer()->isReference()) { + gfx::RectF celBounds = cel->boundsF(); + celBounds.x += m_celOffset.x; + celBounds.y += m_celOffset.y; + transaction.execute(new cmd::SetCelBoundsF(cel, celBounds)); + } + else { + api.setCelPosition(writer.sprite(), cel, + cel->x() + m_celOffset.x, + cel->y() + m_celOffset.y); + } } // Move selection if it was visible @@ -131,7 +148,7 @@ bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg) bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) { - gfx::Point newCursorPos = editor->screenToEditor(msg->position()); + gfx::PointF newCursorPos = editor->screenToEditorF(msg->position()); m_celOffset = newCursorPos - m_cursorStart; @@ -147,9 +164,14 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) for (size_t i=0; isetPosition(celStart + m_celOffset); + gfx::RectF celBounds = m_celStarts[i]; + celBounds.x += m_celOffset.x; + celBounds.y += m_celOffset.y; + + if (cel->layer()->isReference()) + cel->setBoundsF(celBounds); + else + cel->setBounds(gfx::Rect(celBounds)); } // Redraw the new cel position. @@ -161,13 +183,24 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) bool MovingCelState::onUpdateStatusBar(Editor* editor) { - StatusBar::instance()->setStatusText - (0, - ":pos: %3d %3d :offset: %3d %3d", - (int)m_cursorStart.x, - (int)m_cursorStart.y, - (int)m_celOffset.x, - (int)m_celOffset.y); + if (m_hasReference) { + StatusBar::instance()->setStatusText + (0, + ":pos: %.2f %.2f :offset: %.2f %.2f", + m_cursorStart.x, + m_cursorStart.y, + m_celOffset.x, + m_celOffset.y); + } + else { + StatusBar::instance()->setStatusText + (0, + ":pos: %3d %3d :offset: %3d %3d", + int(m_cursorStart.x), + int(m_cursorStart.y), + int(m_celOffset.x), + int(m_celOffset.y)); + } return true; } diff --git a/src/app/ui/editor/moving_cel_state.h b/src/app/ui/editor/moving_cel_state.h index cbb54be68c..d8df83b2ae 100644 --- a/src/app/ui/editor/moving_cel_state.h +++ b/src/app/ui/editor/moving_cel_state.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -25,7 +25,6 @@ namespace app { class MovingCelState : public StandbyState { public: MovingCelState(Editor* editor, ui::MouseMessage* msg); - virtual ~MovingCelState(); virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override; virtual bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override; @@ -36,11 +35,12 @@ namespace app { private: ContextReader m_reader; CelList m_celList; - std::vector m_celStarts; - gfx::Point m_celOffset; - gfx::Point m_cursorStart; + std::vector m_celStarts; + gfx::PointF m_cursorStart; + gfx::PointF m_celOffset; bool m_canceled; bool m_maskVisible; + bool m_hasReference; }; } // namespace app diff --git a/src/doc/cel.cpp b/src/doc/cel.cpp index ed44dbf346..bffd5d723f 100644 --- a/src/doc/cel.cpp +++ b/src/doc/cel.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2001-2015 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -77,6 +77,11 @@ void Cel::setBounds(const gfx::Rect& bounds) m_data->setBounds(bounds); } +void Cel::setBoundsF(const gfx::RectF& bounds) +{ + m_data->setBoundsF(bounds); +} + void Cel::setOpacity(int opacity) { m_data->setOpacity(opacity); diff --git a/src/doc/cel.h b/src/doc/cel.h index aacd1c6887..4688fdcf41 100644 --- a/src/doc/cel.h +++ b/src/doc/cel.h @@ -35,6 +35,7 @@ namespace doc { int y() const { return m_data->position().y; } gfx::Point position() const { return m_data->position(); } const gfx::Rect& bounds() const { return m_data->bounds(); } + const gfx::RectF& boundsF() const { return m_data->boundsF(); } int opacity() const { return m_data->opacity(); } LayerImage* layer() const { return m_layer; } @@ -55,6 +56,7 @@ namespace doc { void setPosition(int x, int y); void setPosition(const gfx::Point& pos); void setBounds(const gfx::Rect& bounds); + void setBoundsF(const gfx::RectF& bounds); void setOpacity(int opacity); virtual int getMemSize() const override { diff --git a/src/doc/cel_data.cpp b/src/doc/cel_data.cpp index 0bddecbbbb..b83b838830 100644 --- a/src/doc/cel_data.cpp +++ b/src/doc/cel_data.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2001-2015 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -20,19 +20,27 @@ namespace doc { CelData::CelData(const ImageRef& image) : WithUserData(ObjectType::CelData) , m_image(image) + , m_opacity(255) , m_bounds(0, 0, image ? image->width(): 0, image ? image->height(): 0) - , m_opacity(255) + , m_boundsF(nullptr) { } CelData::CelData(const CelData& celData) : WithUserData(ObjectType::CelData) , m_image(celData.m_image) - , m_bounds(celData.m_bounds) , m_opacity(celData.m_opacity) + , m_bounds(celData.m_bounds) + , m_boundsF(celData.m_boundsF ? new gfx::RectF(*celData.m_boundsF): + nullptr) +{ +} + +CelData::~CelData() { + delete m_boundsF; } void CelData::setImage(const ImageRef& image) diff --git a/src/doc/cel_data.h b/src/doc/cel_data.h index 3fc2fbf6a9..362b867d5c 100644 --- a/src/doc/cel_data.h +++ b/src/doc/cel_data.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2001-2015 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -20,6 +20,7 @@ namespace doc { public: CelData(const ImageRef& image); CelData(const CelData& celData); + ~CelData(); gfx::Point position() const { return m_bounds.origin(); } const gfx::Rect& bounds() const { return m_bounds; } @@ -29,9 +30,29 @@ namespace doc { void setImage(const ImageRef& image); void setPosition(const gfx::Point& pos) { m_bounds.setOrigin(pos); } - void setBounds(const gfx::Rect& bounds) { m_bounds = bounds; } void setOpacity(int opacity) { m_opacity = opacity; } + void setBounds(const gfx::Rect& bounds) { + m_bounds = bounds; + if (m_boundsF) + *m_boundsF = gfx::RectF(bounds); + } + + void setBoundsF(const gfx::RectF& boundsF) { + if (m_boundsF) + *m_boundsF = boundsF; + else + m_boundsF = new gfx::RectF(boundsF); + + m_bounds = gfx::Rect(boundsF); + } + + const gfx::RectF& boundsF() const { + if (!m_boundsF) + m_boundsF = new gfx::RectF(m_bounds); + return *m_boundsF; + } + virtual int getMemSize() const override { ASSERT(m_image); return sizeof(CelData) + m_image->getMemSize(); @@ -39,8 +60,12 @@ namespace doc { private: ImageRef m_image; - gfx::Rect m_bounds; int m_opacity; + gfx::Rect m_bounds; + + // Special bounds for reference layers that can have subpixel + // position. + mutable gfx::RectF* m_boundsF; }; typedef base::SharedPtr CelDataRef; diff --git a/src/gfx/CMakeLists.txt b/src/gfx/CMakeLists.txt index 7a92f18df9..f3111b7366 100644 --- a/src/gfx/CMakeLists.txt +++ b/src/gfx/CMakeLists.txt @@ -2,7 +2,6 @@ # Copyright (C) 2001-2016 David Capello add_library(gfx-lib - clip.cpp hsv.cpp packing_rects.cpp region.cpp diff --git a/src/gfx/clip.cpp b/src/gfx/clip.cpp deleted file mode 100644 index 3bf84533ba..0000000000 --- a/src/gfx/clip.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Aseprite Gfx Library -// Copyright (c) 2001-2014 David Capello -// -// This file is released under the terms of the MIT license. -// Read LICENSE.txt for more information. - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "gfx/clip.h" - -namespace gfx { - -bool Clip::clip( - int avail_dst_w, - int avail_dst_h, - int avail_src_w, - int avail_src_h) -{ - // Clip srcBounds - - if (src.x < 0) { - size.w += src.x; - dst.x -= src.x; - src.x = 0; - } - - if (src.y < 0) { - size.h += src.y; - dst.y -= src.y; - src.y = 0; - } - - if (src.x + size.w > avail_src_w) - size.w -= src.x + size.w - avail_src_w; - - if (src.y + size.h > avail_src_h) - size.h -= src.y + size.h - avail_src_h; - - // Clip dstBounds - - if (dst.x < 0) { - size.w += dst.x; - src.x -= dst.x; - dst.x = 0; - } - - if (dst.y < 0) { - size.h += dst.y; - src.y -= dst.y; - dst.y = 0; - } - - if (dst.x + size.w > avail_dst_w) - size.w -= dst.x + size.w - avail_dst_w; - - if (dst.y + size.h > avail_dst_h) - size.h -= dst.y + size.h - avail_dst_h; - - return (size.w > 0 && size.h > 0); -} - -} // namespace gfx diff --git a/src/gfx/clip.h b/src/gfx/clip.h index 2bab341632..a8ad02f8ab 100644 --- a/src/gfx/clip.h +++ b/src/gfx/clip.h @@ -1,5 +1,5 @@ // Aseprite Gfx Library -// Copyright (c) 2001-2014 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -14,58 +14,59 @@ namespace gfx { - class Clip { + template + class ClipT { public: - Point dst; - Point src; - Size size; + PointT dst; + PointT src; + SizeT size; - Clip() + ClipT() : dst(0, 0) , src(0, 0) , size(0, 0) { } - Clip(int w, int h) + ClipT(T w, T h) : dst(0, 0) , src(0, 0) , size(w, h) { } - Clip(int dst_x, int dst_y, int src_x, int src_y, int w, int h) + ClipT(T dst_x, T dst_y, T src_x, T src_y, T w, T h) : dst(dst_x, dst_y) , src(src_x, src_y) , size(w, h) { } - Clip(int dst_x, int dst_y, const Rect& srcBounds) + ClipT(T dst_x, T dst_y, const RectT& srcBounds) : dst(dst_x, dst_y) , src(srcBounds.x, srcBounds.y) , size(srcBounds.w, srcBounds.h) { } - Clip(const Point& dst, const Point& src, const Size& size) + ClipT(const PointT& dst, const PointT& src, const SizeT& size) : dst(dst) , src(src) , size(size) { } - Clip(const Point& dst, const Rect& srcBounds) + ClipT(const PointT& dst, const RectT& srcBounds) : dst(dst) , src(srcBounds.x, srcBounds.y) , size(srcBounds.w, srcBounds.h) { } - Clip(const Rect& bounds) + ClipT(const RectT& bounds) : dst(bounds.x, bounds.y) , src(bounds.x, bounds.y) , size(bounds.w, bounds.h) { } - Rect dstBounds() const { return Rect(dst, size); } - Rect srcBounds() const { return Rect(src, size); } + RectT dstBounds() const { return RectT(dst, size); } + RectT srcBounds() const { return RectT(src, size); } - bool operator==(const Clip& other) const { + bool operator==(const ClipT& other) const { return (dst == other.dst && src == other.src && size == other.size); @@ -73,13 +74,58 @@ namespace gfx { bool clip( // Available area - int avail_dst_w, - int avail_dst_h, - int avail_src_w, - int avail_src_h); + T avail_dst_w, + T avail_dst_h, + T avail_src_w, + T avail_src_h) { + // Clip srcBounds + + if (src.x < T(0)) { + size.w += src.x; + dst.x -= src.x; + src.x = T(0); + } + + if (src.y < T(0)) { + size.h += src.y; + dst.y -= src.y; + src.y = T(0); + } + + if (src.x + size.w > avail_src_w) + size.w -= src.x + size.w - avail_src_w; + + if (src.y + size.h > avail_src_h) + size.h -= src.y + size.h - avail_src_h; + + // Clip dstBounds + + if (dst.x < T(0)) { + size.w += dst.x; + src.x -= dst.x; + dst.x = T(0); + } + + if (dst.y < T(0)) { + size.h += dst.y; + src.y -= dst.y; + dst.y = T(0); + } + + if (dst.x + size.w > avail_dst_w) + size.w -= dst.x + size.w - avail_dst_w; + + if (dst.y + size.h > avail_dst_h) + size.h -= dst.y + size.h - avail_dst_h; + + return (size.w > T(0) && size.h > T(0)); + } }; + typedef ClipT Clip; + typedef ClipT ClipF; + } // namespace gfx #endif diff --git a/src/gfx/fwd.h b/src/gfx/fwd.h index 44ae4d9d37..2b1ba5a7fc 100644 --- a/src/gfx/fwd.h +++ b/src/gfx/fwd.h @@ -11,11 +11,13 @@ namespace gfx { template class BorderT; +template class ClipT; template class PointT; template class RectT; template class SizeT; typedef BorderT Border; +typedef ClipT Clip; typedef PointT Point; typedef RectT Rect; typedef SizeT Size; diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 9a97e5d4d5..edd940ab18 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -1,5 +1,5 @@ # Aseprite Render Library -# Copyright (C) 2001-2015 David Capello +# Copyright (C) 2001-2016 David Capello add_library(render-lib get_sprite_pixel.cpp diff --git a/src/render/projection.h b/src/render/projection.h index a6f460e031..d8e479269e 100644 --- a/src/render/projection.h +++ b/src/render/projection.h @@ -55,6 +55,14 @@ namespace render { applyY(r.y+r.h) - v); } + gfx::RectF apply(const gfx::RectF& r) const { + double u = applyX(r.x); + double v = applyY(r.y); + return gfx::RectF(u, v, + applyX(r.x+r.w) - u, + applyY(r.y+r.h) - v); + } + gfx::Rect remove(const gfx::Rect& r) const { int u = removeX(r.x); int v = removeY(r.y); @@ -63,6 +71,14 @@ namespace render { removeY(r.y+r.h) - v); } + gfx::RectF remove(const gfx::RectF& r) const { + double u = removeX(r.x); + double v = removeY(r.y); + return gfx::RectF(u, v, + removeX(r.x+r.w) - u, + removeY(r.y+r.h) - v); + } + private: doc::PixelRatio m_pixelRatio; Zoom m_zoom; diff --git a/src/render/render.cpp b/src/render/render.cpp index bab761567e..de477a5b26 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -446,33 +446,33 @@ void composite_image_scale_down_non_square_pixel_ratio( template void composite_image_general( Image* dst, const Image* src, const Palette* pal, - const gfx::Clip& _area, + const gfx::ClipF& _area, const int opacity, const BlendMode blendMode, - const Projection& proj) + const double sx, + const double sy) { ASSERT(dst); ASSERT(src); ASSERT(DstTraits::pixel_format == dst->pixelFormat()); ASSERT(SrcTraits::pixel_format == src->pixelFormat()); - gfx::Clip area = _area; + gfx::ClipF area = _area; if (!area.clip(dst->width(), dst->height(), - proj.applyX(src->width()), - proj.applyY(src->height()))) + sx*src->width(), sy*src->height())) return; BlenderHelper blender(src, pal, blendMode); - gfx::Rect dstBounds = area.dstBounds(); - gfx::Rect srcBounds = area.srcBounds(); + gfx::RectF dstBounds = area.dstBounds(); + gfx::RectF srcBounds = area.srcBounds(); - for (int y=0; y= 0 && srcX < src->width() && srcY >= 0 && srcY < src->height() && @@ -775,8 +775,9 @@ void Render::renderSprite( dstImage, m_previewImage, m_sprite->palette(frame), - m_previewPos.x, - m_previewPos.y, + gfx::Rect(m_previewPos.x, m_previewPos.y, + m_previewImage->width(), + m_previewImage->height()), area, compositeImage, 255, @@ -899,19 +900,21 @@ void Render::renderImage( const int opacity, const BlendMode blendMode) { - CompositeImageFunc compositeImage = get_image_composition( - dst_image->pixelFormat(), - src_image->pixelFormat(), m_proj); + CompositeImageFunc compositeImage = + get_image_composition( + dst_image->pixelFormat(), + src_image->pixelFormat(), m_proj); if (!compositeImage) return; compositeImage( dst_image, src_image, pal, - gfx::Clip(x, y, 0, 0, - m_proj.applyX(src_image->width()), - m_proj.applyY(src_image->height())), + gfx::ClipF(x, y, 0, 0, + m_proj.applyX(src_image->width()), + m_proj.applyY(src_image->height())), opacity, blendMode, - m_proj); + m_proj.scaleX(), + m_proj.scaleY()); } void Render::renderLayer( @@ -960,21 +963,27 @@ void Render::renderLayer( if (cel) { Palette* pal = m_sprite->palette(frame); const Image* celImage; - gfx::Point celPos; + gfx::RectF celBounds; // Is the 'm_previewImage' set to be used with this layer? if ((m_previewImage) && (m_selectedLayer == layer) && (m_selectedFrame == frame)) { celImage = m_previewImage; - celPos = m_previewPos; + celBounds = gfx::RectF(m_previewPos.x, + m_previewPos.y, + m_previewImage->width(), + m_previewImage->height()); ASSERT(celImage->pixelFormat() == cel->image()->pixelFormat()); } // If not, we use the original cel-image from the images' stock else { celImage = cel->image(); - celPos = cel->position(); + if (cel->layer()->isReference()) + celBounds = cel->boundsF(); + else + celBounds = cel->bounds(); } if (celImage) { @@ -1005,7 +1014,7 @@ void Render::renderLayer( for (auto rc : originalAreas) { renderCel( - image, celImage, pal, celPos, + image, celImage, pal, celBounds, gfx::Clip(area.dst.x+rc.x-area.src.x, area.dst.y+rc.y-area.src.y, rc), compositeImage, opacity, layerBlendMode); @@ -1015,7 +1024,7 @@ void Render::renderLayer( else { renderCel( image, celImage, pal, - celPos, area, compositeImage, + celBounds, area, compositeImage, opacity, layerBlendMode); } } @@ -1044,7 +1053,7 @@ void Render::renderLayer( renderCel( image, m_extraImage, m_sprite->palette(frame), - m_extraCel->position(), + m_extraCel->bounds(), gfx::Clip(area.dst.x+extraArea.x-area.src.x, area.dst.y+extraArea.y-area.src.y, extraArea), @@ -1059,7 +1068,7 @@ void Render::renderCel( Image* dst_image, const Image* cel_image, const Palette* pal, - const gfx::Point& celPos, + const gfx::RectF& celBounds, const gfx::Clip& area, const CompositeImageFunc compositeImage, const int opacity, @@ -1068,8 +1077,7 @@ void Render::renderCel( renderImage(dst_image, cel_image, pal, - celPos.x, - celPos.y, + celBounds, area, compositeImage, opacity, @@ -1080,38 +1088,30 @@ void Render::renderImage( Image* dst_image, const Image* cel_image, const Palette* pal, - const int x, - const int y, + const gfx::RectF& celBounds, const gfx::Clip& area, const CompositeImageFunc compositeImage, const int opacity, const BlendMode blendMode) { - int cel_x = m_proj.applyX(x); - int cel_y = m_proj.applyY(y); - - gfx::Rect srcBounds = - area.srcBounds().createIntersection( - gfx::Rect( - cel_x, - cel_y, - m_proj.applyX(cel_image->width()), - m_proj.applyY(cel_image->height()))); + gfx::RectF scaledBounds = m_proj.apply(celBounds); + gfx::RectF srcBounds = gfx::RectF(area.srcBounds()).createIntersection(scaledBounds); if (srcBounds.isEmpty()) return; compositeImage( dst_image, cel_image, pal, - gfx::Clip( - area.dst.x + srcBounds.x - area.src.x, - area.dst.y + srcBounds.y - area.src.y, - srcBounds.x - cel_x, - srcBounds.y - cel_y, + gfx::ClipF( + double(area.dst.x) + srcBounds.x - double(area.src.x), + double(area.dst.y) + srcBounds.y - double(area.src.y), + srcBounds.x - scaledBounds.x, + srcBounds.y - scaledBounds.y, srcBounds.w, srcBounds.h), opacity, blendMode, - m_proj); + m_proj.scaleX() * double(cel_image->width()) / celBounds.w, + m_proj.scaleY() * double(cel_image->height()) / celBounds.h); } void composite_image(Image* dst, diff --git a/src/render/render.h b/src/render/render.h index c1f945ba50..23b8893b95 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -13,16 +13,13 @@ #include "doc/color.h" #include "doc/frame.h" #include "doc/pixel_format.h" +#include "gfx/clip.h" #include "gfx/point.h" #include "gfx/size.h" #include "render/extra_type.h" #include "render/onionskin_position.h" #include "render/projection.h" -namespace gfx { - class Clip; -} - namespace doc { class Cel; class FrameTag; @@ -93,10 +90,11 @@ namespace render { Image* dst, const Image* src, const Palette* pal, - const gfx::Clip& area, + const gfx::ClipF& area, const int opacity, const BlendMode blendMode, - const Projection& proj); + const double sx, + const double sy); class Render { public: @@ -194,7 +192,7 @@ namespace render { Image* dst_image, const Image* cel_image, const Palette* pal, - const gfx::Point& celPos, + const gfx::RectF& celBounds, const gfx::Clip& area, const CompositeImageFunc compositeImage, const int opacity, @@ -204,8 +202,7 @@ namespace render { Image* dst_image, const Image* cel_image, const Palette* pal, - const int x, - const int y, + const gfx::RectF& celBounds, const gfx::Clip& area, const CompositeImageFunc compositeImage, const int opacity, diff --git a/src/she/surface.h b/src/she/surface.h index 173903165b..c11057a0a8 100644 --- a/src/she/surface.h +++ b/src/she/surface.h @@ -14,10 +14,6 @@ #include -namespace gfx { - class Clip; -} - namespace she { class Font; From 0068a2024a9042d23b5f48004d9772bef2a2a35d Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 11 Oct 2016 11:27:47 -0300 Subject: [PATCH 06/29] Improve composite_image_general() performance --- src/render/render.cpp | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/render/render.cpp b/src/render/render.cpp index de477a5b26..bebe4add27 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -469,21 +469,20 @@ void composite_image_general( int dstY = dstBounds.y; for (int y=0; y= 0 && srcX < src->width() && - srcY >= 0 && srcY < src->height() && - dstX >= 0 && dstX < dst->width() && - dstY >= 0 && dstY < dst->height()) { - put_pixel_fast( - dst, dstX, dstY, - blender(get_pixel_fast(dst, dstX, dstY), - get_pixel_fast(src, srcX, srcY), - opacity)); - } + int srcY = (srcBounds.y+y)/sy; + double srcX = srcBounds.x/sx; + double srcXDelta = 1.0/sx; + int oldSrcX; + + auto dstPtr = get_pixel_address_fast(dst, dstBounds.x, dstY); + auto srcPtr = get_pixel_address_fast(src, int(srcX), srcY); + + for (int x=0; x Date: Tue, 11 Oct 2016 16:41:50 -0300 Subject: [PATCH 07/29] Add flag to show render performance in real-time --- data/pref.xml | 3 +++ src/app/ui/editor/editor.cpp | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/data/pref.xml b/data/pref.xml index 7e14a90ecc..9489039818 100644 --- a/data/pref.xml +++ b/data/pref.xml @@ -218,6 +218,9 @@
+
+
diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 8025e434ef..66b1f97f9b 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -43,6 +43,7 @@ #include "app/ui/toolbar.h" #include "app/ui_context.h" #include "base/bind.h" +#include "base/chrono.h" #include "base/convert_to.h" #include "base/unique_ptr.h" #include "doc/conversion_she.h" @@ -63,6 +64,10 @@ using namespace gfx; using namespace ui; using namespace render; +// TODO these should be grouped in some kind of "performance counters" +static base::Chrono renderChrono; +static double renderElapsed = 0.0; + class EditorPreRenderImpl : public EditorPreRender { public: EditorPreRenderImpl(Editor* editor, Image* image, @@ -1441,7 +1446,22 @@ void Editor::onPaint(ui::PaintEvent& ev) DocumentReader documentReader(m_document, 0); // Draw the sprite in the editor + renderChrono.reset(); drawSpriteUnclippedRect(g, gfx::Rect(0, 0, m_sprite->width(), m_sprite->height())); + renderElapsed = renderChrono.elapsed(); + + // Show performance stats (TODO show performance stats in other widget) + if (Preferences::instance().perf.showRenderTime()) { + View* view = View::getView(this); + gfx::Rect vp = view->viewportBounds(); + char buf[128]; + sprintf(buf, "%.3f", renderElapsed); + g->drawString( + buf, + gfx::rgba(255, 255, 255, 255), + gfx::rgba(0, 0, 0, 255), + vp.origin() - bounds().origin()); + } // Draw the mask boundaries if (m_document->getMaskBoundaries()) { From b83eb89ce4f3e51c95d3b8fef808fe33b3be1fb1 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 11 Oct 2016 16:43:37 -0300 Subject: [PATCH 08/29] Use old composite code (faster code) when possible We use the new general image composite code in case that there is a reference layer or the background grid is so small that we need a pixel-by-pixel color blending. --- src/gfx/clip.h | 7 ++ src/render/render.cpp | 240 +++++++++++++++++++----------------------- src/render/render.h | 5 + 3 files changed, 118 insertions(+), 134 deletions(-) diff --git a/src/gfx/clip.h b/src/gfx/clip.h index a8ad02f8ab..8364e2e063 100644 --- a/src/gfx/clip.h +++ b/src/gfx/clip.h @@ -63,6 +63,13 @@ namespace gfx { , size(bounds.w, bounds.h) { } + template + ClipT(const ClipT& other) + : dst(other.dst) + , src(other.src) + , size(other.size) { + } + RectT dstBounds() const { return RectT(dst, size); } RectT srcBounds() const { return RectT(src, size); } diff --git a/src/render/render.cpp b/src/render/render.cpp index bebe4add27..c861760f8d 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -131,17 +131,14 @@ class BlenderHelper { } }; -#if 0 - template void composite_image_without_scale( - Image* dst, - const Image* src, - const Palette* pal, - const gfx::Clip& _area, + Image* dst, const Image* src, const Palette* pal, + const gfx::ClipF& areaF, const int opacity, const BlendMode blendMode, - const Projection& proj) + const double sx, + const double sy) { ASSERT(dst); ASSERT(src); @@ -150,7 +147,7 @@ void composite_image_without_scale( BlenderHelper blender(src, pal, blendMode); - gfx::Clip area = _area; + gfx::Clip area(areaF); if (!area.clip(dst->width(), dst->height(), src->width(), src->height())) return; @@ -189,33 +186,36 @@ void composite_image_without_scale( template void composite_image_scale_up( - Image* dst, - const Image* src, - const Palette* pal, - const gfx::Clip& _area, + Image* dst, const Image* src, const Palette* pal, + const gfx::ClipF& areaF, const int opacity, const BlendMode blendMode, - const Projection& proj) + const double sx, + const double sy) { ASSERT(dst); ASSERT(src); ASSERT(DstTraits::pixel_format == dst->pixelFormat()); ASSERT(SrcTraits::pixel_format == src->pixelFormat()); - gfx::Clip area = _area; + gfx::Clip area(areaF); if (!area.clip(dst->width(), dst->height(), - proj.applyX(src->width()), - proj.applyY(src->height()))) + int(sx*double(src->width())), + int(sy*double(src->height())))) return; BlenderHelper blender(src, pal, blendMode); int px_x, px_y; - int px_w = proj.applyX(1); - int px_h = proj.applyY(1); + int px_w = sx; + int px_h = sy; int first_px_w = px_w - (area.src.x % px_w); int first_px_h = px_h - (area.src.y % px_h); - gfx::Rect srcBounds = proj.remove(area.srcBounds()); + gfx::Rect srcBounds = area.srcBounds(); + srcBounds.w = (srcBounds.x+srcBounds.w)/px_w - srcBounds.x/px_w; + srcBounds.h = (srcBounds.y+srcBounds.h)/px_h - srcBounds.y/px_h; + srcBounds.x /= px_w; + srcBounds.y /= px_h; if ((area.src.x+area.size.w) % px_w > 0) ++srcBounds.w; if ((area.src.y+area.size.h) % px_h > 0) ++srcBounds.h; if (srcBounds.isEmpty()) @@ -326,29 +326,34 @@ done_with_blit:; template void composite_image_scale_down( Image* dst, const Image* src, const Palette* pal, - const gfx::Clip& _area, + const gfx::ClipF& areaF, const int opacity, const BlendMode blendMode, - const Projection& proj) + const double sx, + const double sy) { ASSERT(dst); ASSERT(src); ASSERT(DstTraits::pixel_format == dst->pixelFormat()); ASSERT(SrcTraits::pixel_format == src->pixelFormat()); - gfx::Clip area = _area; + gfx::Clip area(areaF); if (!area.clip(dst->width(), dst->height(), - proj.applyX(src->width()), - proj.applyY(src->height()))) + int(sx*double(src->width())), + int(sy*double(src->height())))) return; BlenderHelper blender(src, pal, blendMode); - int step_w = proj.removeX(1); - int step_h = proj.removeY(1); + int step_w = 1.0 / sx; + int step_h = 1.0 / sy; if (step_w < 1 || step_h < 1) return; - gfx::Rect srcBounds = proj.remove(area.srcBounds()); + gfx::Rect srcBounds = area.srcBounds(); + srcBounds.w = (srcBounds.x+srcBounds.w)*step_w - srcBounds.x*step_w; + srcBounds.h = (srcBounds.y+srcBounds.h)*step_h - srcBounds.y*step_h; + srcBounds.x *= step_w; + srcBounds.y *= step_h; if (srcBounds.isEmpty()) return; @@ -383,70 +388,10 @@ void composite_image_scale_down( } } -template -void composite_image_scale_down_non_square_pixel_ratio( - Image* dst, const Image* src, const Palette* pal, - const gfx::Clip& _area, - const int opacity, - const BlendMode blendMode, - const Projection& proj) -{ - ASSERT(dst); - ASSERT(src); - ASSERT(DstTraits::pixel_format == dst->pixelFormat()); - ASSERT(SrcTraits::pixel_format == src->pixelFormat()); - - gfx::Clip area = _area; - if (!area.clip(dst->width(), dst->height(), - proj.applyX(src->width()), - proj.applyY(src->height()))) - return; - - BlenderHelper blender(src, pal, blendMode); - float step_w = proj.removeX(1.0f); - float step_h = proj.removeY(1.0f); - if (step_w < 1.0f || step_h < 1.0f) - return; - - gfx::Rect srcBounds = proj.remove(area.srcBounds()); - if (srcBounds.isEmpty()) - return; - - gfx::Rect dstBounds = area.dstBounds(); - - // Lock all necessary bits - LockImageBits dstBits(dst, dstBounds); - auto dst_it = dstBits.begin(); - auto dst_end = dstBits.end(); - - // For each line to draw of the source image... - float v = float(srcBounds.y); - for (int y=0; y= dstBits.begin() && dst_it < dst_end); - - *dst_it = blender(*dst_it, - get_pixel_fast(src, int(u), int(v)), - opacity); - - // Skip columns - u += step_w; - ++dst_it; - } - - // Skip rows - v += step_h; - } -} - -#else - template void composite_image_general( Image* dst, const Image* src, const Palette* pal, - const gfx::ClipF& _area, + const gfx::ClipF& areaF, const int opacity, const BlendMode blendMode, const double sx, @@ -457,14 +402,14 @@ void composite_image_general( ASSERT(DstTraits::pixel_format == dst->pixelFormat()); ASSERT(SrcTraits::pixel_format == src->pixelFormat()); - gfx::ClipF area = _area; + gfx::ClipF area(areaF); if (!area.clip(dst->width(), dst->height(), sx*src->width(), sy*src->height())) return; BlenderHelper blender(src, pal, blendMode); - gfx::RectF dstBounds = area.dstBounds(); + gfx::Rect dstBounds = area.dstBounds(); gfx::RectF srcBounds = area.srcBounds(); int dstY = dstBounds.y; @@ -487,13 +432,14 @@ void composite_image_general( } } -#endif - template -CompositeImageFunc get_image_composition_impl(const Projection& proj) +CompositeImageFunc get_fastest_composition_path(const Projection& proj, + const bool finegrain) { -#if 0 - if (proj.applyX(1) == 1 && proj.applyY(1) == 1) { + if (finegrain) { + return composite_image_general; + } + else if (proj.applyX(1) == 1 && proj.applyY(1) == 1) { return composite_image_without_scale; } else if (proj.scaleX() >= 1.0 && proj.scaleY() >= 1.0) { @@ -502,49 +448,27 @@ CompositeImageFunc get_image_composition_impl(const Projection& proj) // Slower composite function for special cases with odd zoom and non-square pixel ratio else if (((proj.removeX(1) > 1) && (proj.removeX(1) & 1)) || ((proj.removeY(1) > 1) && (proj.removeY(1) & 1))) { - return composite_image_scale_down_non_square_pixel_ratio; + return composite_image_general; } else { return composite_image_scale_down; } -#else - return composite_image_general; -#endif } -CompositeImageFunc get_image_composition(const PixelFormat dstFormat, - const PixelFormat srcFormat, - const Projection& proj) +bool has_visible_reference_layers(const LayerGroup* group) { - switch (srcFormat) { + for (const Layer* child : group->layers()) { + if (!child->isVisible()) + continue; - case IMAGE_RGB: - switch (dstFormat) { - case IMAGE_RGB: return get_image_composition_impl(proj); - case IMAGE_GRAYSCALE: return get_image_composition_impl(proj); - case IMAGE_INDEXED: return get_image_composition_impl(proj); - } - break; - - case IMAGE_GRAYSCALE: - switch (dstFormat) { - case IMAGE_RGB: return get_image_composition_impl(proj); - case IMAGE_GRAYSCALE: return get_image_composition_impl(proj); - case IMAGE_INDEXED: return get_image_composition_impl(proj); - } - break; + if (child->isReference()) + return true; - case IMAGE_INDEXED: - switch (dstFormat) { - case IMAGE_RGB: return get_image_composition_impl(proj); - case IMAGE_GRAYSCALE: return get_image_composition_impl(proj); - case IMAGE_INDEXED: return get_image_composition_impl(proj); - } - break; + if (child->isGroup() && + has_visible_reference_layers(static_cast(child))) + return true; } - - ASSERT(false && "Invalid pixel formats"); - return NULL; + return false; } } // anonymous namespace @@ -674,9 +598,9 @@ void Render::renderLayer( m_sprite = layer->sprite(); CompositeImageFunc compositeImage = - get_image_composition( + getImageComposition( dstImage->pixelFormat(), - m_sprite->pixelFormat(), m_proj); + m_sprite->pixelFormat(), layer); if (!compositeImage) return; @@ -696,9 +620,9 @@ void Render::renderSprite( m_sprite = sprite; CompositeImageFunc compositeImage = - get_image_composition( + getImageComposition( dstImage->pixelFormat(), - m_sprite->pixelFormat(), m_proj); + m_sprite->pixelFormat(), sprite->root()); if (!compositeImage) return; @@ -900,9 +824,9 @@ void Render::renderImage( const BlendMode blendMode) { CompositeImageFunc compositeImage = - get_image_composition( + getImageComposition( dst_image->pixelFormat(), - src_image->pixelFormat(), m_proj); + src_image->pixelFormat(), nullptr); if (!compositeImage) return; @@ -1113,6 +1037,54 @@ void Render::renderImage( m_proj.scaleY() * double(cel_image->height()) / celBounds.h); } +CompositeImageFunc Render::getImageComposition( + const PixelFormat dstFormat, + const PixelFormat srcFormat, + const Layer* layer) +{ + // True if we need blending pixel by pixel. If this is false we can + // blend src+dst one time and repeat the resulting color in dst + // image n-times (where n is the zoom scale). + const bool finegrain = + (m_bgCheckedSize.w < m_proj.applyX(1) || + m_bgCheckedSize.h < m_proj.applyY(1) || + // Check if we are rendering reference images with zoom scale > 1 + ((m_proj.applyX(1) > 1 || m_proj.applyY(1) > 1) && + layer && + layer->isGroup() && + has_visible_reference_layers(static_cast(layer)))); + + switch (srcFormat) { + + case IMAGE_RGB: + switch (dstFormat) { + case IMAGE_RGB: return get_fastest_composition_path(m_proj, finegrain); + case IMAGE_GRAYSCALE: return get_fastest_composition_path(m_proj, finegrain); + case IMAGE_INDEXED: return get_fastest_composition_path(m_proj, finegrain); + } + break; + + case IMAGE_GRAYSCALE: + switch (dstFormat) { + case IMAGE_RGB: return get_fastest_composition_path(m_proj, finegrain); + case IMAGE_GRAYSCALE: return get_fastest_composition_path(m_proj, finegrain); + case IMAGE_INDEXED: return get_fastest_composition_path(m_proj, finegrain); + } + break; + + case IMAGE_INDEXED: + switch (dstFormat) { + case IMAGE_RGB: return get_fastest_composition_path(m_proj, finegrain); + case IMAGE_GRAYSCALE: return get_fastest_composition_path(m_proj, finegrain); + case IMAGE_INDEXED: return get_fastest_composition_path(m_proj, finegrain); + } + break; + } + + ASSERT(false && "Invalid pixel formats"); + return nullptr; +} + void composite_image(Image* dst, const Image* src, const Palette* pal, diff --git a/src/render/render.h b/src/render/render.h index 23b8893b95..e70009378e 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -208,6 +208,11 @@ namespace render { const int opacity, const BlendMode blendMode); + CompositeImageFunc getImageComposition( + const PixelFormat dstFormat, + const PixelFormat srcFormat, + const Layer* layer); + const Sprite* m_sprite; const Layer* m_currentLayer; frame_t m_currentFrame; From 2a5ed858dc7c676a3f71812637cad46ec7379e1b Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 12 Oct 2016 12:48:11 -0300 Subject: [PATCH 09/29] Load/save precise cel bounds for reference layers in .ase file --- docs/files/ase.txt | 16 +++++++++++ src/app/file/ase_format.cpp | 57 ++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/docs/files/ase.txt b/docs/files/ase.txt index 03ebf41e54..8019ae71cc 100644 --- a/docs/files/ase.txt +++ b/docs/files/ase.txt @@ -19,6 +19,7 @@ BYTE An 8-bit unsigned integer value WORD A 16-bit unsigned integer value SIGNED WORD A 16-bit signed integer value DWORD A 32-bit unsigned integer value +FIXED A 32-bit fixed point (16.16) value BYTE[n] "n" bytes. STRING length=WORD (how many characters to read next) string=BYTE[length] @@ -156,6 +157,7 @@ Layer Chunk (0x2004) 8 = Background 16 = Prefer linked cels 32 = The layer group should be displayed collapsed + 64 = The layer is a reference layer WORD Layer type 0 = Normal (image) layer 1 = Group @@ -218,6 +220,20 @@ Cel Chunk (0x2005) http://www.ietf.org/rfc/rfc1951.txt +Cel Extra Chunk (0x2006) +---------------------------------------- + +Adds extra information to the latest read cel. + + DWORD Flags (set to zero) + 1 - precise bounds are set + FIXED Precise X position + FIXED Precise Y position + FIXED Width of the cel in the sprite (scaled in real-time) + FIXED Height of the cel in the sprite + BYTE[16] For future use (set to zero) + + Mask Chunk (0x2016) DEPRECATED ---------------------------------------- diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp index 3b70bc4883..2e52aef124 100644 --- a/src/app/file/ase_format.cpp +++ b/src/app/file/ase_format.cpp @@ -18,6 +18,7 @@ #include "base/file_handle.h" #include "base/path.h" #include "doc/doc.h" +#include "fixmath/fixmath.h" #include "ui/alert.h" #include "zlib.h" @@ -26,12 +27,13 @@ #define ASE_FILE_MAGIC 0xA5E0 #define ASE_FILE_FRAME_MAGIC 0xF1FA -#define ASE_FILE_FLAG_LAYER_WITH_OPACITY 1 +#define ASE_FILE_FLAG_LAYER_WITH_OPACITY 1 #define ASE_FILE_CHUNK_FLI_COLOR2 4 #define ASE_FILE_CHUNK_FLI_COLOR 11 #define ASE_FILE_CHUNK_LAYER 0x2004 #define ASE_FILE_CHUNK_CEL 0x2005 +#define ASE_FILE_CHUNK_CEL_EXTRA 0x2006 #define ASE_FILE_CHUNK_MASK 0x2016 #define ASE_FILE_CHUNK_PATH 0x2017 #define ASE_FILE_CHUNK_FRAME_TAGS 0x2018 @@ -50,6 +52,8 @@ #define ASE_USER_DATA_FLAG_HAS_TEXT 1 #define ASE_USER_DATA_FLAG_HAS_COLOR 2 +#define ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS 1 + namespace app { using namespace base; @@ -119,12 +123,15 @@ static void ase_file_write_palette_chunk(FILE* f, ASE_FrameHeader* frame_header, static Layer* ase_file_read_layer_chunk(FILE* f, ASE_Header* header, Sprite* sprite, Layer** previous_layer, int* current_level); static void ase_file_write_layer_chunk(FILE* f, ASE_FrameHeader* frame_header, const Layer* layer, int child_level); static Cel* ase_file_read_cel_chunk(FILE* f, Sprite* sprite, LayerList& allLayers, frame_t frame, PixelFormat pixelFormat, FileOp* fop, ASE_Header* header, size_t chunk_end); +static void ase_file_read_cel_extra_chunk(FILE* f, Cel* cel); static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header, const Cel* cel, const LayerImage* layer, const layer_t layer_index, const Sprite* sprite, const frame_t firstFrame); +static void ase_file_write_cel_extra_chunk(FILE* f, ASE_FrameHeader* frame_header, + const Cel* cel); static Mask* ase_file_read_mask_chunk(FILE* f); #if 0 static void ase_file_write_mask_chunk(FILE* f, ASE_FrameHeader* frame_header, Mask* mask); @@ -214,6 +221,7 @@ bool AseFormat::onLoad(FileOp* fop) // Prepare variables for layer chunks Layer* last_layer = sprite->root(); WithUserData* last_object_with_user_data = nullptr; + Cel* last_cel = nullptr; int current_level = -1; LayerList allLayers; @@ -287,11 +295,18 @@ bool AseFormat::onLoad(FileOp* fop) sprite->pixelFormat(), fop, &header, chunk_pos+chunk_size); if (cel) { + last_cel = cel; last_object_with_user_data = cel->data(); } break; } + case ASE_FILE_CHUNK_CEL_EXTRA: { + if (last_cel) + ase_file_read_cel_extra_chunk(f, last_cel); + break; + } + case ASE_FILE_CHUNK_MASK: { Mask* mask = ase_file_read_mask_chunk(f); if (mask) @@ -635,6 +650,9 @@ static layer_t ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header, static_cast(layer), layer_index, sprite, firstFrame); + if (layer->isReference()) + ase_file_write_cel_extra_chunk(f, frame_header, cel); + if (!cel->link() && !cel->data()->userData().isEmpty()) { ase_file_write_user_data_chunk(f, frame_header, @@ -1320,6 +1338,25 @@ static Cel* ase_file_read_cel_chunk(FILE* f, return cel.release(); } +static void ase_file_read_cel_extra_chunk(FILE* f, Cel* cel) +{ + // Read chunk data + int flags = fgetl(f); + if (flags & ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS) { + fixmath::fixed x = fgetl(f); + fixmath::fixed y = fgetl(f); + fixmath::fixed w = fgetl(f); + fixmath::fixed h = fgetl(f); + if (w && h) { + gfx::RectF bounds(fixmath::fixtof(x), + fixmath::fixtof(y), + fixmath::fixtof(w), + fixmath::fixtof(h)); + cel->setBoundsF(bounds); + } + } +} + static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header, const Cel* cel, const LayerImage* layer, @@ -1426,6 +1463,24 @@ static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header, } } +static void ase_file_write_cel_extra_chunk(FILE* f, + ASE_FrameHeader* frame_header, + const Cel* cel) +{ + ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_CEL_EXTRA); + + ASSERT(cel->layer()->isReference()); + + gfx::RectF bounds = cel->boundsF(); + + fputl(ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS, f); + fputl(fixmath::ftofix(bounds.x), f); + fputl(fixmath::ftofix(bounds.y), f); + fputl(fixmath::ftofix(bounds.w), f); + fputl(fixmath::ftofix(bounds.h), f); + ase_file_write_padding(f, 16); +} + static Mask* ase_file_read_mask_chunk(FILE* f) { int c, u, v, byte; From 1aa2a4137aabda49defcecc01ffff0b90d3d8ef3 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 12 Oct 2016 14:28:46 -0300 Subject: [PATCH 10/29] Fix mnemonic char for "Add Reference Layer" --- data/gui.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/gui.xml b/data/gui.xml index d4e5af0621..432574e86b 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -680,7 +680,7 @@ - +
From bf4d6f1e4efe0fa0f0acab70b829c7747533397a Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 12 Oct 2016 14:41:32 -0300 Subject: [PATCH 11/29] Ask for a file when we add a new reference layer Also the new reference layer is added centered in the sprite and scaling it to make it fully visible in the canvas. --- data/gui.xml | 1 + src/app/commands/cmd_new_layer.cpp | 84 ++++++++++++++++++++++++++++++ src/app/document_api.cpp | 4 +- src/render/render.cpp | 8 ++- 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/data/gui.xml b/data/gui.xml index 432574e86b..1178146dca 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -682,6 +682,7 @@ + diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp index c258e5545b..6aff9ca9cd 100644 --- a/src/app/commands/cmd_new_layer.cpp +++ b/src/app/commands/cmd_new_layer.cpp @@ -11,6 +11,7 @@ #include "app/app.h" #include "app/cmd/move_layer.h" #include "app/commands/command.h" +#include "app/commands/commands.h" #include "app/commands/params.h" #include "app/context_access.h" #include "app/document_api.h" @@ -21,7 +22,9 @@ #include "app/ui/main_window.h" #include "app/ui/status_bar.h" #include "doc/layer.h" +#include "doc/primitives.h" #include "doc/sprite.h" +#include "render/render.h" #include "ui/ui.h" #include "new_layer.xml.h" @@ -55,6 +58,7 @@ class NewLayerCommand : public Command { Type m_type; Place m_place; bool m_ask; + bool m_fromFile; }; NewLayerCommand::NewLayerCommand() @@ -78,6 +82,7 @@ void NewLayerCommand::onLoadParams(const Params& params) m_type = Type::ReferenceLayer; m_ask = (params.get("ask") == "true"); + m_fromFile = (params.get("from-file") == "true"); m_place = Place::AfterActiveLayer; if (params.get("top") == "true") m_place = Place::Top; @@ -89,6 +94,16 @@ bool NewLayerCommand::onEnabled(Context* context) ContextFlags::HasActiveSprite); } +namespace { +class Scoped { // TODO move this to base library +public: + Scoped(const std::function& func) : m_func(func) { } + ~Scoped() { m_func(); } +private: + std::function m_func; +}; +} + void NewLayerCommand::onExecute(Context* context) { ContextWriter writer(context); @@ -96,12 +111,34 @@ void NewLayerCommand::onExecute(Context* context) Sprite* sprite(writer.sprite()); std::string name; + app::Document* pasteDoc = nullptr; + Scoped destroyPasteDoc( + [&pasteDoc, context]{ + if (pasteDoc) { + context->documents().remove(pasteDoc); + delete pasteDoc; + } + }); + // Default name (m_name is a name specified in params) if (!m_name.empty()) name = m_name; else name = getUniqueLayerName(sprite); + // Select a file to copy its content + if (m_fromFile) { + Document* oldActiveDocument = context->activeDocument(); + Command* openFile = CommandsModule::instance()->getCommandByName(CommandId::OpenFile); + Params params; + params.set("filename", ""); + context->executeCommand(openFile, params); + + // The user have selected another document. + if (oldActiveDocument != context->activeDocument()) + pasteDoc = context->activeDocument(); + } + // If params specify to ask the user about the name... if (m_ask) { // We open the window to ask the name @@ -198,6 +235,53 @@ void NewLayerCommand::onExecute(Context* context) } } + // Paste sprite content + if (pasteDoc && layer->isImage()) { + Sprite* pasteSpr = pasteDoc->sprite(); + render::Render render; + render.setBgType(render::BgType::NONE); + + // Add more frames at the end + if (writer.frame()+pasteSpr->lastFrame() > sprite->lastFrame()) + api.addEmptyFramesTo(sprite, writer.frame()+pasteSpr->lastFrame()); + + // Paste the given sprite as flatten + for (frame_t fr=0; fr<=pasteSpr->lastFrame(); ++fr) { + ImageRef pasteImage(Image::create(sprite->pixelFormat(), + pasteSpr->width(), + pasteSpr->height())); + clear_image(pasteImage.get(), + pasteSpr->transparentColor()); + render.renderSprite(pasteImage.get(), pasteSpr, fr); + + frame_t dstFrame = writer.frame()+fr; + Cel* cel = layer->cel(dstFrame); + if (cel) { + api.replaceImage(sprite, cel->imageRef(), pasteImage); + } + else { + cel = api.addCel(static_cast(layer), + dstFrame, pasteImage); + } + + if (cel) { + if (layer->isReference()) { + gfx::RectF bounds(0, 0, pasteSpr->width(), pasteSpr->height()); + double scale = MIN(double(sprite->width()) / bounds.w, + double(sprite->height()) / bounds.h); + bounds.w *= scale; + bounds.h *= scale; + bounds.x = sprite->width()/2 - bounds.w/2; + bounds.y = sprite->height()/2 - bounds.h/2; + cel->setBoundsF(bounds); + } + else { + cel->setPosition(sprite->width()/2 - pasteSpr->width()/2, + sprite->height()/2 - pasteSpr->height()/2); + } + } + } + } transaction.commit(); } diff --git a/src/app/document_api.cpp b/src/app/document_api.cpp index 7ce6d9731b..613b61859e 100644 --- a/src/app/document_api.cpp +++ b/src/app/document_api.cpp @@ -501,9 +501,7 @@ Cel* DocumentApi::addCel(LayerImage* layer, frame_t frameNumber, const ImageRef& base::UniquePtr cel(new Cel(frameNumber, image)); addCel(layer, cel); - cel.release(); - - return cel; + return cel.release(); } void DocumentApi::replaceImage(Sprite* sprite, const ImageRef& oldImage, const ImageRef& newImage) diff --git a/src/render/render.cpp b/src/render/render.cpp index c861760f8d..e7748334da 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -1033,8 +1033,8 @@ void Render::renderImage( srcBounds.h), opacity, blendMode, - m_proj.scaleX() * double(cel_image->width()) / celBounds.w, - m_proj.scaleY() * double(cel_image->height()) / celBounds.h); + m_proj.scaleX() * celBounds.w / double(cel_image->width()), + m_proj.scaleY() * celBounds.h / double(cel_image->height())); } CompositeImageFunc Render::getImageComposition( @@ -1048,9 +1048,7 @@ CompositeImageFunc Render::getImageComposition( const bool finegrain = (m_bgCheckedSize.w < m_proj.applyX(1) || m_bgCheckedSize.h < m_proj.applyY(1) || - // Check if we are rendering reference images with zoom scale > 1 - ((m_proj.applyX(1) > 1 || m_proj.applyY(1) > 1) && - layer && + (layer && layer->isGroup() && has_visible_reference_layers(static_cast(layer)))); From 1b053de2f2c33cd4246af5ae9ac0ec582b2a824f Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 13 Oct 2016 19:58:42 -0300 Subject: [PATCH 12/29] Fix eyedropper/move tool for reference layers --- src/app/color_picker.cpp | 42 +++++++++++++++++++-------- src/app/color_picker.h | 10 +++++-- src/app/commands/cmd_eyedropper.cpp | 13 ++++++--- src/app/commands/cmd_eyedropper.h | 9 ++++-- src/app/commands/cmd_mask_content.cpp | 7 ++++- src/app/ui/editor/editor.cpp | 5 ++-- src/app/ui/editor/standby_state.cpp | 25 ++++++++++------ src/app/util/wrap_value.h | 12 +++++++- src/doc/sprite.cpp | 20 +++++++++---- src/doc/sprite.h | 2 +- src/render/get_sprite_pixel.cpp | 20 +++++++++---- src/render/get_sprite_pixel.h | 10 +++++-- src/render/render.cpp | 4 +-- src/render/render.h | 2 +- 14 files changed, 132 insertions(+), 49 deletions(-) diff --git a/src/app/color_picker.cpp b/src/app/color_picker.cpp index 3dedc49e5c..31c1a3a5c4 100644 --- a/src/app/color_picker.cpp +++ b/src/app/color_picker.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -30,10 +30,12 @@ ColorPicker::ColorPicker() } void ColorPicker::pickColor(const doc::Site& site, - const gfx::Point& _pos, Mode mode) + const gfx::PointF& _pos, + const render::Projection& proj, + const Mode mode) { const doc::Sprite* sprite = site.sprite(); - gfx::Point pos = _pos; + gfx::PointF pos = _pos; m_alpha = 255; m_color = app::Color::fromMask(); @@ -44,17 +46,18 @@ void ColorPicker::pickColor(const doc::Site& site, DocumentPreferences& docPref = Preferences::instance().document(doc); if (int(docPref.tiled.mode()) & int(filters::TiledMode::X_AXIS)) - pos.x = wrap_value(pos.x, site.sprite()->width()); + pos.x = wrap_value(pos.x, site.sprite()->width()); if (int(docPref.tiled.mode()) & int(filters::TiledMode::Y_AXIS)) - pos.y = wrap_value(pos.y, site.sprite()->height()); + pos.y = wrap_value(pos.y, site.sprite()->height()); } // Get the color from the image if (mode == FromComposition) { // Pick from the composed image m_color = app::Color::fromImage( sprite->pixelFormat(), - render::get_sprite_pixel(sprite, pos.x, pos.y, site.frame())); + render::get_sprite_pixel(sprite, pos.x, pos.y, + site.frame(), proj)); doc::CelList cels; sprite->pickCels(pos.x, pos.y, site.frame(), 128, cels); @@ -62,12 +65,27 @@ void ColorPicker::pickColor(const doc::Site& site, m_layer = cels.front()->layer(); } else { // Pick from the current layer - int u, v; - doc::Image* image = site.image(&u, &v, NULL); - gfx::Point pt(pos.x-u, pos.y-v); - - if (image && image->bounds().contains(pt)) { - doc::color_t imageColor = get_pixel(image, pt.x, pt.y); + const Cel* cel = site.cel(); + if (cel) { + gfx::RectF celBounds; + if (cel->layer()->isReference()) + celBounds = cel->boundsF(); + else + celBounds = cel->bounds(); + + const doc::Image* image = cel->image(); + + if (!celBounds.contains(pos)) + return; + + pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w; + pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h; + const gfx::Point ipos(pos); + if (!image->bounds().contains(ipos)) + return; + + const doc::color_t imageColor = + get_pixel(image, ipos.x, ipos.y); switch (image->pixelFormat()) { case IMAGE_RGB: diff --git a/src/app/color_picker.h b/src/app/color_picker.h index 987d0be967..9a798157e5 100644 --- a/src/app/color_picker.h +++ b/src/app/color_picker.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -16,6 +16,10 @@ namespace doc { class Site; } +namespace render { + class Projection; +} + namespace app { class ColorPicker { @@ -25,7 +29,9 @@ namespace app { ColorPicker(); void pickColor(const doc::Site& site, - const gfx::Point& pos, Mode mode); + const gfx::PointF& pos, + const render::Projection& proj, + const Mode mode); app::Color color() const { return m_color; } int alpha() const { return m_alpha; } diff --git a/src/app/commands/cmd_eyedropper.cpp b/src/app/commands/cmd_eyedropper.cpp index 5075e6281a..d5251f7a5a 100644 --- a/src/app/commands/cmd_eyedropper.cpp +++ b/src/app/commands/cmd_eyedropper.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -40,7 +40,8 @@ EyedropperCommand::EyedropperCommand() } void EyedropperCommand::pickSample(const doc::Site& site, - const gfx::Point& pixelPos, + const gfx::PointF& pixelPos, + const render::Projection& proj, app::Color& color) { // Check if we've to grab alpha channel or the merged color. @@ -51,6 +52,7 @@ void EyedropperCommand::pickSample(const doc::Site& site, ColorPicker picker; picker.pickColor(site, pixelPos, + proj, (allLayers ? ColorPicker::FromComposition: ColorPicker::FromActiveLayer)); @@ -171,7 +173,7 @@ void EyedropperCommand::onExecute(Context* context) } // Pixel position to get - gfx::Point pixelPos = editor->screenToEditor(ui::get_mouse_position()); + gfx::PointF pixelPos = editor->screenToEditorF(ui::get_mouse_position()); // Start with fg/bg color Preferences& pref = Preferences::instance(); @@ -179,7 +181,10 @@ void EyedropperCommand::onExecute(Context* context) m_background ? pref.colorBar.bgColor(): pref.colorBar.fgColor(); - pickSample(editor->getSite(), pixelPos, color); + pickSample(editor->getSite(), + pixelPos, + editor->projection(), + color); if (m_background) pref.colorBar.bgColor(color); diff --git a/src/app/commands/cmd_eyedropper.h b/src/app/commands/cmd_eyedropper.h index 954b0ee7f5..5747a9fe8f 100644 --- a/src/app/commands/cmd_eyedropper.h +++ b/src/app/commands/cmd_eyedropper.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -15,6 +15,10 @@ namespace doc { class Site; } +namespace render { + class Projection; +} + namespace app { class EyedropperCommand : public Command { @@ -24,7 +28,8 @@ namespace app { // Returns the color in the given sprite pos. void pickSample(const doc::Site& site, - const gfx::Point& pixelPos, + const gfx::PointF& pixelPos, + const render::Projection& proj, app::Color& color); protected: diff --git a/src/app/commands/cmd_mask_content.cpp b/src/app/commands/cmd_mask_content.cpp index 9b57bc2f83..f9f7ee3c0d 100644 --- a/src/app/commands/cmd_mask_content.cpp +++ b/src/app/commands/cmd_mask_content.cpp @@ -14,9 +14,11 @@ #include "app/color_utils.h" #include "app/commands/command.h" #include "app/context_access.h" +#include "app/modules/editors.h" #include "app/modules/gui.h" #include "app/tools/tool_box.h" #include "app/transaction.h" +#include "app/ui/editor/editor.h" #include "app/ui/toolbar.h" #include "doc/algorithm/shrink_bounds.h" #include "doc/cel.h" @@ -64,7 +66,10 @@ void MaskContentCommand::onExecute(Context* context) gfx::Color color; if (writer.layer()->isBackground()) { ColorPicker picker; - picker.pickColor(*writer.site(), gfx::Point(0, 0), ColorPicker::FromComposition); + picker.pickColor(*writer.site(), + gfx::PointF(0.0, 0.0), + current_editor->projection(), + ColorPicker::FromComposition); color = color_utils::color_for_layer(picker.color(), writer.layer()); } else diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 66b1f97f9b..b283eedbeb 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -1232,10 +1232,11 @@ app::Color Editor::getColorByPosition(const gfx::Point& mousePos) { Site site = getSite(); if (site.sprite()) { - gfx::Point editorPos = screenToEditor(mousePos); + gfx::PointF editorPos = screenToEditorF(mousePos); ColorPicker picker; - picker.pickColor(site, editorPos, ColorPicker::FromComposition); + picker.pickColor(site, editorPos, m_proj, + ColorPicker::FromComposition); return picker.color(); } else diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 31b7cd8b84..e1669efe54 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -178,10 +178,11 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) if (clickedInk->isCelMovement()) { // Handle "Auto Select Layer" if (editor->isAutoSelectLayer()) { - gfx::Point cursor = editor->screenToEditor(msg->position()); - + gfx::PointF cursor = editor->screenToEditorF(msg->position()); ColorPicker picker; - picker.pickColor(site, cursor, ColorPicker::FromComposition); + picker.pickColor(site, cursor, + editor->projection(), + ColorPicker::FromComposition); auto range = App::instance()->timeline()->range(); @@ -447,7 +448,7 @@ bool StandbyState::onUpdateStatusBar(Editor* editor) { tools::Ink* ink = editor->getCurrentEditorInk(); const Sprite* sprite = editor->sprite(); - gfx::Point spritePos = editor->screenToEditor(ui::get_mouse_position()); + gfx::PointF spritePos = editor->screenToEditorF(ui::get_mouse_position()); if (!sprite) { StatusBar::instance()->clearText(); @@ -456,10 +457,15 @@ bool StandbyState::onUpdateStatusBar(Editor* editor) else if (ink->isEyedropper()) { EyedropperCommand cmd; app::Color color = Preferences::instance().colorBar.fgColor(); - cmd.pickSample(editor->getSite(), spritePos, color); + cmd.pickSample(editor->getSite(), + spritePos, + editor->projection(), + color); char buf[256]; - sprintf(buf, " :pos: %d %d", spritePos.x, spritePos.y); + sprintf(buf, " :pos: %d %d", + int(spritePos.x), + int(spritePos.y)); StatusBar::instance()->showColor(0, buf, color); } @@ -471,7 +477,8 @@ bool StandbyState::onUpdateStatusBar(Editor* editor) char buf[1024]; sprintf( buf, ":pos: %d %d :%s: %d %d", - spritePos.x, spritePos.y, + int(spritePos.x), + int(spritePos.y), (mask ? "selsize": "size"), (mask ? mask->bounds().w: sprite->width()), (mask ? mask->bounds().h: sprite->height())); @@ -485,8 +492,8 @@ bool StandbyState::onUpdateStatusBar(Editor* editor) if (editor->docPref().show.grid()) { auto gb = editor->docPref().grid.bounds(); - int col = (spritePos.x - (gb.x % gb.w)) / gb.w; - int row = (spritePos.y - (gb.y % gb.h)) / gb.h; + int col = (int(spritePos.x) - (gb.x % gb.w)) / gb.w; + int row = (int(spritePos.y) - (gb.y % gb.h)) / gb.h; sprintf( buf+std::strlen(buf), " :grid: %d %d", col, row); } diff --git a/src/app/util/wrap_value.h b/src/app/util/wrap_value.h index 73d0b8f203..ddb02d1089 100644 --- a/src/app/util/wrap_value.h +++ b/src/app/util/wrap_value.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -8,6 +8,8 @@ #define APP_WRAP_VALUE_H_INCLUDED #pragma once +#include + namespace app { template @@ -18,6 +20,14 @@ namespace app { return x % size; } + template<> + inline double wrap_value(const double x, const double size) { + if (x < 0.0) + return size - std::fmod(-(x+1.0), size) - 1.0; + else + return std::fmod(x, size); + } + } // namespace app #endif diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index 00e8718f8b..c8f338c880 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -449,9 +449,10 @@ void Sprite::remapImages(frame_t frameFrom, frame_t frameTo, const Remap& remap) ////////////////////////////////////////////////////////////////////// // Drawing -void Sprite::pickCels(int x, int y, frame_t frame, int opacityThreshold, CelList& cels) const +void Sprite::pickCels(double x, double y, frame_t frame, int opacityThreshold, CelList& cels) const { LayerList layers = allVisibleLayers(); + gfx::PointF pos(x, y); for (int i=(int)layers.size()-1; i>=0; --i) { Layer* layer = layers[i]; @@ -466,13 +467,22 @@ void Sprite::pickCels(int x, int y, frame_t frame, int opacityThreshold, CelList if (!image) continue; - if (!cel->bounds().contains(gfx::Point(x, y))) + gfx::RectF celBounds; + if (cel->layer()->isReference()) + celBounds = cel->boundsF(); + else + celBounds = cel->bounds(); + + if (!celBounds.contains(pos)) continue; - color_t color = get_pixel(image, - x - cel->x(), - y - cel->y()); + const gfx::Point ipos( + (pos.x-celBounds.x)*image->width()/celBounds.w, + (pos.y-celBounds.y)*image->height()/celBounds.h); + if (!image->bounds().contains(ipos)) + continue; + const color_t color = get_pixel(image, ipos.x, ipos.y); bool isOpaque = true; switch (image->pixelFormat()) { diff --git a/src/doc/sprite.h b/src/doc/sprite.h index fd1428ba7d..5712cf33cc 100644 --- a/src/doc/sprite.h +++ b/src/doc/sprite.h @@ -140,7 +140,7 @@ namespace doc { void replaceImage(ObjectId curImageId, const ImageRef& newImage); void getImages(std::vector& images) const; void remapImages(frame_t frameFrom, frame_t frameTo, const Remap& remap); - void pickCels(int x, int y, frame_t frame, int opacityThreshold, CelList& cels) const; + void pickCels(double x, double y, frame_t frame, int opacityThreshold, CelList& cels) const; //////////////////////////////////////// // Iterators diff --git a/src/render/get_sprite_pixel.cpp b/src/render/get_sprite_pixel.cpp index 99ed8b54f9..8c55f32801 100644 --- a/src/render/get_sprite_pixel.cpp +++ b/src/render/get_sprite_pixel.cpp @@ -1,5 +1,5 @@ // Aseprite Render Library -// Copyright (c) 2001-2014 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -16,15 +16,25 @@ namespace render { using namespace doc; -color_t get_sprite_pixel(const Sprite* sprite, int x, int y, frame_t frame) +color_t get_sprite_pixel(const Sprite* sprite, + const double x, + const double y, + const frame_t frame, + const Projection& proj) { color_t color = 0; - if ((x >= 0) && (y >= 0) && (x < sprite->width()) && (y < sprite->height())) { + if ((x >= 0.0) && (x < sprite->width()) && + (y >= 0.0) && (y < sprite->height())) { base::UniquePtr image(Image::create(sprite->pixelFormat(), 1, 1)); - render::Render().renderSprite(image, sprite, frame, - gfx::Clip(0, 0, x, y, 1, 1)); + render::Render render; + render.setProjection(proj); + render.renderSprite( + image, sprite, frame, + gfx::ClipF(0, 0, + proj.applyX(x), + proj.applyY(y), 1, 1)); color = get_pixel(image, 0, 0); } diff --git a/src/render/get_sprite_pixel.h b/src/render/get_sprite_pixel.h index ae0b32d06b..b8090ec9cd 100644 --- a/src/render/get_sprite_pixel.h +++ b/src/render/get_sprite_pixel.h @@ -1,5 +1,5 @@ // Aseprite Render Library -// Copyright (c) 2001-2014 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -17,10 +17,16 @@ namespace doc { namespace render { using namespace doc; + class Projection; + // Gets a pixel from the sprite in the specified position. If in the // specified coordinates there're background this routine will // return the 0 color (the mask-color). - color_t get_sprite_pixel(const Sprite* sprite, int x, int y, frame_t frame); + color_t get_sprite_pixel(const Sprite* sprite, + const double x, + const double y, + const frame_t frame, + const Projection& proj); } // namespace render diff --git a/src/render/render.cpp b/src/render/render.cpp index e7748334da..1bba94613c 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -576,7 +576,7 @@ void Render::renderSprite( { renderSprite( dstImage, sprite, frame, - gfx::Clip(sprite->bounds())); + gfx::ClipF(sprite->bounds())); } void Render::renderLayer( @@ -615,7 +615,7 @@ void Render::renderSprite( Image* dstImage, const Sprite* sprite, frame_t frame, - const gfx::Clip& area) + const gfx::ClipF& area) { m_sprite = sprite; diff --git a/src/render/render.h b/src/render/render.h index e70009378e..7092b41f1a 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -155,7 +155,7 @@ namespace render { Image* dstImage, const Sprite* sprite, frame_t frame, - const gfx::Clip& area); + const gfx::ClipF& area); // Extra functions void renderBackground( From bc939d56383e649af2b5e6fe99d6d530ae0df85f Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 13 Oct 2016 21:19:25 -0300 Subject: [PATCH 13/29] New eyedropper mode to pick colors from reference layers --- data/pref.xml | 1 + src/app/color_picker.cpp | 130 +++++++++++++++++++--------- src/app/color_picker.h | 6 +- src/app/commands/cmd_eyedropper.cpp | 21 +++-- src/app/ui/context_bar.cpp | 1 + src/doc/layer.cpp | 16 ++++ src/doc/layer.h | 1 + src/doc/sprite.cpp | 19 +++- src/doc/sprite.h | 8 +- 9 files changed, 146 insertions(+), 57 deletions(-) diff --git a/data/pref.xml b/data/pref.xml index 9489039818..2229acd4f6 100644 --- a/data/pref.xml +++ b/data/pref.xml @@ -55,6 +55,7 @@ + diff --git a/src/app/color_picker.cpp b/src/app/color_picker.cpp index 31c1a3a5c4..1110edfaa4 100644 --- a/src/app/color_picker.cpp +++ b/src/app/color_picker.cpp @@ -23,6 +23,37 @@ namespace app { +namespace { + +bool get_cel_pixel(const Cel* cel, + const double x, + const double y, + const frame_t frame, + color_t& output) +{ + gfx::RectF celBounds; + if (cel->layer()->isReference()) + celBounds = cel->boundsF(); + else + celBounds = cel->bounds(); + + const doc::Image* image = cel->image(); + gfx::PointF pos(x, y); + if (!celBounds.contains(pos)) + return false; + + pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w; + pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h; + const gfx::Point ipos(pos); + if (!image->bounds().contains(ipos)) + return false; + + output = get_pixel(image, ipos.x, ipos.y); + return true; +} + +} + ColorPicker::ColorPicker() : m_alpha(0) , m_layer(NULL) @@ -53,51 +84,64 @@ void ColorPicker::pickColor(const doc::Site& site, } // Get the color from the image - if (mode == FromComposition) { // Pick from the composed image - m_color = app::Color::fromImage( - sprite->pixelFormat(), - render::get_sprite_pixel(sprite, pos.x, pos.y, - site.frame(), proj)); - - doc::CelList cels; - sprite->pickCels(pos.x, pos.y, site.frame(), 128, cels); - if (!cels.empty()) - m_layer = cels.front()->layer(); - } - else { // Pick from the current layer - const Cel* cel = site.cel(); - if (cel) { - gfx::RectF celBounds; - if (cel->layer()->isReference()) - celBounds = cel->boundsF(); - else - celBounds = cel->bounds(); - - const doc::Image* image = cel->image(); - - if (!celBounds.contains(pos)) - return; - - pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w; - pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h; - const gfx::Point ipos(pos); - if (!image->bounds().contains(ipos)) - return; - - const doc::color_t imageColor = - get_pixel(image, ipos.x, ipos.y); - - switch (image->pixelFormat()) { - case IMAGE_RGB: - m_alpha = doc::rgba_geta(imageColor); - break; - case IMAGE_GRAYSCALE: - m_alpha = doc::graya_geta(imageColor); - break; + switch (mode) { + + // Pick from the composed image + case FromComposition: { + m_color = app::Color::fromImage( + sprite->pixelFormat(), + render::get_sprite_pixel(sprite, pos.x, pos.y, + site.frame(), proj)); + + doc::CelList cels; + sprite->pickCels(pos.x, pos.y, site.frame(), 128, + sprite->allVisibleLayers(), cels); + if (!cels.empty()) + m_layer = cels.front()->layer(); + break; + } + + // Pick from the current layer + case FromActiveLayer: { + const Cel* cel = site.cel(); + if (cel) { + doc::color_t imageColor; + if (!get_cel_pixel(cel, pos.x, pos.y, + site.frame(), imageColor)) + return; + + const doc::Image* image = cel->image(); + switch (image->pixelFormat()) { + case IMAGE_RGB: + m_alpha = doc::rgba_geta(imageColor); + break; + case IMAGE_GRAYSCALE: + m_alpha = doc::graya_geta(imageColor); + break; + } + + m_color = app::Color::fromImage(image->pixelFormat(), imageColor); + m_layer = const_cast(site.layer()); } + break; + } - m_color = app::Color::fromImage(image->pixelFormat(), imageColor); - m_layer = const_cast(site.layer()); + case FromFirstReferenceLayer: { + doc::CelList cels; + sprite->pickCels(pos.x, pos.y, site.frame(), 128, + sprite->allVisibleReferenceLayers(), cels); + + for (const Cel* cel : cels) { + doc::color_t imageColor; + if (get_cel_pixel(cel, pos.x, pos.y, + site.frame(), imageColor)) { + m_color = app::Color::fromImage( + cel->image()->pixelFormat(), imageColor); + m_layer = cel->layer(); + break; + } + } + break; } } } diff --git a/src/app/color_picker.h b/src/app/color_picker.h index 9a798157e5..c3ddc54417 100644 --- a/src/app/color_picker.h +++ b/src/app/color_picker.h @@ -24,7 +24,11 @@ namespace app { class ColorPicker { public: - enum Mode { FromComposition, FromActiveLayer }; + enum Mode { + FromComposition, + FromActiveLayer, + FromFirstReferenceLayer + }; ColorPicker(); diff --git a/src/app/commands/cmd_eyedropper.cpp b/src/app/commands/cmd_eyedropper.cpp index d5251f7a5a..b5727de559 100644 --- a/src/app/commands/cmd_eyedropper.cpp +++ b/src/app/commands/cmd_eyedropper.cpp @@ -46,16 +46,21 @@ void EyedropperCommand::pickSample(const doc::Site& site, { // Check if we've to grab alpha channel or the merged color. Preferences& pref = Preferences::instance(); - bool allLayers = - (pref.eyedropper.sample() == app::gen::EyedropperSample::ALL_LAYERS); + ColorPicker::Mode mode = ColorPicker::FromComposition; + switch (pref.eyedropper.sample()) { + case app::gen::EyedropperSample::ALL_LAYERS: + mode = ColorPicker::FromComposition; + break; + case app::gen::EyedropperSample::CURRENT_LAYER: + mode = ColorPicker::FromActiveLayer; + break; + case app::gen::EyedropperSample::FIRST_REFERENCE_LAYER: + mode = ColorPicker::FromFirstReferenceLayer; + break; + } ColorPicker picker; - picker.pickColor(site, - pixelPos, - proj, - (allLayers ? - ColorPicker::FromComposition: - ColorPicker::FromActiveLayer)); + picker.pickColor(site, pixelPos, proj, mode); app::gen::EyedropperChannel channel = pref.eyedropper.channel(); diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index 391c979b4e..504560c528 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -1260,6 +1260,7 @@ class ContextBar::EyedropperField : public HBox m_sample.addItem("All Layers"); m_sample.addItem("Current Layer"); + m_sample.addItem("First Reference Layer"); addChild(new Label("Pick:")); addChild(&m_channel); diff --git a/src/doc/layer.cpp b/src/doc/layer.cpp index e0f5635d9a..e9f389c9e5 100644 --- a/src/doc/layer.cpp +++ b/src/doc/layer.cpp @@ -391,6 +391,22 @@ void LayerGroup::allVisibleLayers(LayerList& list) const } } +void LayerGroup::allVisibleReferenceLayers(LayerList& list) const +{ + for (Layer* child : m_layers) { + if (!child->isVisible()) + continue; + + if (child->isGroup()) + static_cast(child)->allVisibleReferenceLayers(list); + + if (!child->isReference()) + continue; + + list.push_back(child); + } +} + void LayerGroup::allBrowsableLayers(LayerList& list) const { for (Layer* child : m_layers) { diff --git a/src/doc/layer.h b/src/doc/layer.h index 483743f62f..8bf1a9135f 100644 --- a/src/doc/layer.h +++ b/src/doc/layer.h @@ -192,6 +192,7 @@ namespace doc { void allLayers(LayerList& list) const; layer_t allLayersCount() const; void allVisibleLayers(LayerList& list) const; + void allVisibleReferenceLayers(LayerList& list) const; void allBrowsableLayers(LayerList& list) const; void getCels(CelList& cels) const override; diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index c8f338c880..b9d296b596 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -449,13 +449,17 @@ void Sprite::remapImages(frame_t frameFrom, frame_t frameTo, const Remap& remap) ////////////////////////////////////////////////////////////////////// // Drawing -void Sprite::pickCels(double x, double y, frame_t frame, int opacityThreshold, CelList& cels) const +void Sprite::pickCels(const double x, + const double y, + const frame_t frame, + const int opacityThreshold, + const LayerList& layers, + CelList& cels) const { - LayerList layers = allVisibleLayers(); gfx::PointF pos(x, y); for (int i=(int)layers.size()-1; i>=0; --i) { - Layer* layer = layers[i]; + const Layer* layer = layers[i]; if (!layer->isImage()) continue; @@ -463,7 +467,7 @@ void Sprite::pickCels(double x, double y, frame_t frame, int opacityThreshold, C if (!cel) continue; - Image* image = cel->image(); + const Image* image = cel->image(); if (!image) continue; @@ -522,6 +526,13 @@ LayerList Sprite::allVisibleLayers() const return list; } +LayerList Sprite::allVisibleReferenceLayers() const +{ + LayerList list; + m_root->allVisibleReferenceLayers(list); + return list; +} + LayerList Sprite::allBrowsableLayers() const { LayerList list; diff --git a/src/doc/sprite.h b/src/doc/sprite.h index 5712cf33cc..4d02bb9983 100644 --- a/src/doc/sprite.h +++ b/src/doc/sprite.h @@ -140,13 +140,19 @@ namespace doc { void replaceImage(ObjectId curImageId, const ImageRef& newImage); void getImages(std::vector& images) const; void remapImages(frame_t frameFrom, frame_t frameTo, const Remap& remap); - void pickCels(double x, double y, frame_t frame, int opacityThreshold, CelList& cels) const; + void pickCels(const double x, + const double y, + const frame_t frame, + const int opacityThreshold, + const LayerList& layers, + CelList& cels) const; //////////////////////////////////////// // Iterators LayerList allLayers() const; LayerList allVisibleLayers() const; + LayerList allVisibleReferenceLayers() const; LayerList allBrowsableLayers() const; CelsRange cels() const; From 409a546089f2f1bd4a3c03653bf07151ab9c6f05 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 17 Oct 2016 13:41:13 -0300 Subject: [PATCH 14/29] Show reference layers as 'Reference layer' in status bar --- src/app/ui/timeline.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/ui/timeline.cpp b/src/app/ui/timeline.cpp index e09ee50fcb..81094099a8 100644 --- a/src/app/ui/timeline.cpp +++ b/src/app/ui/timeline.cpp @@ -2629,7 +2629,9 @@ void Timeline::updateStatusBar(ui::Message* msg) case PART_LAYER_TEXT: if (layer != NULL) { - sb->setStatusText(0, "Layer '%s' [%s%s]", + sb->setStatusText( + 0, "%s '%s' [%s%s]", + layer->isReference() ? "Reference layer": "Layer", layer->name().c_str(), layer->isVisible() ? "visible": "hidden", layer->isEditable() ? "": " locked"); From 6ce73a831a3f7dad7cca755f2199eb17e50facf1 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 17 Oct 2016 13:48:52 -0300 Subject: [PATCH 15/29] Don't allow draw/modify reference layers --- src/app/cmd/background_from_layer.cpp | 3 ++- src/app/cmd/layer_from_background.cpp | 3 ++- src/app/commands/cmd_background_from_layer.cpp | 6 ++++-- src/app/commands/cmd_layer_from_background.cpp | 6 ++++-- src/app/context_flags.cpp | 3 +++ src/app/context_flags.h | 1 + src/app/ui/editor/editor.cpp | 3 ++- src/app/ui/editor/standby_state.cpp | 9 +++++++-- src/app/ui/editor/tool_loop_impl.cpp | 9 ++++++++- 9 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/app/cmd/background_from_layer.cpp b/src/app/cmd/background_from_layer.cpp index caf9e31a4e..18dd9a1bc7 100644 --- a/src/app/cmd/background_from_layer.cpp +++ b/src/app/cmd/background_from_layer.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -33,6 +33,7 @@ BackgroundFromLayer::BackgroundFromLayer(Layer* layer) ASSERT(layer); ASSERT(layer->isVisible()); ASSERT(layer->isEditable()); + ASSERT(!layer->isReference()); ASSERT(layer->sprite() != NULL); ASSERT(layer->sprite()->backgroundLayer() == NULL); } diff --git a/src/app/cmd/layer_from_background.cpp b/src/app/cmd/layer_from_background.cpp index edf756fc6f..3b37ee46b8 100644 --- a/src/app/cmd/layer_from_background.cpp +++ b/src/app/cmd/layer_from_background.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -24,6 +24,7 @@ LayerFromBackground::LayerFromBackground(Layer* layer) ASSERT(layer->isVisible()); ASSERT(layer->isEditable()); ASSERT(layer->isBackground()); + ASSERT(!layer->isReference()); ASSERT(layer->sprite() != NULL); ASSERT(layer->sprite()->backgroundLayer() != NULL); diff --git a/src/app/commands/cmd_background_from_layer.cpp b/src/app/commands/cmd_background_from_layer.cpp index 026d993acd..f350e2b2c8 100644 --- a/src/app/commands/cmd_background_from_layer.cpp +++ b/src/app/commands/cmd_background_from_layer.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -44,7 +44,9 @@ bool BackgroundFromLayerCommand::onEnabled(Context* context) ContextFlags::ActiveLayerIsEditable | ContextFlags::ActiveLayerIsImage) && // Doesn't have a background layer - !context->checkFlags(ContextFlags::HasBackgroundLayer); + !context->checkFlags(ContextFlags::HasBackgroundLayer) && + // Isn't a reference layer + !context->checkFlags(ContextFlags::ActiveLayerIsReference); } void BackgroundFromLayerCommand::onExecute(Context* context) diff --git a/src/app/commands/cmd_layer_from_background.cpp b/src/app/commands/cmd_layer_from_background.cpp index 906bf28453..63cb3bcca1 100644 --- a/src/app/commands/cmd_layer_from_background.cpp +++ b/src/app/commands/cmd_layer_from_background.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -44,7 +44,9 @@ bool LayerFromBackgroundCommand::onEnabled(Context* context) ContextFlags::ActiveLayerIsVisible | ContextFlags::ActiveLayerIsEditable | ContextFlags::ActiveLayerIsImage | - ContextFlags::ActiveLayerIsBackground); + ContextFlags::ActiveLayerIsBackground) && + // Isn't a reference layer + !context->checkFlags(ContextFlags::ActiveLayerIsReference); } void LayerFromBackgroundCommand::onExecute(Context* context) diff --git a/src/app/context_flags.cpp b/src/app/context_flags.cpp index 38c7094760..c054a5ba61 100644 --- a/src/app/context_flags.cpp +++ b/src/app/context_flags.cpp @@ -93,6 +93,9 @@ void ContextFlags::updateFlagsFromSite(const Site& site) if (layer->isEditableHierarchy()) m_flags |= ActiveLayerIsEditable; + if (layer->isReference()) + m_flags |= ActiveLayerIsReference; + if (layer->isImage()) { m_flags |= ActiveLayerIsImage; diff --git a/src/app/context_flags.h b/src/app/context_flags.h index 0718e54da0..f71233257d 100644 --- a/src/app/context_flags.h +++ b/src/app/context_flags.h @@ -34,6 +34,7 @@ namespace app { ActiveLayerIsBackground = 1 << 10, ActiveLayerIsVisible = 1 << 11, ActiveLayerIsEditable = 1 << 12, + ActiveLayerIsReference = 1 << 13, }; ContextFlags(); diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index b283eedbeb..791b0b0a11 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -1542,7 +1542,8 @@ bool Editor::canDraw() return (m_layer != NULL && m_layer->isImage() && m_layer->isVisibleHierarchy() && - m_layer->isEditableHierarchy()); + m_layer->isEditableHierarchy() && + !m_layer->isReference()); } bool Editor::isInsideSelection() diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index e1669efe54..b27b9c245f 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -202,8 +202,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) } } - if ((layer) && - (layer->type() == ObjectType::LayerImage)) { + if (layer && layer->isImage()) { // TODO we should be able to move the `Background' with tiled mode if (layer->isBackground()) { StatusBar::instance()->showTip(1000, @@ -274,6 +273,12 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) return true; } + if (layer->isReference()) { + StatusBar::instance()->showTip(1000, + "Layer '%s' is reference, cannot be transformed", layer->name().c_str()); + return true; + } + // Change to MovingPixelsState transformSelection(editor, msg, MoveHandle); return true; diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 1cee210e5b..1bf4711031 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -512,6 +512,12 @@ tools::ToolLoop* create_tool_loop(Editor* editor, Context* context) 1000, "Layer '%s' is locked", layer->name().c_str()); return nullptr; } + // If the active layer is reference. + else if (layer->isReference()) { + StatusBar::instance()->showTip( + 1000, "Layer '%s' is reference, cannot be modified", layer->name().c_str()); + return nullptr; + } } // Get fg/bg colors @@ -620,7 +626,8 @@ tools::ToolLoop* create_tool_loop_preview( Layer* layer = editor->layer(); if (!layer || !layer->isVisibleHierarchy() || - !layer->isEditableHierarchy()) { + !layer->isEditableHierarchy() || + layer->isReference()) { return nullptr; } From 609946c33f11b81424a86cd5ee527e705fd5f341 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 25 Oct 2016 19:15:36 -0300 Subject: [PATCH 16/29] Support resizing a reference cel with Move tool --- src/app/ui/editor/moving_cel_state.cpp | 94 +++++++++++++++++++------- src/app/ui/editor/moving_cel_state.h | 10 ++- src/app/ui/editor/standby_state.cpp | 31 ++++++++- src/app/ui/editor/standby_state.h | 1 + 4 files changed, 107 insertions(+), 29 deletions(-) diff --git a/src/app/ui/editor/moving_cel_state.cpp b/src/app/ui/editor/moving_cel_state.cpp index 234a97c534..b75fb20e3c 100644 --- a/src/app/ui/editor/moving_cel_state.cpp +++ b/src/app/ui/editor/moving_cel_state.cpp @@ -33,10 +33,16 @@ namespace app { using namespace ui; -MovingCelState::MovingCelState(Editor* editor, MouseMessage* msg) +MovingCelState::MovingCelState(Editor* editor, + MouseMessage* msg, + const HandleType handle) : m_reader(UIContext::instance(), 500) + , m_celOffset(0.0, 0.0) + , m_celScale(1.0, 1.0) , m_canceled(false) , m_hasReference(false) + , m_scaled(false) + , m_handle(handle) { ContextWriter writer(m_reader); Document* document = editor->document(); @@ -44,11 +50,14 @@ MovingCelState::MovingCelState(Editor* editor, MouseMessage* msg) LayerImage* layer = static_cast(editor->layer()); ASSERT(layer->isImage()); - Cel* currentCel = layer->cel(editor->frame()); - ASSERT(currentCel); // The cel cannot be null + m_cel = layer->cel(editor->frame()); + ASSERT(m_cel); // The cel cannot be null if (!range.enabled()) - range = DocumentRange(currentCel); + range = DocumentRange(m_cel); + + if (m_cel) + m_celMainSize = m_cel->boundsF().size(); // Record start positions of all cels in selected range for (Cel* cel : get_unique_cels(writer.sprite(), range)) { @@ -68,7 +77,6 @@ MovingCelState::MovingCelState(Editor* editor, MouseMessage* msg) } m_cursorStart = editor->screenToEditorF(msg->position()); - m_celOffset = gfx::PointF(0, 0); editor->captureMouse(); // Hide the mask (temporarily, until mouse-up event) @@ -85,7 +93,7 @@ bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg) // Here we put back the cel into its original coordinate (so we can // add an undoer before). - if ((m_hasReference && m_celOffset != gfx::PointF(0, 0)) || + if ((m_hasReference && (m_celOffset != gfx::PointF(0, 0) || m_scaled)) || (!m_hasReference && gfx::Point(m_celOffset) != gfx::Point(0, 0))) { // Put the cels in the original position. for (size_t i=0; iboundsF(); celBounds.x += m_celOffset.x; celBounds.y += m_celOffset.y; + if (m_scaled) { + celBounds.w *= m_celScale.w; + celBounds.h *= m_celScale.h; + } transaction.execute(new cmd::SetCelBoundsF(cel, celBounds)); } else { @@ -150,15 +162,35 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) { gfx::PointF newCursorPos = editor->screenToEditorF(msg->position()); - m_celOffset = newCursorPos - m_cursorStart; + switch (m_handle) { - if (int(editor->getCustomizationDelegate() - ->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::LockAxis)) { - if (ABS(m_celOffset.x) < ABS(m_celOffset.y)) { - m_celOffset.x = 0; - } - else { - m_celOffset.y = 0; + case MoveHandle: + m_celOffset = newCursorPos - m_cursorStart; + if (int(editor->getCustomizationDelegate() + ->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::LockAxis)) { + if (ABS(m_celOffset.x) < ABS(m_celOffset.y)) { + m_celOffset.x = 0; + } + else { + m_celOffset.y = 0; + } + } + break; + + case ScaleSEHandle: { + gfx::PointF delta(newCursorPos - m_cursorStart); + m_celScale.w = 1.0 + (delta.x / m_celMainSize.w); + m_celScale.h = 1.0 + (delta.y / m_celMainSize.h); + if (m_celScale.w < 1.0/m_celMainSize.w) m_celScale.w = 1.0/m_celMainSize.w; + if (m_celScale.h < 1.0/m_celMainSize.h) m_celScale.h = 1.0/m_celMainSize.h; + + if (int(editor->getCustomizationDelegate() + ->getPressedKeyAction(KeyContext::ScalingSelection) & KeyAction::MaintainAspectRatio)) { + m_celScale.w = m_celScale.h = MAX(m_celScale.w, m_celScale.h); + } + + m_scaled = true; + break; } } @@ -168,6 +200,11 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) celBounds.x += m_celOffset.x; celBounds.y += m_celOffset.y; + if (m_scaled) { + celBounds.w *= m_celScale.w; + celBounds.h *= m_celScale.h; + } + if (cel->layer()->isReference()) cel->setBoundsF(celBounds); else @@ -184,22 +221,29 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) bool MovingCelState::onUpdateStatusBar(Editor* editor) { if (m_hasReference) { - StatusBar::instance()->setStatusText - (0, - ":pos: %.2f %.2f :offset: %.2f %.2f", - m_cursorStart.x, - m_cursorStart.y, - m_celOffset.x, - m_celOffset.y); + if (m_scaled && m_cel) { + StatusBar::instance()->setStatusText + (0, + ":pos: %.2f %.2f :offset: %.2f %.2f :size: %.2f%% %.2f%%", + m_cursorStart.x, m_cursorStart.y, + m_celOffset.x, m_celOffset.y, + 100.0*m_celScale.w*m_celMainSize.w/m_cel->image()->width(), + 100.0*m_celScale.h*m_celMainSize.h/m_cel->image()->height()); + } + else { + StatusBar::instance()->setStatusText + (0, + ":pos: %.2f %.2f :offset: %.2f %.2f", + m_cursorStart.x, m_cursorStart.y, + m_celOffset.x, m_celOffset.y); + } } else { StatusBar::instance()->setStatusText (0, ":pos: %3d %3d :offset: %3d %3d", - int(m_cursorStart.x), - int(m_cursorStart.y), - int(m_celOffset.x), - int(m_celOffset.y)); + int(m_cursorStart.x), int(m_cursorStart.y), + int(m_celOffset.x), int(m_celOffset.y)); } return true; diff --git a/src/app/ui/editor/moving_cel_state.h b/src/app/ui/editor/moving_cel_state.h index d8df83b2ae..9459272b57 100644 --- a/src/app/ui/editor/moving_cel_state.h +++ b/src/app/ui/editor/moving_cel_state.h @@ -11,6 +11,7 @@ #include "app/ui/editor/standby_state.h" #include "app/context_access.h" +#include "app/ui/editor/handle_type.h" #include "doc/cel_list.h" #include @@ -24,7 +25,9 @@ namespace app { class MovingCelState : public StandbyState { public: - MovingCelState(Editor* editor, ui::MouseMessage* msg); + MovingCelState(Editor* editor, + ui::MouseMessage* msg, + const HandleType handle); virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override; virtual bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override; @@ -34,13 +37,18 @@ namespace app { private: ContextReader m_reader; + Cel* m_cel; CelList m_celList; std::vector m_celStarts; gfx::PointF m_cursorStart; gfx::PointF m_celOffset; + gfx::SizeF m_celMainSize; + gfx::SizeF m_celScale; bool m_canceled; bool m_maskVisible; bool m_hasReference; + bool m_scaled; + HandleType m_handle; }; } // namespace app diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index b27b9c245f..1590adb55a 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -117,7 +117,9 @@ void StandbyState::onActiveToolChange(Editor* editor, tools::Tool* tool) // If the user change from a selection tool to a non-selection tool, // or viceversa, we've to show or hide the transformation handles. bool needDecorators = (tool && tool->getInk(0)->isSelection()); - if (m_transformSelectionHandlesAreVisible != needDecorators) { + if (m_transformSelectionHandlesAreVisible != needDecorators || + !editor->layer() || + !editor->layer()->isReference()) { m_transformSelectionHandlesAreVisible = false; editor->invalidate(); } @@ -222,7 +224,11 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) } else { // Change to MovingCelState - editor->setState(EditorStatePtr(new MovingCelState(editor, msg))); + HandleType handle = MoveHandle; + if (resizeCelBounds(editor).contains(msg->position())) + handle = ScaleSEHandle; + + editor->setState(EditorStatePtr(new MovingCelState(editor, msg, handle))); } } @@ -418,7 +424,10 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) return true; } else if (ink->isCelMovement()) { - editor->showMouseCursor(kMoveCursor); + if (resizeCelBounds(editor).contains(mouseScreenPos)) + editor->showMouseCursor(kSizeSECursor); + else + editor->showMouseCursor(kMoveCursor); return true; } else if (ink->isSlice()) { @@ -603,6 +612,22 @@ void StandbyState::onPivotChange(Editor* editor) } } +gfx::Rect StandbyState::resizeCelBounds(Editor* editor) const +{ + gfx::Rect bounds; + Cel* refCel = (editor->layer() && + editor->layer()->isReference() ? + editor->layer()->cel(editor->frame()): nullptr); + if (refCel) { + bounds = editor->editorToScreen(refCel->boundsF()); + bounds.w /= 4; + bounds.h /= 4; + bounds.x += 3*bounds.w; + bounds.y += 3*bounds.h; + } + return bounds; +} + ////////////////////////////////////////////////////////////////////// // Decorator diff --git a/src/app/ui/editor/standby_state.h b/src/app/ui/editor/standby_state.h index 09ec563bfb..fe3951ec91 100644 --- a/src/app/ui/editor/standby_state.h +++ b/src/app/ui/editor/standby_state.h @@ -74,6 +74,7 @@ namespace app { private: void transformSelection(Editor* editor, ui::MouseMessage* msg, HandleType handle); void onPivotChange(Editor* editor); + gfx::Rect resizeCelBounds(Editor* editor) const; Decorator* m_decorator; obs::scoped_connection m_pivotVisConn; From bcb775a88bb32334ed389ae369c298f540755a85 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 26 Oct 2016 15:16:22 -0300 Subject: [PATCH 17/29] Render reference layers only in the sprite editor --- src/app/commands/cmd_fullscreen_preview.cpp | 1 + src/app/ui/editor/editor.cpp | 1 + src/render/render.cpp | 16 +++++++++++++++- src/render/render.h | 7 +++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/app/commands/cmd_fullscreen_preview.cpp b/src/app/commands/cmd_fullscreen_preview.cpp index a6ea7d7d11..ea42b66819 100644 --- a/src/app/commands/cmd_fullscreen_preview.cpp +++ b/src/app/commands/cmd_fullscreen_preview.cpp @@ -173,6 +173,7 @@ class PreviewWindow : public Window { virtual void onPaint(PaintEvent& ev) override { Graphics* g = ev.graphics(); AppRender& render = m_editor->renderEngine(); + render.setRefLayersVisiblity(false); render.setProjection(render::Projection()); render.disableOnionskin(); render.setBgType(render::BgType::TRANSPARENT); diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 791b0b0a11..3723abbe0b 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -546,6 +546,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite // Create a temporary RGB bitmap to draw all to it rendered.reset(Image::create(IMAGE_RGB, rc.w, rc.h, m_renderBuffer)); + m_renderEngine.setRefLayersVisiblity(true); m_renderEngine.setProjection(m_proj); m_renderEngine.setupBackground(m_document, rendered->pixelFormat()); m_renderEngine.disableOnionskin(); diff --git a/src/render/render.cpp b/src/render/render.cpp index 1bba94613c..cb51b10c51 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -474,7 +474,8 @@ bool has_visible_reference_layers(const LayerGroup* group) } // anonymous namespace Render::Render() - : m_sprite(NULL) + : m_flags(0) + , m_sprite(nullptr) , m_currentLayer(NULL) , m_currentFrame(0) , m_extraType(ExtraType::NONE) @@ -491,6 +492,14 @@ Render::Render() { } +void Render::setRefLayersVisiblity(const bool visible) +{ + if (visible) + m_flags |= Flags::ShowRefLayers; + else + m_flags &= ~Flags::ShowRefLayers; +} + void Render::setProjection(const Projection& projection) { m_proj = projection; @@ -882,6 +891,11 @@ void Render::renderLayer( (!render_transparent && !layer->isBackground())) break; + // Ignore reference layers + if (!(m_flags & Flags::ShowRefLayers) && + layer->isReference()) + break; + const Cel* cel = layer->cel(frame); if (cel) { Palette* pal = m_sprite->palette(frame); diff --git a/src/render/render.h b/src/render/render.h index 7092b41f1a..4f47802810 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -97,9 +97,15 @@ namespace render { const double sy); class Render { + enum Flags { + ShowRefLayers = 1, + }; + public: Render(); + void setRefLayersVisiblity(const bool visible); + // Viewport configuration void setProjection(const Projection& projection); @@ -213,6 +219,7 @@ namespace render { const PixelFormat srcFormat, const Layer* layer); + int m_flags; const Sprite* m_sprite; const Layer* m_currentLayer; frame_t m_currentFrame; From f895938dbb9718bc46ce79e9f4996799dfdf424f Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 1 Nov 2016 19:02:11 -0300 Subject: [PATCH 18/29] Fix adding a reference layer with a different color mode --- src/app/commands/cmd_new_layer.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp index 6aff9ca9cd..5bb70ea326 100644 --- a/src/app/commands/cmd_new_layer.cpp +++ b/src/app/commands/cmd_new_layer.cpp @@ -24,6 +24,7 @@ #include "doc/layer.h" #include "doc/primitives.h" #include "doc/sprite.h" +#include "render/quantization.h" #include "render/render.h" #include "ui/ui.h" @@ -247,14 +248,33 @@ void NewLayerCommand::onExecute(Context* context) // Paste the given sprite as flatten for (frame_t fr=0; fr<=pasteSpr->lastFrame(); ++fr) { - ImageRef pasteImage(Image::create(sprite->pixelFormat(), - pasteSpr->width(), - pasteSpr->height())); + ImageRef pasteImage( + Image::create( + pasteSpr->pixelFormat(), + pasteSpr->width(), + pasteSpr->height())); clear_image(pasteImage.get(), pasteSpr->transparentColor()); render.renderSprite(pasteImage.get(), pasteSpr, fr); frame_t dstFrame = writer.frame()+fr; + + if (sprite->pixelFormat() != pasteSpr->pixelFormat() || + sprite->pixelFormat() == IMAGE_INDEXED) { + ImageRef pasteImageConv( + render::convert_pixel_format( + pasteImage.get(), + nullptr, + sprite->pixelFormat(), + DitheringMethod::NONE, + sprite->rgbMap(dstFrame), + pasteSpr->palette(fr), + (pasteSpr->backgroundLayer() ? true: false), + sprite->transparentColor())); + if (pasteImageConv) + pasteImage = pasteImageConv; + } + Cel* cel = layer->cel(dstFrame); if (cel) { api.replaceImage(sprite, cel->imageRef(), pasteImage); From b4747b3ed6a31a284b168c16f4b9486674554ab9 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 2 Nov 2016 15:01:31 -0300 Subject: [PATCH 19/29] Draw ref layer edges correctly (w/subpixel precision) --- src/app/ui/editor/editor.cpp | 30 +++++++++++++++++++++++++----- src/app/ui/editor/editor.h | 2 ++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 3723abbe0b..a26b7ecf8f 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -776,8 +776,14 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc) m_state->requireBrushPreview()) { Cel* cel = (m_layer ? m_layer->cel(m_frame): nullptr); if (cel) { - g->drawRect(theme->colors.editorLayerEdges(), - editorToScreen(cel->bounds()).offset(-bounds().origin())); + gfx::Rect layerEdges; + if (m_layer->isReference()) { + layerEdges = editorToScreenF(cel->boundsF()).offset(gfx::PointF(-bounds().origin())); + } + else { + layerEdges = editorToScreen(cel->bounds()).offset(-bounds().origin()); + } + g->drawRect(theme->colors.editorLayerEdges(), layerEdges); } } @@ -1028,7 +1034,6 @@ gfx::Point Editor::screenToEditor(const gfx::Point& pt) View* view = View::getView(this); Rect vp = view->viewportBounds(); Point scroll = view->viewScroll(); - return gfx::Point( m_proj.removeX(pt.x - vp.x + scroll.x - m_padding.x), m_proj.removeY(pt.y - vp.y + scroll.y - m_padding.y)); @@ -1039,7 +1044,6 @@ gfx::PointF Editor::screenToEditorF(const gfx::Point& pt) View* view = View::getView(this); Rect vp = view->viewportBounds(); Point scroll = view->viewScroll(); - return gfx::PointF( m_proj.removeX(pt.x - vp.x + scroll.x - m_padding.x), m_proj.removeY(pt.y - vp.y + scroll.y - m_padding.y)); @@ -1050,12 +1054,21 @@ Point Editor::editorToScreen(const gfx::Point& pt) View* view = View::getView(this); Rect vp = view->viewportBounds(); Point scroll = view->viewScroll(); - return Point( (vp.x - scroll.x + m_padding.x + m_proj.applyX(pt.x)), (vp.y - scroll.y + m_padding.y + m_proj.applyY(pt.y))); } +gfx::PointF Editor::editorToScreenF(const gfx::PointF& pt) +{ + View* view = View::getView(this); + Rect vp = view->viewportBounds(); + Point scroll = view->viewScroll(); + return PointF( + (vp.x - scroll.x + m_padding.x + m_proj.applyX(pt.x)), + (vp.y - scroll.y + m_padding.y + m_proj.applyY(pt.y))); +} + Rect Editor::screenToEditor(const Rect& rc) { return gfx::Rect( @@ -1070,6 +1083,13 @@ Rect Editor::editorToScreen(const Rect& rc) editorToScreen(rc.point2())); } +gfx::RectF Editor::editorToScreenF(const gfx::RectF& rc) +{ + return gfx::RectF( + editorToScreenF(rc.origin()), + editorToScreenF(rc.point2())); +} + void Editor::add_observer(EditorObserver* observer) { m_observers.add_observer(observer); diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 49706c9c1d..e121bd0191 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -152,8 +152,10 @@ namespace app { gfx::Point screenToEditor(const gfx::Point& pt); gfx::PointF screenToEditorF(const gfx::Point& pt); gfx::Point editorToScreen(const gfx::Point& pt); + gfx::PointF editorToScreenF(const gfx::PointF& pt); gfx::Rect screenToEditor(const gfx::Rect& rc); gfx::Rect editorToScreen(const gfx::Rect& rc); + gfx::RectF editorToScreenF(const gfx::RectF& rc); void add_observer(EditorObserver* observer); void remove_observer(EditorObserver* observer); From 492e71918f6dc59c91c08180614038443070d2a5 Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 2 Nov 2016 16:10:42 -0300 Subject: [PATCH 20/29] Fix undo of a new ref layer --- src/app/commands/cmd_new_layer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/commands/cmd_new_layer.cpp b/src/app/commands/cmd_new_layer.cpp index 5bb70ea326..cae90fcaab 100644 --- a/src/app/commands/cmd_new_layer.cpp +++ b/src/app/commands/cmd_new_layer.cpp @@ -21,6 +21,7 @@ #include "app/transaction.h" #include "app/ui/main_window.h" #include "app/ui/status_bar.h" +#include "app/ui_context.h" #include "doc/layer.h" #include "doc/primitives.h" #include "doc/sprite.h" @@ -136,8 +137,11 @@ void NewLayerCommand::onExecute(Context* context) context->executeCommand(openFile, params); // The user have selected another document. - if (oldActiveDocument != context->activeDocument()) + if (oldActiveDocument != context->activeDocument()) { pasteDoc = context->activeDocument(); + static_cast(context) + ->setActiveDocument(oldActiveDocument); + } } // If params specify to ask the user about the name... From 4e6832782e169c415c9e69d0afde760d62e37202 Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 3 Nov 2016 12:26:33 -0300 Subject: [PATCH 21/29] Fix rendering issues w/ref layers --- src/render/render.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/render/render.cpp b/src/render/render.cpp index cb51b10c51..7855f641a9 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -19,6 +19,8 @@ #include "gfx/clip.h" #include "gfx/region.h" +#include + namespace render { namespace { @@ -409,7 +411,10 @@ void composite_image_general( BlenderHelper blender(src, pal, blendMode); - gfx::Rect dstBounds = area.dstBounds(); + gfx::Rect dstBounds( + area.dstBounds().x, area.dstBounds().y, + std::ceil(area.dstBounds().w), + std::ceil(area.dstBounds().h)); gfx::RectF srcBounds = area.srcBounds(); int dstY = dstBounds.y; From 35c9bd3194fffe61eb539df8bd4955466ddb65fe Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 10:30:40 -0300 Subject: [PATCH 22/29] Move check that a ref layer cannot be transformed to StandbyState::transformSelection() --- src/app/ui/editor/standby_state.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 1590adb55a..aa8c5deffe 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -279,12 +279,6 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) return true; } - if (layer->isReference()) { - StatusBar::instance()->showTip(1000, - "Layer '%s' is reference, cannot be transformed", layer->name().c_str()); - return true; - } - // Change to MovingPixelsState transformSelection(editor, msg, MoveHandle); return true; @@ -541,7 +535,6 @@ void StandbyState::startSelectionTransformation(Editor* editor, void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleType handle) { Document* document = editor->document(); - for (auto docView : UIContext::instance()->getAllDocumentViews(document)) { if (docView->editor()->isMovingPixels()) { // TODO Transfer moving pixels state to this editor @@ -549,6 +542,14 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT } } + Layer* layer = editor->layer(); + if (layer && layer->isReference()) { + StatusBar::instance()->showTip( + 1000, "Layer '%s' is reference, cannot be transformed", + layer->name().c_str()); + return; + } + try { // Clear brush preview, as the extra cel will be replaced with the // transformed image. From 6bb9d599d91542a6dc88867f4599c0d89c839bf1 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 10:50:03 -0300 Subject: [PATCH 23/29] Fix "All layers" eyedropper to pick colors from ref layers too --- src/render/get_sprite_pixel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render/get_sprite_pixel.cpp b/src/render/get_sprite_pixel.cpp index 8c55f32801..a26c2c6f66 100644 --- a/src/render/get_sprite_pixel.cpp +++ b/src/render/get_sprite_pixel.cpp @@ -29,6 +29,7 @@ color_t get_sprite_pixel(const Sprite* sprite, base::UniquePtr image(Image::create(sprite->pixelFormat(), 1, 1)); render::Render render; + render.setRefLayersVisiblity(true); render.setProjection(proj); render.renderSprite( image, sprite, frame, From 8fd1bcec7cada039c34578f547225a3cc935df57 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 11:03:56 -0300 Subject: [PATCH 24/29] Resize cel bounds from ref layers on SpriteSizeCommand --- src/app/commands/cmd_sprite_size.cpp | 57 ++++++++++++++++++---------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/app/commands/cmd_sprite_size.cpp b/src/app/commands/cmd_sprite_size.cpp index 0fed29cf5f..11e3a470ac 100644 --- a/src/app/commands/cmd_sprite_size.cpp +++ b/src/app/commands/cmd_sprite_size.cpp @@ -8,6 +8,7 @@ #include "config.h" #endif +#include "app/cmd/set_cel_bounds.h" #include "app/commands/cmd_sprite_size.h" #include "app/commands/command.h" #include "app/commands/params.h" @@ -48,8 +49,11 @@ class SpriteSizeJob : public Job { int m_new_height; ResizeMethod m_resize_method; - int scale_x(int x) const { return x * m_new_width / m_sprite->width(); } - int scale_y(int y) const { return y * m_new_height / m_sprite->height(); } + template + T scale_x(T x) const { return x * T(m_new_width) / T(m_sprite->width()); } + + template + T scale_y(T y) const { return y * T(m_new_height) / T(m_sprite->height()); } public: @@ -83,33 +87,44 @@ class SpriteSizeJob : public Job { // For each cel... int progress = 0; for (Cel* cel : m_sprite->uniqueCels()) { - // Change its location - api.setCelPosition(m_sprite, cel, scale_x(cel->x()), scale_y(cel->y())); - // Get cel's image Image* image = cel->image(); if (image && !cel->link()) { - // Resize the image - int w = scale_x(image->width()); - int h = scale_y(image->height()); - ImageRef new_image(Image::create(image->pixelFormat(), MAX(1, w), MAX(1, h))); - new_image->setMaskColor(image->maskColor()); - - doc::algorithm::fixup_image_transparent_colors(image); - doc::algorithm::resize_image( - image, new_image.get(), - m_resize_method, - m_sprite->palette(cel->frame()), - m_sprite->rgbMap(cel->frame()), - (cel->layer()->isBackground() ? -1: m_sprite->transparentColor())); - - api.replaceImage(m_sprite, cel->imageRef(), new_image); + // Resize the cel bounds only if it's from a reference layer + if (cel->layer()->isReference()) { + gfx::RectF newBounds = cel->boundsF(); + newBounds.x = scale_x(newBounds.x); + newBounds.y = scale_y(newBounds.y); + newBounds.w = scale_x(newBounds.w); + newBounds.h = scale_y(newBounds.h); + transaction.execute(new cmd::SetCelBoundsF(cel, newBounds)); + } + else { + // Change its location + api.setCelPosition(m_sprite, cel, scale_x(cel->x()), scale_y(cel->y())); + + // Resize the image + int w = scale_x(image->width()); + int h = scale_y(image->height()); + ImageRef new_image(Image::create(image->pixelFormat(), MAX(1, w), MAX(1, h))); + new_image->setMaskColor(image->maskColor()); + + doc::algorithm::fixup_image_transparent_colors(image); + doc::algorithm::resize_image( + image, new_image.get(), + m_resize_method, + m_sprite->palette(cel->frame()), + m_sprite->rgbMap(cel->frame()), + (cel->layer()->isBackground() ? -1: m_sprite->transparentColor())); + + api.replaceImage(m_sprite, cel->imageRef(), new_image); + } } jobProgress((float)progress / cels_count); ++progress; - // cancel all the operation? + // Cancel all the operation? if (isCanceled()) return; // Transaction destructor will undo all operations } From a21d7df3027e1ffdbfa5976d74c66c0794bbb2a4 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 11:39:58 -0300 Subject: [PATCH 25/29] Fix CanvasSizeCommand to crop ref layers correctly --- src/app/document_api.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/document_api.cpp b/src/app/document_api.cpp index 613b61859e..4e877ab7fe 100644 --- a/src/app/document_api.cpp +++ b/src/app/document_api.cpp @@ -28,6 +28,7 @@ #include "app/cmd/remove_frame_tag.h" #include "app/cmd/remove_layer.h" #include "app/cmd/replace_image.h" +#include "app/cmd/set_cel_bounds.h" #include "app/cmd/set_cel_frame.h" #include "app/cmd/set_cel_opacity.h" #include "app/cmd/set_cel_position.h" @@ -112,6 +113,13 @@ void DocumentApi::cropSprite(Sprite* sprite, const gfx::Rect& bounds) replaceImage(sprite, cel->imageRef(), new_image); } } + else if (layer->isReference()) { + // Update the ref cel's bounds + gfx::RectF newBounds = cel->boundsF(); + newBounds.x -= bounds.x; + newBounds.y -= bounds.y; + m_transaction.execute(new cmd::SetCelBoundsF(cel, newBounds)); + } else { // Update the cel's position setCelPosition(sprite, cel, From e4bcdbf131392db2f41a2b19e4f1238bee6c7578 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 11:47:56 -0300 Subject: [PATCH 26/29] FlipCommand can flip ref layer cels correctly --- src/app/commands/cmd_flip.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/app/commands/cmd_flip.cpp b/src/app/commands/cmd_flip.cpp index 7b0cad45b6..dc0a3fd80f 100644 --- a/src/app/commands/cmd_flip.cpp +++ b/src/app/commands/cmd_flip.cpp @@ -13,6 +13,7 @@ #include "app/app.h" #include "app/cmd/flip_mask.h" #include "app/cmd/flip_masked_cel.h" +#include "app/cmd/set_cel_bounds.h" #include "app/cmd/set_mask_position.h" #include "app/cmd/trim_cel.h" #include "app/commands/params.h" @@ -94,6 +95,10 @@ void FlipCommand::onExecute(Context* context) Site site = *writer.site(); for (Cel* cel : cels) { + // TODO add support to flip masked part of a reference layer + if (cel->layer()->isReference()) + continue; + site.frame(cel->frame()); site.layer(cel->layer()); @@ -150,14 +155,27 @@ void FlipCommand::onExecute(Context* context) for (Cel* cel : cels) { Image* image = cel->image(); - api.setCelPosition - (sprite, cel, - (m_flipType == doc::algorithm::FlipHorizontal ? + // Flip reference layer cel + if (cel->layer()->isReference()) { + gfx::RectF bounds = cel->boundsF(); + + if (m_flipType == doc::algorithm::FlipHorizontal) + bounds.x = sprite->width() - bounds.w - bounds.x; + if (m_flipType == doc::algorithm::FlipVertical) + bounds.y = sprite->height() - bounds.h - bounds.y; + + transaction.execute(new cmd::SetCelBoundsF(cel, bounds)); + } + else { + api.setCelPosition + (sprite, cel, + (m_flipType == doc::algorithm::FlipHorizontal ? sprite->width() - image->width() - cel->x(): cel->x()), - (m_flipType == doc::algorithm::FlipVertical ? + (m_flipType == doc::algorithm::FlipVertical ? sprite->height() - image->height() - cel->y(): cel->y())); + } api.flipImage(image, image->bounds(), m_flipType); } From e89bea49dfc865e26213e3f7ab14103d37de9e4b Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 12:10:59 -0300 Subject: [PATCH 27/29] Fix RotateCommand for ref layer cels --- src/app/commands/cmd_rotate.cpp | 51 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/app/commands/cmd_rotate.cpp b/src/app/commands/cmd_rotate.cpp index 6930ba7b44..69eaa74932 100644 --- a/src/app/commands/cmd_rotate.cpp +++ b/src/app/commands/cmd_rotate.cpp @@ -9,6 +9,7 @@ #endif #include "app/app.h" +#include "app/cmd/set_cel_bounds.h" #include "app/commands/cmd_rotate.h" #include "app/commands/params.h" #include "app/context_access.h" @@ -57,6 +58,29 @@ class RotateJob : public Job { protected: + template + void rotate_rect(gfx::RectT& newBounds) { + const gfx::RectT bounds = newBounds; + switch (m_angle) { + case 180: + newBounds.x = m_sprite->width() - bounds.x - bounds.w; + newBounds.y = m_sprite->height() - bounds.y - bounds.h; + break; + case 90: + newBounds.x = m_sprite->height() - bounds.y - bounds.h; + newBounds.y = bounds.x; + newBounds.w = bounds.h; + newBounds.h = bounds.w; + break; + case -90: + newBounds.x = bounds.y; + newBounds.y = m_sprite->width() - bounds.x - bounds.w; + newBounds.w = bounds.h; + newBounds.h = bounds.w; + break; + } + } + // [working thread] virtual void onJob() { @@ -69,22 +93,17 @@ class RotateJob : public Job { if (!image) continue; - switch (m_angle) { - case 180: - api.setCelPosition(m_sprite, cel, - m_sprite->width() - cel->x() - image->width(), - m_sprite->height() - cel->y() - image->height()); - break; - case 90: - api.setCelPosition(m_sprite, cel, - m_sprite->height() - cel->y() - image->height(), - cel->x()); - break; - case -90: - api.setCelPosition(m_sprite, cel, - cel->y(), - m_sprite->width() - cel->x() - image->width()); - break; + if (cel->layer()->isReference()) { + gfx::RectF bounds = cel->boundsF(); + rotate_rect(bounds); + if (cel->boundsF() != bounds) + transaction.execute(new cmd::SetCelBoundsF(cel, bounds)); + } + else { + gfx::Rect bounds = cel->bounds(); + rotate_rect(bounds); + if (bounds.origin() != cel->bounds().origin()) + api.setCelPosition(m_sprite, cel, bounds.x, bounds.y); } } From 6aee7657311b8f76749686ac20ca19e09be6f9b6 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 12:35:45 -0300 Subject: [PATCH 28/29] Fix copy and paste of ref layer cels --- src/app/util/create_cel_copy.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/util/create_cel_copy.cpp b/src/app/util/create_cel_copy.cpp index bb419759b2..0c2b9ccfbc 100644 --- a/src/app/util/create_cel_copy.cpp +++ b/src/app/util/create_cel_copy.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -70,7 +70,14 @@ Cel* create_cel_copy(const Cel* srcCel, 0, 0, 255, BlendMode::SRC); } - dstCel->setPosition(srcCel->position()); + if (srcCel->layer() && + srcCel->layer()->isReference()) { + dstCel->setBoundsF(srcCel->boundsF()); + } + else { + dstCel->setPosition(srcCel->position()); + } + dstCel->setOpacity(srcCel->opacity()); dstCel->data()->setUserData(srcCel->data()->userData()); From b5225d0f30fb2874118e71d5fd421fd6cf26f75e Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 7 Nov 2016 15:09:52 -0300 Subject: [PATCH 29/29] Fix copying/moving layers from between regular and reference layers --- src/app/cmd/copy_cel.cpp | 2 +- src/app/cmd/move_cel.cpp | 4 +-- src/app/document.cpp | 1 + src/app/util/create_cel_copy.cpp | 46 +++++++++++++++++++++++++------- src/app/util/create_cel_copy.h | 3 ++- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/app/cmd/copy_cel.cpp b/src/app/cmd/copy_cel.cpp index f8fadbc481..3aba00b748 100644 --- a/src/app/cmd/copy_cel.cpp +++ b/src/app/cmd/copy_cel.cpp @@ -115,7 +115,7 @@ void CopyCel::onExecute() dstCel->setFrame(m_dstFrame); } else - dstCel = create_cel_copy(srcCel, dstSprite, m_dstFrame); + dstCel = create_cel_copy(srcCel, dstSprite, dstLayer, m_dstFrame); executeAndAdd(new cmd::AddCel(dstLayer, dstCel)); } diff --git a/src/app/cmd/move_cel.cpp b/src/app/cmd/move_cel.cpp index 245e581659..c99464a2fd 100644 --- a/src/app/cmd/move_cel.cpp +++ b/src/app/cmd/move_cel.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -119,7 +119,7 @@ void MoveCel::onExecute() executeAndAdd(new cmd::SetCelFrame(srcCel, m_dstFrame)); } else { - dstCel = create_cel_copy(srcCel, dstSprite, m_dstFrame); + dstCel = create_cel_copy(srcCel, dstSprite, dstLayer, m_dstFrame); executeAndAdd(new cmd::AddCel(dstLayer, dstCel)); executeAndAdd(new cmd::ClearCel(srcCel)); diff --git a/src/app/document.cpp b/src/app/document.cpp index f85ea0610b..a83dbc6c75 100644 --- a/src/app/document.cpp +++ b/src/app/document.cpp @@ -319,6 +319,7 @@ void Document::copyLayerContent(const Layer* sourceLayer0, Document* destDoc, La else { newCel.reset(create_cel_copy(sourceCel, destLayer->sprite(), + destLayer, sourceCel->frame())); linked.insert(std::make_pair(sourceCel->data()->id(), newCel.get())); } diff --git a/src/app/util/create_cel_copy.cpp b/src/app/util/create_cel_copy.cpp index 0c2b9ccfbc..d917a9db4e 100644 --- a/src/app/util/create_cel_copy.cpp +++ b/src/app/util/create_cel_copy.cpp @@ -9,20 +9,25 @@ #endif #include "base/unique_ptr.h" +#include "doc/algorithm/resize_image.h" #include "doc/cel.h" #include "doc/image.h" #include "doc/layer.h" #include "doc/palette.h" +#include "doc/primitives.h" #include "doc/sprite.h" #include "render/quantization.h" #include "render/render.h" +#include + namespace app { using namespace doc; Cel* create_cel_copy(const Cel* srcCel, const Sprite* dstSprite, + const Layer* dstLayer, const frame_t dstFrame) { const Image* celImage = srcCel->image(); @@ -33,12 +38,13 @@ Cel* create_cel_copy(const Cel* srcCel, celImage->width(), celImage->height())))); - // If both images are indexed but with different palette, we can - // convert the source cel to RGB first. - if (dstSprite->pixelFormat() == IMAGE_INDEXED && - celImage->pixelFormat() == IMAGE_INDEXED && - srcCel->sprite()->palette(srcCel->frame())->countDiff( - dstSprite->palette(dstFrame), nullptr, nullptr)) { + if ((dstSprite->pixelFormat() != celImage->pixelFormat()) || + // If both images are indexed but with different palette, we can + // convert the source cel to RGB first. + (dstSprite->pixelFormat() == IMAGE_INDEXED && + celImage->pixelFormat() == IMAGE_INDEXED && + srcCel->sprite()->palette(srcCel->frame())->countDiff( + dstSprite->palette(dstFrame), nullptr, nullptr))) { ImageRef tmpImage(Image::create(IMAGE_RGB, celImage->width(), celImage->height())); tmpImage->clear(0); @@ -70,12 +76,32 @@ Cel* create_cel_copy(const Cel* srcCel, 0, 0, 255, BlendMode::SRC); } - if (srcCel->layer() && - srcCel->layer()->isReference()) { - dstCel->setBoundsF(srcCel->boundsF()); + // Resize a referecen cel to a non-reference layer + if (srcCel->layer()->isReference() && !dstLayer->isReference()) { + gfx::RectF srcBounds = srcCel->boundsF(); + + base::UniquePtr dstCel2( + new Cel(dstFrame, + ImageRef(Image::create(dstSprite->pixelFormat(), + std::ceil(srcBounds.w), + std::ceil(srcBounds.h))))); + algorithm::resize_image( + dstCel->image(), dstCel2->image(), + algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR, + nullptr, nullptr, 0); + + dstCel.reset(dstCel2.release()); + dstCel->setPosition(gfx::Point(srcBounds.origin())); } + // Copy original cel bounds else { - dstCel->setPosition(srcCel->position()); + if (srcCel->layer() && + srcCel->layer()->isReference()) { + dstCel->setBoundsF(srcCel->boundsF()); + } + else { + dstCel->setPosition(srcCel->position()); + } } dstCel->setOpacity(srcCel->opacity()); diff --git a/src/app/util/create_cel_copy.h b/src/app/util/create_cel_copy.h index cfa6c7e4b5..4ab16ba52d 100644 --- a/src/app/util/create_cel_copy.h +++ b/src/app/util/create_cel_copy.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2015 David Capello +// Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -19,6 +19,7 @@ namespace app { Cel* create_cel_copy(const Cel* srcCel, const Sprite* dstSprite, + const Layer* dstLayer, const frame_t dstFrame); } // namespace app