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