1 #include "toonz/boardsettings.h"
2 
3 // TnzLib includes
4 #include "toonz/toonzscene.h"
5 #include "toonz/tproject.h"
6 #include "toonz/sceneproperties.h"
7 #include "toonz/toonzfolders.h"
8 #include "toutputproperties.h"
9 #include "tsystem.h"
10 
11 #include <QImage>
12 #include <QDate>
13 #include <QDateTime>
14 #include <QFontMetricsF>
15 #include <QMap>
16 
17 namespace {
18 QMap<BoardItem::Type, std::wstring> strs = {
19     {BoardItem::FreeText, L"FreeText"},
20     {BoardItem::ProjectName, L"ProjectName"},
21     {BoardItem::SceneName, L"SceneName"},
22     {BoardItem::Duration_Frame, L"Duration_Frame"},
23     {BoardItem::Duration_SecFrame, L"Duration_SecFrame"},
24     {BoardItem::Duration_HHMMSSFF, L"Duration_HHMMSSFF"},
25     {BoardItem::CurrentDate, L"CurrentDate"},
26     {BoardItem::CurrentDateTime, L"CurrentDateTime"},
27     {BoardItem::UserName, L"UserName"},
28     {BoardItem::ScenePath_Aliased, L"ScenePath_Aliased"},
29     {BoardItem::ScenePath_Full, L"ScenePath_Full"},
30     {BoardItem::MoviePath_Aliased, L"MoviePath_Aliased"},
31     {BoardItem::MoviePath_Full, L"MoviePath_Full"},
32     {BoardItem::Image, L"Image"}};
33 
type2String(BoardItem::Type type)34 std::wstring type2String(BoardItem::Type type) { return strs.value(type, L""); }
string2Type(std::wstring str)35 BoardItem::Type string2Type(std::wstring str) {
36   return strs.key(str, BoardItem::TypeCount);
37 }
38 };  // namespace
39 
BoardItem()40 BoardItem::BoardItem() {
41   m_name            = "Item";
42   m_type            = ProjectName;
43   m_rect            = QRectF(0.1, 0.1, 0.8, 0.8);
44   m_maximumFontSize = 300;
45   m_color           = Qt::black;
46 }
47 
getContentText(ToonzScene * scene)48 QString BoardItem::getContentText(ToonzScene *scene) {
49   auto getDuration = [&]() {
50     TOutputProperties *oprop = scene->getProperties()->getOutputProperties();
51     int from, to, step;
52     if (oprop->getRange(from, to, step))
53       return to - from + 1;
54     else
55       return scene->getFrameCount();
56   };
57 
58   switch (m_type) {
59   case FreeText:
60     return m_text;
61     break;
62   case ProjectName:
63     return scene->getProject()->getName().getQString();
64     break;
65   case SceneName:
66     return QString::fromStdWString(scene->getSceneName());
67     break;
68   case Duration_Frame:
69     return QString::number(getDuration());
70     break;
71   case Duration_SecFrame: {
72     TOutputProperties *oprop = scene->getProperties()->getOutputProperties();
73     int fps                  = (int)oprop->getFrameRate();
74     int frame                = getDuration();
75     return QString("%1 + %2").arg(QString::number(frame / fps),
76                                   QString::number(frame % fps));
77   } break;
78   case Duration_HHMMSSFF: {
79     TOutputProperties *oprop = scene->getProperties()->getOutputProperties();
80     int fps                  = (int)oprop->getFrameRate();
81     int frame                = getDuration();
82     int hh                   = frame / (fps * 60 * 60);
83     frame -= hh * fps * 60 * 60;
84     int mm = frame / (fps * 60);
85     frame -= mm * fps * 60;
86     int ss = frame / fps;
87     int ff = frame % fps;
88     return QString::number(hh).rightJustified(2, '0') + ":" +
89            QString::number(mm).rightJustified(2, '0') + ":" +
90            QString::number(ss).rightJustified(2, '0') + ":" +
91            QString::number(ff).rightJustified(2, '0');
92   } break;
93   case CurrentDate:
94     return QDate::currentDate().toString(Qt::DefaultLocaleLongDate);
95     break;
96   case CurrentDateTime:
97     return QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate);
98     break;
99   case UserName:
100     return TSystem::getUserName();
101     break;
102   case ScenePath_Aliased:
103     return scene->codeFilePath(scene->getScenePath()).getQString();
104     break;
105   case ScenePath_Full:
106     return scene->decodeFilePath(scene->getScenePath()).getQString();
107     break;
108   case MoviePath_Aliased: {
109     TOutputProperties *oprop = scene->getProperties()->getOutputProperties();
110     return scene->codeFilePath(oprop->getPath()).getQString();
111   } break;
112   case MoviePath_Full: {
113     TOutputProperties *oprop = scene->getProperties()->getOutputProperties();
114     return scene->decodeFilePath(oprop->getPath()).getQString();
115   } break;
116   default:
117     break;
118   }
119   return QString();
120 }
121 
getItemRect(QSize imgSize)122 QRectF BoardItem::getItemRect(QSize imgSize) {
123   QSizeF imgSizeF(imgSize);
124   return QRectF(
125       imgSizeF.width() * m_rect.left(), imgSizeF.height() * m_rect.top(),
126       imgSizeF.width() * m_rect.width(), imgSizeF.height() * m_rect.height());
127 }
128 
drawItem(QPainter & p,QSize imgSize,int shrink,ToonzScene * scene)129 void BoardItem::drawItem(QPainter &p, QSize imgSize, int shrink,
130                          ToonzScene *scene) {
131   QRectF itemRect = getItemRect(imgSize);
132 
133   if (m_type == Image) {
134     if (m_imgPath.isEmpty()) return;
135     TFilePath decodedPath = scene->decodeFilePath(m_imgPath);
136     QImage img(decodedPath.getQString());
137     if (m_imgARMode == Qt::KeepAspectRatio) {
138       float ratio = std::min((float)itemRect.width() / (float)img.width(),
139                              (float)itemRect.height() / (float)img.height());
140       QSizeF imgSize((float)img.width() * ratio, (float)img.height() * ratio);
141       QPointF imgTopLeft =
142           itemRect.topLeft() +
143           QPointF((itemRect.width() - imgSize.width()) * 0.5f,
144                   (itemRect.height() - imgSize.height()) * 0.5f);
145 
146       p.drawImage(QRectF(imgTopLeft, imgSize), img);
147     } else if (m_imgARMode == Qt::IgnoreAspectRatio)
148       p.drawImage(itemRect, img);
149     return;
150   }
151 
152   QString contentText = getContentText(scene);
153 
154   QFont tmpFont(m_font);
155   tmpFont.setPixelSize(100);
156   QFontMetricsF tmpFm(tmpFont);
157   QRectF tmpBounding =
158       tmpFm.boundingRect(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText);
159 
160   float ratio = std::min(itemRect.width() / tmpBounding.width(),
161                          itemRect.height() / tmpBounding.height());
162 
163   // compute the font size which will just fit the item region
164   int fontSize = (int)(100.0f * ratio);
165   tmpFont.setPixelSize(fontSize);
166   tmpFm = QFontMetricsF(tmpFont);
167   tmpBounding =
168       tmpFm.boundingRect(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText);
169   bool isInRect;
170   if (itemRect.width() >= tmpBounding.width() &&
171       itemRect.height() >= tmpBounding.height())
172     isInRect = true;
173   else
174     isInRect = false;
175   while (1) {
176     fontSize += (isInRect) ? 1 : -1;
177     if (fontSize <= 0)  // cannot draw
178       return;
179     tmpFont.setPixelSize(fontSize);
180     tmpFm = QFontMetricsF(tmpFont);
181     tmpBounding =
182         tmpFm.boundingRect(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText);
183 
184     bool newIsInRect = (itemRect.width() >= tmpBounding.width() &&
185                         itemRect.height() >= tmpBounding.height());
186     if (isInRect != newIsInRect) {
187       if (isInRect) fontSize--;
188       break;
189     }
190   }
191 
192   //----
193   fontSize = std::min(fontSize, m_maximumFontSize / shrink);
194 
195   QFont font(m_font);
196   font.setPixelSize(fontSize);
197 
198   p.setFont(font);
199   p.setPen(m_color);
200 
201   if (m_type == FreeText)
202     p.drawText(itemRect, Qt::AlignLeft | Qt::AlignTop, contentText);
203   else
204     p.drawText(itemRect, Qt::AlignCenter, contentText);
205 }
206 
saveData(TOStream & os)207 void BoardItem::saveData(TOStream &os) {
208   os.child("type") << type2String(m_type);
209   os.child("name") << m_name;
210   os.child("rect") << m_rect.x() << m_rect.y() << m_rect.width()
211                    << m_rect.height();
212 
213   if (m_type == Image) {
214     // if the path is in library folder, then save the relative path
215     TFilePath libFp = ToonzFolder::getLibraryFolder();
216     if (libFp.isAncestorOf(m_imgPath))
217       os.child("imgPath") << 1 << m_imgPath - libFp;
218     else
219       os.child("imgPath") << 0 << m_imgPath;
220     os.child("imgARMode") << (int)m_imgARMode;
221   } else {
222     if (m_type == FreeText) os.child("text") << m_text;
223 
224     os.child("maximumFontSize") << m_maximumFontSize;
225     os.child("color") << m_color.red() << m_color.green() << m_color.blue()
226                       << m_color.alpha();
227     os.child("font") << m_font.family() << (int)(m_font.bold() ? 1 : 0)
228                      << (int)(m_font.italic() ? 1 : 0);
229   }
230 }
231 
loadData(TIStream & is)232 void BoardItem::loadData(TIStream &is) {
233   std::string tagName;
234   while (is.matchTag(tagName)) {
235     if (tagName == "type") {
236       std::wstring typeStr;
237       is >> typeStr;
238       m_type = string2Type(typeStr);
239     } else if (tagName == "name") {
240       std::wstring str;
241       is >> str;
242       m_name = QString::fromStdWString(str);
243     } else if (tagName == "rect") {
244       double x, y, width, height;
245       is >> x >> y >> width >> height;
246       m_rect = QRectF(x, y, width, height);
247     } else if (tagName == "imgPath") {
248       int isInLibrary;
249       TFilePath fp;
250       is >> isInLibrary >> fp;
251       if (isInLibrary == 1)
252         m_imgPath = ToonzFolder::getLibraryFolder() + fp;
253       else
254         m_imgPath = fp;
255     } else if (tagName == "imgARMode") {
256       int mode;
257       is >> mode;
258       m_imgARMode = (Qt::AspectRatioMode)mode;
259     } else if (tagName == "text") {
260       std::wstring str;
261       is >> str;
262       m_text = QString::fromStdWString(str);
263     } else if (tagName == "maximumFontSize") {
264       is >> m_maximumFontSize;
265     } else if (tagName == "color") {
266       int r, g, b, a;
267       is >> r >> g >> b >> a;
268       m_color = QColor(r, g, b, a);
269     } else if (tagName == "font") {
270       QString family;
271       int isBold, isItalic;
272       is >> family >> isBold >> isItalic;
273       m_font.setFamily(family);
274       m_font.setBold(isBold == 1);
275       m_font.setItalic(isItalic == 1);
276     } else
277       throw TException("unexpected tag: " + tagName);
278     is.closeChild();
279   }
280 }
281 
282 //======================================================================================
283 
BoardSettings()284 BoardSettings::BoardSettings() {
285   // add one item as an example
286   m_items.push_back(BoardItem());
287 }
288 
getBoardImage(TDimension & dim,int shrink,ToonzScene * scene)289 QImage BoardSettings::getBoardImage(TDimension &dim, int shrink,
290                                     ToonzScene *scene) {
291   QImage img(dim.lx, dim.ly, QImage::Format_ARGB32);
292 
293   QPainter painter(&img);
294 
295   painter.fillRect(img.rect(), Qt::white);
296 
297   // draw each item
298   for (int i = m_items.size() - 1; i >= 0; i--)
299     m_items[i].drawItem(painter, img.rect().size(), shrink, scene);
300 
301   painter.end();
302 
303   return img;
304 }
305 
getBoardRaster(TDimension & dim,int shrink,ToonzScene * scene)306 TRaster32P BoardSettings::getBoardRaster(TDimension &dim, int shrink,
307                                          ToonzScene *scene) {
308   QImage img = getBoardImage(dim, shrink, scene);
309 
310   // convert QImage to TRaster
311   TRaster32P boardRas(dim);
312   int img_y = img.height() - 1;
313   for (int j = 0; j < dim.ly; j++, img_y--) {
314     TPixel32 *pix = boardRas->pixels(j);
315     QRgb *img_p   = (QRgb *)img.scanLine(img_y);
316     for (int i = 0; i < dim.lx; i++, pix++, img_p++) {
317       (*pix).r = (typename TPixel32::Channel)(qRed(*img_p));
318       (*pix).g = (typename TPixel32::Channel)(qGreen(*img_p));
319       (*pix).b = (typename TPixel32::Channel)(qBlue(*img_p));
320       (*pix).m = (typename TPixel32::Channel)(qAlpha(*img_p));
321     }
322   }
323   return boardRas;
324 }
325 
addNewItem(int insertAt)326 void BoardSettings::addNewItem(int insertAt) {
327   m_items.insert(insertAt, BoardItem());
328 }
329 
removeItem(int index)330 void BoardSettings::removeItem(int index) {
331   if (index < 0 || index >= m_items.size()) return;
332   m_items.removeAt(index);
333 }
334 
swapItems(int i,int j)335 void BoardSettings::swapItems(int i, int j) { m_items.swap(i, j); }
336 
saveData(TOStream & os,bool forPreset)337 void BoardSettings::saveData(TOStream &os, bool forPreset) {
338   if (!forPreset) os.child("active") << (int)((m_active) ? 1 : 0);
339   os.child("duration") << m_duration;
340   if (!m_items.isEmpty()) {
341     os.openChild("boardItems");
342     for (int i = 0; i < getItemCount(); i++) {
343       os.openChild("item");
344       m_items[i].saveData(os);
345       os.closeChild();
346     }
347     os.closeChild();
348   }
349 }
350 
loadData(TIStream & is)351 void BoardSettings::loadData(TIStream &is) {
352   std::string tagName;
353   while (is.matchTag(tagName)) {
354     if (tagName == "active") {
355       int val;
356       is >> val;
357       setActive(val == 1);
358     } else if (tagName == "duration") {
359       is >> m_duration;
360     } else if (tagName == "boardItems") {
361       m_items.clear();
362       while (is.matchTag(tagName)) {
363         if (tagName == "item") {
364           BoardItem item;
365           item.loadData(is);
366           m_items.append(item);
367         } else
368           throw TException("unexpected tag: " + tagName);
369         is.closeChild();
370       }
371     } else
372       throw TException("unexpected tag: " + tagName);
373     is.closeChild();
374   }
375 }