1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "app/tools/tool_box.h"
12
13 #include "app/gui_xml.h"
14 #include "app/i18n/strings.h"
15 #include "app/tools/controller.h"
16 #include "app/tools/ink.h"
17 #include "app/tools/intertwine.h"
18 #include "app/tools/point_shape.h"
19 #include "app/tools/stroke.h"
20 #include "app/tools/tool_group.h"
21 #include "app/tools/tool_loop.h"
22 #include "base/bind.h"
23 #include "base/exception.h"
24 #include "doc/algo.h"
25 #include "doc/algorithm/floodfill.h"
26 #include "doc/algorithm/polygon.h"
27 #include "doc/brush.h"
28 #include "doc/compressed_image.h"
29 #include "doc/image_impl.h"
30 #include "doc/mask.h"
31 #include "fixmath/fixmath.h"
32
33 #include <algorithm>
34 #include <cstdlib>
35
36 #include "app/tools/controllers.h"
37 #include "app/tools/inks.h"
38 #include "app/tools/intertwiners.h"
39 #include "app/tools/point_shapes.h"
40
41 namespace app {
42 namespace tools {
43
44 using namespace gfx;
45
46 const char* WellKnownTools::RectangularMarquee = "rectangular_marquee";
47 const char* WellKnownTools::Lasso = "lasso";
48 const char* WellKnownTools::Pencil = "pencil";
49 const char* WellKnownTools::Eraser = "eraser";
50 const char* WellKnownTools::Eyedropper = "eyedropper";
51 const char* WellKnownTools::Hand = "hand";
52
53 const char* WellKnownInks::Selection = "selection";
54 const char* WellKnownInks::Paint = "paint";
55 const char* WellKnownInks::PaintFg = "paint_fg";
56 const char* WellKnownInks::PaintBg = "paint_bg";
57 const char* WellKnownInks::PaintCopy = "paint_copy";
58 const char* WellKnownInks::PaintLockAlpha = "paint_lock_alpha";
59 const char* WellKnownInks::Shading = "shading";
60 const char* WellKnownInks::Gradient = "gradient";
61 const char* WellKnownInks::Eraser = "eraser";
62 const char* WellKnownInks::ReplaceFgWithBg = "replace_fg_with_bg";
63 const char* WellKnownInks::ReplaceBgWithFg = "replace_bg_with_fg";
64 const char* WellKnownInks::PickFg = "pick_fg";
65 const char* WellKnownInks::PickBg = "pick_bg";
66 const char* WellKnownInks::Zoom = "zoom";
67 const char* WellKnownInks::Scroll = "scroll";
68 const char* WellKnownInks::Move = "move";
69 const char* WellKnownInks::Slice = "slice";
70 const char* WellKnownInks::MoveSlice = "move_slice";
71 const char* WellKnownInks::Blur = "blur";
72 const char* WellKnownInks::Jumble = "jumble";
73
74 const char* WellKnownControllers::Freehand = "freehand";
75 const char* WellKnownControllers::PointByPoint = "point_by_point";
76 const char* WellKnownControllers::OnePoints = "one_point";
77 const char* WellKnownControllers::TwoPoints = "two_points";
78 const char* WellKnownControllers::FourPoints = "four_points";
79 const char* WellKnownControllers::LineFreehand = "line_freehand";
80
81 const char* WellKnownIntertwiners::None = "none";
82 const char* WellKnownIntertwiners::FirstPoint = "first_point";
83 const char* WellKnownIntertwiners::AsLines = "as_lines";
84 const char* WellKnownIntertwiners::AsRectangles = "as_rectangles";
85 const char* WellKnownIntertwiners::AsEllipses = "as_ellipses";
86 const char* WellKnownIntertwiners::AsBezier = "as_bezier";
87 const char* WellKnownIntertwiners::AsPixelPerfect = "as_pixel_perfect";
88
89 const char* WellKnownPointShapes::None = "none";
90 const char* WellKnownPointShapes::Pixel = "pixel";
91 const char* WellKnownPointShapes::Brush = "brush";
92 const char* WellKnownPointShapes::FloodFill = "floodfill";
93 const char* WellKnownPointShapes::Spray = "spray";
94
95 namespace {
96
97 struct deleter {
98 template<typename T>
operator ()app::tools::__anon9d13d2250111::deleter99 void operator()(T* p) { delete p; }
100
101 template<typename A, typename B>
operator ()app::tools::__anon9d13d2250111::deleter102 void operator()(std::pair<A,B>& p) { delete p.second; }
103 };
104
105 } // anonymous namespace
106
ToolBox()107 ToolBox::ToolBox()
108 {
109 m_xmlTranslator.setStringIdPrefix("tools");
110
111 m_inks[WellKnownInks::Selection] = new SelectionInk();
112 m_inks[WellKnownInks::Paint] = new PaintInk(PaintInk::Simple);
113 m_inks[WellKnownInks::PaintFg] = new PaintInk(PaintInk::WithFg);
114 m_inks[WellKnownInks::PaintBg] = new PaintInk(PaintInk::WithBg);
115 m_inks[WellKnownInks::PaintCopy] = new PaintInk(PaintInk::Copy);
116 m_inks[WellKnownInks::PaintLockAlpha] = new PaintInk(PaintInk::LockAlpha);
117 m_inks[WellKnownInks::Gradient] = new GradientInk();
118 m_inks[WellKnownInks::Shading] = new ShadingInk();
119 m_inks[WellKnownInks::Eraser] = new EraserInk(EraserInk::Eraser);
120 m_inks[WellKnownInks::ReplaceFgWithBg] = new EraserInk(EraserInk::ReplaceFgWithBg);
121 m_inks[WellKnownInks::ReplaceBgWithFg] = new EraserInk(EraserInk::ReplaceBgWithFg);
122 m_inks[WellKnownInks::PickFg] = new PickInk(PickInk::Fg);
123 m_inks[WellKnownInks::PickBg] = new PickInk(PickInk::Bg);
124 m_inks[WellKnownInks::Zoom] = new ZoomInk();
125 m_inks[WellKnownInks::Scroll] = new ScrollInk();
126 m_inks[WellKnownInks::Move] = new MoveInk();
127 m_inks[WellKnownInks::Slice] = new SliceInk();
128 m_inks[WellKnownInks::Blur] = new BlurInk();
129 m_inks[WellKnownInks::Jumble] = new JumbleInk();
130
131 m_controllers[WellKnownControllers::Freehand] = new FreehandController();
132 m_controllers[WellKnownControllers::PointByPoint] = new PointByPointController();
133 m_controllers[WellKnownControllers::OnePoints] = new OnePointController();
134 m_controllers[WellKnownControllers::TwoPoints] = new TwoPointsController();
135 m_controllers[WellKnownControllers::FourPoints] = new FourPointsController();
136 m_controllers[WellKnownControllers::LineFreehand] = new LineFreehandController();
137
138 m_pointshapers[WellKnownPointShapes::None] = new NonePointShape();
139 m_pointshapers[WellKnownPointShapes::Pixel] = new PixelPointShape();
140 m_pointshapers[WellKnownPointShapes::Brush] = new BrushPointShape();
141 m_pointshapers[WellKnownPointShapes::FloodFill] = new FloodFillPointShape();
142 m_pointshapers[WellKnownPointShapes::Spray] = new SprayPointShape();
143
144 m_intertwiners[WellKnownIntertwiners::None] = new IntertwineNone();
145 m_intertwiners[WellKnownIntertwiners::FirstPoint] = new IntertwineFirstPoint();
146 m_intertwiners[WellKnownIntertwiners::AsLines] = new IntertwineAsLines();
147 m_intertwiners[WellKnownIntertwiners::AsRectangles] = new IntertwineAsRectangles();
148 m_intertwiners[WellKnownIntertwiners::AsEllipses] = new IntertwineAsEllipses();
149 m_intertwiners[WellKnownIntertwiners::AsBezier] = new IntertwineAsBezier();
150 m_intertwiners[WellKnownIntertwiners::AsPixelPerfect] = new IntertwineAsPixelPerfect();
151
152 loadTools();
153
154 // When the language is change, we reload the toolbox stirngs/tooltips.
155 Strings::instance()->LanguageChange.connect(
156 [this]{ loadTools(); });
157 }
158
~ToolBox()159 ToolBox::~ToolBox()
160 {
161 std::for_each(m_tools.begin(), m_tools.end(), deleter());
162 std::for_each(m_groups.begin(), m_groups.end(), deleter());
163 std::for_each(m_intertwiners.begin(), m_intertwiners.end(), deleter());
164 std::for_each(m_pointshapers.begin(), m_pointshapers.end(), deleter());
165 std::for_each(m_controllers.begin(), m_controllers.end(), deleter());
166 std::for_each(m_inks.begin(), m_inks.end(), deleter());
167 }
168
getToolById(const std::string & id)169 Tool* ToolBox::getToolById(const std::string& id)
170 {
171 for (ToolIterator it = begin(), end = this->end(); it != end; ++it) {
172 Tool* tool = *it;
173 if (tool->getId() == id)
174 return tool;
175 }
176 return nullptr;
177 }
178
getInkById(const std::string & id)179 Ink* ToolBox::getInkById(const std::string& id)
180 {
181 return m_inks[id];
182 }
183
getControllerById(const std::string & id)184 Controller* ToolBox::getControllerById(const std::string& id)
185 {
186 return m_controllers[id];
187 }
188
getIntertwinerById(const std::string & id)189 Intertwine* ToolBox::getIntertwinerById(const std::string& id)
190 {
191 return m_intertwiners[id];
192 }
193
getPointShapeById(const std::string & id)194 PointShape* ToolBox::getPointShapeById(const std::string& id)
195 {
196 return m_pointshapers[id];
197 }
198
loadTools()199 void ToolBox::loadTools()
200 {
201 LOG("TOOL: Loading tools...\n");
202
203 XmlDocumentRef doc(GuiXml::instance()->doc());
204 TiXmlHandle handle(doc.get());
205
206 // For each group
207 TiXmlElement* xmlGroup = handle.FirstChild("gui").FirstChild("tools").FirstChild("group").ToElement();
208 while (xmlGroup) {
209 const char* groupId = xmlGroup->Attribute("id");
210 if (!groupId)
211 throw base::Exception("The configuration file has a <group> without 'id' or 'text' attributes.");
212
213 LOG(VERBOSE) << "TOOL: Group " << groupId << "\n";
214
215 // Find an existent ToolGroup (this is useful in case we are
216 // reloading tool text/tooltips).
217 ToolGroup* toolGroup = nullptr;
218 for (auto g : m_groups) {
219 if (g->id() == groupId) {
220 toolGroup = g;
221 break;
222 }
223 }
224 if (toolGroup == nullptr) {
225 toolGroup = new ToolGroup(groupId);
226 m_groups.push_back(toolGroup);
227 }
228
229 // For each tool
230 TiXmlNode* xmlToolNode = xmlGroup->FirstChild("tool");
231 TiXmlElement* xmlTool = xmlToolNode ? xmlToolNode->ToElement(): NULL;
232 while (xmlTool) {
233 const char* toolId = xmlTool->Attribute("id");
234 std::string toolText = m_xmlTranslator(xmlTool, "text");
235 std::string toolTips = m_xmlTranslator(xmlTool, "tooltip");
236 const char* defaultBrushSize = xmlTool->Attribute("default_brush_size");
237
238 Tool* tool = nullptr;
239 for (auto t : m_tools) {
240 if (t->getId() == toolId) {
241 tool = t;
242 break;
243 }
244 }
245 if (tool == nullptr) {
246 tool = new Tool(toolGroup, toolId);
247 m_tools.push_back(tool);
248 }
249
250 tool->setText(toolText);
251 tool->setTips(toolTips);
252 tool->setDefaultBrushSize(
253 defaultBrushSize ? std::strtol(defaultBrushSize, nullptr, 10): 1);
254
255 LOG(VERBOSE) << "TOOL: Tool " << toolId << " in group " << groupId << " found\n";
256
257 loadToolProperties(xmlTool, tool, 0, "left");
258 loadToolProperties(xmlTool, tool, 1, "right");
259
260 xmlTool = xmlTool->NextSiblingElement();
261 }
262
263 xmlGroup = xmlGroup->NextSiblingElement();
264 }
265
266 LOG("TOOL: Done. %d tools, %d groups.\n", m_tools.size(), m_groups.size());
267 }
268
loadToolProperties(TiXmlElement * xmlTool,Tool * tool,int button,const std::string & suffix)269 void ToolBox::loadToolProperties(TiXmlElement* xmlTool, Tool* tool, int button, const std::string& suffix)
270 {
271 const char* tool_id = tool->getId().c_str();
272 const char* fill = xmlTool->Attribute(("fill_"+suffix).c_str());
273 const char* ink = xmlTool->Attribute(("ink_"+suffix).c_str());
274 const char* controller = xmlTool->Attribute(("controller_"+suffix).c_str());
275 const char* pointshape = xmlTool->Attribute(("pointshape_"+suffix).c_str());
276 const char* intertwine = xmlTool->Attribute(("intertwine_"+suffix).c_str());
277 const char* tracepolicy = xmlTool->Attribute(("tracepolicy_"+suffix).c_str());
278
279 if (!fill) fill = xmlTool->Attribute("fill");
280 if (!ink) ink = xmlTool->Attribute("ink");
281 if (!controller) controller = xmlTool->Attribute("controller");
282 if (!pointshape) pointshape = xmlTool->Attribute("pointshape");
283 if (!intertwine) intertwine = xmlTool->Attribute("intertwine");
284 if (!tracepolicy) tracepolicy = xmlTool->Attribute("tracepolicy");
285
286 // Fill
287 Fill fill_value = FillNone;
288 if (fill) {
289 if (strcmp(fill, "none") == 0)
290 fill_value = FillNone;
291 else if (strcmp(fill, "always") == 0)
292 fill_value = FillAlways;
293 else if (strcmp(fill, "optional") == 0)
294 fill_value = FillOptional;
295 else
296 throw base::Exception("Invalid fill '%s' specified in '%s' tool.\n", fill, tool_id);
297 }
298
299 // Find the ink
300 std::map<std::string, Ink*>::iterator it_ink
301 = m_inks.find(ink ? ink: "");
302 if (it_ink == m_inks.end())
303 throw base::Exception("Invalid ink '%s' specified in '%s' tool.\n", ink, tool_id);
304
305 // Find the controller
306 std::map<std::string, Controller*>::iterator it_controller
307 = m_controllers.find(controller ? controller: "none");
308 if (it_controller == m_controllers.end())
309 throw base::Exception("Invalid controller '%s' specified in '%s' tool.\n", controller, tool_id);
310
311 // Find the point_shape
312 std::map<std::string, PointShape*>::iterator it_pointshaper
313 = m_pointshapers.find(pointshape ? pointshape: "none");
314 if (it_pointshaper == m_pointshapers.end())
315 throw base::Exception("Invalid point-shape '%s' specified in '%s' tool.\n", pointshape, tool_id);
316
317 // Find the intertwiner
318 std::map<std::string, Intertwine*>::iterator it_intertwiner
319 = m_intertwiners.find(intertwine ? intertwine: "none");
320 if (it_intertwiner == m_intertwiners.end())
321 throw base::Exception("Invalid intertwiner '%s' specified in '%s' tool.\n", intertwine, tool_id);
322
323 // Trace policy
324 TracePolicy tracepolicy_value = TracePolicy::Last;
325 if (tracepolicy) {
326 if (strcmp(tracepolicy, "accumulate") == 0)
327 tracepolicy_value = TracePolicy::Accumulate;
328 else if (strcmp(tracepolicy, "last") == 0)
329 tracepolicy_value = TracePolicy::Last;
330 else if (strcmp(tracepolicy, "overlap") == 0)
331 tracepolicy_value = TracePolicy::Overlap;
332 else
333 throw base::Exception("Invalid trace-policy '%s' specified in '%s' tool.\n", tracepolicy, tool_id);
334 }
335
336 // Setup the tool properties
337 tool->setFill(button, fill_value);
338 tool->setInk(button, it_ink->second);
339 tool->setController(button, it_controller->second);
340 tool->setPointShape(button, it_pointshaper->second);
341 tool->setIntertwine(button, it_intertwiner->second);
342 tool->setTracePolicy(button, tracepolicy_value);
343 }
344
345 } // namespace tools
346 } // namespace app
347