mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-06 06:50:07 +00:00
Add possibility to create brushes from selection
Changes: * Add doc::kImageBrushType * Add doc::BrushPattern enum * Add pattern and pattern origin properties to doc::Brush * Add NewBrush and DiscardBrush commands (new Ctrl+B shortcut to create brushes) * Add BrushInkProcessing
This commit is contained in:
parent
98cd5056b3
commit
dfeff22b2f
@ -309,6 +309,7 @@
|
||||
<param name="quantity" value="1" />
|
||||
</key>
|
||||
|
||||
<key command="NewBrush" shortcut="Ctrl+B" mac="Cmd+B" />
|
||||
</commands>
|
||||
|
||||
<!-- Keyboard shortcuts to select tools -->
|
||||
|
@ -98,6 +98,9 @@
|
||||
<option id="new_version" type="std::string" />
|
||||
<option id="new_url" type="std::string" />
|
||||
</section>
|
||||
<section id="brush">
|
||||
<option id="pattern" type="doc::BrushPattern" default="doc::BrushPattern::ALIGNED_FROM_SRC" />
|
||||
</section>
|
||||
</global>
|
||||
|
||||
<tool>
|
||||
@ -109,9 +112,9 @@
|
||||
<option id="ink" type="InkType" />
|
||||
<option id="freehand_algorithm" type="FreehandAlgorithm" />
|
||||
<section id="brush">
|
||||
<option id="type" type="BrushType" />
|
||||
<option id="size" type="int" />
|
||||
<option id="angle" type="int" />
|
||||
<option id="type" type="BrushType" default="BrushType::CIRCLE" />
|
||||
<option id="size" type="int" default="1" />
|
||||
<option id="angle" type="int" default="0" />
|
||||
</section>
|
||||
<section id="spray">
|
||||
<option id="width" type="int" default="16" />
|
||||
|
@ -142,6 +142,7 @@ add_library(app-lib
|
||||
commands/cmd_cut.cpp
|
||||
commands/cmd_deselect_mask.cpp
|
||||
commands/cmd_developer_console.cpp
|
||||
commands/cmd_discard_brush.cpp
|
||||
commands/cmd_duplicate_layer.cpp
|
||||
commands/cmd_duplicate_sprite.cpp
|
||||
commands/cmd_duplicate_view.cpp
|
||||
@ -173,6 +174,7 @@ add_library(app-lib
|
||||
commands/cmd_merge_down_layer.cpp
|
||||
commands/cmd_move_cel.cpp
|
||||
commands/cmd_move_mask.cpp
|
||||
commands/cmd_new_brush.cpp
|
||||
commands/cmd_new_file.cpp
|
||||
commands/cmd_new_frame.cpp
|
||||
commands/cmd_new_frame_tag.cpp
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/context.h"
|
||||
#include "ui/manager.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -59,11 +60,20 @@ void CancelCommand::onExecute(Context* context)
|
||||
break;
|
||||
|
||||
case All:
|
||||
// Discard brush
|
||||
{
|
||||
Command* discardBrush = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush);
|
||||
context->executeCommand(discardBrush);
|
||||
}
|
||||
|
||||
// Deselect mask
|
||||
if (context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::HasVisibleMask)) {
|
||||
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
|
||||
context->executeCommand(cmd);
|
||||
Command* deselectMask = CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
|
||||
context->executeCommand(deselectMask);
|
||||
}
|
||||
|
||||
ui::Manager::getDefault()->invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
63
src/app/commands/cmd_discard_brush.cpp
Normal file
63
src/app/commands/cmd_discard_brush.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/settings/settings.h"
|
||||
#include "app/tools/tool_box.h"
|
||||
#include "app/ui/context_bar.h"
|
||||
#include "app/ui/editor/tool_loop_impl.h"
|
||||
#include "app/ui/main_window.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class DiscardBrushCommand : public Command {
|
||||
public:
|
||||
DiscardBrushCommand();
|
||||
Command* clone() const override { return new DiscardBrushCommand(*this); }
|
||||
|
||||
protected:
|
||||
bool onEnabled(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
};
|
||||
|
||||
DiscardBrushCommand::DiscardBrushCommand()
|
||||
: Command("DiscardBrush",
|
||||
"Discard Brush",
|
||||
CmdUIOnlyFlag)
|
||||
{
|
||||
}
|
||||
|
||||
bool DiscardBrushCommand::onEnabled(Context* context)
|
||||
{
|
||||
return is_tool_loop_brush_image();
|
||||
}
|
||||
|
||||
void DiscardBrushCommand::onExecute(Context* context)
|
||||
{
|
||||
discard_tool_loop_brush_image();
|
||||
|
||||
// Update context bar
|
||||
ISettings* settings = UIContext::instance()->settings();
|
||||
App::instance()->getMainWindow()->getContextBar()
|
||||
->updateFromTool(settings->getCurrentTool());
|
||||
}
|
||||
|
||||
Command* CommandFactory::createDiscardBrushCommand()
|
||||
{
|
||||
return new DiscardBrushCommand();
|
||||
}
|
||||
|
||||
} // namespace app
|
@ -13,6 +13,7 @@
|
||||
#include "app/color.h"
|
||||
#include "app/color_picker.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/modules/editors.h"
|
||||
#include "app/settings/settings.h"
|
||||
@ -72,7 +73,13 @@ void EyedropperCommand::onExecute(Context* context)
|
||||
if (!sprite)
|
||||
return;
|
||||
|
||||
// pixel position to get
|
||||
// Discard current image brush
|
||||
{
|
||||
Command* discardBrush = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush);
|
||||
context->executeCommand(discardBrush);
|
||||
}
|
||||
|
||||
// Pixel position to get
|
||||
gfx::Point pixelPos = editor->screenToEditor(ui::get_mouse_position());
|
||||
|
||||
// Check if we've to grab alpha channel or the merged color.
|
||||
|
81
src/app/commands/cmd_new_brush.cpp
Normal file
81
src/app/commands/cmd_new_brush.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/settings/settings.h"
|
||||
#include "app/tools/tool_box.h"
|
||||
#include "app/ui/context_bar.h"
|
||||
#include "app/ui/editor/tool_loop_impl.h"
|
||||
#include "app/ui/main_window.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "doc/mask.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class NewBrushCommand : public Command {
|
||||
public:
|
||||
NewBrushCommand();
|
||||
Command* clone() const override { return new NewBrushCommand(*this); }
|
||||
|
||||
protected:
|
||||
bool onEnabled(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
};
|
||||
|
||||
NewBrushCommand::NewBrushCommand()
|
||||
: Command("NewBrush",
|
||||
"New Brush",
|
||||
CmdUIOnlyFlag)
|
||||
{
|
||||
}
|
||||
|
||||
bool NewBrushCommand::onEnabled(Context* context)
|
||||
{
|
||||
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::HasVisibleMask);
|
||||
}
|
||||
|
||||
void NewBrushCommand::onExecute(Context* context)
|
||||
{
|
||||
// TODO if there is no mask available, create a new Editor state to select the brush bounds
|
||||
|
||||
doc::ImageRef image(new_image_from_mask(UIContext::instance()->activeSite()));
|
||||
if (!image) {
|
||||
ui::Alert::show("Error<<There is no selected area to create a brush.||&OK");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set brush
|
||||
set_tool_loop_brush_image(
|
||||
image, context->activeDocument()->mask()->bounds().getOrigin());
|
||||
|
||||
// Set pencil as current tool
|
||||
ISettings* settings = UIContext::instance()->settings();
|
||||
tools::Tool* pencil =
|
||||
App::instance()->getToolBox()->getToolById(tools::WellKnownTools::Pencil);
|
||||
settings->setCurrentTool(pencil);
|
||||
|
||||
// Deselect mask
|
||||
Command* cmd =
|
||||
CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
|
||||
UIContext::instance()->executeCommand(cmd);
|
||||
}
|
||||
|
||||
Command* CommandFactory::createNewBrushCommand()
|
||||
{
|
||||
return new NewBrushCommand();
|
||||
}
|
||||
|
||||
} // namespace app
|
@ -29,6 +29,7 @@ FOR_EACH_COMMAND(Cut)
|
||||
FOR_EACH_COMMAND(DeselectMask)
|
||||
FOR_EACH_COMMAND(Despeckle)
|
||||
FOR_EACH_COMMAND(DeveloperConsole)
|
||||
FOR_EACH_COMMAND(DiscardBrush)
|
||||
FOR_EACH_COMMAND(DuplicateLayer)
|
||||
FOR_EACH_COMMAND(DuplicateSprite)
|
||||
FOR_EACH_COMMAND(DuplicateView)
|
||||
@ -67,6 +68,7 @@ FOR_EACH_COMMAND(MaskContent)
|
||||
FOR_EACH_COMMAND(MergeDownLayer)
|
||||
FOR_EACH_COMMAND(MoveCel)
|
||||
FOR_EACH_COMMAND(MoveMask)
|
||||
FOR_EACH_COMMAND(NewBrush)
|
||||
FOR_EACH_COMMAND(NewFile)
|
||||
FOR_EACH_COMMAND(NewFrame)
|
||||
FOR_EACH_COMMAND(NewFrameTag)
|
||||
|
@ -12,10 +12,6 @@
|
||||
#include "app/pref/option.h"
|
||||
#include "doc/documents_observer.h"
|
||||
|
||||
namespace filters {
|
||||
enum class TiledMode;
|
||||
}
|
||||
|
||||
#include "generated_pref_types.h"
|
||||
|
||||
#include <map>
|
||||
|
@ -699,6 +699,63 @@ private:
|
||||
color_t m_color;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Brush Ink
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<typename ImageTraits>
|
||||
class BrushInkProcessing : public DoubleInkProcessing<BrushInkProcessing<ImageTraits>, ImageTraits> {
|
||||
public:
|
||||
BrushInkProcessing(ToolLoop* loop) {
|
||||
m_brush = loop->getBrush();
|
||||
m_opacity = loop->getOpacity();
|
||||
m_width = m_brush->bounds().w;
|
||||
m_height = m_brush->bounds().h;
|
||||
m_u = (loop->getOffset().x + m_brush->patternOrigin().x) % m_width;
|
||||
m_v = (loop->getOffset().y + m_brush->patternOrigin().y) % m_height;
|
||||
}
|
||||
|
||||
void processPixel(int x, int y) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
private:
|
||||
void alignPixelPoint(int& x, int& y) {
|
||||
x = (x - m_u) % m_width;
|
||||
y = (y - m_v) % m_height;
|
||||
if (x < 0) x = m_width - ((-x) % m_width);
|
||||
if (y < 0) y = m_height - ((-y) % m_height);
|
||||
}
|
||||
|
||||
Brush* m_brush;
|
||||
int m_opacity;
|
||||
int m_u, m_v, m_width, m_height;
|
||||
};
|
||||
|
||||
template<>
|
||||
void BrushInkProcessing<RgbTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
// TODO convert brush image to RGB
|
||||
color_t c = get_pixel(m_brush->image(), x, y);
|
||||
*m_dstAddress = rgba_blend_normal(*m_srcAddress, c, m_opacity);
|
||||
}
|
||||
|
||||
template<>
|
||||
void BrushInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
// TODO convert brush image to grayscale
|
||||
color_t c = get_pixel(m_brush->image(), x, y);
|
||||
*m_dstAddress = graya_blend_normal(*m_srcAddress, c, m_opacity);
|
||||
}
|
||||
|
||||
template<>
|
||||
void BrushInkProcessing<IndexedTraits>::processPixel(int x, int y) {
|
||||
alignPixelPoint(x, y);
|
||||
// TODO convert brush image to indexed
|
||||
color_t c = get_pixel(m_brush->image(), x, y);
|
||||
*m_dstAddress = c;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum {
|
||||
@ -711,6 +768,7 @@ enum {
|
||||
INK_JUMBLE,
|
||||
INK_SHADING,
|
||||
INK_XOR,
|
||||
INK_BRUSH,
|
||||
MAX_INKS
|
||||
};
|
||||
|
||||
@ -737,7 +795,8 @@ AlgoHLine ink_processing[][3] =
|
||||
DEFINE_INK(ReplaceInkProcessing),
|
||||
DEFINE_INK(JumbleInkProcessing),
|
||||
DEFINE_INK(ShadingInkProcessing),
|
||||
DEFINE_INK(XorInkProcessing)
|
||||
DEFINE_INK(XorInkProcessing),
|
||||
DEFINE_INK(BrushInkProcessing)
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
@ -60,7 +60,10 @@ public:
|
||||
|
||||
switch (m_type) {
|
||||
case Opaque:
|
||||
m_proc = ink_processing[INK_OPAQUE][depth];
|
||||
if (loop->getBrush()->type() == doc::kImageBrushType)
|
||||
m_proc = ink_processing[INK_BRUSH][depth];
|
||||
else
|
||||
m_proc = ink_processing[INK_OPAQUE][depth];
|
||||
break;
|
||||
case SetAlpha:
|
||||
m_proc = ink_processing[INK_SETALPHA][depth];
|
||||
@ -69,9 +72,12 @@ public:
|
||||
m_proc = ink_processing[INK_LOCKALPHA][depth];
|
||||
break;
|
||||
default:
|
||||
m_proc = (loop->getOpacity() == 255 ?
|
||||
ink_processing[INK_OPAQUE][depth]:
|
||||
ink_processing[INK_TRANSPARENT][depth]);
|
||||
if (loop->getBrush()->type() == doc::kImageBrushType)
|
||||
m_proc = ink_processing[INK_BRUSH][depth];
|
||||
else
|
||||
m_proc = (loop->getOpacity() == 255 ?
|
||||
ink_processing[INK_OPAQUE][depth]:
|
||||
ink_processing[INK_TRANSPARENT][depth]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -33,12 +33,14 @@ public:
|
||||
class BrushPointShape : public PointShape {
|
||||
Brush* m_brush;
|
||||
base::SharedPtr<CompressedImage> m_compressedImage;
|
||||
bool m_firstPoint;
|
||||
|
||||
public:
|
||||
|
||||
void preparePointShape(ToolLoop* loop) override {
|
||||
m_brush = loop->getBrush();
|
||||
m_compressedImage.reset(new CompressedImage(m_brush->image()));
|
||||
m_firstPoint = true;
|
||||
}
|
||||
|
||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
||||
@ -47,6 +49,22 @@ public:
|
||||
x += m_brush->bounds().x;
|
||||
y += m_brush->bounds().y;
|
||||
|
||||
if (m_firstPoint) {
|
||||
m_firstPoint = false;
|
||||
if (m_brush->type() == kImageBrushType) {
|
||||
if (m_brush->pattern() == BrushPattern::ALIGNED_TO_DST ||
|
||||
m_brush->pattern() == BrushPattern::PAINT_BRUSH) {
|
||||
m_brush->setPatternOrigin(gfx::Point(x, y)-loop->getOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (m_brush->type() == kImageBrushType &&
|
||||
m_brush->pattern() == BrushPattern::PAINT_BRUSH) {
|
||||
m_brush->setPatternOrigin(gfx::Point(x, y)-loop->getOffset());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto scanline : *m_compressedImage) {
|
||||
int u = x+scanline.x;
|
||||
doInkHline(u, y+scanline.y, u+scanline.w-1, loop);
|
||||
|
@ -41,6 +41,7 @@ namespace tools {
|
||||
using namespace gfx;
|
||||
|
||||
const char* WellKnownTools::RectangularMarquee = "rectangular_marquee";
|
||||
const char* WellKnownTools::Pencil = "pencil";
|
||||
const char* WellKnownTools::Eraser = "eraser";
|
||||
const char* WellKnownTools::Eyedropper = "eyedropper";
|
||||
|
||||
|
@ -22,6 +22,7 @@ namespace app {
|
||||
|
||||
namespace WellKnownTools {
|
||||
extern const char* RectangularMarquee;
|
||||
extern const char* Pencil;
|
||||
extern const char* Eraser;
|
||||
extern const char* Eyedropper;
|
||||
};
|
||||
|
@ -12,7 +12,9 @@
|
||||
#include "app/ui/context_bar.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/settings/ink_type.h"
|
||||
#include "app/settings/selection_mode.h"
|
||||
#include "app/settings/settings.h"
|
||||
@ -24,6 +26,7 @@
|
||||
#include "app/tools/tool_box.h"
|
||||
#include "app/ui/button_set.h"
|
||||
#include "app/ui/color_button.h"
|
||||
#include "app/ui/editor/tool_loop_impl.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/bind.h"
|
||||
@ -217,6 +220,52 @@ private:
|
||||
BrushTypeField* m_brushType;
|
||||
};
|
||||
|
||||
class ContextBar::BrushPatternField : public ComboBox
|
||||
{
|
||||
public:
|
||||
BrushPatternField() : m_lock(false) {
|
||||
addItem("Pattern aligned from source");
|
||||
addItem("Pattern aligned to destination");
|
||||
addItem("Paint brush");
|
||||
}
|
||||
|
||||
void setBrushPattern(BrushPattern type) {
|
||||
int index = 0;
|
||||
|
||||
switch (type) {
|
||||
case BrushPattern::ALIGNED_FROM_SRC: index = 0; break;
|
||||
case BrushPattern::ALIGNED_TO_DST: index = 1; break;
|
||||
case BrushPattern::PAINT_BRUSH: index = 2; break;
|
||||
}
|
||||
|
||||
m_lock = true;
|
||||
setSelectedItemIndex(index);
|
||||
m_lock = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
void onChange() override {
|
||||
ComboBox::onChange();
|
||||
|
||||
if (m_lock)
|
||||
return;
|
||||
|
||||
BrushPattern type = BrushPattern::ALIGNED_FROM_SRC;
|
||||
|
||||
switch (getSelectedItemIndex()) {
|
||||
case 0: type = BrushPattern::ALIGNED_FROM_SRC; break;
|
||||
case 1: type = BrushPattern::ALIGNED_TO_DST; break;
|
||||
case 2: type = BrushPattern::PAINT_BRUSH; break;
|
||||
}
|
||||
|
||||
ISettings* settings = UIContext::instance()->settings();
|
||||
Tool* currentTool = settings->getCurrentTool();
|
||||
App::instance()->preferences().brush.pattern(type);
|
||||
}
|
||||
|
||||
bool m_lock;
|
||||
};
|
||||
|
||||
class ContextBar::ToleranceField : public IntEntry
|
||||
{
|
||||
public:
|
||||
@ -745,6 +794,8 @@ ContextBar::ContextBar()
|
||||
addChild(m_brushType = new BrushTypeField());
|
||||
addChild(m_brushSize = new BrushSizeField());
|
||||
addChild(m_brushAngle = new BrushAngleField(m_brushType));
|
||||
addChild(m_discardBrush = new Button("Discard Brush"));
|
||||
addChild(m_brushPatternField = new BrushPatternField());
|
||||
|
||||
addChild(m_toleranceLabel = new Label("Tolerance:"));
|
||||
addChild(m_tolerance = new ToleranceField());
|
||||
@ -776,6 +827,8 @@ ContextBar::ContextBar()
|
||||
#endif
|
||||
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
||||
|
||||
addChild(m_newBrush = new Button("New Brush"));
|
||||
|
||||
setup_mini_font(m_toleranceLabel);
|
||||
setup_mini_font(m_opacityLabel);
|
||||
|
||||
@ -796,6 +849,8 @@ ContextBar::ContextBar()
|
||||
"component is used to setup the opacity level of all drawing tools.\n\n"
|
||||
"When unchecked -the default behavior- the color is picked\n"
|
||||
"from the composition of all sprite layers.", JI_LEFT | JI_TOP);
|
||||
tooltipManager->addTooltipFor(m_newBrush,
|
||||
"Create a brush from the selection.", JI_BOTTOM);
|
||||
m_selectionMode->setupTooltips(tooltipManager);
|
||||
m_dropPixels->setupTooltips(tooltipManager);
|
||||
m_freehandAlgo->setupTooltips(tooltipManager);
|
||||
@ -804,6 +859,8 @@ ContextBar::ContextBar()
|
||||
App::instance()->BrushAngleAfterChange.connect(&ContextBar::onBrushAngleChange, this);
|
||||
App::instance()->CurrentToolChange.connect(&ContextBar::onCurrentToolChange, this);
|
||||
m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
|
||||
m_newBrush->Click.connect(Bind<void>(&ContextBar::onNewBrush, this));
|
||||
m_discardBrush->Click.connect(Bind<void>(&ContextBar::onDiscardBrush, this));
|
||||
|
||||
onCurrentToolChange();
|
||||
}
|
||||
@ -878,6 +935,8 @@ void ContextBar::updateFromTool(tools::Tool* tool)
|
||||
m_brushType->setBrushSettings(brushSettings);
|
||||
m_brushSize->setTextf("%d", brushSettings->getSize());
|
||||
m_brushAngle->setTextf("%d", brushSettings->getAngle());
|
||||
m_brushPatternField->setBrushPattern(
|
||||
App::instance()->preferences().brush.pattern());
|
||||
|
||||
m_tolerance->setTextf("%d", toolSettings->getTolerance());
|
||||
m_contiguous->setSelected(toolSettings->getContiguous());
|
||||
@ -898,6 +957,9 @@ void ContextBar::updateFromTool(tools::Tool* tool)
|
||||
tool->getInk(1)->isPaint() ||
|
||||
tool->getInk(1)->isEffect());
|
||||
|
||||
// True if we have an image as brush
|
||||
bool hasImageBrush = (is_tool_loop_brush_image() ? true: false);
|
||||
|
||||
// True if the current tool is eyedropper.
|
||||
bool isEyedropper =
|
||||
(tool->getInk(0)->isEyedropper() ||
|
||||
@ -928,11 +990,13 @@ void ContextBar::updateFromTool(tools::Tool* tool)
|
||||
tool->getController(1)->isFreehand());
|
||||
|
||||
// Show/Hide fields
|
||||
m_brushType->setVisible(hasOpacity);
|
||||
m_brushSize->setVisible(hasOpacity);
|
||||
m_brushAngle->setVisible(hasOpacity);
|
||||
m_brushType->setVisible(hasOpacity && !hasImageBrush);
|
||||
m_brushSize->setVisible(hasOpacity && !hasImageBrush);
|
||||
m_brushAngle->setVisible(hasOpacity && !hasImageBrush);
|
||||
m_discardBrush->setVisible(hasOpacity && hasImageBrush);
|
||||
m_brushPatternField->setVisible(hasOpacity && hasImageBrush);
|
||||
m_opacityLabel->setVisible(hasOpacity);
|
||||
m_inkType->setVisible(hasInk);
|
||||
m_inkType->setVisible(hasInk && !hasImageBrush);
|
||||
m_inkOpacity->setVisible(hasOpacity);
|
||||
m_grabAlpha->setVisible(isEyedropper);
|
||||
m_autoSelectLayer->setVisible(isMove);
|
||||
@ -944,6 +1008,7 @@ void ContextBar::updateFromTool(tools::Tool* tool)
|
||||
m_selectionOptionsBox->setVisible(hasSelectOptions);
|
||||
m_selectionMode->setVisible(true);
|
||||
m_dropPixels->setVisible(false);
|
||||
m_newBrush->setVisible(hasSelectOptions);
|
||||
|
||||
layout();
|
||||
}
|
||||
@ -977,4 +1042,16 @@ void ContextBar::updateAutoSelectLayer(bool state)
|
||||
m_autoSelectLayer->setSelected(state);
|
||||
}
|
||||
|
||||
void ContextBar::onNewBrush()
|
||||
{
|
||||
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::NewBrush);
|
||||
UIContext::instance()->executeCommand(cmd);
|
||||
}
|
||||
|
||||
void ContextBar::onDiscardBrush()
|
||||
{
|
||||
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush);
|
||||
UIContext::instance()->executeCommand(cmd);
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
namespace ui {
|
||||
class Box;
|
||||
class Button;
|
||||
class Label;
|
||||
}
|
||||
|
||||
@ -51,6 +52,8 @@ namespace app {
|
||||
void onBrushAngleChange();
|
||||
void onCurrentToolChange();
|
||||
void onDropPixels(ContextBarObserver::DropAction action);
|
||||
void onNewBrush();
|
||||
void onDiscardBrush();
|
||||
|
||||
class BrushTypeField;
|
||||
class BrushAngleField;
|
||||
@ -65,6 +68,7 @@ namespace app {
|
||||
class TransparentColorField;
|
||||
class RotAlgorithmField;
|
||||
class FreehandAlgorithmField;
|
||||
class BrushPatternField;
|
||||
class GrabAlphaField;
|
||||
class DropPixelsField;
|
||||
class AutoSelectLayerField;
|
||||
@ -83,6 +87,9 @@ namespace app {
|
||||
AutoSelectLayerField* m_autoSelectLayer;
|
||||
ui::Box* m_freehandBox;
|
||||
FreehandAlgorithmField* m_freehandAlgo;
|
||||
ui::Button* m_newBrush;
|
||||
ui::Button* m_discardBrush;
|
||||
BrushPatternField* m_brushPatternField;
|
||||
ui::Box* m_sprayBox;
|
||||
SprayWidthField* m_sprayWidth;
|
||||
SpraySpeedField* m_spraySpeed;
|
||||
|
@ -119,7 +119,7 @@ void Editor::set_cursor_color(const app::Color& color)
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int brush_size_thick = 0;
|
||||
static Brush* current_brush = NULL;
|
||||
static base::SharedPtr<Brush> current_brush;
|
||||
|
||||
static void on_palette_change_update_cursor_color()
|
||||
{
|
||||
@ -158,15 +158,12 @@ static Brush* editor_get_current_brush(Editor* editor)
|
||||
if (!current_brush ||
|
||||
current_brush->type() != brush_settings->getType() ||
|
||||
current_brush->size() != brush_settings->getSize() ||
|
||||
current_brush->angle() != brush_settings->getAngle()) {
|
||||
delete current_brush;
|
||||
current_brush = new Brush(
|
||||
brush_settings->getType(),
|
||||
brush_settings->getSize(),
|
||||
brush_settings->getAngle());
|
||||
current_brush->angle() != brush_settings->getAngle() ||
|
||||
is_tool_loop_brush_image()) {
|
||||
current_brush = get_tool_loop_brush(brush_settings);
|
||||
}
|
||||
|
||||
return current_brush;
|
||||
return current_brush.get();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -192,8 +189,7 @@ void Editor::editor_cursor_exit()
|
||||
if (cursor_bound.seg != NULL)
|
||||
base_free(cursor_bound.seg);
|
||||
|
||||
delete current_brush;
|
||||
current_brush = NULL;
|
||||
current_brush.reset();
|
||||
}
|
||||
|
||||
// Draws the brush cursor inside the specified editor.
|
||||
@ -255,10 +251,6 @@ void Editor::drawBrushPreview(const gfx::Point& pos, bool refresh)
|
||||
Brush* brush = editor_get_current_brush(this);
|
||||
gfx::Rect brushBounds = brush->bounds();
|
||||
brushBounds.offset(spritePos);
|
||||
// brushBounds.x = m_zoom.remove(m_zoom.apply(brushBounds.x));
|
||||
// brushBounds.y = m_zoom.remove(m_zoom.apply(brushBounds.y));
|
||||
// brushBounds = m_zoom.remove(m_zoom.apply(brushBounds));
|
||||
// brushBounds.enlarge(1);
|
||||
|
||||
// Create the extra cel to show the brush preview
|
||||
Site site = getSite();
|
||||
@ -294,6 +286,7 @@ void Editor::drawBrushPreview(const gfx::Point& pos, bool refresh)
|
||||
this, UIContext::instance(), extraImage,
|
||||
-gfx::Point(brushBounds.x,
|
||||
brushBounds.y));
|
||||
|
||||
if (loop) {
|
||||
loop->getInk()->prepareInk(loop);
|
||||
loop->getIntertwine()->prepareIntertwine();
|
||||
|
@ -47,6 +47,57 @@ namespace app {
|
||||
|
||||
using namespace ui;
|
||||
|
||||
// TODO improve the design of the brush image
|
||||
|
||||
static base::SharedPtr<Brush> special_brush;
|
||||
static base::SharedPtr<Brush> last_brush;
|
||||
static gfx::Point brush_origin;
|
||||
|
||||
void set_tool_loop_brush_image(doc::ImageRef& image,
|
||||
const gfx::Point& origin)
|
||||
{
|
||||
special_brush.reset(new Brush());
|
||||
special_brush->setImage(image.get());
|
||||
special_brush->setPatternOrigin(brush_origin = origin);
|
||||
}
|
||||
|
||||
bool is_tool_loop_brush_image()
|
||||
{
|
||||
return (special_brush ? true: false);
|
||||
}
|
||||
|
||||
void discard_tool_loop_brush_image()
|
||||
{
|
||||
special_brush.reset();
|
||||
}
|
||||
|
||||
Image* get_tool_loop_brush_image()
|
||||
{
|
||||
if (special_brush)
|
||||
return special_brush->image();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
base::SharedPtr<Brush> get_tool_loop_brush(IBrushSettings* brushSettings)
|
||||
{
|
||||
base::SharedPtr<Brush> brush;
|
||||
|
||||
if (special_brush) {
|
||||
brush = special_brush;
|
||||
brush->setPattern(App::instance()->preferences().brush.pattern());
|
||||
brush->setPatternOrigin(brush_origin);
|
||||
}
|
||||
else {
|
||||
brush.reset(
|
||||
new Brush(
|
||||
brushSettings->getType(),
|
||||
brushSettings->getSize(),
|
||||
brushSettings->getAngle()));
|
||||
}
|
||||
return brush;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// For ToolLoopController
|
||||
|
||||
@ -55,7 +106,7 @@ class ToolLoopImpl : public tools::ToolLoop,
|
||||
Editor* m_editor;
|
||||
Context* m_context;
|
||||
tools::Tool* m_tool;
|
||||
Brush* m_brush;
|
||||
base::SharedPtr<Brush> m_brush;
|
||||
Document* m_document;
|
||||
Sprite* m_sprite;
|
||||
Layer* m_layer;
|
||||
@ -153,10 +204,7 @@ public:
|
||||
IBrushSettings* brush_settings = m_toolSettings->getBrush();
|
||||
ASSERT(brush_settings != NULL);
|
||||
|
||||
m_brush = new Brush(
|
||||
brush_settings->getType(),
|
||||
brush_settings->getSize(),
|
||||
brush_settings->getAngle());
|
||||
m_brush = get_tool_loop_brush(brush_settings);
|
||||
|
||||
if (m_ink->isSelection())
|
||||
m_useMask = false;
|
||||
@ -229,7 +277,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
delete m_brush;
|
||||
delete m_shadeTable;
|
||||
|
||||
if (redraw)
|
||||
@ -237,7 +284,7 @@ public:
|
||||
}
|
||||
|
||||
tools::Tool* getTool() override { return m_tool; }
|
||||
Brush* getBrush() override { return m_brush; }
|
||||
Brush* getBrush() override { return m_brush.get(); }
|
||||
Document* getDocument() override { return m_document; }
|
||||
Sprite* sprite() override { return m_sprite; }
|
||||
Layer* getLayer() override { return m_layer; }
|
||||
@ -404,7 +451,7 @@ class PreviewToolLoopImpl : public tools::ToolLoop,
|
||||
Editor* m_editor;
|
||||
Context* m_context;
|
||||
tools::Tool* m_tool;
|
||||
Brush* m_brush;
|
||||
base::SharedPtr<Brush> m_brush;
|
||||
Document* m_document;
|
||||
Sprite* m_sprite;
|
||||
Layer* m_layer;
|
||||
@ -462,11 +509,7 @@ public:
|
||||
IBrushSettings* brush_settings = m_toolSettings->getBrush();
|
||||
ASSERT(brush_settings != NULL);
|
||||
|
||||
m_brush = new Brush(
|
||||
brush_settings->getType(),
|
||||
brush_settings->getSize(),
|
||||
brush_settings->getAngle());
|
||||
|
||||
m_brush = get_tool_loop_brush(brush_settings);
|
||||
m_opacity = m_toolSettings->getOpacity();
|
||||
m_tolerance = m_toolSettings->getTolerance();
|
||||
m_contiguous = m_toolSettings->getContiguous();
|
||||
@ -488,7 +531,7 @@ public:
|
||||
// IToolLoop interface
|
||||
void dispose() override { }
|
||||
tools::Tool* getTool() override { return m_tool; }
|
||||
Brush* getBrush() override { return m_brush; }
|
||||
Brush* getBrush() override { return m_brush.get(); }
|
||||
Document* getDocument() override { return m_document; }
|
||||
Sprite* sprite() override { return m_sprite; }
|
||||
Layer* getLayer() override { return m_layer; }
|
||||
|
@ -9,20 +9,34 @@
|
||||
#define APP_UI_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/shared_ptr.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/fwd.h"
|
||||
|
||||
namespace doc {
|
||||
class Brush;
|
||||
class Image;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class Context;
|
||||
class Editor;
|
||||
class IBrushSettings;
|
||||
|
||||
namespace tools {
|
||||
class ToolLoop;
|
||||
}
|
||||
|
||||
void set_tool_loop_brush_image(
|
||||
doc::ImageRef& image,
|
||||
const gfx::Point& origin);
|
||||
bool is_tool_loop_brush_image();
|
||||
void discard_tool_loop_brush_image();
|
||||
doc::Image* get_tool_loop_brush_image();
|
||||
|
||||
base::SharedPtr<doc::Brush> get_tool_loop_brush(
|
||||
IBrushSettings* brushSettings);
|
||||
|
||||
tools::ToolLoop* create_tool_loop(
|
||||
Editor* editor, Context* context);
|
||||
|
||||
|
@ -24,6 +24,7 @@ Brush::Brush()
|
||||
m_type = kCircleBrushType;
|
||||
m_size = 1;
|
||||
m_angle = 0;
|
||||
m_pattern = BrushPattern::DEFAULT;
|
||||
|
||||
regenerate();
|
||||
}
|
||||
@ -33,6 +34,7 @@ Brush::Brush(BrushType type, int size, int angle)
|
||||
m_type = type;
|
||||
m_size = size;
|
||||
m_angle = angle;
|
||||
m_pattern = BrushPattern::DEFAULT;
|
||||
|
||||
regenerate();
|
||||
}
|
||||
@ -42,6 +44,8 @@ Brush::Brush(const Brush& brush)
|
||||
m_type = brush.m_type;
|
||||
m_size = brush.m_size;
|
||||
m_angle = brush.m_angle;
|
||||
m_pattern = brush.m_pattern;
|
||||
m_patternOrigin = brush.m_patternOrigin;
|
||||
|
||||
regenerate();
|
||||
}
|
||||
@ -54,7 +58,10 @@ Brush::~Brush()
|
||||
void Brush::setType(BrushType type)
|
||||
{
|
||||
m_type = type;
|
||||
regenerate();
|
||||
if (m_type != kImageBrushType)
|
||||
regenerate();
|
||||
else
|
||||
clean();
|
||||
}
|
||||
|
||||
void Brush::setSize(int size)
|
||||
@ -69,6 +76,15 @@ void Brush::setAngle(int angle)
|
||||
regenerate();
|
||||
}
|
||||
|
||||
void Brush::setImage(const Image* image)
|
||||
{
|
||||
m_type = kImageBrushType;
|
||||
m_image.reset(Image::createCopy(image));
|
||||
m_bounds = gfx::Rect(
|
||||
-m_image.get()->width()/2, -m_image.get()->height()/2,
|
||||
m_image.get()->width(), m_image.get()->height());
|
||||
}
|
||||
|
||||
// Cleans the brush's data (image and region).
|
||||
void Brush::clean()
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define DOC_BRUSH_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/brush_pattern.h"
|
||||
#include "doc/brush_type.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/point.h"
|
||||
@ -32,21 +33,33 @@ namespace doc {
|
||||
int angle() const { return m_angle; }
|
||||
Image* image() { return m_image.get(); }
|
||||
|
||||
BrushPattern pattern() { return m_pattern; }
|
||||
gfx::Point patternOrigin() { return m_patternOrigin; }
|
||||
|
||||
const gfx::Rect& bounds() const { return m_bounds; }
|
||||
|
||||
void setType(BrushType type);
|
||||
void setSize(int size);
|
||||
void setAngle(int angle);
|
||||
void setImage(const Image* image);
|
||||
void setPattern(BrushPattern pattern) {
|
||||
m_pattern = pattern;
|
||||
}
|
||||
void setPatternOrigin(const gfx::Point& patternOrigin) {
|
||||
m_patternOrigin = patternOrigin;
|
||||
}
|
||||
|
||||
private:
|
||||
void clean();
|
||||
void regenerate();
|
||||
|
||||
BrushType m_type; // Type of brush
|
||||
BrushType m_type; // Type of brush
|
||||
int m_size; // Size (diameter)
|
||||
int m_angle; // Angle in degrees 0-360
|
||||
ImageRef m_image; // Image of the brush
|
||||
gfx::Rect m_bounds;
|
||||
BrushPattern m_pattern; // How the image should be replicated
|
||||
gfx::Point m_patternOrigin; // From what position the brush was taken
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
|
22
src/doc/brush_pattern.h
Normal file
22
src/doc/brush_pattern.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2001-2015 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef DOC_BRUSH_PATTERN_H_INCLUDED
|
||||
#define DOC_BRUSH_PATTERN_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
namespace doc {
|
||||
|
||||
enum class BrushPattern {
|
||||
DEFAULT = 0,
|
||||
ALIGNED_FROM_SRC = 0,
|
||||
ALIGNED_TO_DST = 1,
|
||||
PAINT_BRUSH = 2,
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2001-2014 David Capello
|
||||
// Copyright (c) 2001-2015 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -14,9 +14,10 @@ namespace doc {
|
||||
kCircleBrushType = 0,
|
||||
kSquareBrushType = 1,
|
||||
kLineBrushType = 2,
|
||||
kImageBrushType = 3,
|
||||
|
||||
kFirstBrushType = kCircleBrushType,
|
||||
kLastBrushType = kLineBrushType,
|
||||
kLastBrushType = kImageBrushType,
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
|
@ -186,6 +186,7 @@ void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
|
||||
<< "#include \"app/color.h\"\n"
|
||||
<< "#include \"app/pref/option.h\"\n"
|
||||
<< "#include \"doc/anidir.h\"\n"
|
||||
<< "#include \"doc/brush_pattern.h\"\n"
|
||||
<< "#include \"doc/frame.h\"\n"
|
||||
<< "#include \"gfx/rect.h\"\n"
|
||||
<< "#include \"filters/tiled_mode.h\"\n"
|
||||
|
Loading…
Reference in New Issue
Block a user