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