diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 733a07029..9538c2328 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -588,7 +588,7 @@ add_library(app-lib tools/pick_ink.cpp tools/point_shape.cpp tools/stroke.cpp - tools/symmetries.cpp + tools/symmetry.cpp tools/tool_box.cpp tools/tool_loop_manager.cpp tools/velocity.cpp diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h index 0ef8ec4e3..321649304 100644 --- a/src/app/tools/point_shapes.h +++ b/src/app/tools/point_shapes.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -8,6 +8,7 @@ #include "app/util/wrap_point.h" #include "app/tools/ink.h" +#include "doc/algorithm/flip_image.h" #include "render/gradient.h" namespace app { @@ -42,7 +43,7 @@ class BrushPointShape : public PointShape { bool m_firstPoint; Brush* m_lastBrush; BrushType m_origBrushType; - std::shared_ptr m_compressedImage; + std::array, 4> m_compressedImages; // For dynamics DynamicsOptions m_dynamics; bool m_useDynamics; @@ -173,9 +174,7 @@ public: // TODO cache compressed images (or remove them completelly) if (m_lastBrush != brush) { m_lastBrush = brush; - m_compressedImage.reset(new CompressedImage(brush->image(), - brush->maskBitmap(), - false)); + m_compressedImages.fill(nullptr); } x += brush->bounds().x; @@ -208,7 +207,7 @@ public: ink->prepareForPointShape(loop, m_firstPoint, x, y); - for (auto scanline : *m_compressedImage) { + for (auto scanline : getCompressedImage(pt.symmetry)) { int u = x+scanline.x; ink->prepareVForPointShape(loop, y+scanline.y); doInkHline(u, y+scanline.y, u+scanline.w-1, loop); @@ -222,6 +221,47 @@ public: area.y += y; } +private: + CompressedImage& getCompressedImage(gen::SymmetryMode symmetryMode) { + auto& compressPtr = m_compressedImages[int(symmetryMode)]; + if (!compressPtr) { + switch (symmetryMode) { + case gen::SymmetryMode::NONE: { + compressPtr.reset(new CompressedImage(m_lastBrush->image(), + m_lastBrush->maskBitmap(), + false)); + break; + } + case gen::SymmetryMode::HORIZONTAL: + case gen::SymmetryMode::VERTICAL: { + std::unique_ptr tempImage(Image::createCopy(m_lastBrush->image())); + doc::algorithm::FlipType flip = + (symmetryMode == gen::SymmetryMode::HORIZONTAL)? + doc::algorithm::FlipType::FlipHorizontal: + doc::algorithm::FlipType::FlipVertical; + doc::algorithm::flip_image(tempImage.get(), tempImage->bounds(), flip); + compressPtr.reset(new CompressedImage(tempImage.get(), + m_lastBrush->maskBitmap(), + false)); + break; + } + case gen::SymmetryMode::BOTH: { + std::unique_ptr tempImage(Image::createCopy(m_lastBrush->image())); + doc::algorithm::flip_image(tempImage.get(), + tempImage->bounds(), + doc::algorithm::FlipType::FlipVertical); + doc::algorithm::flip_image(tempImage.get(), + tempImage->bounds(), + doc::algorithm::FlipType::FlipHorizontal); + compressPtr.reset(new CompressedImage(tempImage.get(), + m_lastBrush->maskBitmap(), + false)); + break; + } + } + } + return *compressPtr; + } }; class FloodFillPointShape : public PointShape { diff --git a/src/app/tools/stroke.h b/src/app/tools/stroke.h index 2b184ccb9..010edfc18 100644 --- a/src/app/tools/stroke.h +++ b/src/app/tools/stroke.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2015 David Capello // // This program is distributed under the terms of @@ -9,6 +9,7 @@ #define APP_TOOLS_STROKE_H_INCLUDED #pragma once +#include "app/pref/preferences.h" #include "gfx/point.h" #include "gfx/rect.h" @@ -25,6 +26,7 @@ namespace app { float size = 0.0f; float angle = 0.0f; float gradient = 0.0f; + gen::SymmetryMode symmetry = gen::SymmetryMode::NONE; Pt() { } Pt(const gfx::Point& point) : x(point.x), y(point.y) { } Pt(int x, int y) : x(x), y(y) { } diff --git a/src/app/tools/symmetries.cpp b/src/app/tools/symmetries.cpp deleted file mode 100644 index 78557c2f1..000000000 --- a/src/app/tools/symmetries.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Aseprite -// Copyright (C) 2020 Igara Studio S.A. -// Copyright (C) 2015-2017 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/tools/symmetries.h" - -#include "app/tools/point_shape.h" -#include "app/tools/stroke.h" -#include "app/tools/tool_loop.h" -#include "doc/brush.h" - -namespace app { -namespace tools { - -void HorizontalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& strokes, - ToolLoop* loop) -{ - int brushSize, brushCenter; - if (loop->getPointShape()->isFloodFill()) { - brushSize = 1; - brushCenter = 0; - } - else { - // TODO we should flip the brush center+image+bitmap or just do - // the symmetry of all pixels - auto brush = loop->getBrush(); - brushSize = brush->bounds().w; - brushCenter = brush->center().x; - } - - strokes.push_back(mainStroke); - - Stroke stroke2; - const bool isDynamic = loop->getDynamics().isDynamic(); - for (const auto& pt : mainStroke) { - Stroke::Pt pt2 = pt; - if (isDynamic) { - brushSize = pt2.size; - brushCenter = (brushSize - brushSize % 2) / 2; - } - pt2.x = m_x - ((pt.x-brushCenter) - m_x + 1) - (brushSize - brushCenter - 1); - stroke2.addPoint(pt2); - } - strokes.push_back(stroke2); -} - -void VerticalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& strokes, - ToolLoop* loop) -{ - int brushSize, brushCenter; - if (loop->getPointShape()->isFloodFill()) { - brushSize = 1; - brushCenter = 0; - } - else { - auto brush = loop->getBrush(); - brushSize = brush->bounds().h; - brushCenter = brush->center().y; - } - - strokes.push_back(mainStroke); - - Stroke stroke2; - const bool isDynamic = loop->getDynamics().isDynamic(); - for (const auto& pt : mainStroke) { - Stroke::Pt pt2 = pt; - if (isDynamic) { - brushSize = pt2.size; - brushCenter = (brushSize - brushSize % 2) / 2; - } - pt2.y = m_y - ((pt.y-brushCenter) - m_y + 1) - (brushSize - brushCenter - 1); - stroke2.addPoint(pt2); - } - strokes.push_back(stroke2); -} - -void SymmetryCombo::generateStrokes(const Stroke& mainStroke, Strokes& strokes, - ToolLoop* loop) -{ - Strokes strokes0; - m_a->generateStrokes(mainStroke, strokes0, loop); - for (const Stroke& stroke : strokes0) - m_b->generateStrokes(stroke, strokes, loop); -} - -} // namespace tools -} // namespace app diff --git a/src/app/tools/symmetries.h b/src/app/tools/symmetries.h deleted file mode 100644 index 36d0f8004..000000000 --- a/src/app/tools/symmetries.h +++ /dev/null @@ -1,50 +0,0 @@ -// Aseprite -// Copyright (C) 2015-2018 David Capello -// -// This program is distributed under the terms of -// the End-User License Agreement for Aseprite. - -#ifndef APP_TOOLS_SYMMETRIES_H_INCLUDED -#define APP_TOOLS_SYMMETRIES_H_INCLUDED -#pragma once - -#include "app/tools/stroke.h" -#include "app/tools/symmetry.h" - -#include - -namespace app { -namespace tools { - -class HorizontalSymmetry : public Symmetry { -public: - HorizontalSymmetry(double x) : m_x(x) { } - void generateStrokes(const Stroke& mainStroke, Strokes& strokes, - ToolLoop* loop) override; -private: - double m_x; -}; - -class VerticalSymmetry : public Symmetry { -public: - VerticalSymmetry(double y) : m_y(y) { } - void generateStrokes(const Stroke& mainStroke, Strokes& strokes, - ToolLoop* loop) override; -private: - double m_y; -}; - -class SymmetryCombo : public Symmetry { -public: - SymmetryCombo(Symmetry* a, Symmetry* b) : m_a(a), m_b(b) { } - void generateStrokes(const Stroke& mainStroke, Strokes& strokes, - ToolLoop* loop) override; -private: - std::unique_ptr m_a; - std::unique_ptr m_b; -}; - -} // namespace tools -} // namespace app - -#endif diff --git a/src/app/tools/symmetry.cpp b/src/app/tools/symmetry.cpp new file mode 100644 index 000000000..8e6e0ecef --- /dev/null +++ b/src/app/tools/symmetry.cpp @@ -0,0 +1,91 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/tools/symmetry.h" + + #include "app/tools/point_shape.h" + #include "app/tools/tool_loop.h" + +namespace app { +namespace tools { + +void Symmetry::generateStrokes(const Stroke& stroke, Strokes& strokes, + ToolLoop* loop) +{ + Stroke stroke2; + strokes.push_back(stroke); + gen::SymmetryMode symmetryMode = loop->getSymmetry()->mode(); + switch (symmetryMode) { + case gen::SymmetryMode::NONE: + ASSERT(false); + break; + + case gen::SymmetryMode::HORIZONTAL: + case gen::SymmetryMode::VERTICAL: + calculateSymmetricalStroke(stroke, stroke2, loop, symmetryMode); + strokes.push_back(stroke2); + break; + + case gen::SymmetryMode::BOTH: { + calculateSymmetricalStroke(stroke, stroke2, loop, gen::SymmetryMode::HORIZONTAL); + strokes.push_back(stroke2); + + Stroke stroke3; + calculateSymmetricalStroke(stroke, stroke3, loop, gen::SymmetryMode::VERTICAL); + strokes.push_back(stroke3); + + Stroke stroke4; + calculateSymmetricalStroke(stroke3, stroke4, loop, gen::SymmetryMode::BOTH); + strokes.push_back(stroke4); + break; + } + } +} + +void Symmetry::calculateSymmetricalStroke(const Stroke& refStroke, Stroke& stroke, + ToolLoop* loop, gen::SymmetryMode symmetryMode) +{ + int brushSize, brushCenter; + if (loop->getPointShape()->isFloodFill()) { + brushSize = 1; + brushCenter = 0; + } + else { + // TODO we should flip the brush center+image+bitmap or just do + // the symmetry of all pixels + auto brush = loop->getBrush(); + if (symmetryMode == gen::SymmetryMode::HORIZONTAL || symmetryMode == gen::SymmetryMode::BOTH) { + brushSize = brush->bounds().w; + brushCenter = brush->center().x; + } + else { + brushSize = brush->bounds().h; + brushCenter = brush->center().y; + } + } + + const bool isDynamic = loop->getDynamics().isDynamic(); + for (const auto& pt : refStroke) { + if (isDynamic) { + brushSize = pt.size; + brushCenter = (brushSize - brushSize % 2) / 2; + } + Stroke::Pt pt2 = pt; + pt2.symmetry = symmetryMode; + if (symmetryMode == gen::SymmetryMode::HORIZONTAL || symmetryMode == gen::SymmetryMode::BOTH) + pt2.x = 2 * (m_x + brushCenter) - pt2.x - brushSize; + else + pt2.y = 2 * (m_y + brushCenter) - pt2.y - brushSize; + stroke.addPoint(pt2); + } +} + +} // namespace tools +} // namespace app diff --git a/src/app/tools/symmetry.h b/src/app/tools/symmetry.h index 5a11f7dfe..22564cac7 100644 --- a/src/app/tools/symmetry.h +++ b/src/app/tools/symmetry.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2021 Igara Studio S.A. // Copyright (C) 2015 David Capello // // This program is distributed under the terms of @@ -9,24 +10,34 @@ #pragma once #include "app/tools/stroke.h" - -#include +#include "app/pref/preferences.h" namespace app { - namespace tools { +namespace tools { - class ToolLoop; +class ToolLoop; - // This class controls user input. - class Symmetry { - public: - virtual ~Symmetry() { } +class Symmetry { +public: + Symmetry(gen::SymmetryMode symmetryMode, double x, double y) + : m_symmetryMode(symmetryMode) + , m_x(x) + , m_y(y) { + } - // The "stroke" must be relative to the sprite origin. - virtual void generateStrokes(const Stroke& stroke, Strokes& strokes, ToolLoop* loop) = 0; - }; + void generateStrokes(const Stroke& stroke, Strokes& strokes, ToolLoop* loop); - } // namespace tools + gen::SymmetryMode mode() const { return m_symmetryMode; } + +private: + void calculateSymmetricalStroke(const Stroke& refStroke, Stroke& stroke, + ToolLoop* loop, gen::SymmetryMode symmetryMode); + + gen::SymmetryMode m_symmetryMode; + double m_x, m_y; +}; + +} // namespace tools } // namespace app #endif diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 5d486aed3..9d0c9ab78 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -29,7 +29,7 @@ #include "app/tools/freehand_algorithm.h" #include "app/tools/ink.h" #include "app/tools/point_shape.h" -#include "app/tools/symmetries.h" +#include "app/tools/symmetry.h" #include "app/tools/tool.h" #include "app/tools/tool_box.h" #include "app/tools/tool_loop.h" @@ -204,27 +204,10 @@ public: // Symmetry mode if (Preferences::instance().symmetryMode.enabled()) { - switch (m_docPref.symmetry.mode()) { - - case app::gen::SymmetryMode::NONE: - ASSERT(m_symmetry == nullptr); - break; - - case app::gen::SymmetryMode::HORIZONTAL: - m_symmetry.reset(new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis())); - break; - - case app::gen::SymmetryMode::VERTICAL: - m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis())); - break; - - case app::gen::SymmetryMode::BOTH: - m_symmetry.reset( - new app::tools::SymmetryCombo( - new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()), - new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()))); - break; - } + if (m_docPref.symmetry.mode() != gen::SymmetryMode::NONE) + m_symmetry.reset(new tools::Symmetry(m_docPref.symmetry.mode(), + m_docPref.symmetry.xAxis(), + m_docPref.symmetry.yAxis())); } // Ignore opacity for these inks