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:
David Capello 2015-04-27 00:08:04 -03:00
parent 98cd5056b3
commit dfeff22b2f
24 changed files with 488 additions and 51 deletions

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ namespace app {
namespace WellKnownTools {
extern const char* RectangularMarquee;
extern const char* Pencil;
extern const char* Eraser;
extern const char* Eyedropper;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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