diff --git a/data/extensions/aseprite-theme/dark/sheet.png b/data/extensions/aseprite-theme/dark/sheet.png
index 7bec79932..b35d4a8b5 100644
Binary files a/data/extensions/aseprite-theme/dark/sheet.png and b/data/extensions/aseprite-theme/dark/sheet.png differ
diff --git a/data/extensions/aseprite-theme/dark/theme.xml b/data/extensions/aseprite-theme/dark/theme.xml
index b2aca3149..76bf663fc 100644
--- a/data/extensions/aseprite-theme/dark/theme.xml
+++ b/data/extensions/aseprite-theme/dark/theme.xml
@@ -367,9 +367,9 @@
-
-
-
+
+
+
@@ -454,6 +454,8 @@
+
+
diff --git a/data/extensions/aseprite-theme/sheet.png b/data/extensions/aseprite-theme/sheet.png
index 7559d69ab..b894d51b5 100644
Binary files a/data/extensions/aseprite-theme/sheet.png and b/data/extensions/aseprite-theme/sheet.png differ
diff --git a/data/extensions/aseprite-theme/theme.xml b/data/extensions/aseprite-theme/theme.xml
index bfcd79e0c..3763d0279 100644
--- a/data/extensions/aseprite-theme/theme.xml
+++ b/data/extensions/aseprite-theme/theme.xml
@@ -363,9 +363,9 @@
-
-
-
+
+
+
@@ -450,6 +450,8 @@
+
+
diff --git a/data/gui.xml b/data/gui.xml
index cfb1e0358..d78dd99ea 100644
--- a/data/gui.xml
+++ b/data/gui.xml
@@ -548,6 +548,12 @@
+
+
+
+
+
+
diff --git a/data/pref.xml b/data/pref.xml
index d2cb449cc..4abdfec3f 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -94,6 +94,10 @@
+
+
+
+
diff --git a/data/strings/en.ini b/data/strings/en.ini
index 897d3274e..d2ae8303b 100644
--- a/data/strings/en.ini
+++ b/data/strings/en.ini
@@ -1881,6 +1881,8 @@ image_preset_text = Text
toggle = Toggle Symmetry
toggle_horizontal = Toggle Horizontal Symmetry
toggle_vertical = Toggle Vertical Symmetry
+toggle_right_diagonal = Toggle 45° Symmetry
+toggle_left_diagonal = Toggle -45° Symmetry
show_options = Symmetry Options
reset_position = Reset Symmetry to Center
reset_position_to_view_center = Reset Symmetry to View Center
diff --git a/src/app/commands/cmd_symmetry_mode.cpp b/src/app/commands/cmd_symmetry_mode.cpp
index 479399221..31dc4ed1f 100644
--- a/src/app/commands/cmd_symmetry_mode.cpp
+++ b/src/app/commands/cmd_symmetry_mode.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018 Igara Studio S.A.
+// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@@ -49,6 +49,10 @@ std::string SymmetryModeCommand::onGetFriendlyName() const
return Strings::symmetry_toggle_horizontal();
case app::gen::SymmetryMode::VERTICAL:
return Strings::symmetry_toggle_vertical();
+ case app::gen::SymmetryMode::RIGHT_DIAG:
+ return Strings::symmetry_toggle_right_diagonal();
+ case app::gen::SymmetryMode::LEFT_DIAG:
+ return Strings::symmetry_toggle_left_diagonal();
default:
return Strings::symmetry_toggle();
}
@@ -59,6 +63,8 @@ void SymmetryModeCommand::onLoadParams(const Params& params)
std::string mode = params.get("orientation");
if (mode == "vertical") m_mode = app::gen::SymmetryMode::VERTICAL;
else if (mode == "horizontal") m_mode = app::gen::SymmetryMode::HORIZONTAL;
+ else if (mode == "right_diagonal") m_mode = app::gen::SymmetryMode::RIGHT_DIAG;
+ else if (mode == "left_diagonal") m_mode = app::gen::SymmetryMode::LEFT_DIAG;
else m_mode = app::gen::SymmetryMode::NONE;
}
diff --git a/src/app/tools/ink.h b/src/app/tools/ink.h
index 9f959e864..570c39fff 100644
--- a/src/app/tools/ink.h
+++ b/src/app/tools/ink.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2022 Igara Studio S.A.
+// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@@ -104,7 +104,8 @@ namespace app {
virtual void prepareForStrokes(ToolLoop* loop, Strokes& strokes) { }
// Called for each point shape.
- virtual void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) { }
+ virtual void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex symmetry) { }
virtual void prepareVForPointShape(ToolLoop* loop, int y) { }
virtual void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) { }
virtual void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) { }
diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h
index 44b63ddc6..e0149ddf8 100644
--- a/src/app/tools/ink_processing.h
+++ b/src/app/tools/ink_processing.h
@@ -6,6 +6,7 @@
// the End-User License Agreement for Aseprite.
#include "app/color_utils.h"
+#include "app/tools/symmetry.h"
#include "app/util/wrap_point.h"
#include "app/util/wrap_value.h"
#include "doc/blend_funcs.h"
@@ -33,7 +34,8 @@ public:
virtual ~BaseInkProcessing() { }
virtual void processScanline(int x1, int y, int x2, ToolLoop* loop) = 0;
virtual void prepareForStrokes(ToolLoop* loop, Strokes& strokes) { }
- virtual void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) { }
+ virtual void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) { }
virtual void prepareVForPointShape(ToolLoop* loop, int y) { }
virtual void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) { }
virtual void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) { }
@@ -136,7 +138,8 @@ public:
CopyInkProcessing(ToolLoop* loop) {
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = loop->getPrimaryColor();
if (loop->getLayer()->isBackground()) {
@@ -166,7 +169,8 @@ public:
: m_opacity(loop->getOpacity()) {
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = loop->getPrimaryColor();
}
@@ -207,7 +211,8 @@ public:
, m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = m_palette->getEntry(loop->getPrimaryColor());
}
@@ -246,7 +251,8 @@ public:
m_opacity = loop->getOpacity();
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = loop->getPrimaryColor();
}
@@ -280,7 +286,8 @@ public:
m_colorIndex(loop->getFgColor()) {
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = m_palette->getEntry(loop->getPrimaryColor());
}
@@ -318,7 +325,8 @@ public:
m_opacity = loop->getOpacity();
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = loop->getPrimaryColor();
}
@@ -351,7 +359,8 @@ public:
m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
m_color = (int(loop->getPrimaryColor()) == m_maskIndex ?
(m_palette->getEntry(loop->getPrimaryColor()) & rgba_rgb_mask):
(m_palette->getEntry(loop->getPrimaryColor())));
@@ -1129,10 +1138,6 @@ public:
m_bgColor = loop->getSecondaryColor();
m_palette = loop->getPalette();
m_brush = loop->getBrush();
- m_brushImage = (m_brush->patternImage() ? m_brush->patternImage():
- m_brush->image());
- m_brushMask = m_brush->maskBitmap();
- m_patternAlign = m_brush->pattern();
m_opacity = loop->getOpacity();
m_width = m_brush->bounds().w;
m_height = m_brush->bounds().h;
@@ -1147,14 +1152,22 @@ public:
}
else
m_transparentColor = 0;
+ m_isBoundsRotated = false;
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex index) override {
+ m_isBoundsRotated = does_symmetry_rotate_image(index);
+ m_brushImage = m_brush->getSymmetryImage(index);
+ m_brushMask = m_brush->getSymmetryMask(index);
+ m_patternAlign = m_brush->pattern();
if (m_patternAlign != BrushPattern::ALIGNED_TO_SRC) {
+ const int brushW = brushWidth();
+ const int brushH = brushHeight();
// Case: during painting process with PaintBucket Tool
if (loop->getPointShape()->isFloodFill()) {
- m_u = x - m_brush->bounds().w / 2;
- m_v = y - m_brush->bounds().h / 2;
+ m_u = x - brushW / 2;
+ m_v = y - brushH / 2;
}
// Case: during brush preview of PaintBucket Tool
else if (loop->getController()->isOnePoint()) {
@@ -1162,51 +1175,54 @@ public:
m_v = 0;
}
else {
- m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x) % m_width;
- m_v = ((m_brush->patternOrigin().y % loop->sprite()->height()) - loop->getCelOrigin().y) % m_height;
+ m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x) % brushW;
+ m_v = ((m_brush->patternOrigin().y % loop->sprite()->height()) - loop->getCelOrigin().y) % brushH;
}
}
}
void prepareVForPointShape(ToolLoop* loop, int y) override {
+ const int brushH = brushHeight();
if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
- m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height;
- if (m_v < 0) m_v += m_height;
+ m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % brushH;
+ if (m_v < 0) m_v += brushH;
}
else {
int spriteH = loop->sprite()->height();
if (y/spriteH > 0)
// 'y' is outside of the center tile.
- m_v = (m_brush->patternOrigin().y + m_height - (y/spriteH) * spriteH) % m_height;
+ m_v = (m_brush->patternOrigin().y + brushH - (y/spriteH) * spriteH) % brushH;
else
// 'y' is inside of the center tile.
- m_v = ((m_brush->patternOrigin().y % spriteH) - loop->getCelOrigin().y) % m_height;
+ m_v = ((m_brush->patternOrigin().y % spriteH) - loop->getCelOrigin().y) % brushH;
}
}
void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) override {
+ const int brushW = brushWidth();
if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
- m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
- if (m_u < 0) m_u += m_height;
+ m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % brushW;
+ if (m_u < 0) m_u += brushHeight();
}
else {
- m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x ) % m_width;
+ m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x ) % brushW;
if (x1/loop->sprite()->width() > 0)
- m_u = (m_brush->patternOrigin().x + m_width - (x1/loop->sprite()->width()) * loop->sprite()->width()) % m_width;
+ m_u = (m_brush->patternOrigin().x + brushW - (x1/loop->sprite()->width()) * loop->sprite()->width()) % brushW;
}
}
void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) override {
+ const int brushW(brushWidth());
if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
- m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
- if (m_u < 0) m_u += m_height;
+ m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % brushW;
+ if (m_u < 0) m_u += brushHeight();
return;
}
else {
if (leftSlice)
- m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x ) % m_width;
+ m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x ) % brushW;
else
- m_u = (m_brush->patternOrigin().x + m_width - (x1/loop->sprite()->width() + 1) * loop->sprite()->width()) % m_width;
+ m_u = (m_brush->patternOrigin().x + brushW - (x1/loop->sprite()->width() + 1) * loop->sprite()->width()) % brushW;
}
}
@@ -1224,12 +1240,16 @@ public:
protected:
bool alignPixelPoint(int& x0, int& y0) {
- int x = (x0 - m_u) % m_width;
- int y = (y0 - m_v) % m_height;
- if (x < 0) x = m_width - ((-x) % m_width);
- if (y < 0) y = m_height - ((-y) % m_height);
- if (m_brushMask && !get_pixel_fast(m_brushMask, x, y))
+ const int brushW = brushWidth();
+ const int brushH = brushHeight();
+ int x = (x0 - m_u) % brushW;
+ int y = (y0 - m_v) % brushH;
+ if (x < 0) x = brushW - ((-x) % brushW);
+ if (y < 0) y = brushH - ((-y) % brushH);
+
+ if (m_brushMask &&
+ !get_pixel_fast(m_brushMask, x, y))
return false;
if (m_brush->patternImage()) {
@@ -1246,10 +1266,17 @@ protected:
return true;
}
+ inline int brushWidth() const {
+ return m_isBoundsRotated ? m_height : m_width;
+ }
+ inline int brushHeight() const {
+ return m_isBoundsRotated ? m_width : m_height;
+ }
+
color_t m_fgColor;
color_t m_bgColor;
const Palette* m_palette;
- const Brush* m_brush;
+ Brush* m_brush;
const Image* m_brushImage;
const Image* m_brushMask;
BrushPattern m_patternAlign;
@@ -1259,6 +1286,7 @@ protected:
// which is the background color in order to translate to transparent color
// in a RGBA sprite.
color_t m_transparentColor;
+ bool m_isBoundsRotated;
};
template<>
diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h
index ffeead9ae..1bcdb520d 100644
--- a/src/app/tools/inks.h
+++ b/src/app/tools/inks.h
@@ -31,9 +31,10 @@ public:
m_proc->processScanline(x1, y, x2, loop);
}
- void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
+ void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y,
+ doc::SymmetryIndex symmetry) override {
ASSERT(m_proc);
- m_proc->prepareForPointShape(loop, firstPoint, x, y);
+ m_proc->prepareForPointShape(loop, firstPoint, x, y, symmetry);
}
void prepareVForPointShape(ToolLoop* loop, int y) override {
diff --git a/src/app/tools/intertwiners.h b/src/app/tools/intertwiners.h
index fd3f4742b..d8245a295 100644
--- a/src/app/tools/intertwiners.h
+++ b/src/app/tools/intertwiners.h
@@ -617,7 +617,7 @@ private:
// IntertwineAsPixelPerfect.joinStroke() method.
void savePointshapeStrokePtArea(ToolLoop* loop, const tools::Stroke::Pt& pt) {
gfx::Rect r;
- loop->getPointShape()->getModifiedArea(loop, pt.x, pt.y, r);
+ loop->getPointShape()->getModifiedArea(loop, pt.x, pt.y, pt.symmetry, r);
gfx::Region rgn(r);
// By wrapping the modified area's position when tiled mode is active, the
@@ -689,7 +689,7 @@ private:
ASSERT(m_tempTileset);
gfx::Rect r;
- loop->getPointShape()->getModifiedArea(loop, pt.x, pt.y, r);
+ loop->getPointShape()->getModifiedArea(loop, pt.x, pt.y, pt.symmetry, r);
r.offset(-loop->getCelOrigin());
auto tilesPts = m_dstGrid.tilesInCanvasRegion(gfx::Region(r));
diff --git a/src/app/tools/point_shape.h b/src/app/tools/point_shape.h
index 6d4228713..f04233255 100644
--- a/src/app/tools/point_shape.h
+++ b/src/app/tools/point_shape.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2020 Igara Studio S.A.
+// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@@ -28,7 +28,8 @@ namespace app {
// The x, y position must be relative to the cel/src/dst image origin.
virtual void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) = 0;
- virtual void getModifiedArea(ToolLoop* loop, int x, int y, gfx::Rect& area) = 0;
+ virtual void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, gfx::Rect& area) = 0;
protected:
// Calls loop->getInk()->inkHline() function for each horizontal-scanline
diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h
index 2f114be6a..b2825f98b 100644
--- a/src/app/tools/point_shapes.h
+++ b/src/app/tools/point_shapes.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2022 Igara Studio S.A.
+// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@@ -8,7 +8,9 @@
#include "app/util/wrap_point.h"
#include "app/tools/ink.h"
+#include "app/tools/symmetry.h"
#include "doc/algorithm/flip_image.h"
+#include "doc/primitives.h"
#include "render/gradient.h"
#include
@@ -23,7 +25,8 @@ public:
// Do nothing
}
- void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
+ void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, Rect& area) override {
// Do nothing
}
};
@@ -33,11 +36,12 @@ public:
bool isPixel() override { return true; }
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
- loop->getInk()->prepareForPointShape(loop, true, pt.x, pt.y);
+ loop->getInk()->prepareForPointShape(loop, true, pt.x, pt.y, pt.symmetry);
doInkHline(pt.x, pt.y, pt.x, loop);
}
- void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
+ void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, Rect& area) override {
area = Rect(x, y, 1, 1);
}
};
@@ -51,11 +55,12 @@ public:
const doc::Grid& grid = loop->getGrid();
gfx::Point newPos = grid.canvasToTile(pt.toPoint());
- loop->getInk()->prepareForPointShape(loop, true, newPos.x, newPos.y);
+ loop->getInk()->prepareForPointShape(loop, true, newPos.x, newPos.y, pt.symmetry);
doInkHline(newPos.x, newPos.y, newPos.x, loop);
}
- void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
+ void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, Rect& area) override {
const doc::Grid& grid = loop->getGrid();
area = grid.alignBounds(Rect(x, y, 1, 1));
}
@@ -65,7 +70,7 @@ class BrushPointShape : public PointShape {
bool m_firstPoint;
Brush* m_lastBrush;
BrushType m_origBrushType;
- std::array, 4> m_compressedImages;
+ std::array, int(SymmetryIndex::ELEMENTS)> m_compressedImages;
// For dynamics
DynamicsOptions m_dynamics;
bool m_useDynamics;
@@ -213,8 +218,15 @@ public:
m_compressedImages.fill(nullptr);
}
- x += brush->bounds().x;
- y += brush->bounds().y;
+ if (brush->type() == kImageBrushType &&
+ does_symmetry_rotate_image(pt.symmetry)) {
+ x += brush->bounds().y;
+ y += brush->bounds().x;
+ }
+ else {
+ x += brush->bounds().x;
+ y += brush->bounds().y;
+ }
if (m_firstPoint) {
if ((brush->type() == kImageBrushType) &&
@@ -241,7 +253,7 @@ public:
y = wrap_value(y, loop->sprite()->height());
}
- ink->prepareForPointShape(loop, m_firstPoint, x, y);
+ ink->prepareForPointShape(loop, m_firstPoint, x, y, pt.symmetry);
for (auto scanline : getCompressedImage(pt.symmetry)) {
int u = x+scanline.x;
@@ -251,50 +263,24 @@ public:
m_firstPoint = false;
}
- void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
- area = loop->getBrush()->bounds();
+ void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, Rect& area) override {
+ auto bounds = loop->getBrush()->bounds();
+ if (does_symmetry_rotate_image(symmetry))
+ area = gfx::Rect(bounds.y, bounds.x, bounds.h, bounds.w);
+ else
+ area = bounds;
area.x += x;
area.y += y;
}
private:
- CompressedImage& getCompressedImage(gen::SymmetryMode symmetryMode) {
- auto& compressPtr = m_compressedImages[int(symmetryMode)];
+ CompressedImage& getCompressedImage(doc::SymmetryIndex index) {
+ auto& compressPtr = m_compressedImages[int(index)];
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;
- }
- }
+ compressPtr.reset(new CompressedImage(m_lastBrush->getSymmetryImage(index),
+ m_lastBrush->getSymmetryMask(index),
+ false));
}
return *compressPtr;
}
@@ -319,7 +305,7 @@ public:
wpt, true);
}
- loop->getInk()->prepareForPointShape(loop, true, wpt.x, wpt.y);
+ loop->getInk()->prepareForPointShape(loop, true, wpt.x, wpt.y, pt.symmetry);
doc::algorithm::floodfill(
srcImage,
@@ -334,7 +320,8 @@ public:
loop, (AlgoHLine)doInkHline);
}
- void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
+ void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, Rect& area) override {
area = floodfillBounds(loop, x, y);
}
@@ -387,7 +374,7 @@ public:
}
void transformPoint(ToolLoop* loop, const Stroke::Pt& pt) override {
- loop->getInk()->prepareForPointShape(loop, true, pt.x, pt.y);
+ loop->getInk()->prepareForPointShape(loop, true, pt.x, pt.y, pt.symmetry);
int spray_width = loop->getSprayWidth();
int spray_speed = loop->getSpraySpeed();
@@ -418,15 +405,16 @@ public:
}
}
- void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
+ void getModifiedArea(ToolLoop* loop, int x, int y,
+ doc::SymmetryIndex symmetry, Rect& area) override {
int spray_width = loop->getSprayWidth();
Point p1(x-spray_width, y-spray_width);
Point p2(x+spray_width, y+spray_width);
Rect area1;
Rect area2;
- m_subPointShape.getModifiedArea(loop, p1.x, p1.y, area1);
- m_subPointShape.getModifiedArea(loop, p2.x, p2.y, area2);
+ m_subPointShape.getModifiedArea(loop, p1.x, p1.y, symmetry, area1);
+ m_subPointShape.getModifiedArea(loop, p2.x, p2.y, symmetry, area2);
area = area1.createUnion(area2);
}
diff --git a/src/app/tools/stroke.h b/src/app/tools/stroke.h
index 010edfc18..15e989f44 100644
--- a/src/app/tools/stroke.h
+++ b/src/app/tools/stroke.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2021 Igara Studio S.A.
+// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@@ -10,6 +10,7 @@
#pragma once
#include "app/pref/preferences.h"
+#include "doc/brush.h"
#include "gfx/point.h"
#include "gfx/rect.h"
@@ -26,7 +27,7 @@ namespace app {
float size = 0.0f;
float angle = 0.0f;
float gradient = 0.0f;
- gen::SymmetryMode symmetry = gen::SymmetryMode::NONE;
+ doc::SymmetryIndex symmetry = doc::SymmetryIndex::ORIGINAL;
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/symmetry.cpp b/src/app/tools/symmetry.cpp
index 8e6e0ecef..7750318c5 100644
--- a/src/app/tools/symmetry.cpp
+++ b/src/app/tools/symmetry.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2021 Igara Studio S.A.
+// Copyright (C) 2021-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -12,6 +12,7 @@
#include "app/tools/point_shape.h"
#include "app/tools/tool_loop.h"
+ #include "doc/brush.h"
namespace app {
namespace tools {
@@ -28,61 +29,147 @@ void Symmetry::generateStrokes(const Stroke& stroke, Strokes& strokes,
break;
case gen::SymmetryMode::HORIZONTAL:
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::FLIPPED_X);
+ strokes.push_back(stroke2);
+ break;
case gen::SymmetryMode::VERTICAL:
- calculateSymmetricalStroke(stroke, stroke2, loop, symmetryMode);
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::FLIPPED_Y);
strokes.push_back(stroke2);
break;
case gen::SymmetryMode::BOTH: {
- calculateSymmetricalStroke(stroke, stroke2, loop, gen::SymmetryMode::HORIZONTAL);
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::FLIPPED_X);
strokes.push_back(stroke2);
Stroke stroke3;
- calculateSymmetricalStroke(stroke, stroke3, loop, gen::SymmetryMode::VERTICAL);
+ calculateSymmetricalStroke(stroke, stroke3, loop, doc::SymmetryIndex::FLIPPED_Y);
strokes.push_back(stroke3);
Stroke stroke4;
- calculateSymmetricalStroke(stroke3, stroke4, loop, gen::SymmetryMode::BOTH);
+ calculateSymmetricalStroke(stroke3, stroke4, loop, doc::SymmetryIndex::FLIPPED_XY);
strokes.push_back(stroke4);
break;
}
+
+ case gen::SymmetryMode::RIGHT_DIAG: {
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::ROT_FLIP_270);
+ strokes.push_back(stroke2);
+ break;
+ }
+
+ case gen::SymmetryMode::LEFT_DIAG: {
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::ROT_FLIP_90);
+ strokes.push_back(stroke2);
+ break;
+ }
+
+ case gen::SymmetryMode::BOTH_DIAG: {
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::ROT_FLIP_270);
+ strokes.push_back(stroke2);
+
+ Stroke stroke3;
+ calculateSymmetricalStroke(stroke, stroke3, loop, doc::SymmetryIndex::ROT_FLIP_90);
+ strokes.push_back(stroke3);
+
+ Stroke stroke4;
+ calculateSymmetricalStroke(stroke, stroke4, loop, doc::SymmetryIndex::FLIPPED_XY, true);
+ strokes.push_back(stroke4);
+ break;
+ }
+
+ case gen::SymmetryMode::ALL: {
+ calculateSymmetricalStroke(stroke, stroke2, loop, doc::SymmetryIndex::FLIPPED_X);
+ strokes.push_back(stroke2);
+
+ Stroke stroke3;
+ calculateSymmetricalStroke(stroke, stroke3, loop, doc::SymmetryIndex::FLIPPED_Y);
+ strokes.push_back(stroke3);
+
+ Stroke stroke4;
+ calculateSymmetricalStroke(stroke3, stroke4, loop, doc::SymmetryIndex::FLIPPED_XY);
+ strokes.push_back(stroke4);
+
+ Stroke stroke5;
+ calculateSymmetricalStroke(stroke, stroke5, loop, doc::SymmetryIndex::ROT_FLIP_90);
+ strokes.push_back(stroke5);
+
+ Stroke stroke6;
+ calculateSymmetricalStroke(stroke5, stroke6, loop, doc::SymmetryIndex::ROTATED_270);
+ strokes.push_back(stroke6);
+
+ Stroke stroke7;
+ calculateSymmetricalStroke(stroke, stroke7, loop, doc::SymmetryIndex::ROT_FLIP_270);
+ strokes.push_back(stroke7);
+
+ Stroke stroke8;
+ calculateSymmetricalStroke(stroke7, stroke8, loop, doc::SymmetryIndex::ROTATED_90);
+ strokes.push_back(stroke8);
+ break;
+ }
}
}
-void Symmetry::calculateSymmetricalStroke(const Stroke& refStroke, Stroke& stroke,
- ToolLoop* loop, gen::SymmetryMode symmetryMode)
+void Symmetry::calculateSymmetricalStroke(const Stroke& refStroke,
+ Stroke& stroke,
+ ToolLoop* loop,
+ const doc::SymmetryIndex symmetry,
+ const bool isDoubleDiagonalSymmetry)
{
- 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;
+ gfx::Size brushSize(1, 1);
+ gfx::Point brushCenter(0, 0);
+ auto brush = loop->getBrush();
+ if (!loop->getPointShape()->isFloodFill()) {
+ if (!does_symmetry_rotate_image(symmetry)) {
+ brushSize = brush->bounds().size();
+ brushCenter = brush->center();
}
else {
- brushSize = brush->bounds().h;
- brushCenter = brush->center().y;
+ brushSize = gfx::Size(brush->bounds().h,
+ brush->bounds().w);
+ brushCenter = gfx::Point(brush->center().y,
+ brush->center().x);
}
}
const bool isDynamic = loop->getDynamics().isDynamic();
for (const auto& pt : refStroke) {
if (isDynamic) {
- brushSize = pt.size;
- brushCenter = (brushSize - brushSize % 2) / 2;
+ brushSize = gfx::Size(pt.size, pt.size);
+ int center = (brushSize.w - brushSize.w % 2) / 2;
+ brushCenter = gfx::Point(center, center);
}
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;
+ pt2.symmetry = symmetry;
+ switch (symmetry) {
+ case doc::SymmetryIndex::ROT_FLIP_270: {
+ int adj_x = 0;
+ int adj_y = 0;
+ if (m_x - double(int(m_x)) > 0) adj_y = 1;
+ if (m_y - double(int(m_y)) > 0) adj_x = 1;
+ if (adj_x == 1 && adj_y == 1) { adj_x = 0; adj_y = 0; }
+ pt2.x = -pt.y + m_x + m_y - (brushSize.w % 2 ? 1 : 0) + adj_x;
+ pt2.y = -pt.x + m_x + m_y - (brushSize.h % 2 ? 1 : 0) + adj_y;
+ break;
+ }
+ case doc::SymmetryIndex::ROT_FLIP_90:
+ pt2.x = pt.y + m_x - m_y + (m_x - int(m_x));
+ pt2.y = pt.x - m_x + m_y + (m_y - int(m_y));
+ break;
+ case doc::SymmetryIndex::ROTATED_90:
+ case doc::SymmetryIndex::ROTATED_270:
+ pt2.y = 2 * m_y - pt.y - (brushSize.h % 2 ? 1 : 0);
+ break;
+ case doc::SymmetryIndex::FLIPPED_X:
+ case doc::SymmetryIndex::FLIPPED_XY: {
+ pt2.x = 2 * (m_x + brushCenter.x) - pt.x - brushSize.w;
+ if (isDoubleDiagonalSymmetry)
+ pt2.y = 2 * (m_y + brushCenter.y) - pt.y - brushSize.h;
+ break;
+ }
+ default:
+ pt2.y = 2 * (m_y + brushCenter.y) - pt.y - brushSize.h;
+ break;
+ }
stroke.addPoint(pt2);
}
}
diff --git a/src/app/tools/symmetry.h b/src/app/tools/symmetry.h
index 22564cac7..a58719dda 100644
--- a/src/app/tools/symmetry.h
+++ b/src/app/tools/symmetry.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2021 Igara Studio S.A.
+// Copyright (C) 2021-2024 Igara Studio S.A.
// Copyright (C) 2015 David Capello
//
// This program is distributed under the terms of
@@ -11,12 +11,20 @@
#include "app/tools/stroke.h"
#include "app/pref/preferences.h"
+#include "doc/brush.h"
namespace app {
namespace tools {
class ToolLoop;
+static inline bool does_symmetry_rotate_image(doc::SymmetryIndex symmetry) {
+ return symmetry == doc::SymmetryIndex::ROTATED_90 ||
+ symmetry == doc::SymmetryIndex::ROTATED_270 ||
+ symmetry == doc::SymmetryIndex::ROT_FLIP_90 ||
+ symmetry == doc::SymmetryIndex::ROT_FLIP_270;
+}
+
class Symmetry {
public:
Symmetry(gen::SymmetryMode symmetryMode, double x, double y)
@@ -30,8 +38,11 @@ public:
gen::SymmetryMode mode() const { return m_symmetryMode; }
private:
- void calculateSymmetricalStroke(const Stroke& refStroke, Stroke& stroke,
- ToolLoop* loop, gen::SymmetryMode symmetryMode);
+ void calculateSymmetricalStroke(const Stroke& refStroke,
+ Stroke& stroke,
+ ToolLoop* loop,
+ const doc::SymmetryIndex symmetry,
+ const bool isDoubleDiagonalSymmetry = false);
gen::SymmetryMode m_symmetryMode;
double m_x, m_y;
diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp
index ae266772d..7c258d2be 100644
--- a/src/app/tools/tool_loop_manager.cpp
+++ b/src/app/tools/tool_loop_manager.cpp
@@ -44,8 +44,7 @@ using namespace filters;
ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
: m_toolLoop(toolLoop)
, m_canceled(false)
- , m_brushSize0(toolLoop->getBrush()->size())
- , m_brushAngle0(toolLoop->getBrush()->angle())
+ , m_brush0(toolLoop->getBrush())
, m_dynamics(toolLoop->getDynamics())
{
}
@@ -209,7 +208,7 @@ void ToolLoopManager::movement(Pointer pointer)
doLoopStep(false);
}
-void ToolLoopManager::disableMouseStabilizer()
+void ToolLoopManager::disableMouseStabilizer()
{
// Disable mouse stabilizer for the current ToolLoopManager
m_dynamics.stabilizer = false;
@@ -341,12 +340,14 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes)
m_toolLoop->getPointShape()->getModifiedArea(
m_toolLoop,
strokeBounds.x,
- strokeBounds.y, r1);
+ strokeBounds.y,
+ stroke.firstPoint().symmetry, r1);
m_toolLoop->getPointShape()->getModifiedArea(
m_toolLoop,
strokeBounds.x+strokeBounds.w-1,
- strokeBounds.y+strokeBounds.h-1, r2);
+ strokeBounds.y+strokeBounds.h-1,
+ stroke.firstPoint().symmetry, r2);
m_dirtyArea.createUnion(m_dirtyArea, Region(r1.createUnion(r2)));
}
@@ -371,8 +372,8 @@ Stroke::Pt ToolLoopManager::getSpriteStrokePt(const Pointer& pointer)
{
// Convert the screen point to a sprite point
Stroke::Pt spritePoint = pointer.point();
- spritePoint.size = m_brushSize0;
- spritePoint.angle = m_brushAngle0;
+ spritePoint.size = m_brush0->size();
+ spritePoint.angle = m_brush0->angle();
// Center the input to some grid point if needed
snapToGrid(spritePoint);
diff --git a/src/app/tools/tool_loop_manager.h b/src/app/tools/tool_loop_manager.h
index 160915348..474f37beb 100644
--- a/src/app/tools/tool_loop_manager.h
+++ b/src/app/tools/tool_loop_manager.h
@@ -99,10 +99,9 @@ private:
Pointer m_lastPointer;
gfx::Region m_dirtyArea;
gfx::Region m_nextDirtyArea;
- const int m_brushSize0;
- const int m_brushAngle0;
DynamicsOptions m_dynamics;
gfx::PointF m_stabilizerCenter;
+ doc::Brush* m_brush0 = nullptr;
};
} // namespace tools
diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp
index cf88e5821..cc758865d 100644
--- a/src/app/ui/context_bar.cpp
+++ b/src/app/ui/context_bar.cpp
@@ -1525,18 +1525,22 @@ protected:
class ContextBar::SymmetryField : public ButtonSet {
public:
- SymmetryField() : ButtonSet(3) {
+ SymmetryField() : ButtonSet(5) {
setMultiMode(MultiMode::Set);
- auto* theme = SkinTheme::get(this);
+ auto theme = SkinTheme::get(this);
addItem(theme->parts.horizontalSymmetry(), theme->styles.symmetryField());
addItem(theme->parts.verticalSymmetry(), theme->styles.symmetryField());
+ addItem(theme->parts.rightDiagonalSymmetry(), theme->styles.symmetryField());
+ addItem(theme->parts.leftDiagonalSymmetry(), theme->styles.symmetryField());
addItem("...", theme->styles.symmetryOptions());
}
void setupTooltips(TooltipManager* tooltipManager) {
tooltipManager->addTooltipFor(at(0), Strings::symmetry_toggle_horizontal(), BOTTOM);
tooltipManager->addTooltipFor(at(1), Strings::symmetry_toggle_vertical(), BOTTOM);
- tooltipManager->addTooltipFor(at(2), Strings::symmetry_show_options(), BOTTOM);
+ tooltipManager->addTooltipFor(at(2), Strings::symmetry_toggle_right_diagonal(), BOTTOM);
+ tooltipManager->addTooltipFor(at(3), Strings::symmetry_toggle_left_diagonal(), BOTTOM);
+ tooltipManager->addTooltipFor(at(4), Strings::symmetry_show_options(), BOTTOM);
}
void updateWithCurrentDocument() {
@@ -1548,6 +1552,8 @@ public:
at(0)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::HORIZONTAL) ? true: false);
at(1)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::VERTICAL) ? true: false);
+ at(2)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::RIGHT_DIAG) ? true: false);
+ at(3)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::LEFT_DIAG) ? true: false);
}
private:
@@ -1561,16 +1567,70 @@ private:
DocumentPreferences& docPref =
Preferences::instance().document(doc);
+ auto oldMode = docPref.symmetry.mode();
int mode = 0;
if (at(0)->isSelected()) mode |= int(app::gen::SymmetryMode::HORIZONTAL);
if (at(1)->isSelected()) mode |= int(app::gen::SymmetryMode::VERTICAL);
+ if (at(2)->isSelected()) mode |= int(app::gen::SymmetryMode::RIGHT_DIAG);
+ if (at(3)->isSelected()) mode |= int(app::gen::SymmetryMode::LEFT_DIAG);
+
+ // Non sense symmetries filter:
+ // - H + 1Diag
+ // - V + 1Diag
+ // - H + V + 1Diag
+ const bool HorV = (mode & int(app::gen::SymmetryMode::HORIZONTAL)) ||
+ (mode & int(app::gen::SymmetryMode::VERTICAL));
+ const bool HxorV = !(mode & int(app::gen::SymmetryMode::HORIZONTAL)) !=
+ !(mode & int(app::gen::SymmetryMode::VERTICAL));
+ const bool RDxorLD = !(mode & int(app::gen::SymmetryMode::RIGHT_DIAG)) !=
+ !(mode & int(app::gen::SymmetryMode::LEFT_DIAG));
+ if (oldMode == gen::SymmetryMode::HORIZONTAL ||
+ oldMode == gen::SymmetryMode::VERTICAL ||
+ oldMode == gen::SymmetryMode::BOTH) {
+ if (HorV && RDxorLD) {
+ mode = int(app::gen::SymmetryMode::ALL);
+ at(0)->setSelected(true);
+ at(1)->setSelected(true);
+ at(2)->setSelected(true);
+ at(3)->setSelected(true);
+ }
+ }
+ else if (oldMode == gen::SymmetryMode::ALL) {
+ if (HxorV) {
+ mode = int(app::gen::SymmetryMode::BOTH_DIAG);
+ at(0)->setSelected(false);
+ at(1)->setSelected(false);
+ }
+ else if (RDxorLD) {
+ mode = int(app::gen::SymmetryMode::BOTH);
+ at(2)->setSelected(false);
+ at(3)->setSelected(false);
+ }
+ }
+ else if ((oldMode == gen::SymmetryMode::RIGHT_DIAG ||
+ oldMode == gen::SymmetryMode::LEFT_DIAG ||
+ oldMode == gen::SymmetryMode::BOTH_DIAG) &&
+ HorV) {
+ mode = int(app::gen::SymmetryMode::ALL);
+ at(0)->setSelected(true);
+ at(1)->setSelected(true);
+ at(2)->setSelected(true);
+ at(3)->setSelected(true);
+ }
+ // Non sense symmetries filter end
+
+ if (at(0)->isSelected()) mode |= int(app::gen::SymmetryMode::HORIZONTAL);
+ if (at(1)->isSelected()) mode |= int(app::gen::SymmetryMode::VERTICAL);
+ if (at(2)->isSelected()) mode |= int(app::gen::SymmetryMode::RIGHT_DIAG);
+ if (at(3)->isSelected()) mode |= int(app::gen::SymmetryMode::LEFT_DIAG);
+
if (app::gen::SymmetryMode(mode) != docPref.symmetry.mode()) {
docPref.symmetry.mode(app::gen::SymmetryMode(mode));
// Redraw symmetry rules
doc->notifyGeneralUpdate();
}
- else if (at(2)->isSelected()) {
- auto item = at(2);
+ else if (at(4)->isSelected()) {
+ auto* item = at(4);
gfx::Rect bounds = item->bounds();
item->setSelected(false);
@@ -2429,7 +2489,6 @@ void ContextBar::setActiveBrushBySlot(tools::Tool* tool, int slot)
// the slot.
if (brush.hasFlag(BrushSlot::Flags::ImageColor))
brush.brush()->resetImageColors();
-
setActiveBrush(brush.brush());
}
else {
diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp
index 8dde0b689..31ad9df44 100644
--- a/src/app/ui/editor/editor.cpp
+++ b/src/app/ui/editor/editor.cpp
@@ -955,6 +955,54 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
enclosingRect.w);
}
}
+ if (mode & int(app::gen::SymmetryMode::RIGHT_DIAG)) {
+ double y = m_docPref.symmetry.yAxis();
+ double x = m_docPref.symmetry.xAxis();
+ gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
+ // Bottom point intersection:
+ gfx::Point bottomLeft(enclosingRect.x + m_proj.applyY(mainTilePosition().x) + int(m_proj.applyX(x))
+ - (enclosingRect.h - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyY(y))),
+ enclosingRect.y2());
+ if (bottomLeft.x < enclosingRect.x) {
+ // Left intersection
+ bottomLeft.y = enclosingRect.y2() - enclosingRect.x + bottomLeft.x;
+ bottomLeft.x = enclosingRect.x;
+ }
+ // Top intersection
+ gfx::Point topRight(enclosingRect.x + m_proj.applyY(mainTilePosition().x) + int(m_proj.applyX(x))
+ + m_proj.applyY(mainTilePosition().y) + int(m_proj.applyY(y)),
+ enclosingRect.y);
+ if (enclosingRect.x2() < topRight.x) {
+ // Right intersection
+ topRight.y = enclosingRect.y + topRight.x - enclosingRect.x2();
+ topRight.x = enclosingRect.x2();
+ }
+ g->drawLine(color, bottomLeft, topRight);
+ }
+ if (mode & int(app::gen::SymmetryMode::LEFT_DIAG)) {
+ double y = m_docPref.symmetry.yAxis();
+ double x = m_docPref.symmetry.xAxis();
+ gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
+ // Bottom point intersection:
+ gfx::Point bottomRight(enclosingRect.x + m_proj.applyY(mainTilePosition().x) + int(m_proj.applyX(x))
+ + (enclosingRect.h - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyX(y))),
+ enclosingRect.y2());
+ if (enclosingRect.x2() < bottomRight.x) {
+ // Left intersection
+ bottomRight.y = enclosingRect.y2() - bottomRight.x + enclosingRect.x2();
+ bottomRight.x = enclosingRect.x2();
+ }
+ // Top intersection
+ gfx::Point topLeft(enclosingRect.x + m_proj.applyY(mainTilePosition().x) + int(m_proj.applyX(x))
+ - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyY(y)),
+ enclosingRect.y);
+ if (topLeft.x < enclosingRect.x) {
+ // Right intersection
+ topLeft.y = enclosingRect.y + enclosingRect.x - topLeft.x;
+ topLeft.x = enclosingRect.x;
+ }
+ g->drawLine(color, topLeft, bottomRight);
+ }
}
// Draw active layer/cel edges
diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp
index b4d84d5a9..8a20a6e90 100644
--- a/src/app/ui/editor/standby_state.cpp
+++ b/src/app/ui/editor/standby_state.cpp
@@ -1107,7 +1107,9 @@ bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, Handles& handle
auto theme = skin::SkinTheme::get(editor);
os::Surface* part = theme->parts.transformationHandle()->bitmap(0);
- if (int(mode) & int(app::gen::SymmetryMode::HORIZONTAL)) {
+ if ((int(mode) & int(app::gen::SymmetryMode::HORIZONTAL)) ||
+ (int(mode) & int(app::gen::SymmetryMode::RIGHT_DIAG)) ||
+ (int(mode) & int(app::gen::SymmetryMode::LEFT_DIAG))) {
double pos = symmetry.xAxis();
gfx::PointF pt1, pt2;
@@ -1128,7 +1130,9 @@ bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, Handles& handle
gfx::Rect(int(pt2.x), int(pt2.y), part->width(), part->height())));
}
- if (int(mode) & int(app::gen::SymmetryMode::VERTICAL)) {
+ if ((int(mode) & int(app::gen::SymmetryMode::VERTICAL)) ||
+ (int(mode) & int(app::gen::SymmetryMode::RIGHT_DIAG)) ||
+ (int(mode) & int(app::gen::SymmetryMode::LEFT_DIAG))) {
double pos = symmetry.yAxis();
gfx::PointF pt1, pt2;