mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-16 22:18:30 +00:00
Add diagonal symmetry (fix #1171)
Added symmetry UI buttons, 45 and -45 degrees axis. Now are possible activate all four symmetries, both diagonal symmetries, individual diagonal symmetry, and the old horizontal/vertical combinations. Also added symmetry to image brushes (prior to this fix: only the brush position was symmetrical, the image brush was not flipped).
This commit is contained in:
parent
053a538272
commit
b7aa5d5ebe
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -367,9 +367,9 @@
|
||||
<part id="icon_white" x="64" y="256" w="16" h="16" />
|
||||
<part id="icon_transparent" x="80" y="256" w="16" h="16" />
|
||||
<part id="color_wheel_indicator" x="48" y="192" w="4" h="4" />
|
||||
<part id="no_symmetry" x="144" y="240" w="13" h="13" />
|
||||
<part id="horizontal_symmetry" x="160" y="240" w="13" h="13" />
|
||||
<part id="vertical_symmetry" x="176" y="240" w="13" h="13" />
|
||||
<part id="no_symmetry" x="128" y="240" w="13" h="13" />
|
||||
<part id="horizontal_symmetry" x="144" y="240" w="13" h="13" />
|
||||
<part id="vertical_symmetry" x="160" y="240" w="13" h="13" />
|
||||
<part id="icon_arrow_down" x="144" y="256" w="7" h="4" />
|
||||
<part id="icon_close" x="152" y="256" w="7" h="7" />
|
||||
<part id="icon_search" x="160" y="256" w="8" h="8" />
|
||||
@ -454,6 +454,8 @@
|
||||
<part id="multi_win_icon" x="104" y="256" w="8" h="7" />
|
||||
<part id="spin_up" x="128" y="256" w="5" h="3" />
|
||||
<part id="spin_down" x="128" y="259" w="5" h="3" />
|
||||
<part id="right_diagonal_symmetry" x="176" y="240" w="13" h="13" />
|
||||
<part id="left_diagonal_symmetry" x="192" y="240" w="13" h="13" />
|
||||
</parts>
|
||||
<styles>
|
||||
<style id="box" />
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -363,9 +363,9 @@
|
||||
<part id="icon_white" x="64" y="256" w="16" h="16" />
|
||||
<part id="icon_transparent" x="80" y="256" w="16" h="16" />
|
||||
<part id="color_wheel_indicator" x="48" y="192" w="4" h="4" />
|
||||
<part id="no_symmetry" x="144" y="240" w="13" h="13" />
|
||||
<part id="horizontal_symmetry" x="160" y="240" w="13" h="13" />
|
||||
<part id="vertical_symmetry" x="176" y="240" w="13" h="13" />
|
||||
<part id="no_symmetry" x="128" y="240" w="13" h="13" />
|
||||
<part id="horizontal_symmetry" x="144" y="240" w="13" h="13" />
|
||||
<part id="vertical_symmetry" x="160" y="240" w="13" h="13" />
|
||||
<part id="icon_arrow_down" x="144" y="256" w="7" h="4" />
|
||||
<part id="icon_close" x="152" y="256" w="7" h="7" />
|
||||
<part id="icon_search" x="160" y="256" w="8" h="8" />
|
||||
@ -450,6 +450,8 @@
|
||||
<part id="multi_win_icon" x="104" y="256" w="8" h="7" />
|
||||
<part id="spin_up" x="128" y="256" w="5" h="3" />
|
||||
<part id="spin_down" x="128" y="259" w="5" h="3" />
|
||||
<part id="right_diagonal_symmetry" x="176" y="240" w="13" h="13" />
|
||||
<part id="left_diagonal_symmetry" x="192" y="240" w="13" h="13" />
|
||||
</parts>
|
||||
<styles>
|
||||
<style id="box" />
|
||||
|
@ -548,6 +548,12 @@
|
||||
<key command="SymmetryMode">
|
||||
<param name="orientation" value="horizontal" />
|
||||
</key>
|
||||
<key command="SymmetryMode">
|
||||
<param name="orientation" value="right_diagonal" />
|
||||
</key>
|
||||
<key command="SymmetryMode">
|
||||
<param name="orientation" value="left_diagonal" />
|
||||
</key>
|
||||
<key command="AutocropSprite" />
|
||||
<key command="AutocropSprite">
|
||||
<param name="byGrid" value="true" />
|
||||
|
@ -94,6 +94,10 @@
|
||||
<value id="HORIZONTAL" value="1" />
|
||||
<value id="VERTICAL" value="2" />
|
||||
<value id="BOTH" value="3" />
|
||||
<value id="RIGHT_DIAG" value="4" />
|
||||
<value id="LEFT_DIAG" value="8" />
|
||||
<value id="BOTH_DIAG" value="12" />
|
||||
<value id="ALL" value="15" />
|
||||
</enum>
|
||||
<enum id="PaintingCursorType">
|
||||
<value id="SIMPLE_CROSSHAIR" value="0" />
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) { }
|
||||
|
@ -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<BitmapTraits>(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<BitmapTraits>(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<>
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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 <array>
|
||||
@ -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<std::shared_ptr<CompressedImage>, 4> m_compressedImages;
|
||||
std::array<std::shared_ptr<CompressedImage>, 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<Image> 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<Image> 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);
|
||||
}
|
||||
|
@ -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) { }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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<double>(x))
|
||||
- (enclosingRect.h - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyY<double>(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<double>(x))
|
||||
+ m_proj.applyY(mainTilePosition().y) + int(m_proj.applyY<double>(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<double>(x))
|
||||
+ (enclosingRect.h - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyX<double>(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<double>(x))
|
||||
- m_proj.applyY(mainTilePosition().y) - int(m_proj.applyY<double>(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
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user