David Capello 2023-08-09 21:56:55 -03:00
parent 2406e2b197
commit 7358626859
5 changed files with 235 additions and 20 deletions

View File

@ -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">

View File

@ -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

View File

@ -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;

View File

@ -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) {

View File

@ -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);