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 | |
40 | namespace app { |
41 | namespace tools { |
42 | |
43 | using namespace gfx; |
44 | |
45 | const char* WellKnownTools::RectangularMarquee = "rectangular_marquee" ; |
46 | const char* WellKnownTools::Lasso = "lasso" ; |
47 | const char* WellKnownTools::Pencil = "pencil" ; |
48 | const char* WellKnownTools::Eraser = "eraser" ; |
49 | const char* WellKnownTools::Eyedropper = "eyedropper" ; |
50 | const char* WellKnownTools::Hand = "hand" ; |
51 | const char* WellKnownTools::Move = "move" ; |
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::PaintAlphaCompositing = "paint_alpha_compositing" ; |
58 | const char* WellKnownInks::PaintCopy = "paint_copy" ; |
59 | const char* WellKnownInks::PaintLockAlpha = "paint_lock_alpha" ; |
60 | const char* WellKnownInks::Shading = "shading" ; |
61 | const char* WellKnownInks::Gradient = "gradient" ; |
62 | const char* WellKnownInks::Eraser = "eraser" ; |
63 | const char* WellKnownInks::ReplaceFgWithBg = "replace_fg_with_bg" ; |
64 | const char* WellKnownInks::ReplaceBgWithFg = "replace_bg_with_fg" ; |
65 | const char* WellKnownInks::PickFg = "pick_fg" ; |
66 | const char* WellKnownInks::PickBg = "pick_bg" ; |
67 | const char* WellKnownInks::Zoom = "zoom" ; |
68 | const char* WellKnownInks::Scroll = "scroll" ; |
69 | const char* WellKnownInks::Move = "move" ; |
70 | const char* WellKnownInks::SelectLayerAndMove = "select_layer_and_move" ; |
71 | const char* WellKnownInks::Slice = "slice" ; |
72 | const char* WellKnownInks::MoveSlice = "move_slice" ; |
73 | const char* WellKnownInks::Blur = "blur" ; |
74 | const char* WellKnownInks::Jumble = "jumble" ; |
75 | |
76 | const char* WellKnownControllers::Freehand = "freehand" ; |
77 | const char* WellKnownControllers::PointByPoint = "point_by_point" ; |
78 | const char* WellKnownControllers::OnePoints = "one_point" ; |
79 | const char* WellKnownControllers::TwoPoints = "two_points" ; |
80 | const char* WellKnownControllers::FourPoints = "four_points" ; |
81 | const char* WellKnownControllers::LineFreehand = "line_freehand" ; |
82 | |
83 | const char* WellKnownIntertwiners::None = "none" ; |
84 | const char* WellKnownIntertwiners::FirstPoint = "first_point" ; |
85 | const char* WellKnownIntertwiners::AsLines = "as_lines" ; |
86 | const char* WellKnownIntertwiners::AsRectangles = "as_rectangles" ; |
87 | const char* WellKnownIntertwiners::AsEllipses = "as_ellipses" ; |
88 | const char* WellKnownIntertwiners::AsBezier = "as_bezier" ; |
89 | const char* WellKnownIntertwiners::AsPixelPerfect = "as_pixel_perfect" ; |
90 | |
91 | const char* WellKnownPointShapes::None = "none" ; |
92 | const char* WellKnownPointShapes::Pixel = "pixel" ; |
93 | const char* WellKnownPointShapes::Tile = "tile" ; |
94 | const char* WellKnownPointShapes::Brush = "brush" ; |
95 | const char* WellKnownPointShapes::FloodFill = "floodfill" ; |
96 | const char* WellKnownPointShapes::Spray = "spray" ; |
97 | |
98 | namespace { |
99 | |
100 | struct 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 | |
110 | ToolBox::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 | |
165 | ToolBox::~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 | |
175 | Tool* 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 | |
185 | Ink* ToolBox::getInkById(const std::string& id) |
186 | { |
187 | return m_inks[id]; |
188 | } |
189 | |
190 | Controller* ToolBox::getControllerById(const std::string& id) |
191 | { |
192 | return m_controllers[id]; |
193 | } |
194 | |
195 | Intertwine* ToolBox::getIntertwinerById(const std::string& id) |
196 | { |
197 | return m_intertwiners[id]; |
198 | } |
199 | |
200 | PointShape* ToolBox::getPointShapeById(const std::string& id) |
201 | { |
202 | return m_pointshapers[id]; |
203 | } |
204 | |
205 | void 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 | |
275 | void 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 | |