mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 15:32:38 +00:00
Add pixel-perfect drawing mode for freehand tools (issue 315)
* Added new app::tools::IntertwineAsPixelPerfect intertwiner * Added app::tools::FreehandAlgorithm enum * Added app::tools::WellKnownIntertwiners constants
This commit is contained in:
parent
f19aae9232
commit
1554875618
31
src/app/settings/freehand_algorithm.h
Normal file
31
src/app/settings/freehand_algorithm.h
Normal file
@ -0,0 +1,31 @@
|
||||
/* Aseprite
|
||||
* Copyright (C) 2001-2013 David Capello
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef APP_SETTINGS_FREEHAND_ALGORITHM_H_INCLUDED
|
||||
#define APP_SETTINGS_FREEHAND_ALGORITHM_H_INCLUDED
|
||||
|
||||
namespace app {
|
||||
|
||||
enum FreehandAlgorithm {
|
||||
kDefaultFreehandAlgorithm,
|
||||
kPixelPerfectFreehandAlgorithm,
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif // APP_SETTINGS_FREEHAND_ALGORITHM_H_INCLUDED
|
@ -41,4 +41,4 @@ namespace app {
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif // SETTINGS_INK_TYPE_H_INCLUDED
|
||||
#endif // APP_SETTINGS_INK_TYPE_H_INCLUDED
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define APP_SETTINGS_SETTINGS_H_INCLUDED
|
||||
|
||||
#include "app/color.h"
|
||||
#include "app/settings/freehand_algorithm.h"
|
||||
#include "app/settings/ink_type.h"
|
||||
#include "app/settings/rotation_algorithm.h"
|
||||
#include "gfx/point.h"
|
||||
@ -91,6 +92,7 @@ namespace app {
|
||||
virtual int getSprayWidth() = 0;
|
||||
virtual int getSpraySpeed() = 0;
|
||||
virtual InkType getInkType() = 0;
|
||||
virtual FreehandAlgorithm getFreehandAlgorithm() = 0;
|
||||
|
||||
virtual void setOpacity(int opacity) = 0;
|
||||
virtual void setTolerance(int tolerance) = 0;
|
||||
@ -99,6 +101,7 @@ namespace app {
|
||||
virtual void setSprayWidth(int width) = 0;
|
||||
virtual void setSpraySpeed(int speed) = 0;
|
||||
virtual void setInkType(InkType inkType) = 0;
|
||||
virtual void setFreehandAlgorithm(FreehandAlgorithm algorithm) = 0;
|
||||
|
||||
virtual void addObserver(ToolSettingsObserver* observer) = 0;
|
||||
virtual void removeObserver(ToolSettingsObserver* observer) = 0;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define APP_SETTINGS_SETTINGS_OBSERVERS_H_INCLUDED
|
||||
|
||||
#include "app/color.h"
|
||||
#include "app/settings/freehand_algorithm.h"
|
||||
#include "app/settings/ink_type.h"
|
||||
#include "app/settings/rotation_algorithm.h"
|
||||
#include "raster/pen_type.h"
|
||||
@ -51,6 +52,7 @@ namespace app {
|
||||
virtual void onSetSprayWidth(int newSprayWidth) {}
|
||||
virtual void onSetSpraySpeed(int newSpraySpeed) {}
|
||||
virtual void onSetInkType(InkType newInkType) {}
|
||||
virtual void onSetFreehandAlgorithm(FreehandAlgorithm algorithm) {}
|
||||
};
|
||||
|
||||
class SelectionSettingsObserver {
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "app/color_swatches.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/settings/document_settings.h"
|
||||
#include "app/tools/controller.h"
|
||||
#include "app/tools/point_shape.h"
|
||||
#include "app/tools/tool.h"
|
||||
#include "app/tools/tool_box.h"
|
||||
@ -537,6 +538,7 @@ class UIToolSettingsImpl
|
||||
int m_spray_width;
|
||||
int m_spray_speed;
|
||||
InkType m_inkType;
|
||||
FreehandAlgorithm m_freehandAlgorithm;
|
||||
|
||||
public:
|
||||
|
||||
@ -554,6 +556,7 @@ public:
|
||||
m_spray_width = 16;
|
||||
m_spray_speed = 32;
|
||||
m_inkType = (InkType)get_config_int(cfg_section.c_str(), "InkType", (int)kDefaultInk);
|
||||
m_freehandAlgorithm = kDefaultFreehandAlgorithm;
|
||||
|
||||
m_pen.enableSignals(false);
|
||||
m_pen.setType((PenType)get_config_int(cfg_section.c_str(), "PenType", (int)PEN_TYPE_CIRCLE));
|
||||
@ -566,6 +569,12 @@ public:
|
||||
m_spray_width = get_config_int(cfg_section.c_str(), "SprayWidth", m_spray_width);
|
||||
m_spray_speed = get_config_int(cfg_section.c_str(), "SpraySpeed", m_spray_speed);
|
||||
}
|
||||
|
||||
if (m_tool->getController(0)->isFreehand() ||
|
||||
m_tool->getController(1)->isFreehand()) {
|
||||
m_freehandAlgorithm = (FreehandAlgorithm)get_config_int(cfg_section.c_str(), "FreehandAlgorithm", (int)kDefaultFreehandAlgorithm);
|
||||
setFreehandAlgorithm(m_freehandAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
~UIToolSettingsImpl()
|
||||
@ -586,6 +595,11 @@ public:
|
||||
set_config_int(cfg_section.c_str(), "SpraySpeed", m_spray_speed);
|
||||
}
|
||||
|
||||
if (m_tool->getController(0)->isFreehand() ||
|
||||
m_tool->getController(1)->isFreehand()) {
|
||||
set_config_int(cfg_section.c_str(), "FreehandAlgorithm", m_freehandAlgorithm);
|
||||
}
|
||||
|
||||
set_config_bool(cfg_section.c_str(), "PreviewFilled", m_previewFilled);
|
||||
}
|
||||
|
||||
@ -598,6 +612,7 @@ public:
|
||||
int getSprayWidth() OVERRIDE { return m_spray_width; }
|
||||
int getSpraySpeed() OVERRIDE { return m_spray_speed; }
|
||||
InkType getInkType() OVERRIDE { return m_inkType; }
|
||||
FreehandAlgorithm getFreehandAlgorithm() OVERRIDE { return m_freehandAlgorithm; }
|
||||
|
||||
void setOpacity(int opacity) OVERRIDE { m_opacity = opacity; }
|
||||
void setTolerance(int tolerance) OVERRIDE { m_tolerance = tolerance; }
|
||||
@ -606,6 +621,21 @@ public:
|
||||
void setSprayWidth(int width) OVERRIDE { m_spray_width = width; }
|
||||
void setSpraySpeed(int speed) OVERRIDE { m_spray_speed = speed; }
|
||||
void setInkType(InkType inkType) OVERRIDE { m_inkType = inkType; }
|
||||
void setFreehandAlgorithm(FreehandAlgorithm algorithm) OVERRIDE {
|
||||
m_freehandAlgorithm = algorithm;
|
||||
|
||||
tools::ToolBox* toolBox = App::instance()->getToolBox();
|
||||
for (int i=0; i<2; ++i) {
|
||||
if (algorithm == kPixelPerfectFreehandAlgorithm) {
|
||||
m_tool->setIntertwine(i, toolBox->getIntertwinerById(tools::WellKnownIntertwiners::AsPixelPerfect));
|
||||
m_tool->setTracePolicy(i, tools::TracePolicyLast);
|
||||
}
|
||||
else {
|
||||
m_tool->setIntertwine(i, toolBox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines));
|
||||
m_tool->setTracePolicy(i, tools::TracePolicyAccumulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addObserver(ToolSettingsObserver* observer) OVERRIDE {
|
||||
base::Observable<ToolSettingsObserver>::addObserver(observer);
|
||||
|
@ -35,7 +35,9 @@ namespace app {
|
||||
typedef std::vector<gfx::Point> Points;
|
||||
|
||||
virtual ~Controller() { }
|
||||
|
||||
virtual bool canSnapToGrid() { return true; }
|
||||
virtual bool isFreehand() { return false; }
|
||||
|
||||
// Called when the user starts drawing and each time a new button is
|
||||
// pressed. The controller could be sure that this method is called
|
||||
|
@ -31,6 +31,10 @@ using namespace gfx;
|
||||
// Controls clicks for tools like pencil
|
||||
class FreehandController : public Controller {
|
||||
public:
|
||||
bool isFreehand()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void pressButton(Points& points, const Point& point)
|
||||
{
|
||||
points.push_back(point);
|
||||
|
@ -33,6 +33,7 @@ namespace app {
|
||||
|
||||
virtual ~Intertwine() { }
|
||||
virtual bool snapByAngle() { return false; }
|
||||
virtual void prepareIntertwine() { }
|
||||
virtual void joinPoints(ToolLoop* loop, const Points& points) = 0;
|
||||
virtual void fillPoints(ToolLoop* loop, const Points& points) = 0;
|
||||
|
||||
|
@ -220,5 +220,80 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class IntertwineAsPixelPerfect : public Intertwine {
|
||||
struct PPData {
|
||||
Points& pts;
|
||||
ToolLoop* loop;
|
||||
PPData(Points& pts, ToolLoop* loop) : pts(pts), loop(loop) { }
|
||||
};
|
||||
|
||||
static void pixelPerfectLine(int x, int y, PPData* data)
|
||||
{
|
||||
gfx::Point newPoint(x, y);
|
||||
|
||||
if (data->pts.empty()
|
||||
|| data->pts[data->pts.size()-1] != newPoint) {
|
||||
data->pts.push_back(newPoint);
|
||||
}
|
||||
}
|
||||
|
||||
Points m_pts;
|
||||
|
||||
public:
|
||||
void prepareIntertwine() OVERRIDE {
|
||||
m_pts.clear();
|
||||
}
|
||||
|
||||
void joinPoints(ToolLoop* loop, const Points& points) OVERRIDE {
|
||||
if (points.size() == 0)
|
||||
return;
|
||||
else if (m_pts.empty() && points.size() == 1) {
|
||||
m_pts = points;
|
||||
}
|
||||
else {
|
||||
PPData data(m_pts, loop);
|
||||
|
||||
for (size_t c=0; c+1<points.size(); ++c) {
|
||||
int x1 = points[c].x;
|
||||
int y1 = points[c].y;
|
||||
int x2 = points[c+1].x;
|
||||
int y2 = points[c+1].y;
|
||||
|
||||
algo_line(x1, y1, x2, y2,
|
||||
(void*)&data,
|
||||
(AlgoPixel)&IntertwineAsPixelPerfect::pixelPerfectLine);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t c=0; c<m_pts.size(); ++c) {
|
||||
// We ignore a pixel that is between other two pixels in the
|
||||
// corner of a L-like shape.
|
||||
if (c > 0 && c+1 < m_pts.size()
|
||||
&& (m_pts[c-1].x == m_pts[c].x || m_pts[c-1].y == m_pts[c].y)
|
||||
&& (m_pts[c+1].x == m_pts[c].x || m_pts[c+1].y == m_pts[c].y)
|
||||
&& m_pts[c-1].x != m_pts[c+1].x
|
||||
&& m_pts[c-1].y != m_pts[c+1].y) {
|
||||
++c;
|
||||
}
|
||||
|
||||
doPointshapePoint(m_pts[c].x, m_pts[c].y, loop);
|
||||
}
|
||||
}
|
||||
|
||||
void fillPoints(ToolLoop* loop, const Points& points)
|
||||
{
|
||||
if (points.size() < 3) {
|
||||
joinPoints(loop, points);
|
||||
return;
|
||||
}
|
||||
|
||||
// Contour
|
||||
joinPoints(loop, points);
|
||||
|
||||
// Fill content
|
||||
algo_polygon(points.size(), (const int*)&points[0], loop, (AlgoHLine)doPointshapeHline);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
} // namespace app
|
||||
|
@ -69,6 +69,13 @@ const char* WellKnownInks::Move = "move";
|
||||
const char* WellKnownInks::Blur = "blur";
|
||||
const char* WellKnownInks::Jumble = "jumble";
|
||||
|
||||
const char* WellKnownIntertwiners::None = "none";
|
||||
const char* WellKnownIntertwiners::AsLines = "as_lines";
|
||||
const char* WellKnownIntertwiners::AsRectangles = "as_rectangles";
|
||||
const char* WellKnownIntertwiners::AsEllipses = "as_ellipses";
|
||||
const char* WellKnownIntertwiners::AsBezier = "as_bezier";
|
||||
const char* WellKnownIntertwiners::AsPixelPerfect = "as_pixel_perfect";
|
||||
|
||||
ToolBox::ToolBox()
|
||||
{
|
||||
PRINTF("Toolbox module: installing\n");
|
||||
@ -102,11 +109,12 @@ ToolBox::ToolBox()
|
||||
m_pointshapers["floodfill"] = new FloodFillPointShape();
|
||||
m_pointshapers["spray"] = new SprayPointShape();
|
||||
|
||||
m_intertwiners["none"] = new IntertwineNone();
|
||||
m_intertwiners["as_lines"] = new IntertwineAsLines();
|
||||
m_intertwiners["as_rectangles"] = new IntertwineAsRectangles();
|
||||
m_intertwiners["as_ellipses"] = new IntertwineAsEllipses();
|
||||
m_intertwiners["as_bezier"] = new IntertwineAsBezier();
|
||||
m_intertwiners[WellKnownIntertwiners::None] = new IntertwineNone();
|
||||
m_intertwiners[WellKnownIntertwiners::AsLines] = new IntertwineAsLines();
|
||||
m_intertwiners[WellKnownIntertwiners::AsRectangles] = new IntertwineAsRectangles();
|
||||
m_intertwiners[WellKnownIntertwiners::AsEllipses] = new IntertwineAsEllipses();
|
||||
m_intertwiners[WellKnownIntertwiners::AsBezier] = new IntertwineAsBezier();
|
||||
m_intertwiners[WellKnownIntertwiners::AsPixelPerfect] = new IntertwineAsPixelPerfect();
|
||||
|
||||
loadTools();
|
||||
|
||||
@ -152,9 +160,14 @@ Ink* ToolBox::getInkById(const std::string& id)
|
||||
return m_inks[id];
|
||||
}
|
||||
|
||||
Intertwine* ToolBox::getIntertwinerById(const std::string& id)
|
||||
{
|
||||
return m_intertwiners[id];
|
||||
}
|
||||
|
||||
void ToolBox::loadTools()
|
||||
{
|
||||
PRINTF("Loading ASEPRITE tools\n");
|
||||
PRINTF("Loading Aseprite tools\n");
|
||||
|
||||
XmlDocumentRef doc(GuiXml::instance()->doc());
|
||||
TiXmlHandle handle(doc);
|
||||
|
@ -53,6 +53,15 @@ namespace app {
|
||||
extern const char* Jumble;
|
||||
};
|
||||
|
||||
namespace WellKnownIntertwiners {
|
||||
extern const char* None;
|
||||
extern const char* AsLines;
|
||||
extern const char* AsRectangles;
|
||||
extern const char* AsEllipses;
|
||||
extern const char* AsBezier;
|
||||
extern const char* AsPixelPerfect;
|
||||
};
|
||||
|
||||
typedef std::list<Tool*> ToolList;
|
||||
typedef ToolList::iterator ToolIterator;
|
||||
typedef ToolList::const_iterator ToolConstIterator;
|
||||
@ -75,6 +84,7 @@ namespace app {
|
||||
|
||||
Tool* getToolById(const std::string& id);
|
||||
Ink* getInkById(const std::string& id);
|
||||
Intertwine* getIntertwinerById(const std::string& id);
|
||||
int getGroupsCount() const { return m_groups.size(); }
|
||||
|
||||
private:
|
||||
|
@ -67,6 +67,7 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer)
|
||||
|
||||
// Prepare the ink
|
||||
m_toolLoop->getInk()->prepareInk(m_toolLoop);
|
||||
m_toolLoop->getIntertwine()->prepareIntertwine();
|
||||
|
||||
// Prepare preview image (the destination image will be our preview
|
||||
// in the tool-loop time, so we can see what we are drawing)
|
||||
|
@ -27,9 +27,11 @@
|
||||
#include "app/settings/ink_type.h"
|
||||
#include "app/settings/settings.h"
|
||||
#include "app/settings/settings_observers.h"
|
||||
#include "app/tools/controller.h"
|
||||
#include "app/tools/ink.h"
|
||||
#include "app/tools/point_shape.h"
|
||||
#include "app/tools/tool.h"
|
||||
#include "app/tools/tool_box.h"
|
||||
#include "app/ui/button_set.h"
|
||||
#include "app/ui/color_button.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
@ -387,6 +389,24 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class ContextBar::FreehandAlgorithmField : public CheckBox
|
||||
{
|
||||
public:
|
||||
FreehandAlgorithmField() : CheckBox("Pixel-perfect") {
|
||||
}
|
||||
|
||||
void onClick(Event& ev) OVERRIDE {
|
||||
CheckBox::onClick(ev);
|
||||
|
||||
ISettings* settings = UIContext::instance()->getSettings();
|
||||
Tool* currentTool = settings->getCurrentTool();
|
||||
settings->getToolSettings(currentTool)
|
||||
->setFreehandAlgorithm(isSelected() ?
|
||||
kPixelPerfectFreehandAlgorithm:
|
||||
kDefaultFreehandAlgorithm);
|
||||
}
|
||||
};
|
||||
|
||||
ContextBar::ContextBar()
|
||||
: Box(JI_HORIZONTAL)
|
||||
{
|
||||
@ -412,6 +432,9 @@ ContextBar::ContextBar()
|
||||
// addChild(new InkShadeField());
|
||||
// addChild(new InkSelectionField());
|
||||
|
||||
addChild(m_freehandBox = new HBox());
|
||||
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
||||
|
||||
addChild(m_sprayBox = new HBox());
|
||||
m_sprayBox->addChild(new Label("Spray:"));
|
||||
m_sprayBox->addChild(m_sprayWidth = new SprayWidthField());
|
||||
@ -488,6 +511,8 @@ void ContextBar::onCurrentToolChange()
|
||||
m_inkType->setInkType(toolSettings->getInkType());
|
||||
m_inkOpacity->setTextf("%d", toolSettings->getOpacity());
|
||||
|
||||
m_freehandAlgo->setSelected(toolSettings->getFreehandAlgorithm() == kPixelPerfectFreehandAlgorithm);
|
||||
|
||||
m_sprayWidth->setValue(toolSettings->getSprayWidth());
|
||||
m_spraySpeed->setValue(toolSettings->getSpraySpeed());
|
||||
|
||||
@ -512,6 +537,10 @@ void ContextBar::onCurrentToolChange()
|
||||
bool hasSelectOptions = (currentTool->getInk(0)->isSelection() ||
|
||||
currentTool->getInk(1)->isSelection());
|
||||
|
||||
bool isFreehand =
|
||||
(currentTool->getController(0)->isFreehand() ||
|
||||
currentTool->getController(1)->isFreehand());
|
||||
|
||||
// Show/Hide fields
|
||||
m_brushLabel->setVisible(hasOpacity);
|
||||
m_brushType->setVisible(hasOpacity);
|
||||
@ -521,6 +550,7 @@ void ContextBar::onCurrentToolChange()
|
||||
m_inkLabel->setVisible(hasInk);
|
||||
m_inkType->setVisible(hasInk);
|
||||
m_inkOpacity->setVisible(hasOpacity);
|
||||
m_freehandBox->setVisible(isFreehand);
|
||||
m_toleranceLabel->setVisible(hasTolerance);
|
||||
m_tolerance->setVisible(hasTolerance);
|
||||
m_sprayBox->setVisible(hasSprayOptions);
|
||||
|
@ -52,6 +52,7 @@ namespace app {
|
||||
class SpraySpeedField;
|
||||
class TransparentColorField;
|
||||
class RotAlgorithmField;
|
||||
class FreehandAlgorithmField;
|
||||
|
||||
ui::Label* m_brushLabel;
|
||||
BrushTypeField* m_brushType;
|
||||
@ -63,6 +64,8 @@ namespace app {
|
||||
InkTypeField* m_inkType;
|
||||
ui::Label* m_opacityLabel;
|
||||
InkOpacityField* m_inkOpacity;
|
||||
ui::Box* m_freehandBox;
|
||||
FreehandAlgorithmField* m_freehandAlgo;
|
||||
ui::Box* m_sprayBox;
|
||||
SprayWidthField* m_sprayWidth;
|
||||
SpraySpeedField* m_spraySpeed;
|
||||
|
Loading…
x
Reference in New Issue
Block a user