mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-05 18:40:37 +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">
|
||||
<param name="change" value="decrement-size" />
|
||||
</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 -->
|
||||
<key command="ChangeBrush" shortcut="Alt+1">
|
||||
|
@ -359,6 +359,10 @@ ChangeBrush_DecrementAngle = Decrement Angle
|
||||
ChangeBrush_DecrementSize = Decrement Size
|
||||
ChangeBrush_IncrementAngle = Increment Angle
|
||||
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_IncrementFgIndex = Increment Foreground Index
|
||||
ChangeColor_DecrementFgIndex = Decrement Foreground Index
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2023 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -8,8 +9,6 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
@ -17,14 +16,26 @@
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/tools/active_tool.h"
|
||||
#include "app/tools/ink.h"
|
||||
#include "app/tools/tool.h"
|
||||
#include "app/ui/context_bar.h"
|
||||
#include "app/ui/main_window.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "doc/algorithm/flip_image.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/tile.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace doc;
|
||||
using namespace doc::algorithm;
|
||||
|
||||
class ChangeBrushCommand : public Command {
|
||||
enum Change {
|
||||
None,
|
||||
@ -32,6 +43,10 @@ class ChangeBrushCommand : public Command {
|
||||
DecrementSize,
|
||||
IncrementAngle,
|
||||
DecrementAngle,
|
||||
FlipX,
|
||||
FlipY,
|
||||
FlipD,
|
||||
Rotate90CW,
|
||||
CustomBrush,
|
||||
};
|
||||
|
||||
@ -63,6 +78,10 @@ void ChangeBrushCommand::onLoadParams(const Params& params)
|
||||
else if (change == "decrement-size") m_change = DecrementSize;
|
||||
else if (change == "increment-angle") m_change = IncrementAngle;
|
||||
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;
|
||||
|
||||
if (m_change == CustomBrush)
|
||||
@ -73,37 +92,206 @@ void ChangeBrushCommand::onLoadParams(const Params& params)
|
||||
|
||||
void ChangeBrushCommand::onExecute(Context* context)
|
||||
{
|
||||
// Change the brush of the selected tool in the toolbar (not the
|
||||
// active tool which might be different, e.g. the quick tool)
|
||||
tools::Tool* tool = App::instance()->activeToolManager()->activeTool();
|
||||
ToolPreferences::Brush& brush =
|
||||
Preferences::instance().tool(tool).brush;
|
||||
auto app = App::instance();
|
||||
auto atm = app->activeToolManager();
|
||||
auto& pref = Preferences::instance();
|
||||
auto contextBar = (app->mainWindow() ? app->mainWindow()->getContextBar():
|
||||
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) {
|
||||
|
||||
case None:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
case IncrementSize:
|
||||
if (brush.size() < doc::Brush::kMaxBrushSize)
|
||||
brush.size(brush.size()+1);
|
||||
break;
|
||||
case DecrementSize:
|
||||
if (brush.size() > doc::Brush::kMinBrushSize)
|
||||
brush.size(brush.size()-1);
|
||||
// Resize x2 or x0.5 when resizing image brushes
|
||||
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;
|
||||
|
||||
case IncrementAngle:
|
||||
if (brush.angle() < 180)
|
||||
brush.angle(brush.angle()+1);
|
||||
if (brushPref.angle() < 180)
|
||||
brushPref.angle(brushPref.angle()+1);
|
||||
break;
|
||||
|
||||
case DecrementAngle:
|
||||
if (brush.angle() > 0)
|
||||
brush.angle(brush.angle()-1);
|
||||
if (brushPref.angle() > 0)
|
||||
brushPref.angle(brushPref.angle()-1);
|
||||
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:
|
||||
App::instance()->contextBar()
|
||||
->setActiveBrushBySlot(tool, m_slot);
|
||||
if (contextBar)
|
||||
contextBar->setActiveBrushBySlot(tool, m_slot);
|
||||
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
|
||||
@ -115,6 +303,10 @@ std::string ChangeBrushCommand::onGetFriendlyName() const
|
||||
case DecrementSize: change = Strings::commands_ChangeBrush_DecrementSize(); break;
|
||||
case IncrementAngle: change = Strings::commands_ChangeBrush_IncrementAngle(); 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:
|
||||
change = fmt::format(Strings::commands_ChangeBrush_CustomBrush(), m_slot);
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -144,6 +144,12 @@ void ActiveToolManager::newQuickToolSelectedFromEditor(Tool* tool)
|
||||
m_quickTool = tool;
|
||||
}
|
||||
|
||||
void ActiveToolManager::brushChanged()
|
||||
{
|
||||
ActiveToolChangeTrigger trigger(this);
|
||||
m_quickTool = nullptr;
|
||||
}
|
||||
|
||||
void ActiveToolManager::regularTipProximity()
|
||||
{
|
||||
if (m_proximityTool != nullptr) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -42,6 +42,7 @@ public:
|
||||
// modify the active tool.
|
||||
void newToolSelectedInToolBar(Tool* tool);
|
||||
void newQuickToolSelectedFromEditor(Tool* tool);
|
||||
void brushChanged();
|
||||
void regularTipProximity();
|
||||
void eraserTipProximity();
|
||||
void pressButton(const Pointer& pointer);
|
||||
|
Loading…
x
Reference in New Issue
Block a user