1 // Aseprite
2 // Copyright (C) 2001-2016 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/app_brushes.h"
12 #include "app/resource_finder.h"
13 #include "app/tools/ink_type.h"
14 #include "app/xml_document.h"
15 #include "app/xml_exception.h"
16 #include "base/base64.h"
17 #include "base/convert_to.h"
18 #include "base/fs.h"
19 #include "base/serialization.h"
20 #include "doc/brush.h"
21 #include "doc/color.h"
22 #include "doc/image.h"
23 #include "doc/image_impl.h"
24
25 #include <fstream>
26
27 namespace app {
28
29 using namespace doc;
30 using namespace base::serialization;
31 using namespace base::serialization::little_endian;
32
33 namespace {
34
load_xml_image(const TiXmlElement * imageElem)35 ImageRef load_xml_image(const TiXmlElement* imageElem)
36 {
37 ImageRef image;
38 int w, h;
39 if (imageElem->QueryIntAttribute("width", &w) != TIXML_SUCCESS ||
40 imageElem->QueryIntAttribute("height", &h) != TIXML_SUCCESS ||
41 w < 0 || w > 9999 ||
42 h < 0 || h > 9999)
43 return image;
44
45 auto formatValue = imageElem->Attribute("format");
46 if (!formatValue)
47 return image;
48
49 const char* pixels_base64 = imageElem->GetText();
50 if (!pixels_base64)
51 return image;
52
53 base::buffer data;
54 base::decode_base64(pixels_base64, data);
55 auto it = data.begin(), end = data.end();
56
57 std::string formatStr = formatValue;
58 if (formatStr == "rgba") {
59 image.reset(Image::create(IMAGE_RGB, w, h));
60 LockImageBits<RgbTraits> pixels(image.get());
61 for (auto& pixel : pixels) {
62 if ((end - it) < 4)
63 break;
64
65 int r = *it; ++it;
66 int g = *it; ++it;
67 int b = *it; ++it;
68 int a = *it; ++it;
69
70 pixel = doc::rgba(r, g, b, a);
71 }
72 }
73 else if (formatStr == "grayscale") {
74 image.reset(Image::create(IMAGE_GRAYSCALE, w, h));
75 LockImageBits<GrayscaleTraits> pixels(image.get());
76 for (auto& pixel : pixels) {
77 if ((end - it) < 2)
78 break;
79
80 int v = *it; ++it;
81 int a = *it; ++it;
82
83 pixel = doc::graya(v, a);
84 }
85 }
86 else if (formatStr == "indexed") {
87 image.reset(Image::create(IMAGE_INDEXED, w, h));
88 LockImageBits<IndexedTraits> pixels(image.get());
89 for (auto& pixel : pixels) {
90 if (it == end)
91 break;
92
93 pixel = *it;
94 ++it;
95 }
96 }
97 else if (formatStr == "bitmap") {
98 image.reset(Image::create(IMAGE_BITMAP, w, h));
99 LockImageBits<BitmapTraits> pixels(image.get());
100 for (auto& pixel : pixels) {
101 if (it == end)
102 break;
103
104 pixel = *it;
105 ++it;
106 }
107 }
108 return image;
109 }
110
save_xml_image(TiXmlElement * imageElem,const Image * image)111 void save_xml_image(TiXmlElement* imageElem, const Image* image)
112 {
113 int w = image->width();
114 int h = image->height();
115 imageElem->SetAttribute("width", w);
116 imageElem->SetAttribute("height", h);
117
118 std::string format;
119 switch (image->pixelFormat()) {
120 case IMAGE_RGB: format = "rgba"; break;
121 case IMAGE_GRAYSCALE: format = "grayscale"; break;
122 case IMAGE_INDEXED: format = "indexed"; break;
123 case IMAGE_BITMAP: format = "bitmap"; break; // TODO add "bitmap" format
124 }
125 ASSERT(!format.empty());
126 if (!format.empty())
127 imageElem->SetAttribute("format", format.c_str());
128
129 base::buffer data;
130 data.reserve(h * image->getRowStrideSize());
131 switch (image->pixelFormat()) {
132 case IMAGE_RGB:{
133 const LockImageBits<RgbTraits> pixels(image);
134 for (const auto& pixel : pixels) {
135 data.push_back(doc::rgba_getr(pixel));
136 data.push_back(doc::rgba_getg(pixel));
137 data.push_back(doc::rgba_getb(pixel));
138 data.push_back(doc::rgba_geta(pixel));
139 }
140 break;
141 }
142 case IMAGE_GRAYSCALE:{
143 const LockImageBits<GrayscaleTraits> pixels(image);
144 for (const auto& pixel : pixels) {
145 data.push_back(doc::graya_getv(pixel));
146 data.push_back(doc::graya_geta(pixel));
147 }
148 break;
149 }
150 case IMAGE_INDEXED: {
151 const LockImageBits<IndexedTraits> pixels(image);
152 for (const auto& pixel : pixels) {
153 data.push_back(pixel);
154 }
155 break;
156 }
157 case IMAGE_BITMAP: {
158 // Here we save bitmap format as indexed
159 const LockImageBits<BitmapTraits> pixels(image);
160 for (const auto& pixel : pixels) {
161 data.push_back(pixel); // TODO save bitmap format as bitmap
162 }
163 break;
164 }
165 }
166
167 std::string data_base64;
168 base::encode_base64(data, data_base64);
169 TiXmlText textElem(data_base64.c_str());
170 imageElem->InsertEndChild(textElem);
171 }
172
173 } // anonymous namespace
174
AppBrushes()175 AppBrushes::AppBrushes()
176 {
177 m_standard.push_back(BrushRef(new Brush(kCircleBrushType, 7, 0)));
178 m_standard.push_back(BrushRef(new Brush(kSquareBrushType, 7, 0)));
179 m_standard.push_back(BrushRef(new Brush(kLineBrushType, 7, 44)));
180
181 try {
182 std::string fn = userBrushesFilename();
183 if (base::is_file(fn))
184 load(fn);
185 }
186 catch (const std::exception& ex) {
187 LOG(ERROR) << "BRSH: Error loading user brushes: " << ex.what() << "\n";
188 }
189 }
190
~AppBrushes()191 AppBrushes::~AppBrushes()
192 {
193 save(userBrushesFilename());
194 }
195
addBrushSlot(const BrushSlot & brush)196 AppBrushes::slot_id AppBrushes::addBrushSlot(const BrushSlot& brush)
197 {
198 // Use an empty slot
199 for (size_t i=0; i<m_slots.size(); ++i) {
200 if (!m_slots[i].locked() || m_slots[i].isEmpty()) {
201 m_slots[i] = brush;
202 return i+1;
203 }
204 }
205
206 m_slots.push_back(brush);
207 ItemsChange();
208 return slot_id(m_slots.size()); // Returns the slot
209 }
210
removeBrushSlot(slot_id slot)211 void AppBrushes::removeBrushSlot(slot_id slot)
212 {
213 --slot;
214 if (slot >= 0 && slot < (int)m_slots.size()) {
215 m_slots[slot] = BrushSlot();
216
217 // Erase empty trailing slots
218 while (!m_slots.empty() &&
219 m_slots[m_slots.size()-1].isEmpty())
220 m_slots.erase(--m_slots.end());
221
222 ItemsChange();
223 }
224 }
225
removeAllBrushSlots()226 void AppBrushes::removeAllBrushSlots()
227 {
228 while (!m_slots.empty())
229 m_slots.erase(--m_slots.end());
230
231 ItemsChange();
232 }
233
hasBrushSlot(slot_id slot) const234 bool AppBrushes::hasBrushSlot(slot_id slot) const
235 {
236 --slot;
237 return (slot >= 0 && slot < (int)m_slots.size() &&
238 !m_slots[slot].isEmpty());
239 }
240
getBrushSlot(slot_id slot) const241 BrushSlot AppBrushes::getBrushSlot(slot_id slot) const
242 {
243 --slot;
244 if (slot >= 0 && slot < (int)m_slots.size())
245 return m_slots[slot];
246 else
247 return BrushSlot();
248 }
249
setBrushSlot(slot_id slot,const BrushSlot & brush)250 void AppBrushes::setBrushSlot(slot_id slot, const BrushSlot& brush)
251 {
252 --slot;
253 if (slot >= 0 && slot < (int)m_slots.size()) {
254 m_slots[slot] = brush;
255 ItemsChange();
256 }
257 }
258
lockBrushSlot(slot_id slot)259 void AppBrushes::lockBrushSlot(slot_id slot)
260 {
261 --slot;
262 if (slot >= 0 && slot < (int)m_slots.size() &&
263 !m_slots[slot].isEmpty()) {
264 m_slots[slot].setLocked(true);
265 }
266 }
267
unlockBrushSlot(slot_id slot)268 void AppBrushes::unlockBrushSlot(slot_id slot)
269 {
270 --slot;
271 if (slot >= 0 && slot < (int)m_slots.size() &&
272 !m_slots[slot].isEmpty()) {
273 m_slots[slot].setLocked(false);
274 }
275 }
276
isBrushSlotLocked(slot_id slot) const277 bool AppBrushes::isBrushSlotLocked(slot_id slot) const
278 {
279 --slot;
280 if (slot >= 0 && slot < (int)m_slots.size() &&
281 !m_slots[slot].isEmpty()) {
282 return m_slots[slot].locked();
283 }
284 else
285 return false;
286 }
287
288 static const int kBrushFlags =
289 int(BrushSlot::Flags::BrushType) |
290 int(BrushSlot::Flags::BrushSize) |
291 int(BrushSlot::Flags::BrushAngle);
292
load(const std::string & filename)293 void AppBrushes::load(const std::string& filename)
294 {
295 XmlDocumentRef doc = app::open_xml(filename);
296 TiXmlHandle handle(doc.get());
297 TiXmlElement* brushElem = handle
298 .FirstChild("brushes")
299 .FirstChild("brush").ToElement();
300
301 while (brushElem) {
302 // flags
303 int flags = 0;
304 BrushRef brush;
305 app::Color fgColor;
306 app::Color bgColor;
307 tools::InkType inkType = tools::InkType::DEFAULT;
308 int inkOpacity = 255;
309 Shade shade;
310 bool pixelPerfect = false;
311
312 // Brush
313 const char* type = brushElem->Attribute("type");
314 const char* size = brushElem->Attribute("size");
315 const char* angle = brushElem->Attribute("angle");
316 if (type || size || angle) {
317 if (type) flags |= int(BrushSlot::Flags::BrushType);
318 if (size) flags |= int(BrushSlot::Flags::BrushSize);
319 if (angle) flags |= int(BrushSlot::Flags::BrushAngle);
320 brush.reset(
321 new Brush(
322 (type ? string_id_to_brush_type(type): kFirstBrushType),
323 (size ? base::convert_to<int>(std::string(size)): 1),
324 (angle ? base::convert_to<int>(std::string(angle)): 0)));
325 }
326
327 // Brush image
328 ImageRef image, mask;
329 if (TiXmlElement* imageElem = brushElem->FirstChildElement("image"))
330 image = load_xml_image(imageElem);
331 if (TiXmlElement* maskElem = brushElem->FirstChildElement("mask"))
332 mask = load_xml_image(maskElem);
333
334 if (image) {
335 if (!brush)
336 brush.reset(new Brush());
337 brush->setImage(image.get(), mask.get());
338 }
339
340 // Colors
341 if (TiXmlElement* fgcolorElem = brushElem->FirstChildElement("fgcolor")) {
342 if (auto value = fgcolorElem->Attribute("value")) {
343 fgColor = app::Color::fromString(value);
344 flags |= int(BrushSlot::Flags::FgColor);
345 }
346 }
347
348 if (TiXmlElement* bgcolorElem = brushElem->FirstChildElement("bgcolor")) {
349 if (auto value = bgcolorElem->Attribute("value")) {
350 bgColor = app::Color::fromString(value);
351 flags |= int(BrushSlot::Flags::BgColor);
352 }
353 }
354
355 // Ink
356 if (TiXmlElement* inkTypeElem = brushElem->FirstChildElement("inktype")) {
357 if (auto value = inkTypeElem->Attribute("value")) {
358 inkType = app::tools::string_id_to_ink_type(value);
359 flags |= int(BrushSlot::Flags::InkType);
360 }
361 }
362
363 if (TiXmlElement* inkOpacityElem = brushElem->FirstChildElement("inkopacity")) {
364 if (auto value = inkOpacityElem->Attribute("value")) {
365 inkOpacity = base::convert_to<int>(std::string(value));
366 flags |= int(BrushSlot::Flags::InkOpacity);
367 }
368 }
369
370 // Shade
371 if (TiXmlElement* shadeElem = brushElem->FirstChildElement("shade")) {
372 if (auto value = shadeElem->Attribute("value")) {
373 shade = shade_from_string(value);
374 flags |= int(BrushSlot::Flags::Shade);
375 }
376 }
377
378 // Pixel-perfect
379 if (TiXmlElement* pixelPerfectElem = brushElem->FirstChildElement("pixelperfect")) {
380 pixelPerfect = bool_attr_is_true(pixelPerfectElem, "value");
381 flags |= int(BrushSlot::Flags::PixelPerfect);
382 }
383
384 // Image color (enabled by default for backward compatibility)
385 if (!brushElem->Attribute("imagecolor") ||
386 bool_attr_is_true(brushElem, "imagecolor"))
387 flags |= int(BrushSlot::Flags::ImageColor);
388
389 if (flags != 0)
390 flags |= int(BrushSlot::Flags::Locked);
391
392 BrushSlot brushSlot(BrushSlot::Flags(flags),
393 brush, fgColor, bgColor,
394 inkType, inkOpacity, shade,
395 pixelPerfect);
396 m_slots.push_back(brushSlot);
397
398 brushElem = brushElem->NextSiblingElement();
399 }
400 }
401
save(const std::string & filename) const402 void AppBrushes::save(const std::string& filename) const
403 {
404 XmlDocumentRef doc(new TiXmlDocument());
405 TiXmlElement brushesElem("brushes");
406
407 //<?xml version="1.0" encoding="utf-8"?>
408
409 for (const auto& slot : m_slots) {
410 TiXmlElement brushElem("brush");
411 if (slot.locked()) {
412 // Flags
413 int flags = int(slot.flags());
414
415 // This slot might not have a brush. (E.g. a slot that changes
416 // the pixel perfect mode only.)
417 if (!slot.hasBrush())
418 flags &= ~kBrushFlags;
419
420 // Brush type
421 if (slot.hasBrush()) {
422 ASSERT(slot.brush());
423
424 if (flags & int(BrushSlot::Flags::BrushType)) {
425 brushElem.SetAttribute(
426 "type", brush_type_to_string_id(slot.brush()->type()).c_str());
427 }
428
429 if (flags & int(BrushSlot::Flags::BrushSize)) {
430 brushElem.SetAttribute("size", slot.brush()->size());
431 }
432
433 if (flags & int(BrushSlot::Flags::BrushAngle)) {
434 brushElem.SetAttribute("angle", slot.brush()->angle());
435 }
436
437 if (slot.brush()->type() == kImageBrushType &&
438 slot.brush()->image()) {
439 TiXmlElement elem("image");
440 save_xml_image(&elem, slot.brush()->image());
441 brushElem.InsertEndChild(elem);
442
443 if (slot.brush()->maskBitmap()) {
444 TiXmlElement maskElem("mask");
445 save_xml_image(&maskElem, slot.brush()->maskBitmap());
446 brushElem.InsertEndChild(maskElem);
447 }
448
449 // Image color
450 brushElem.SetAttribute(
451 "imagecolor",
452 (flags & int(BrushSlot::Flags::ImageColor)) ? "true": "false");
453 }
454 }
455
456 // Colors
457 if (flags & int(BrushSlot::Flags::FgColor)) {
458 TiXmlElement elem("fgcolor");
459 elem.SetAttribute("value", slot.fgColor().toString().c_str());
460 brushElem.InsertEndChild(elem);
461 }
462
463 if (flags & int(BrushSlot::Flags::BgColor)) {
464 TiXmlElement elem("bgcolor");
465 elem.SetAttribute("value", slot.bgColor().toString().c_str());
466 brushElem.InsertEndChild(elem);
467 }
468
469 // Ink
470 if (flags & int(BrushSlot::Flags::InkType)) {
471 TiXmlElement elem("inktype");
472 elem.SetAttribute(
473 "value", app::tools::ink_type_to_string_id(slot.inkType()).c_str());
474 brushElem.InsertEndChild(elem);
475 }
476
477 if (flags & int(BrushSlot::Flags::InkOpacity)) {
478 TiXmlElement elem("inkopacity");
479 elem.SetAttribute("value", slot.inkOpacity());
480 brushElem.InsertEndChild(elem);
481 }
482
483 // Shade
484 if (flags & int(BrushSlot::Flags::Shade)) {
485 TiXmlElement elem("shade");
486 elem.SetAttribute("value", shade_to_string(slot.shade()).c_str());
487 brushElem.InsertEndChild(elem);
488 }
489
490 // Pixel-perfect
491 if (flags & int(BrushSlot::Flags::PixelPerfect)) {
492 TiXmlElement elem("pixelperfect");
493 elem.SetAttribute("value", slot.pixelPerfect() ? "true": "false");
494 brushElem.InsertEndChild(elem);
495 }
496 }
497
498 brushesElem.InsertEndChild(brushElem);
499 }
500
501 TiXmlDeclaration declaration("1.0", "utf-8", "");
502 doc->InsertEndChild(declaration);
503 doc->InsertEndChild(brushesElem);
504 save_xml(doc, filename);
505 }
506
507 // static
userBrushesFilename()508 std::string AppBrushes::userBrushesFilename()
509 {
510 ResourceFinder rf;
511 rf.includeUserDir("user.aseprite-brushes");
512 return rf.getFirstOrCreateDefault();
513 }
514
515 } // namespace app
516