mirror of
https://github.com/aseprite/aseprite.git
synced 2024-09-02 13:09:27 +00:00
Add flip/rotate brush support to ChangeBrush command (#1222)
Implement part of: https://github.com/aseprite/aseprite/issues/1222 https://steamcommunity.com/app/431730/discussions/1/1479856439033920884/ https://community.aseprite.org/t/flipping-rotating-the-current-brush/1854
This commit is contained in:
parent
2406e2b197
commit
7358626859
12
data/gui.xml
12
data/gui.xml
@ -224,6 +224,18 @@
|
|||||||
<key command="ChangeBrush" shortcut="Minus Pad">
|
<key command="ChangeBrush" shortcut="Minus Pad">
|
||||||
<param name="change" value="decrement-size" />
|
<param name="change" value="decrement-size" />
|
||||||
</key>
|
</key>
|
||||||
|
<key command="ChangeBrush" shortcut="Space+H">
|
||||||
|
<param name="change" value="flip-x" />
|
||||||
|
</key>
|
||||||
|
<key command="ChangeBrush" shortcut="Space+V">
|
||||||
|
<param name="change" value="flip-y" />
|
||||||
|
</key>
|
||||||
|
<key command="ChangeBrush" shortcut="Space+D">
|
||||||
|
<param name="change" value="flip-d" />
|
||||||
|
</key>
|
||||||
|
<key command="ChangeBrush" shortcut="Space+R">
|
||||||
|
<param name="change" value="rotate-90cw" />
|
||||||
|
</key>
|
||||||
|
|
||||||
<!-- Custom brushes -->
|
<!-- Custom brushes -->
|
||||||
<key command="ChangeBrush" shortcut="Alt+1">
|
<key command="ChangeBrush" shortcut="Alt+1">
|
||||||
|
@ -359,6 +359,10 @@ ChangeBrush_DecrementAngle = Decrement Angle
|
|||||||
ChangeBrush_DecrementSize = Decrement Size
|
ChangeBrush_DecrementSize = Decrement Size
|
||||||
ChangeBrush_IncrementAngle = Increment Angle
|
ChangeBrush_IncrementAngle = Increment Angle
|
||||||
ChangeBrush_IncrementSize = Increment Size
|
ChangeBrush_IncrementSize = Increment Size
|
||||||
|
ChangeBrush_FlipX = Flip Horizontally
|
||||||
|
ChangeBrush_FlipY = Flip Vertically
|
||||||
|
ChangeBrush_FlipD = Flip Diagonally
|
||||||
|
ChangeBrush_Rotate90CW = Rotate 90 CW
|
||||||
ChangeColor = Change Color: {0}
|
ChangeColor = Change Color: {0}
|
||||||
ChangeColor_IncrementFgIndex = Increment Foreground Index
|
ChangeColor_IncrementFgIndex = Increment Foreground Index
|
||||||
ChangeColor_DecrementFgIndex = Decrement Foreground Index
|
ChangeColor_DecrementFgIndex = Decrement Foreground Index
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (c) 2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -8,8 +9,6 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
#include "app/commands/command.h"
|
#include "app/commands/command.h"
|
||||||
#include "app/commands/params.h"
|
#include "app/commands/params.h"
|
||||||
@ -17,14 +16,26 @@
|
|||||||
#include "app/i18n/strings.h"
|
#include "app/i18n/strings.h"
|
||||||
#include "app/pref/preferences.h"
|
#include "app/pref/preferences.h"
|
||||||
#include "app/tools/active_tool.h"
|
#include "app/tools/active_tool.h"
|
||||||
|
#include "app/tools/ink.h"
|
||||||
#include "app/tools/tool.h"
|
#include "app/tools/tool.h"
|
||||||
#include "app/ui/context_bar.h"
|
#include "app/ui/context_bar.h"
|
||||||
|
#include "app/ui/main_window.h"
|
||||||
#include "base/convert_to.h"
|
#include "base/convert_to.h"
|
||||||
|
#include "doc/algorithm/flip_image.h"
|
||||||
#include "doc/brush.h"
|
#include "doc/brush.h"
|
||||||
|
#include "doc/image_ref.h"
|
||||||
|
#include "doc/primitives.h"
|
||||||
|
#include "doc/tile.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
|
using namespace doc;
|
||||||
|
using namespace doc::algorithm;
|
||||||
|
|
||||||
class ChangeBrushCommand : public Command {
|
class ChangeBrushCommand : public Command {
|
||||||
enum Change {
|
enum Change {
|
||||||
None,
|
None,
|
||||||
@ -32,6 +43,10 @@ class ChangeBrushCommand : public Command {
|
|||||||
DecrementSize,
|
DecrementSize,
|
||||||
IncrementAngle,
|
IncrementAngle,
|
||||||
DecrementAngle,
|
DecrementAngle,
|
||||||
|
FlipX,
|
||||||
|
FlipY,
|
||||||
|
FlipD,
|
||||||
|
Rotate90CW,
|
||||||
CustomBrush,
|
CustomBrush,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,6 +78,10 @@ void ChangeBrushCommand::onLoadParams(const Params& params)
|
|||||||
else if (change == "decrement-size") m_change = DecrementSize;
|
else if (change == "decrement-size") m_change = DecrementSize;
|
||||||
else if (change == "increment-angle") m_change = IncrementAngle;
|
else if (change == "increment-angle") m_change = IncrementAngle;
|
||||||
else if (change == "decrement-angle") m_change = DecrementAngle;
|
else if (change == "decrement-angle") m_change = DecrementAngle;
|
||||||
|
else if (change == "flip-x") m_change = FlipX;
|
||||||
|
else if (change == "flip-y") m_change = FlipY;
|
||||||
|
else if (change == "flip-d") m_change = FlipD;
|
||||||
|
else if (change == "rotate-90cw") m_change = Rotate90CW;
|
||||||
else if (change == "custom") m_change = CustomBrush;
|
else if (change == "custom") m_change = CustomBrush;
|
||||||
|
|
||||||
if (m_change == CustomBrush)
|
if (m_change == CustomBrush)
|
||||||
@ -73,37 +92,206 @@ void ChangeBrushCommand::onLoadParams(const Params& params)
|
|||||||
|
|
||||||
void ChangeBrushCommand::onExecute(Context* context)
|
void ChangeBrushCommand::onExecute(Context* context)
|
||||||
{
|
{
|
||||||
// Change the brush of the selected tool in the toolbar (not the
|
auto app = App::instance();
|
||||||
// active tool which might be different, e.g. the quick tool)
|
auto atm = app->activeToolManager();
|
||||||
tools::Tool* tool = App::instance()->activeToolManager()->activeTool();
|
auto& pref = Preferences::instance();
|
||||||
ToolPreferences::Brush& brush =
|
auto contextBar = (app->mainWindow() ? app->mainWindow()->getContextBar():
|
||||||
Preferences::instance().tool(tool).brush;
|
nullptr);
|
||||||
|
const BrushRef brush = (contextBar ? contextBar->activeBrush():
|
||||||
|
nullptr);
|
||||||
|
const bool isImageBrush = (brush && brush->type() == kImageBrushType);
|
||||||
|
|
||||||
|
// Change the brush of the active tool (i.e. quick tool) only if the
|
||||||
|
// active tool is of paint/eraser (e.g. proximity tool for eraser),
|
||||||
|
// in other case (e.g. if the active tool is the Hand tool because
|
||||||
|
// the user pressed the Space bar modifier), we'll change the brush
|
||||||
|
// of the selected tool in the toolbar (ignoring the quick tool).
|
||||||
|
tools::Tool* tool = atm->activeTool();
|
||||||
|
if (!tool->getInk(0)->isPaint() &&
|
||||||
|
!tool->getInk(0)->isEffect() &&
|
||||||
|
!tool->getInk(0)->isEraser() &&
|
||||||
|
!tool->getInk(0)->isShading()) {
|
||||||
|
tool = atm->selectedTool();
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolPreferences::Brush& brushPref = pref.tool(tool).brush;
|
||||||
|
|
||||||
switch (m_change) {
|
switch (m_change) {
|
||||||
|
|
||||||
case None:
|
case None:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IncrementSize:
|
case IncrementSize:
|
||||||
if (brush.size() < doc::Brush::kMaxBrushSize)
|
|
||||||
brush.size(brush.size()+1);
|
|
||||||
break;
|
|
||||||
case DecrementSize:
|
case DecrementSize:
|
||||||
if (brush.size() > doc::Brush::kMinBrushSize)
|
// Resize x2 or x0.5 when resizing image brushes
|
||||||
brush.size(brush.size()-1);
|
if (isImageBrush) {
|
||||||
|
double scale = 1.0;
|
||||||
|
switch (m_change) {
|
||||||
|
case IncrementSize: scale = 2.0; break;
|
||||||
|
case DecrementSize: scale = 0.5; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Size size = brush->bounds().size();
|
||||||
|
size = gfx::Size(std::max<int>(1, size.w * scale),
|
||||||
|
std::max<int>(1, size.h * scale));
|
||||||
|
|
||||||
|
ImageRef newImg(Image::create(brush->image()->pixelFormat(), size.w, size.h));
|
||||||
|
ImageRef newMsk(Image::create(IMAGE_BITMAP, size.w, size.h));
|
||||||
|
const color_t bg = brush->image()->maskColor();
|
||||||
|
newImg->setMaskColor(bg);
|
||||||
|
|
||||||
|
newImg->clear(bg);
|
||||||
|
newMsk->clear(0);
|
||||||
|
|
||||||
|
resize_image(brush->image(), newImg.get(),
|
||||||
|
RESIZE_METHOD_NEAREST_NEIGHBOR,
|
||||||
|
nullptr, nullptr, bg);
|
||||||
|
|
||||||
|
resize_image(brush->maskBitmap(), newMsk.get(),
|
||||||
|
RESIZE_METHOD_NEAREST_NEIGHBOR,
|
||||||
|
nullptr, nullptr, 0);
|
||||||
|
|
||||||
|
// Create a copy of the brush (to avoid modifying the original
|
||||||
|
// brush from the AppBrushes stock)
|
||||||
|
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||||
|
newBrush->setImage(newImg.get(),
|
||||||
|
newMsk.get());
|
||||||
|
contextBar->setActiveBrush(newBrush);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (m_change) {
|
||||||
|
case IncrementSize:
|
||||||
|
if (brushPref.size() < Brush::kMaxBrushSize)
|
||||||
|
brushPref.size(brushPref.size()+1);
|
||||||
|
break;
|
||||||
|
case DecrementSize:
|
||||||
|
if (brushPref.size() > Brush::kMinBrushSize)
|
||||||
|
brushPref.size(brushPref.size()-1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IncrementAngle:
|
case IncrementAngle:
|
||||||
if (brush.angle() < 180)
|
if (brushPref.angle() < 180)
|
||||||
brush.angle(brush.angle()+1);
|
brushPref.angle(brushPref.angle()+1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DecrementAngle:
|
case DecrementAngle:
|
||||||
if (brush.angle() > 0)
|
if (brushPref.angle() > 0)
|
||||||
brush.angle(brush.angle()-1);
|
brushPref.angle(brushPref.angle()-1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FlipX:
|
||||||
|
case FlipY:
|
||||||
|
if (isImageBrush) {
|
||||||
|
ImageRef newImg(Image::createCopy(brush->image()));
|
||||||
|
ImageRef newMsk(Image::createCopy(brush->maskBitmap()));
|
||||||
|
const gfx::Rect bounds = newImg->bounds();
|
||||||
|
|
||||||
|
switch (m_change) {
|
||||||
|
case FlipX:
|
||||||
|
flip_image(newImg.get(), bounds, FlipType::FlipHorizontal);
|
||||||
|
flip_image(newMsk.get(), bounds, FlipType::FlipHorizontal);
|
||||||
|
break;
|
||||||
|
case FlipY:
|
||||||
|
flip_image(newImg.get(), bounds, FlipType::FlipVertical);
|
||||||
|
flip_image(newMsk.get(), bounds, FlipType::FlipVertical);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||||
|
newBrush->setImage(newImg.get(),
|
||||||
|
newMsk.get());
|
||||||
|
contextBar->setActiveBrush(newBrush);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (m_change) {
|
||||||
|
case FlipX:
|
||||||
|
brushPref.angle(brushPref.angle() < 0 ? 180 + brushPref.angle():
|
||||||
|
180 - brushPref.angle());
|
||||||
|
break;
|
||||||
|
case FlipY:
|
||||||
|
brushPref.angle(-brushPref.angle());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FlipD:
|
||||||
|
case Rotate90CW:
|
||||||
|
if (isImageBrush) {
|
||||||
|
const gfx::Rect origBounds = brush->bounds();
|
||||||
|
const int m = std::max(origBounds.w, origBounds.h);
|
||||||
|
const gfx::Rect maxBounds(0, 0, m, m);
|
||||||
|
ImageRef newImg(Image::create(brush->image()->pixelFormat(), m, m));
|
||||||
|
ImageRef newMsk(Image::create(IMAGE_BITMAP, m, m));
|
||||||
|
const color_t bg = brush->image()->maskColor();
|
||||||
|
newImg->setMaskColor(bg);
|
||||||
|
|
||||||
|
newImg->clear(bg);
|
||||||
|
newMsk->clear(0);
|
||||||
|
|
||||||
|
const gfx::Clip clip(0, 0, 0, 0, origBounds.w, origBounds.h);
|
||||||
|
newImg->copy(brush->image(), clip);
|
||||||
|
newMsk->copy(brush->maskBitmap(), clip);
|
||||||
|
|
||||||
|
gfx::Rect cropBounds;
|
||||||
|
|
||||||
|
switch (m_change) {
|
||||||
|
|
||||||
|
case FlipD:
|
||||||
|
flip_image(newImg.get(), maxBounds, FlipType::FlipDiagonal);
|
||||||
|
flip_image(newMsk.get(), maxBounds, FlipType::FlipDiagonal);
|
||||||
|
cropBounds = gfx::Rect(0, 0, origBounds.h, origBounds.w);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Rotate90CW:
|
||||||
|
// To rotate 90cw:
|
||||||
|
//
|
||||||
|
// A B -> C A
|
||||||
|
// C D D B
|
||||||
|
//
|
||||||
|
// We can flip Y and then flip D:
|
||||||
|
//
|
||||||
|
// A B -> C D -> C A
|
||||||
|
// C D A B D B
|
||||||
|
//
|
||||||
|
flip_image(newImg.get(), maxBounds, FlipType::FlipVertical);
|
||||||
|
flip_image(newImg.get(), maxBounds, FlipType::FlipDiagonal);
|
||||||
|
flip_image(newMsk.get(), maxBounds, FlipType::FlipVertical);
|
||||||
|
flip_image(newMsk.get(), maxBounds, FlipType::FlipDiagonal);
|
||||||
|
|
||||||
|
cropBounds = gfx::Rect(m - origBounds.h, 0,
|
||||||
|
origBounds.h, origBounds.w);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageRef newImg2(crop_image(newImg.get(), cropBounds, bg));
|
||||||
|
ImageRef newMsk2(crop_image(newMsk.get(), cropBounds, bg));
|
||||||
|
|
||||||
|
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||||
|
newBrush->setImage(newImg.get(),
|
||||||
|
newMsk.get());
|
||||||
|
contextBar->setActiveBrush(newBrush);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case CustomBrush:
|
case CustomBrush:
|
||||||
App::instance()->contextBar()
|
if (contextBar)
|
||||||
->setActiveBrushBySlot(tool, m_slot);
|
contextBar->setActiveBrushBySlot(tool, m_slot);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify the ActiveToolManager that the active brush changed, so we
|
||||||
|
// can show the new brush in the real (non-quick) active tool.
|
||||||
|
//
|
||||||
|
// E.g. Space key activates the Hand quick tool, Space+H flips the
|
||||||
|
// brush, but we are still in the Hand tool, in this case we'd like
|
||||||
|
// to go back to the original tool (e.g. Pencil) and show the new
|
||||||
|
// brush.
|
||||||
|
atm->brushChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ChangeBrushCommand::onGetFriendlyName() const
|
std::string ChangeBrushCommand::onGetFriendlyName() const
|
||||||
@ -115,6 +303,10 @@ std::string ChangeBrushCommand::onGetFriendlyName() const
|
|||||||
case DecrementSize: change = Strings::commands_ChangeBrush_DecrementSize(); break;
|
case DecrementSize: change = Strings::commands_ChangeBrush_DecrementSize(); break;
|
||||||
case IncrementAngle: change = Strings::commands_ChangeBrush_IncrementAngle(); break;
|
case IncrementAngle: change = Strings::commands_ChangeBrush_IncrementAngle(); break;
|
||||||
case DecrementAngle: change = Strings::commands_ChangeBrush_DecrementAngle(); break;
|
case DecrementAngle: change = Strings::commands_ChangeBrush_DecrementAngle(); break;
|
||||||
|
case FlipX: change = Strings::commands_ChangeBrush_FlipX(); break;
|
||||||
|
case FlipY: change = Strings::commands_ChangeBrush_FlipY(); break;
|
||||||
|
case FlipD: change = Strings::commands_ChangeBrush_FlipD(); break;
|
||||||
|
case Rotate90CW: change = Strings::commands_ChangeBrush_Rotate90CW(); break;
|
||||||
case CustomBrush:
|
case CustomBrush:
|
||||||
change = fmt::format(Strings::commands_ChangeBrush_CustomBrush(), m_slot);
|
change = fmt::format(Strings::commands_ChangeBrush_CustomBrush(), m_slot);
|
||||||
break;
|
break;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2016 David Capello
|
// Copyright (C) 2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -144,6 +144,12 @@ void ActiveToolManager::newQuickToolSelectedFromEditor(Tool* tool)
|
|||||||
m_quickTool = tool;
|
m_quickTool = tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActiveToolManager::brushChanged()
|
||||||
|
{
|
||||||
|
ActiveToolChangeTrigger trigger(this);
|
||||||
|
m_quickTool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void ActiveToolManager::regularTipProximity()
|
void ActiveToolManager::regularTipProximity()
|
||||||
{
|
{
|
||||||
if (m_proximityTool != nullptr) {
|
if (m_proximityTool != nullptr) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2020 Igara Studio S.A.
|
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2016 David Capello
|
// Copyright (C) 2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -42,6 +42,7 @@ public:
|
|||||||
// modify the active tool.
|
// modify the active tool.
|
||||||
void newToolSelectedInToolBar(Tool* tool);
|
void newToolSelectedInToolBar(Tool* tool);
|
||||||
void newQuickToolSelectedFromEditor(Tool* tool);
|
void newQuickToolSelectedFromEditor(Tool* tool);
|
||||||
|
void brushChanged();
|
||||||
void regularTipProximity();
|
void regularTipProximity();
|
||||||
void eraserTipProximity();
|
void eraserTipProximity();
|
||||||
void pressButton(const Pointer& pointer);
|
void pressButton(const Pointer& pointer);
|
||||||
|
Loading…
Reference in New Issue
Block a user