1 
2 
3 #include "toonzrasterbrushtool.h"
4 
5 // TnzTools includes
6 #include "tools/toolhandle.h"
7 #include "tools/toolutils.h"
8 #include "tools/tooloptions.h"
9 #include "bluredbrush.h"
10 
11 // TnzQt includes
12 #include "toonzqt/dvdialog.h"
13 #include "toonzqt/imageutils.h"
14 
15 // TnzLib includes
16 #include "toonz/tobjecthandle.h"
17 #include "toonz/txsheethandle.h"
18 #include "toonz/txshlevelhandle.h"
19 #include "toonz/tframehandle.h"
20 #include "toonz/tcolumnhandle.h"
21 #include "toonz/txsheet.h"
22 #include "toonz/tstageobject.h"
23 #include "toonz/tstageobjectspline.h"
24 #include "toonz/rasterstrokegenerator.h"
25 #include "toonz/ttileset.h"
26 #include "toonz/txshsimplelevel.h"
27 #include "toonz/toonzimageutils.h"
28 #include "toonz/palettecontroller.h"
29 #include "toonz/stage2.h"
30 #include "toonz/preferences.h"
31 #include "toonz/tpalettehandle.h"
32 #include "toonz/mypaintbrushstyle.h"
33 
34 // TnzCore includes
35 #include "tstream.h"
36 #include "tcolorstyles.h"
37 #include "tvectorimage.h"
38 #include "tenv.h"
39 #include "tregion.h"
40 #include "tinbetween.h"
41 
42 #include "tgl.h"
43 #include "trop.h"
44 
45 // Qt includes
46 #include <QPainter>
47 
48 using namespace ToolUtils;
49 
50 TEnv::DoubleVar RasterBrushMinSize("InknpaintRasterBrushMinSize", 1);
51 TEnv::DoubleVar RasterBrushMaxSize("InknpaintRasterBrushMaxSize", 5);
52 TEnv::DoubleVar BrushSmooth("InknpaintBrushSmooth", 0);
53 TEnv::IntVar BrushDrawOrder("InknpaintBrushDrawOrder", 0);
54 TEnv::IntVar RasterBrushPencilMode("InknpaintRasterBrushPencilMode", 0);
55 TEnv::IntVar BrushPressureSensitivity("InknpaintBrushPressureSensitivity", 1);
56 TEnv::DoubleVar RasterBrushHardness("RasterBrushHardness", 100);
57 TEnv::DoubleVar RasterBrushModifierSize("RasterBrushModifierSize", 0);
58 TEnv::StringVar RasterBrushPreset("RasterBrushPreset", "<custom>");
59 
60 //-------------------------------------------------------------------
61 #define CUSTOM_WSTR L"<custom>"
62 //-------------------------------------------------------------------
63 //
64 // (Da mettere in libreria) : funzioni che spezzano una stroke
65 // nei suoi punti angolosi. Lo facciamo specialmente per limitare
66 // i problemi di fill.
67 //
68 //-------------------------------------------------------------------
69 
70 //
71 // Split a stroke in n+1 parts, according to n parameter values
72 // Input:
73 //      stroke            = stroke to split
74 //      parameterValues[] = vector of parameters where I want to split the
75 //      stroke
76 //                          assert: 0<a[0]<a[1]<...<a[n-1]<1
77 // Output:
78 //      strokes[]         = the split strokes
79 //
80 // note: stroke is unchanged
81 //
82 
split(TStroke * stroke,const std::vector<double> & parameterValues,std::vector<TStroke * > & strokes)83 static void split(TStroke *stroke, const std::vector<double> &parameterValues,
84                   std::vector<TStroke *> &strokes) {
85   TThickPoint p2;
86   std::vector<TThickPoint> points;
87   TThickPoint lastPoint = stroke->getControlPoint(0);
88   int n                 = parameterValues.size();
89   int chunk;
90   double t;
91   int last_chunk = -1, startPoint = 0;
92   double lastLocT = 0;
93 
94   for (int i = 0; i < n; i++) {
95     points.push_back(lastPoint);  // Add first point of the stroke
96     double w =
97         parameterValues[i];  // Global parameter. along the stroke 0<=w<=1
98     stroke->getChunkAndT(w, chunk,
99                          t);  // t: local parameter in the chunk-th quadratic
100 
101     if (i == 0)
102       startPoint = 1;
103     else {
104       int indexAfterLastT =
105           stroke->getControlPointIndexAfterParameter(parameterValues[i - 1]);
106       startPoint = indexAfterLastT;
107       if ((indexAfterLastT & 1) && lastLocT != 1) startPoint++;
108     }
109     int endPoint = 2 * chunk + 1;
110     if (lastLocT != 1 && i > 0) {
111       if (last_chunk != chunk || t == 1)
112         points.push_back(p2);  // If the last local t is not an extreme
113                                // add the point p2
114     }
115 
116     for (int j = startPoint; j < endPoint; j++)
117       points.push_back(stroke->getControlPoint(j));
118 
119     TThickPoint p, A, B, C;
120     p       = stroke->getPoint(w);
121     C       = stroke->getControlPoint(2 * chunk + 2);
122     B       = stroke->getControlPoint(2 * chunk + 1);
123     A       = stroke->getControlPoint(2 * chunk);
124     p.thick = A.thick;
125 
126     if (last_chunk != chunk) {
127       TThickPoint p1 = (1 - t) * A + t * B;
128       points.push_back(p1);
129       p.thick = p1.thick;
130     } else {
131       if (t != 1) {
132         // If the i-th cut point belong to the same chunk of the (i-1)-th cut
133         // point.
134         double tInters  = lastLocT / t;
135         TThickPoint p11 = (1 - t) * A + t * B;
136         TThickPoint p1  = (1 - tInters) * p11 + tInters * p;
137         points.push_back(p1);
138         p.thick = p1.thick;
139       }
140     }
141 
142     points.push_back(p);
143 
144     if (t != 1) p2 = (1 - t) * B + t * C;
145 
146     assert(points.size() & 1);
147 
148     // Add new stroke
149     TStroke *strokeAdd = new TStroke(points);
150     strokeAdd->setStyle(stroke->getStyle());
151     strokeAdd->outlineOptions() = stroke->outlineOptions();
152     strokes.push_back(strokeAdd);
153 
154     lastPoint  = p;
155     last_chunk = chunk;
156     lastLocT   = t;
157     points.clear();
158   }
159   // Add end stroke
160   points.push_back(lastPoint);
161 
162   if (lastLocT != 1) points.push_back(p2);
163 
164   startPoint =
165       stroke->getControlPointIndexAfterParameter(parameterValues[n - 1]);
166   if ((stroke->getControlPointIndexAfterParameter(parameterValues[n - 1]) &
167        1) &&
168       lastLocT != 1)
169     startPoint++;
170   for (int j = startPoint; j < stroke->getControlPointCount(); j++)
171     points.push_back(stroke->getControlPoint(j));
172 
173   assert(points.size() & 1);
174   TStroke *strokeAdd = new TStroke(points);
175   strokeAdd->setStyle(stroke->getStyle());
176   strokeAdd->outlineOptions() = stroke->outlineOptions();
177   strokes.push_back(strokeAdd);
178   points.clear();
179 }
180 
181 // Compute Parametric Curve Curvature
182 // By Formula:
183 // k(t)=(|p'(t) x p''(t)|)/Norm2(p')^3
184 // p(t) is parametric curve
185 // Input:
186 //      dp  = First Derivate.
187 //      ddp = Second Derivate
188 // Output:
189 //      return curvature value.
190 //      Note: if the curve is a single point (that's dp=0) or it is a straight
191 //      line (that's ddp=0) return 0
192 
curvature(TPointD dp,TPointD ddp)193 static double curvature(TPointD dp, TPointD ddp) {
194   if (dp == TPointD(0, 0))
195     return 0;
196   else
197     return fabs(cross(dp, ddp) / pow(norm2(dp), 1.5));
198 }
199 
200 // Find the max curvature points of a stroke.
201 // Input:
202 //      stroke.
203 //      angoloLim =  Value (radians) of the Corner between two tangent vector.
204 //                   Up this value the two corner can be considered angular.
205 //      curvMaxLim = Value of the max curvature.
206 //                   Up this value the point can be considered a max curvature
207 //                   point.
208 // Output:
209 //      parameterValues = vector of max curvature parameter points
210 
findMaxCurvPoints(TStroke * stroke,const float & angoloLim,const float & curvMaxLim,std::vector<double> & parameterValues)211 static void findMaxCurvPoints(TStroke *stroke, const float &angoloLim,
212                               const float &curvMaxLim,
213                               std::vector<double> &parameterValues) {
214   TPointD tg1, tg2;  // Tangent vectors
215 
216   TPointD dp, ddp;  // First and Second derivate.
217 
218   parameterValues.clear();
219   int cpn = stroke ? stroke->getControlPointCount() : 0;
220   for (int j = 2; j < cpn; j += 2) {
221     TPointD p0 = stroke->getControlPoint(j - 2);
222     TPointD p1 = stroke->getControlPoint(j - 1);
223     TPointD p2 = stroke->getControlPoint(j);
224 
225     TPointD q = p1 - (p0 + p2) * 0.5;
226 
227     // Search corner point
228     if (j > 2) {
229       tg2 = -p0 + p2 + 2 * q;  // Tangent vector to this chunk at t=0
230       double prod_scal =
231           tg2 * tg1;  // Inner product between tangent vectors at t=0.
232       assert(tg1 != TPointD(0, 0) || tg2 != TPointD(0, 0));
233       // Compute corner between two tangent vectors
234       double angolo =
235           acos(prod_scal / (pow(norm2(tg2), 0.5) * pow(norm2(tg1), 0.5)));
236 
237       // Add corner point
238       if (angolo > angoloLim) {
239         double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)),
240                                      0);  //  transform lacal t to global t
241         parameterValues.push_back(w);
242       }
243     }
244     tg1 = -p0 + p2 - 2 * q;  // Tangent vector to this chunk at t=1
245 
246     // End search corner point
247 
248     // Search max curvature point
249     // Value of t where the curvature function has got an extreme.
250     // (Point where first derivate is null)
251     double estremo_int = 0;
252     double t           = -1;
253     if (q != TPointD(0, 0)) {
254       t = 0.25 *
255           (2 * q.x * q.x + 2 * q.y * q.y - q.x * p0.x + q.x * p2.x -
256            q.y * p0.y + q.y * p2.y) /
257           (q.x * q.x + q.y * q.y);
258 
259       dp  = -p0 + p2 + 2 * q - 4 * t * q;  // First derivate of the curve
260       ddp = -4 * q;                        // Second derivate of the curve
261       estremo_int = curvature(dp, ddp);
262 
263       double h    = 0.01;
264       dp          = -p0 + p2 + 2 * q - 4 * (t + h) * q;
265       double c_dx = curvature(dp, ddp);
266       dp          = -p0 + p2 + 2 * q - 4 * (t - h) * q;
267       double c_sx = curvature(dp, ddp);
268       // Check the point is a max and not a minimum
269       if (estremo_int < c_dx && estremo_int < c_sx) {
270         estremo_int = 0;
271       }
272     }
273     double curv_max = estremo_int;
274 
275     // Compute curvature at the extreme of interval [0,1]
276     // Compute curvature at t=0 (Left extreme)
277     dp                = -p0 + p2 + 2 * q;
278     double estremo_sx = curvature(dp, ddp);
279 
280     // Compute curvature at t=1 (Right extreme)
281     dp                = -p0 + p2 - 2 * q;
282     double estremo_dx = curvature(dp, ddp);
283 
284     // Compare curvature at the extreme of interval [0,1] with the internal
285     // value
286     double t_ext;
287     if (estremo_sx >= estremo_dx)
288       t_ext = 0;
289     else
290       t_ext = 1;
291     double maxEstremi = std::max(estremo_dx, estremo_sx);
292     if (maxEstremi > estremo_int) {
293       t        = t_ext;
294       curv_max = maxEstremi;
295     }
296 
297     // Add max curvature point
298     if (t >= 0 && t <= 1 && curv_max > curvMaxLim) {
299       double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)),
300                                    t);  // transform local t to global t
301       parameterValues.push_back(w);
302     }
303     // End search max curvature point
304   }
305   // Delete duplicate of parameterValues
306   // Because some max cuvature point can coincide with the corner point
307   if ((int)parameterValues.size() > 1) {
308     std::sort(parameterValues.begin(), parameterValues.end());
309     parameterValues.erase(
310         std::unique(parameterValues.begin(), parameterValues.end()),
311         parameterValues.end());
312   }
313 }
314 
addStroke(TTool::Application * application,const TVectorImageP & vi,TStroke * stroke,bool breakAngles,bool frameCreated,bool levelCreated,TXshSimpleLevel * sLevel=NULL,TFrameId fid=TFrameId::NO_FRAME)315 static void addStroke(TTool::Application *application, const TVectorImageP &vi,
316                       TStroke *stroke, bool breakAngles, bool frameCreated,
317                       bool levelCreated, TXshSimpleLevel *sLevel = NULL,
318                       TFrameId fid = TFrameId::NO_FRAME) {
319   QMutexLocker lock(vi->getMutex());
320 
321   if (application->getCurrentObject()->isSpline()) {
322     application->getCurrentXsheet()->notifyXsheetChanged();
323     return;
324   }
325 
326   std::vector<double> corners;
327   std::vector<TStroke *> strokes;
328 
329   const float angoloLim =
330       1;  // Value (radians) of the Corner between two tangent vector.
331           // Up this value the two corner can be considered angular.
332   const float curvMaxLim = 0.8;  // Value of the max curvature.
333   // Up this value the point can be considered a max curvature point.
334 
335   findMaxCurvPoints(stroke, angoloLim, curvMaxLim, corners);
336   TXshSimpleLevel *sl;
337   if (!sLevel) {
338     sl = application->getCurrentLevel()->getSimpleLevel();
339   } else {
340     sl = sLevel;
341   }
342   TFrameId id = application->getCurrentTool()->getTool()->getCurrentFid();
343   if (id == TFrameId::NO_FRAME && fid != TFrameId::NO_FRAME) id = fid;
344   if (!corners.empty()) {
345     if (breakAngles)
346       split(stroke, corners, strokes);
347     else
348       strokes.push_back(new TStroke(*stroke));
349 
350     int n = strokes.size();
351 
352     TUndoManager::manager()->beginBlock();
353     for (int i = 0; i < n; i++) {
354       std::vector<TFilledRegionInf> *fillInformation =
355           new std::vector<TFilledRegionInf>;
356       ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation,
357                                                        stroke->getBBox());
358       TStroke *str = new TStroke(*strokes[i]);
359       vi->addStroke(str);
360       TUndoManager::manager()->add(new UndoPencil(str, fillInformation, sl, id,
361                                                   frameCreated, levelCreated));
362     }
363     TUndoManager::manager()->endBlock();
364   } else {
365     std::vector<TFilledRegionInf> *fillInformation =
366         new std::vector<TFilledRegionInf>;
367     ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation,
368                                                      stroke->getBBox());
369     TStroke *str = new TStroke(*stroke);
370     vi->addStroke(str);
371     TUndoManager::manager()->add(new UndoPencil(str, fillInformation, sl, id,
372                                                 frameCreated, levelCreated));
373   }
374 
375   // Update regions. It will call roundStroke() in
376   // TVectorImage::Imp::findIntersections().
377   // roundStroke() will slightly modify all the stroke positions.
378   // It is needed to update information for Fill Check.
379   vi->findRegions();
380 
381   for (int k = 0; k < (int)strokes.size(); k++) delete strokes[k];
382   strokes.clear();
383 
384   application->getCurrentTool()->getTool()->notifyImageChanged();
385 }
386 
387 //-------------------------------------------------------------------
388 //
389 // Gennaro: end
390 //
391 //-------------------------------------------------------------------
392 
393 //===================================================================
394 //
395 // Helper functions and classes
396 //
397 //-------------------------------------------------------------------
398 
399 namespace {
400 
401 //-------------------------------------------------------------------
402 
addStrokeToImage(TTool::Application * application,const TVectorImageP & vi,TStroke * stroke,bool breakAngles,bool frameCreated,bool levelCreated,TXshSimpleLevel * sLevel=NULL,TFrameId id=TFrameId::NO_FRAME)403 void addStrokeToImage(TTool::Application *application, const TVectorImageP &vi,
404                       TStroke *stroke, bool breakAngles, bool frameCreated,
405                       bool levelCreated, TXshSimpleLevel *sLevel = NULL,
406                       TFrameId id = TFrameId::NO_FRAME) {
407   QMutexLocker lock(vi->getMutex());
408   addStroke(application, vi.getPointer(), stroke, breakAngles, frameCreated,
409             levelCreated, sLevel, id);
410   // la notifica viene gia fatta da addStroke!
411   // getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
412 }
413 
414 //---------------------------------------------------------------------------------------------------------
415 
416 enum DrawOrder { OverAll = 0, UnderAll, PaletteOrder };
417 
getAboveStyleIdSet(int styleId,TPaletteP palette,QSet<int> & aboveStyles)418 void getAboveStyleIdSet(int styleId, TPaletteP palette,
419                         QSet<int> &aboveStyles) {
420   if (!palette) return;
421   for (int p = 0; p < palette->getPageCount(); p++) {
422     TPalette::Page *page = palette->getPage(p);
423     for (int s = 0; s < page->getStyleCount(); s++) {
424       int tmpId = page->getStyleId(s);
425       if (tmpId == styleId) return;
426       if (tmpId != 0) aboveStyles.insert(tmpId);
427     }
428   }
429 }
430 
431 //=========================================================================================================
432 
433 class RasterBrushUndo final : public TRasterUndo {
434   std::vector<TThickPoint> m_points;
435   int m_styleId;
436   bool m_selective;
437   bool m_isPaletteOrder;
438   bool m_isPencil;
439 
440 public:
RasterBrushUndo(TTileSetCM32 * tileSet,const std::vector<TThickPoint> & points,int styleId,bool selective,TXshSimpleLevel * level,const TFrameId & frameId,bool isPencil,bool isFrameCreated,bool isLevelCreated,bool isPaletteOrder)441   RasterBrushUndo(TTileSetCM32 *tileSet, const std::vector<TThickPoint> &points,
442                   int styleId, bool selective, TXshSimpleLevel *level,
443                   const TFrameId &frameId, bool isPencil, bool isFrameCreated,
444                   bool isLevelCreated, bool isPaletteOrder)
445       : TRasterUndo(tileSet, level, frameId, isFrameCreated, isLevelCreated, 0)
446       , m_points(points)
447       , m_styleId(styleId)
448       , m_selective(selective)
449       , m_isPencil(isPencil)
450       , m_isPaletteOrder(isPaletteOrder) {}
451 
redo() const452   void redo() const override {
453     insertLevelAndFrameIfNeeded();
454     TToonzImageP image = getImage();
455     TRasterCM32P ras   = image->getRaster();
456     RasterStrokeGenerator m_rasterTrack(ras, BRUSH, NONE, m_styleId,
457                                         m_points[0], m_selective, 0,
458                                         !m_isPencil, m_isPaletteOrder);
459     if (m_isPaletteOrder) {
460       QSet<int> aboveStyleIds;
461       getAboveStyleIdSet(m_styleId, image->getPalette(), aboveStyleIds);
462       m_rasterTrack.setAboveStyleIds(aboveStyleIds);
463     }
464     m_rasterTrack.setPointsSequence(m_points);
465     m_rasterTrack.generateStroke(m_isPencil);
466     image->setSavebox(image->getSavebox() +
467                       m_rasterTrack.getBBox(m_rasterTrack.getPointsSequence()));
468     ToolUtils::updateSaveBox();
469     TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
470     notifyImageChanged();
471   }
472 
getSize() const473   int getSize() const override {
474     return sizeof(*this) + TRasterUndo::getSize();
475   }
getToolName()476   QString getToolName() override { return QString("Brush Tool"); }
getHistoryType()477   int getHistoryType() override { return HistoryType::BrushTool; }
478 };
479 
480 //=========================================================================================================
481 
482 class RasterBluredBrushUndo final : public TRasterUndo {
483   std::vector<TThickPoint> m_points;
484   int m_styleId;
485   DrawOrder m_drawOrder;
486   int m_maxThick;
487   double m_hardness;
488 
489 public:
RasterBluredBrushUndo(TTileSetCM32 * tileSet,const std::vector<TThickPoint> & points,int styleId,DrawOrder drawOrder,TXshSimpleLevel * level,const TFrameId & frameId,int maxThick,double hardness,bool isFrameCreated,bool isLevelCreated)490   RasterBluredBrushUndo(TTileSetCM32 *tileSet,
491                         const std::vector<TThickPoint> &points, int styleId,
492                         DrawOrder drawOrder, TXshSimpleLevel *level,
493                         const TFrameId &frameId, int maxThick, double hardness,
494                         bool isFrameCreated, bool isLevelCreated)
495       : TRasterUndo(tileSet, level, frameId, isFrameCreated, isLevelCreated, 0)
496       , m_points(points)
497       , m_styleId(styleId)
498       , m_drawOrder(drawOrder)
499       , m_maxThick(maxThick)
500       , m_hardness(hardness) {}
501 
redo() const502   void redo() const override {
503     if (m_points.size() == 0) return;
504     insertLevelAndFrameIfNeeded();
505     TToonzImageP image     = getImage();
506     TRasterCM32P ras       = image->getRaster();
507     TRasterCM32P backupRas = ras->clone();
508     TRaster32P workRaster(ras->getSize());
509     QRadialGradient brushPad = ToolUtils::getBrushPad(m_maxThick, m_hardness);
510     workRaster->clear();
511     BluredBrush brush(workRaster, m_maxThick, brushPad, false);
512 
513     if (m_drawOrder == PaletteOrder) {
514       QSet<int> aboveStyleIds;
515       getAboveStyleIdSet(m_styleId, image->getPalette(), aboveStyleIds);
516       brush.setAboveStyleIds(aboveStyleIds);
517     }
518 
519     std::vector<TThickPoint> points;
520     points.push_back(m_points[0]);
521     TRect bbox = brush.getBoundFromPoints(points);
522     brush.addPoint(m_points[0], 1);
523     brush.updateDrawing(ras, ras, bbox, m_styleId, (int)m_drawOrder);
524     if (m_points.size() > 1) {
525       points.clear();
526       points.push_back(m_points[0]);
527       points.push_back(m_points[1]);
528       bbox = brush.getBoundFromPoints(points);
529       brush.addArc(m_points[0], (m_points[1] + m_points[0]) * 0.5, m_points[1],
530                    1, 1);
531       brush.updateDrawing(ras, backupRas, bbox, m_styleId, (int)m_drawOrder);
532       int i;
533       for (i = 1; i + 2 < (int)m_points.size(); i = i + 2) {
534         points.clear();
535         points.push_back(m_points[i]);
536         points.push_back(m_points[i + 1]);
537         points.push_back(m_points[i + 2]);
538         bbox = brush.getBoundFromPoints(points);
539         brush.addArc(m_points[i], m_points[i + 1], m_points[i + 2], 1, 1);
540         brush.updateDrawing(ras, backupRas, bbox, m_styleId, (int)m_drawOrder);
541       }
542     }
543     ToolUtils::updateSaveBox();
544     TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
545     notifyImageChanged();
546   }
547 
getSize() const548   int getSize() const override {
549     return sizeof(*this) + TRasterUndo::getSize();
550   }
551 
getToolName()552   QString getToolName() override { return QString("Brush Tool"); }
getHistoryType()553   int getHistoryType() override { return HistoryType::BrushTool; }
554 };
555 
556 //=========================================================================================================
557 
558 class MyPaintBrushUndo final : public TRasterUndo {
559   TPoint m_offset;
560   QString m_id;
561 
562 public:
MyPaintBrushUndo(TTileSetCM32 * tileSet,TXshSimpleLevel * level,const TFrameId & frameId,bool isFrameCreated,bool isLevelCreated,const TRasterCM32P & ras,const TPoint & offset)563   MyPaintBrushUndo(TTileSetCM32 *tileSet, TXshSimpleLevel *level,
564                    const TFrameId &frameId, bool isFrameCreated,
565                    bool isLevelCreated, const TRasterCM32P &ras,
566                    const TPoint &offset)
567       : TRasterUndo(tileSet, level, frameId, isFrameCreated, isLevelCreated, 0)
568       , m_offset(offset) {
569     static int counter = 0;
570     m_id = QString("MyPaintBrushUndo") + QString::number(counter++);
571     TImageCache::instance()->add(m_id.toStdString(),
572                                  TToonzImageP(ras, TRect(ras->getSize())));
573   }
574 
~MyPaintBrushUndo()575   ~MyPaintBrushUndo() { TImageCache::instance()->remove(m_id); }
576 
redo() const577   void redo() const override {
578     insertLevelAndFrameIfNeeded();
579 
580     TToonzImageP image = getImage();
581     TRasterCM32P ras   = image->getRaster();
582 
583     TImageP srcImg =
584         TImageCache::instance()->get(m_id.toStdString(), false)->cloneImage();
585     TToonzImageP tSrcImg = srcImg;
586     assert(tSrcImg);
587     ras->copy(tSrcImg->getRaster(), m_offset);
588     ToolUtils::updateSaveBox();
589     TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged();
590     notifyImageChanged();
591   }
592 
getSize() const593   int getSize() const override {
594     return sizeof(*this) + TRasterUndo::getSize();
595   }
596 
getToolName()597   QString getToolName() override { return QString("Brush Tool"); }
getHistoryType()598   int getHistoryType() override { return HistoryType::BrushTool; }
599 };
600 
601 //=========================================================================================================
602 
computeThickness(double pressure,const TDoublePairProperty & property)603 double computeThickness(double pressure, const TDoublePairProperty &property) {
604   double t      = pressure * pressure * pressure;
605   double thick0 = property.getValue().first;
606   double thick1 = property.getValue().second;
607   if (thick1 < 0.0001) thick0 = thick1 = 0.0;
608   return (thick0 + (thick1 - thick0) * t) * 0.5;
609 }
610 
611 //---------------------------------------------------------------------------------------------------------
612 
computeThickness(double pressure,const TIntPairProperty & property)613 int computeThickness(double pressure, const TIntPairProperty &property) {
614   double t   = pressure * pressure * pressure;
615   int thick0 = property.getValue().first;
616   int thick1 = property.getValue().second;
617   return tround(thick0 + (thick1 - thick0) * t);
618 }
619 
620 }  // namespace
621 
622 //--------------------------------------------------------------------------------------------------
623 
CatmullRomInterpolate(const TThickPoint & P0,const TThickPoint & P1,const TThickPoint & P2,const TThickPoint & P3,int samples,std::vector<TThickPoint> & points)624 static void CatmullRomInterpolate(const TThickPoint &P0, const TThickPoint &P1,
625                                   const TThickPoint &P2, const TThickPoint &P3,
626                                   int samples,
627                                   std::vector<TThickPoint> &points) {
628   double x0 = P1.x;
629   double x1 = (-P0.x + P2.x) * 0.5f;
630   double x2 = P0.x - 2.5f * P1.x + 2.0f * P2.x - 0.5f * P3.x;
631   double x3 = -0.5f * P0.x + 1.5f * P1.x - 1.5f * P2.x + 0.5f * P3.x;
632 
633   double y0 = P1.y;
634   double y1 = (-P0.y + P2.y) * 0.5f;
635   double y2 = P0.y - 2.5f * P1.y + 2.0f * P2.y - 0.5f * P3.y;
636   double y3 = -0.5f * P0.y + 1.5f * P1.y - 1.5f * P2.y + 0.5f * P3.y;
637 
638   double z0 = P1.thick;
639   double z1 = (-P0.thick + P2.thick) * 0.5f;
640   double z2 = P0.thick - 2.5f * P1.thick + 2.0f * P2.thick - 0.5f * P3.thick;
641   double z3 =
642       -0.5f * P0.thick + 1.5f * P1.thick - 1.5f * P2.thick + 0.5f * P3.thick;
643 
644   for (int i = 1; i <= samples; ++i) {
645     double t  = i / (double)(samples + 1);
646     double t2 = t * t;
647     double t3 = t2 * t;
648     TThickPoint p;
649     p.x     = x0 + x1 * t + x2 * t2 + x3 * t3;
650     p.y     = y0 + y1 * t + y2 * t2 + y3 * t3;
651     p.thick = z0 + z1 * t + z2 * t2 + z3 * t3;
652     points.push_back(p);
653   }
654 }
655 
656 //--------------------------------------------------------------------------------------------------
657 
Smooth(std::vector<TThickPoint> & points,int radius)658 static void Smooth(std::vector<TThickPoint> &points, int radius) {
659   int n = (int)points.size();
660   if (radius < 1 || n < 3) {
661     return;
662   }
663 
664   std::vector<TThickPoint> result;
665 
666   float d = 1.0f / (radius * 2 + 1);
667 
668   for (int i = 1; i < n - 1; ++i) {
669     int lower = i - radius;
670     int upper = i + radius;
671 
672     TThickPoint total;
673     total.x     = 0;
674     total.y     = 0;
675     total.thick = 0;
676 
677     for (int j = lower; j <= upper; ++j) {
678       int idx = j;
679       if (idx < 0) {
680         idx = 0;
681       } else if (idx >= n) {
682         idx = n - 1;
683       }
684       total.x += points[idx].x;
685       total.y += points[idx].y;
686       total.thick += points[idx].thick;
687     }
688 
689     total.x *= d;
690     total.y *= d;
691     total.thick *= d;
692     result.push_back(total);
693   }
694 
695   for (int i = 1; i < n - 1; ++i) {
696     points[i].x     = result[i - 1].x;
697     points[i].y     = result[i - 1].y;
698     points[i].thick = result[i - 1].thick;
699   }
700 
701   if (points.size() >= 3) {
702     std::vector<TThickPoint> pts;
703     CatmullRomInterpolate(points[0], points[0], points[1], points[2], 10, pts);
704     std::vector<TThickPoint>::iterator it = points.begin() + 1;
705     points.insert(it, pts.begin(), pts.end());
706 
707     pts.clear();
708     CatmullRomInterpolate(points[n - 3], points[n - 2], points[n - 1],
709                           points[n - 1], 10, pts);
710     it = points.begin();
711     it += n - 1;
712     points.insert(it, pts.begin(), pts.end());
713   }
714 }
715 
716 //--------------------------------------------------------------------------------------------------
717 
beginStroke(int smooth)718 void SmoothStroke::beginStroke(int smooth) {
719   m_smooth      = smooth;
720   m_outputIndex = 0;
721   m_readIndex   = -1;
722   m_rawPoints.clear();
723   m_outputPoints.clear();
724 }
725 
726 //--------------------------------------------------------------------------------------------------
727 
addPoint(const TThickPoint & point)728 void SmoothStroke::addPoint(const TThickPoint &point) {
729   if (m_rawPoints.size() > 0 && m_rawPoints.back().x == point.x &&
730       m_rawPoints.back().y == point.y) {
731     return;
732   }
733   m_rawPoints.push_back(point);
734   generatePoints();
735 }
736 
737 //--------------------------------------------------------------------------------------------------
738 
endStroke()739 void SmoothStroke::endStroke() {
740   generatePoints();
741   // force enable the output all segments
742   m_outputIndex = m_outputPoints.size() - 1;
743 }
744 
745 //--------------------------------------------------------------------------------------------------
746 
clearPoints()747 void SmoothStroke::clearPoints() {
748   m_outputIndex = 0;
749   m_readIndex   = -1;
750   m_outputPoints.clear();
751   m_rawPoints.clear();
752 }
753 
754 //--------------------------------------------------------------------------------------------------
755 
getSmoothPoints(std::vector<TThickPoint> & smoothPoints)756 void SmoothStroke::getSmoothPoints(std::vector<TThickPoint> &smoothPoints) {
757   int n = m_outputPoints.size();
758   for (int i = m_readIndex + 1; i <= m_outputIndex && i < n; ++i) {
759     smoothPoints.push_back(m_outputPoints[i]);
760   }
761   m_readIndex = m_outputIndex;
762 }
763 
764 //--------------------------------------------------------------------------------------------------
765 
generatePoints()766 void SmoothStroke::generatePoints() {
767   int n = (int)m_rawPoints.size();
768   if (n == 0) {
769     return;
770   }
771 
772   // if m_smooth = 0, then skip whole smoothing process
773   if (m_smooth == 0) {
774     for (int i = m_outputIndex; i < (int)m_outputPoints.size(); ++i) {
775       if (m_outputPoints[i] != m_rawPoints[i]) {
776         break;
777       }
778       ++m_outputIndex;
779     }
780     m_outputPoints = m_rawPoints;
781     return;
782   }
783 
784   std::vector<TThickPoint> smoothedPoints;
785   // Add more stroke samples before applying the smoothing
786   // This is because the raw inputs points are too few to support smooth result,
787   // especially on stroke ends
788   smoothedPoints.push_back(m_rawPoints.front());
789   for (int i = 1; i < n; ++i) {
790     const TThickPoint &p1 = m_rawPoints[i - 1];
791     const TThickPoint &p2 = m_rawPoints[i];
792     const TThickPoint &p0 = i - 2 >= 0 ? m_rawPoints[i - 2] : p1;
793     const TThickPoint &p3 = i + 1 < n ? m_rawPoints[i + 1] : p2;
794 
795     int samples = 8;
796     CatmullRomInterpolate(p0, p1, p2, p3, samples, smoothedPoints);
797     smoothedPoints.push_back(p2);
798   }
799   // Apply the 1D box filter
800   // Multiple passes result in better quality and fix the stroke ends break
801   // issue
802   for (int i = 0; i < 3; ++i) {
803     Smooth(smoothedPoints, m_smooth);
804   }
805   // Compare the new smoothed stroke with old one
806   // Enable the output for unchanged parts
807   int outputNum = (int)m_outputPoints.size();
808   for (int i = m_outputIndex; i < outputNum; ++i) {
809     if (m_outputPoints[i] != smoothedPoints[i]) {
810       break;
811     }
812     ++m_outputIndex;
813   }
814   m_outputPoints = smoothedPoints;
815 }
816 
817 //===================================================================
818 //
819 // ToonzRasterBrushTool
820 //
821 //-----------------------------------------------------------------------------
822 
ToonzRasterBrushTool(std::string name,int targetType)823 ToonzRasterBrushTool::ToonzRasterBrushTool(std::string name, int targetType)
824     : TTool(name)
825     , m_rasThickness("Size", 1, 1000, 1, 5)
826     , m_smooth("Smooth:", 0, 50, 0)
827     , m_hardness("Hardness:", 0, 100, 100)
828     , m_preset("Preset:")
829     , m_drawOrder("Draw Order:")
830     , m_pencil("Pencil", false)
831     , m_pressure("Pressure", true)
832     , m_modifierSize("ModifierSize", -3, 3, 0, true)
833     , m_rasterTrack(0)
834     , m_styleId(0)
835     , m_bluredBrush(0)
836     , m_active(false)
837     , m_enabled(false)
838     , m_isPrompting(false)
839     , m_firstTime(true)
840     , m_presetsLoaded(false)
841     , m_targetType(targetType)
842     , m_workingFrameId(TFrameId())
843     , m_notifier(0) {
844   bind(targetType);
845 
846   m_rasThickness.setNonLinearSlider();
847 
848   m_prop[0].bind(m_rasThickness);
849   m_prop[0].bind(m_hardness);
850   m_prop[0].bind(m_smooth);
851   m_prop[0].bind(m_drawOrder);
852   m_prop[0].bind(m_modifierSize);
853   m_prop[0].bind(m_pencil);
854   m_pencil.setId("PencilMode");
855 
856   m_drawOrder.addValue(L"Over All");
857   m_drawOrder.addValue(L"Under All");
858   m_drawOrder.addValue(L"Palette Order");
859   m_drawOrder.setId("DrawOrder");
860 
861   m_prop[0].bind(m_pressure);
862 
863   m_prop[0].bind(m_preset);
864   m_preset.setId("BrushPreset");
865   m_preset.addValue(CUSTOM_WSTR);
866   m_pressure.setId("PressureSensitivity");
867 }
868 
869 //-------------------------------------------------------------------------------------------------------
870 
createOptionsBox()871 ToolOptionsBox *ToonzRasterBrushTool::createOptionsBox() {
872   TPaletteHandle *currPalette =
873       TTool::getApplication()->getPaletteController()->getCurrentLevelPalette();
874   ToolHandle *currTool = TTool::getApplication()->getCurrentTool();
875   return new BrushToolOptionsBox(0, this, currPalette, currTool);
876 }
877 
878 //-------------------------------------------------------------------------------------------------------
879 
drawLine(const TPointD & point,const TPointD & centre,bool horizontal,bool isDecimal)880 void ToonzRasterBrushTool::drawLine(const TPointD &point, const TPointD &centre,
881                                     bool horizontal, bool isDecimal) {
882   if (!isDecimal) {
883     if (horizontal) {
884       tglDrawSegment(TPointD(point.x - 1.5, point.y + 0.5) + centre,
885                      TPointD(point.x - 0.5, point.y + 0.5) + centre);
886       tglDrawSegment(TPointD(point.y - 0.5, -point.x + 1.5) + centre,
887                      TPointD(point.y - 0.5, -point.x + 0.5) + centre);
888       tglDrawSegment(TPointD(-point.x + 0.5, -point.y + 0.5) + centre,
889                      TPointD(-point.x - 0.5, -point.y + 0.5) + centre);
890       tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
891                      TPointD(-point.y - 0.5, point.x + 0.5) + centre);
892 
893       tglDrawSegment(TPointD(point.y - 0.5, point.x + 0.5) + centre,
894                      TPointD(point.y - 0.5, point.x - 0.5) + centre);
895       tglDrawSegment(TPointD(point.x - 0.5, -point.y + 0.5) + centre,
896                      TPointD(point.x - 1.5, -point.y + 0.5) + centre);
897       tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
898                      TPointD(-point.y - 0.5, -point.x + 1.5) + centre);
899       tglDrawSegment(TPointD(-point.x - 0.5, point.y + 0.5) + centre,
900                      TPointD(-point.x + 0.5, point.y + 0.5) + centre);
901     } else {
902       tglDrawSegment(TPointD(point.x - 1.5, point.y + 1.5) + centre,
903                      TPointD(point.x - 1.5, point.y + 0.5) + centre);
904       tglDrawSegment(TPointD(point.x - 1.5, point.y + 0.5) + centre,
905                      TPointD(point.x - 0.5, point.y + 0.5) + centre);
906       tglDrawSegment(TPointD(point.y + 0.5, -point.x + 1.5) + centre,
907                      TPointD(point.y - 0.5, -point.x + 1.5) + centre);
908       tglDrawSegment(TPointD(point.y - 0.5, -point.x + 1.5) + centre,
909                      TPointD(point.y - 0.5, -point.x + 0.5) + centre);
910       tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 0.5) + centre,
911                      TPointD(-point.x + 0.5, -point.y + 0.5) + centre);
912       tglDrawSegment(TPointD(-point.x + 0.5, -point.y + 0.5) + centre,
913                      TPointD(-point.x - 0.5, -point.y + 0.5) + centre);
914       tglDrawSegment(TPointD(-point.y - 1.5, point.x - 0.5) + centre,
915                      TPointD(-point.y - 0.5, point.x - 0.5) + centre);
916       tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
917                      TPointD(-point.y - 0.5, point.x + 0.5) + centre);
918 
919       tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
920                      TPointD(point.y - 0.5, point.x - 0.5) + centre);
921       tglDrawSegment(TPointD(point.y - 0.5, point.x - 0.5) + centre,
922                      TPointD(point.y - 0.5, point.x + 0.5) + centre);
923       tglDrawSegment(TPointD(point.x - 1.5, -point.y - 0.5) + centre,
924                      TPointD(point.x - 1.5, -point.y + 0.5) + centre);
925       tglDrawSegment(TPointD(point.x - 1.5, -point.y + 0.5) + centre,
926                      TPointD(point.x - 0.5, -point.y + 0.5) + centre);
927       tglDrawSegment(TPointD(-point.y - 1.5, -point.x + 1.5) + centre,
928                      TPointD(-point.y - 0.5, -point.x + 1.5) + centre);
929       tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 1.5) + centre,
930                      TPointD(-point.y - 0.5, -point.x + 0.5) + centre);
931       tglDrawSegment(TPointD(-point.x + 0.5, point.y + 1.5) + centre,
932                      TPointD(-point.x + 0.5, point.y + 0.5) + centre);
933       tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
934                      TPointD(-point.x - 0.5, point.y + 0.5) + centre);
935     }
936   } else {
937     if (horizontal) {
938       tglDrawSegment(TPointD(point.x - 0.5, point.y + 0.5) + centre,
939                      TPointD(point.x + 0.5, point.y + 0.5) + centre);
940       tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
941                      TPointD(point.y + 0.5, point.x + 0.5) + centre);
942       tglDrawSegment(TPointD(point.y + 0.5, -point.x + 0.5) + centre,
943                      TPointD(point.y + 0.5, -point.x - 0.5) + centre);
944       tglDrawSegment(TPointD(point.x + 0.5, -point.y - 0.5) + centre,
945                      TPointD(point.x - 0.5, -point.y - 0.5) + centre);
946       tglDrawSegment(TPointD(-point.x - 0.5, -point.y - 0.5) + centre,
947                      TPointD(-point.x + 0.5, -point.y - 0.5) + centre);
948       tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
949                      TPointD(-point.y - 0.5, -point.x - 0.5) + centre);
950       tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
951                      TPointD(-point.y - 0.5, point.x + 0.5) + centre);
952       tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
953                      TPointD(-point.x - 0.5, point.y + 0.5) + centre);
954     } else {
955       tglDrawSegment(TPointD(point.x - 0.5, point.y + 1.5) + centre,
956                      TPointD(point.x - 0.5, point.y + 0.5) + centre);
957       tglDrawSegment(TPointD(point.x - 0.5, point.y + 0.5) + centre,
958                      TPointD(point.x + 0.5, point.y + 0.5) + centre);
959       tglDrawSegment(TPointD(point.y + 1.5, point.x - 0.5) + centre,
960                      TPointD(point.y + 0.5, point.x - 0.5) + centre);
961       tglDrawSegment(TPointD(point.y + 0.5, point.x - 0.5) + centre,
962                      TPointD(point.y + 0.5, point.x + 0.5) + centre);
963       tglDrawSegment(TPointD(point.y + 1.5, -point.x + 0.5) + centre,
964                      TPointD(point.y + 0.5, -point.x + 0.5) + centre);
965       tglDrawSegment(TPointD(point.y + 0.5, -point.x + 0.5) + centre,
966                      TPointD(point.y + 0.5, -point.x - 0.5) + centre);
967       tglDrawSegment(TPointD(point.x - 0.5, -point.y - 1.5) + centre,
968                      TPointD(point.x - 0.5, -point.y - 0.5) + centre);
969       tglDrawSegment(TPointD(point.x - 0.5, -point.y - 0.5) + centre,
970                      TPointD(point.x + 0.5, -point.y - 0.5) + centre);
971 
972       tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 1.5) + centre,
973                      TPointD(-point.x + 0.5, -point.y - 0.5) + centre);
974       tglDrawSegment(TPointD(-point.x + 0.5, -point.y - 0.5) + centre,
975                      TPointD(-point.x - 0.5, -point.y - 0.5) + centre);
976       tglDrawSegment(TPointD(-point.y - 1.5, -point.x + 0.5) + centre,
977                      TPointD(-point.y - 0.5, -point.x + 0.5) + centre);
978       tglDrawSegment(TPointD(-point.y - 0.5, -point.x + 0.5) + centre,
979                      TPointD(-point.y - 0.5, -point.x - 0.5) + centre);
980       tglDrawSegment(TPointD(-point.y - 1.5, point.x - 0.5) + centre,
981                      TPointD(-point.y - 0.5, point.x - 0.5) + centre);
982       tglDrawSegment(TPointD(-point.y - 0.5, point.x - 0.5) + centre,
983                      TPointD(-point.y - 0.5, point.x + 0.5) + centre);
984       tglDrawSegment(TPointD(-point.x + 0.5, point.y + 1.5) + centre,
985                      TPointD(-point.x + 0.5, point.y + 0.5) + centre);
986       tglDrawSegment(TPointD(-point.x + 0.5, point.y + 0.5) + centre,
987                      TPointD(-point.x - 0.5, point.y + 0.5) + centre);
988     }
989   }
990 }
991 
992 //-------------------------------------------------------------------------------------------------------
993 
drawEmptyCircle(TPointD pos,int thick,bool isLxEven,bool isLyEven,bool isPencil)994 void ToonzRasterBrushTool::drawEmptyCircle(TPointD pos, int thick,
995                                            bool isLxEven, bool isLyEven,
996                                            bool isPencil) {
997   if (isLxEven) pos.x += 0.5;
998   if (isLyEven) pos.y += 0.5;
999 
1000   if (!isPencil)
1001     tglDrawCircle(pos, (thick + 1) * 0.5);
1002   else {
1003     int x = 0, y = tround((thick * 0.5) - 0.5);
1004     int d           = 3 - 2 * (int)(thick * 0.5);
1005     bool horizontal = true, isDecimal = thick % 2 != 0;
1006     drawLine(TPointD(x, y), pos, horizontal, isDecimal);
1007     while (y > x) {
1008       if (d < 0) {
1009         d          = d + 4 * x + 6;
1010         horizontal = true;
1011       } else {
1012         d          = d + 4 * (x - y) + 10;
1013         horizontal = false;
1014         y--;
1015       }
1016       x++;
1017       drawLine(TPointD(x, y), pos, horizontal, isDecimal);
1018     }
1019   }
1020 }
1021 
1022 //-------------------------------------------------------------------------------------------------------
1023 
getCenteredCursorPos(const TPointD & originalCursorPos)1024 TPointD ToonzRasterBrushTool::getCenteredCursorPos(
1025     const TPointD &originalCursorPos) {
1026   if (m_isMyPaintStyleSelected) return originalCursorPos;
1027   TXshLevelHandle *levelHandle = m_application->getCurrentLevel();
1028   TXshSimpleLevel *level = levelHandle ? levelHandle->getSimpleLevel() : 0;
1029   TDimension resolution =
1030       level ? level->getProperties()->getImageRes() : TDimension(0, 0);
1031 
1032   bool xEven = (resolution.lx % 2 == 0);
1033   bool yEven = (resolution.ly % 2 == 0);
1034 
1035   TPointD centeredCursorPos = originalCursorPos;
1036 
1037   if (xEven) centeredCursorPos.x -= 0.5;
1038   if (yEven) centeredCursorPos.y -= 0.5;
1039 
1040   return centeredCursorPos;
1041 }
1042 
1043 //-------------------------------------------------------------------------------------------------------
1044 
updateTranslation()1045 void ToonzRasterBrushTool::updateTranslation() {
1046   m_rasThickness.setQStringName(tr("Size"));
1047   m_hardness.setQStringName(tr("Hardness:"));
1048   m_smooth.setQStringName(tr("Smooth:"));
1049   m_drawOrder.setQStringName(tr("Draw Order:"));
1050   m_drawOrder.setItemUIName(L"Over All", tr("Over All"));
1051   m_drawOrder.setItemUIName(L"Under All", tr("Under All"));
1052   m_drawOrder.setItemUIName(L"Palette Order", tr("Palette Order"));
1053   m_modifierSize.setQStringName(tr("Size"));
1054 
1055   // m_filled.setQStringName(tr("Filled"));
1056   m_preset.setQStringName(tr("Preset:"));
1057   m_preset.setItemUIName(CUSTOM_WSTR, tr("<custom>"));
1058   m_pencil.setQStringName(tr("Pencil"));
1059   m_pressure.setQStringName(tr("Pressure"));
1060 }
1061 
1062 //---------------------------------------------------------------------------------------------------
1063 
updateWorkAndBackupRasters(const TRect & rect)1064 void ToonzRasterBrushTool::updateWorkAndBackupRasters(const TRect &rect) {
1065   TToonzImageP ti = TImageP(getImage(false, 1));
1066   if (!ti) return;
1067 
1068   TRasterCM32P ras = ti->getRaster();
1069 
1070   if (m_isMyPaintStyleSelected) {
1071     const int denominator = 8;
1072     TRect enlargedRect    = rect + m_lastRect;
1073     int dx                = (enlargedRect.getLx() - 1) / denominator + 1;
1074     int dy                = (enlargedRect.getLy() - 1) / denominator + 1;
1075 
1076     if (m_lastRect.isEmpty()) {
1077       enlargedRect.x0 -= dx;
1078       enlargedRect.y0 -= dy;
1079       enlargedRect.x1 += dx;
1080       enlargedRect.y1 += dy;
1081 
1082       TRect _rect = enlargedRect * ras->getBounds();
1083       if (_rect.isEmpty()) return;
1084 
1085       m_workRas->extract(_rect)->copy(ras->extract(_rect));
1086       m_backupRas->extract(_rect)->copy(ras->extract(_rect));
1087     } else {
1088       if (enlargedRect.x0 < m_lastRect.x0) enlargedRect.x0 -= dx;
1089       if (enlargedRect.y0 < m_lastRect.y0) enlargedRect.y0 -= dy;
1090       if (enlargedRect.x1 > m_lastRect.x1) enlargedRect.x1 += dx;
1091       if (enlargedRect.y1 > m_lastRect.y1) enlargedRect.y1 += dy;
1092 
1093       TRect _rect = enlargedRect * ras->getBounds();
1094       if (_rect.isEmpty()) return;
1095 
1096       TRect _lastRect    = m_lastRect * ras->getBounds();
1097       QList<TRect> rects = ToolUtils::splitRect(_rect, _lastRect);
1098       for (int i = 0; i < rects.size(); i++) {
1099         m_workRas->extract(rects[i])->copy(ras->extract(rects[i]));
1100         m_backupRas->extract(rects[i])->copy(ras->extract(rects[i]));
1101       }
1102     }
1103 
1104     m_lastRect = enlargedRect;
1105     return;
1106   }
1107 
1108   TRect _rect     = rect * ras->getBounds();
1109   TRect _lastRect = m_lastRect * ras->getBounds();
1110 
1111   if (_rect.isEmpty()) return;
1112 
1113   if (m_lastRect.isEmpty()) {
1114     m_workRas->extract(_rect)->clear();
1115     m_backupRas->extract(_rect)->copy(ras->extract(_rect));
1116     return;
1117   }
1118 
1119   QList<TRect> rects = ToolUtils::splitRect(_rect, _lastRect);
1120   for (int i = 0; i < rects.size(); i++) {
1121     m_workRas->extract(rects[i])->clear();
1122     m_backupRas->extract(rects[i])->copy(ras->extract(rects[i]));
1123   }
1124 }
1125 
1126 //---------------------------------------------------------------------------------------------------
1127 
onActivate()1128 void ToonzRasterBrushTool::onActivate() {
1129   if (!m_notifier) m_notifier = new ToonzRasterBrushToolNotifier(this);
1130 
1131   if (m_firstTime) {
1132     m_firstTime = false;
1133 
1134     std::wstring wpreset =
1135         QString::fromStdString(RasterBrushPreset.getValue()).toStdWString();
1136     if (wpreset != CUSTOM_WSTR) {
1137       initPresets();
1138       if (!m_preset.isValue(wpreset)) wpreset = CUSTOM_WSTR;
1139       m_preset.setValue(wpreset);
1140       RasterBrushPreset = m_preset.getValueAsString();
1141       loadPreset();
1142     } else
1143       loadLastBrush();
1144   }
1145   m_brushPad = ToolUtils::getBrushPad(m_rasThickness.getValue().second,
1146                                       m_hardness.getValue() * 0.01);
1147   setWorkAndBackupImages();
1148 
1149   m_brushTimer.start();
1150   // TODO:app->editImageOrSpline();
1151 }
1152 
1153 //--------------------------------------------------------------------------------------------------
1154 
onDeactivate()1155 void ToonzRasterBrushTool::onDeactivate() {
1156   /*---
1157    * ドラッグ中にツールが切り替わった場合に備え、onDeactivateにもMouseReleaseと同じ処理を行う
1158    * ---*/
1159   if (m_tileSaver) {
1160     bool isValid = m_enabled && m_active;
1161     m_enabled    = false;
1162     m_active     = false;
1163     if (isValid) {
1164       finishRasterBrush(m_mousePos,
1165                         1); /*-- 最後のストロークの筆圧は1とする --*/
1166     }
1167   }
1168   m_workRas   = TRaster32P();
1169   m_backupRas = TRasterCM32P();
1170 }
1171 
1172 //--------------------------------------------------------------------------------------------------
1173 
askRead(const TRect & rect)1174 bool ToonzRasterBrushTool::askRead(const TRect &rect) { return askWrite(rect); }
1175 
1176 //--------------------------------------------------------------------------------------------------
1177 
askWrite(const TRect & rect)1178 bool ToonzRasterBrushTool::askWrite(const TRect &rect) {
1179   if (rect.isEmpty()) return true;
1180   m_strokeRect += rect;
1181   m_strokeSegmentRect += rect;
1182   updateWorkAndBackupRasters(rect);
1183   m_tileSaver->save(rect);
1184   return true;
1185 }
1186 
1187 //--------------------------------------------------------------------------------------------------
1188 
preLeftButtonDown()1189 bool ToonzRasterBrushTool::preLeftButtonDown() {
1190   touchImage();
1191   if (m_isFrameCreated) {
1192     setWorkAndBackupImages();
1193     // When the xsheet frame is selected, whole viewer will be updated from
1194     // SceneViewer::onXsheetChanged() on adding a new frame.
1195     // We need to take care of a case when the level frame is selected.
1196     if (m_application->getCurrentFrame()->isEditingLevel()) invalidate();
1197   }
1198   return true;
1199 }
1200 
1201 //--------------------------------------------------------------------------------------------------
1202 
leftButtonDown(const TPointD & pos,const TMouseEvent & e)1203 void ToonzRasterBrushTool::leftButtonDown(const TPointD &pos,
1204                                           const TMouseEvent &e) {
1205   TTool::Application *app = TTool::getApplication();
1206   if (!app) return;
1207 
1208   int col   = app->getCurrentColumn()->getColumnIndex();
1209   m_enabled = col >= 0 || app->getCurrentFrame()->isEditingLevel();
1210   // todo: gestire autoenable
1211   if (!m_enabled) return;
1212 
1213   TPointD centeredPos = getCenteredCursorPos(pos);
1214 
1215   m_currentColor = TPixel32::Black;
1216   m_active       = !!getImage(true);
1217   if (!m_active) {
1218     m_active = !!touchImage();
1219   }
1220   if (!m_active) return;
1221 
1222   if (m_active) {
1223     // nel caso che il colore corrente sia un cleanup/studiopalette color
1224     // oppure il colore di un colorfield
1225     m_styleId       = app->getCurrentLevelStyleIndex();
1226     TColorStyle *cs = app->getCurrentLevelStyle();
1227     if (cs) {
1228       TRasterStyleFx *rfx = cs ? cs->getRasterStyleFx() : 0;
1229       m_active = cs != 0 && (cs->isStrokeStyle() || (rfx && rfx->isInkStyle()));
1230       m_currentColor   = cs->getAverageColor();
1231       m_currentColor.m = 255;
1232     } else {
1233       m_styleId      = 1;
1234       m_currentColor = TPixel32::Black;
1235     }
1236   }
1237 
1238   // assert(0<=m_styleId && m_styleId<2);
1239   TImageP img = getImage(true);
1240   TToonzImageP ri(img);
1241   TRasterCM32P ras = ri->getRaster();
1242   if (ras) {
1243     TPointD rasCenter = ras->getCenterD();
1244     m_tileSet         = new TTileSetCM32(ras->getSize());
1245     m_tileSaver       = new TTileSaverCM32(ras, m_tileSet);
1246     double maxThick   = m_rasThickness.getValue().second;
1247     double thickness  = (m_pressure.getValue())
1248                            ? computeThickness(e.m_pressure, m_rasThickness) * 2
1249                            : maxThick;
1250 
1251     /*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する
1252      * ---*/
1253     if (m_pressure.getValue() && e.m_pressure == 1.0)
1254       thickness = m_rasThickness.getValue().first;
1255 
1256     TPointD halfThick(maxThick * 0.5, maxThick * 0.5);
1257     TRectD invalidateRect(centeredPos - halfThick, centeredPos + halfThick);
1258     TPointD dpi;
1259     ri->getDpi(dpi.x, dpi.y);
1260     TRectD previousTipRect(m_brushPos - halfThick, m_brushPos + halfThick);
1261     if (dpi.x > Stage::inch || dpi.y > Stage::inch)
1262       previousTipRect *= dpi.x / Stage::inch;
1263     invalidateRect += previousTipRect;
1264 
1265     // if the drawOrder mode = "Palette Order",
1266     // get styleId list which is above the current style in the palette
1267     DrawOrder drawOrder = (DrawOrder)m_drawOrder.getIndex();
1268     QSet<int> aboveStyleIds;
1269     if (drawOrder == PaletteOrder) {
1270       getAboveStyleIdSet(m_styleId, ri->getPalette(), aboveStyleIds);
1271     }
1272 
1273     // mypaint brush case
1274     if (m_isMyPaintStyleSelected) {
1275       TPointD point(centeredPos + rasCenter);
1276       double pressure =
1277           m_pressure.getValue() && e.isTablet() ? e.m_pressure : 0.5;
1278       updateCurrentStyle();
1279       if (!(m_workRas && m_backupRas)) setWorkAndBackupImages();
1280       m_workRas->lock();
1281       mypaint::Brush mypaintBrush;
1282       TMyPaintBrushStyle *mypaintStyle =
1283           dynamic_cast<TMyPaintBrushStyle *>(app->getCurrentLevelStyle());
1284       {  // applyToonzBrushSettings
1285         mypaintBrush.fromBrush(mypaintStyle->getBrush());
1286         double modifierSize = m_modifierSize.getValue() * log(2.0);
1287         float baseSize =
1288             mypaintBrush.getBaseValue(MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC);
1289         mypaintBrush.setBaseValue(MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
1290                                   baseSize + modifierSize);
1291       }
1292       m_toonz_brush = new MyPaintToonzBrush(m_workRas, *this, mypaintBrush);
1293       m_strokeRect.empty();
1294       m_strokeSegmentRect.empty();
1295       m_toonz_brush->beginStroke();
1296       m_toonz_brush->strokeTo(point, pressure, restartBrushTimer());
1297       TRect updateRect = m_strokeSegmentRect * ras->getBounds();
1298       if (!updateRect.isEmpty()) {
1299         // ras->extract(updateRect)->copy(m_workRas->extract(updateRect));
1300         m_toonz_brush->updateDrawing(ri->getRaster(), m_backupRas, m_strokeRect,
1301                                      m_styleId);
1302       }
1303       m_lastRect = m_strokeRect;
1304 
1305       TPointD thickOffset(m_maxCursorThick * 0.5, m_maxCursorThick * 0.5);
1306       invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
1307       invalidateRect +=
1308           TRectD(centeredPos - thickOffset, centeredPos + thickOffset);
1309       invalidateRect +=
1310           TRectD(m_brushPos - thickOffset, m_brushPos + thickOffset);
1311     } else if (m_hardness.getValue() == 100 || m_pencil.getValue()) {
1312       /*-- Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる
1313        * --*/
1314       if (!m_pencil.getValue()) thickness -= 1.0;
1315 
1316       TThickPoint thickPoint(centeredPos + convert(ras->getCenter()),
1317                              thickness);
1318       m_rasterTrack = new RasterStrokeGenerator(
1319           ras, BRUSH, NONE, m_styleId, thickPoint, drawOrder != OverAll, 0,
1320           !m_pencil.getValue(), drawOrder == PaletteOrder);
1321 
1322       if (drawOrder == PaletteOrder)
1323         m_rasterTrack->setAboveStyleIds(aboveStyleIds);
1324 
1325       m_tileSaver->save(m_rasterTrack->getLastRect());
1326       m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue());
1327 
1328       std::vector<TThickPoint> pts;
1329       if (m_smooth.getValue() == 0) {
1330         pts.push_back(thickPoint);
1331       } else {
1332         m_smoothStroke.beginStroke(m_smooth.getValue());
1333         m_smoothStroke.addPoint(thickPoint);
1334         m_smoothStroke.getSmoothPoints(pts);
1335       }
1336     } else {
1337       m_points.clear();
1338       TThickPoint point(centeredPos + rasCenter, thickness);
1339       m_points.push_back(point);
1340       m_bluredBrush = new BluredBrush(m_workRas, maxThick, m_brushPad, false);
1341 
1342       if (drawOrder == PaletteOrder)
1343         m_bluredBrush->setAboveStyleIds(aboveStyleIds);
1344 
1345       m_strokeRect = m_bluredBrush->getBoundFromPoints(m_points);
1346       updateWorkAndBackupRasters(m_strokeRect);
1347       m_tileSaver->save(m_strokeRect);
1348       m_bluredBrush->addPoint(point, 1);
1349       m_bluredBrush->updateDrawing(ri->getRaster(), m_backupRas, m_strokeRect,
1350                                    m_styleId, drawOrder);
1351       m_lastRect = m_strokeRect;
1352 
1353       std::vector<TThickPoint> pts;
1354       if (m_smooth.getValue() == 0) {
1355         pts.push_back(point);
1356       } else {
1357         m_smoothStroke.beginStroke(m_smooth.getValue());
1358         m_smoothStroke.addPoint(point);
1359         m_smoothStroke.getSmoothPoints(pts);
1360       }
1361     }
1362     /*-- 作業中のFidを登録 --*/
1363     m_workingFrameId = getFrameId();
1364 
1365     invalidate(invalidateRect.enlarge(2));
1366   }
1367   // updating m_brushPos is needed to refresh viewer properly
1368   m_mousePos = pos;
1369   m_brushPos = getCenteredCursorPos(pos);
1370 }
1371 
1372 //-------------------------------------------------------------------------------------------------------------
1373 
leftButtonDrag(const TPointD & pos,const TMouseEvent & e)1374 void ToonzRasterBrushTool::leftButtonDrag(const TPointD &pos,
1375                                           const TMouseEvent &e) {
1376   if (!m_enabled || !m_active) {
1377     m_mousePos = pos;
1378     m_brushPos = getCenteredCursorPos(pos);
1379     return;
1380   }
1381 
1382   TPointD centeredPos = getCenteredCursorPos(pos);
1383 
1384   TToonzImageP ti   = TImageP(getImage(true));
1385   TPointD rasCenter = ti->getRaster()->getCenterD();
1386   int maxThickness  = m_rasThickness.getValue().second;
1387   double thickness  = (m_pressure.getValue())
1388                          ? computeThickness(e.m_pressure, m_rasThickness) * 2
1389                          : maxThickness;
1390   TRectD invalidateRect;
1391   if (m_isMyPaintStyleSelected) {
1392     TRasterP ras = ti->getRaster();
1393     TPointD point(centeredPos + rasCenter);
1394     double pressure =
1395         m_pressure.getValue() && e.isTablet() ? e.m_pressure : 0.5;
1396 
1397     m_strokeSegmentRect.empty();
1398     m_toonz_brush->strokeTo(point, pressure, restartBrushTimer());
1399     TRect updateRect = m_strokeSegmentRect * ras->getBounds();
1400     if (!updateRect.isEmpty()) {
1401       // ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
1402       m_toonz_brush->updateDrawing(ras, m_backupRas, m_strokeSegmentRect,
1403                                    m_styleId);
1404     }
1405     m_lastRect = m_strokeRect;
1406 
1407     TPointD thickOffset(m_maxCursorThick * 0.5, m_maxCursorThick * 0.5);
1408     invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
1409     invalidateRect +=
1410         TRectD(centeredPos - thickOffset, centeredPos + thickOffset);
1411     invalidateRect +=
1412         TRectD(m_brushPos - thickOffset, m_brushPos + thickOffset);
1413   } else if (m_rasterTrack &&
1414              (m_hardness.getValue() == 100 || m_pencil.getValue())) {
1415     /*-- Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる
1416      * --*/
1417     if (!m_pencil.getValue()) thickness -= 1.0;
1418 
1419     TThickPoint thickPoint(centeredPos + rasCenter, thickness);
1420     std::vector<TThickPoint> pts;
1421     if (m_smooth.getValue() == 0) {
1422       pts.push_back(thickPoint);
1423     } else {
1424       m_smoothStroke.addPoint(thickPoint);
1425       m_smoothStroke.getSmoothPoints(pts);
1426     }
1427     for (size_t i = 0; i < pts.size(); ++i) {
1428       const TThickPoint &thickPoint = pts[i];
1429       m_rasterTrack->add(thickPoint);
1430       m_tileSaver->save(m_rasterTrack->getLastRect());
1431       m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue());
1432       std::vector<TThickPoint> brushPoints = m_rasterTrack->getPointsSequence();
1433       int m                                = (int)brushPoints.size();
1434       std::vector<TThickPoint> points;
1435       if (m == 3) {
1436         points.push_back(brushPoints[0]);
1437         points.push_back(brushPoints[1]);
1438       } else {
1439         points.push_back(brushPoints[m - 4]);
1440         points.push_back(brushPoints[m - 3]);
1441         points.push_back(brushPoints[m - 2]);
1442       }
1443       invalidateRect += ToolUtils::getBounds(points, maxThickness) - rasCenter;
1444     }
1445   } else {
1446     // antialiased brush
1447     assert(m_workRas.getPointer() && m_backupRas.getPointer());
1448     TThickPoint thickPoint(centeredPos + rasCenter, thickness);
1449     std::vector<TThickPoint> pts;
1450     if (m_smooth.getValue() == 0) {
1451       pts.push_back(thickPoint);
1452     } else {
1453       m_smoothStroke.addPoint(thickPoint);
1454       m_smoothStroke.getSmoothPoints(pts);
1455     }
1456     for (size_t i = 0; i < pts.size(); ++i) {
1457       TThickPoint old = m_points.back();
1458 
1459       const TThickPoint &point = pts[i];
1460       TThickPoint mid((old + point) * 0.5, (point.thick + old.thick) * 0.5);
1461       m_points.push_back(mid);
1462       m_points.push_back(point);
1463 
1464       TRect bbox;
1465       int m = (int)m_points.size();
1466       std::vector<TThickPoint> points;
1467       if (m == 3) {
1468         // ho appena cominciato. devo disegnare un segmento
1469         TThickPoint pa = m_points.front();
1470         points.push_back(pa);
1471         points.push_back(mid);
1472         bbox = m_bluredBrush->getBoundFromPoints(points);
1473         updateWorkAndBackupRasters(bbox + m_lastRect);
1474         m_tileSaver->save(bbox);
1475         m_bluredBrush->addArc(pa, (mid + pa) * 0.5, mid, 1, 1);
1476         m_lastRect += bbox;
1477       } else {
1478         points.push_back(m_points[m - 4]);
1479         points.push_back(old);
1480         points.push_back(mid);
1481         bbox = m_bluredBrush->getBoundFromPoints(points);
1482         updateWorkAndBackupRasters(bbox + m_lastRect);
1483         m_tileSaver->save(bbox);
1484         m_bluredBrush->addArc(m_points[m - 4], old, mid, 1, 1);
1485         m_lastRect += bbox;
1486       }
1487       invalidateRect += ToolUtils::getBounds(points, maxThickness) - rasCenter;
1488 
1489       m_bluredBrush->updateDrawing(ti->getRaster(), m_backupRas, bbox,
1490                                    m_styleId, m_drawOrder.getIndex());
1491       m_strokeRect += bbox;
1492     }
1493   }
1494 
1495   // clear & draw brush tip when drawing smooth stroke
1496   if (m_smooth.getValue() != 0) {
1497     TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
1498     invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
1499     invalidateRect += TRectD(centeredPos - halfThick, centeredPos + halfThick);
1500   }
1501 
1502   m_mousePos = pos;
1503   m_brushPos = getCenteredCursorPos(pos);
1504 
1505   invalidate(invalidateRect.enlarge(2));
1506 }
1507 
1508 //---------------------------------------------------------------------------------------------------------------
1509 
leftButtonUp(const TPointD & pos,const TMouseEvent & e)1510 void ToonzRasterBrushTool::leftButtonUp(const TPointD &pos,
1511                                         const TMouseEvent &e) {
1512   bool isValid = m_enabled && m_active;
1513   m_enabled    = false;
1514   m_active     = false;
1515   if (!isValid) {
1516     return;
1517   }
1518   TPointD centeredPos = getCenteredCursorPos(pos);
1519   double pressure = m_pressure.getValue() && e.isTablet() ? e.m_pressure : 0.5;
1520   finishRasterBrush(centeredPos, pressure);
1521   int tc = ToonzCheck::instance()->getChecks();
1522   if (tc & ToonzCheck::eGap || tc & ToonzCheck::eAutoclose) invalidate();
1523 }
1524 
1525 //---------------------------------------------------------------------------------------------------------------
1526 /*!
1527  * ドラッグ中にツールが切り替わった場合に備え、onDeactivate時とMouseRelease時にと同じ終了処理を行う
1528  */
finishRasterBrush(const TPointD & pos,double pressureVal)1529 void ToonzRasterBrushTool::finishRasterBrush(const TPointD &pos,
1530                                              double pressureVal) {
1531   TToonzImageP ti = TImageP(getImage(true));
1532 
1533   if (!ti) return;
1534 
1535   TPointD rasCenter         = ti->getRaster()->getCenterD();
1536   TTool::Application *app   = TTool::getApplication();
1537   TXshLevel *level          = app->getCurrentLevel()->getLevel();
1538   TXshSimpleLevelP simLevel = level->getSimpleLevel();
1539 
1540   /*--
1541    * 描画中にカレントフレームが変わっても、描画開始時のFidに対してUndoを記録する
1542    * --*/
1543   TFrameId frameId =
1544       m_workingFrameId.isEmptyFrame() ? getCurrentFid() : m_workingFrameId;
1545   if (m_isMyPaintStyleSelected) {
1546     TRasterCM32P ras = ti->getRaster();
1547     TPointD point(pos + rasCenter);
1548     double pressure = m_pressure.getValue() ? pressureVal : 0.5;
1549 
1550     m_strokeSegmentRect.empty();
1551     m_toonz_brush->strokeTo(point, pressure, restartBrushTimer());
1552     m_toonz_brush->endStroke();
1553     TRect updateRect = m_strokeSegmentRect * ras->getBounds();
1554     if (!updateRect.isEmpty()) {
1555       // ras->extract(updateRect)->copy(m_workRaster->extract(updateRect));
1556       m_toonz_brush->updateDrawing(ras, m_backupRas, m_strokeSegmentRect,
1557                                    m_styleId);
1558     }
1559     TPointD thickOffset(m_maxCursorThick * 0.5,
1560                         m_maxCursorThick * 0.5);  // TODO
1561     TRectD invalidateRect = convert(m_strokeSegmentRect) - rasCenter;
1562     invalidateRect += TRectD(pos - thickOffset, pos + thickOffset);
1563     invalidate(invalidateRect.enlarge(2.0));
1564 
1565     if (m_toonz_brush) {
1566       delete m_toonz_brush;
1567       m_toonz_brush = 0;
1568     }
1569 
1570     m_lastRect.empty();
1571     m_workRas->unlock();
1572 
1573     if (m_tileSet->getTileCount() > 0) {
1574       TRasterCM32P subras = ras->extract(m_strokeRect)->clone();
1575       TUndoManager::manager()->add(new MyPaintBrushUndo(
1576           m_tileSet, simLevel.getPointer(), frameId, m_isFrameCreated,
1577           m_isLevelCreated, subras, m_strokeRect.getP00()));
1578     }
1579 
1580   } else if (m_rasterTrack &&
1581              (m_hardness.getValue() == 100 || m_pencil.getValue())) {
1582     double thickness = m_pressure.getValue()
1583                            ? computeThickness(pressureVal, m_rasThickness)
1584                            : m_rasThickness.getValue().second;
1585 
1586     /*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/
1587     if (m_pressure.getValue() && pressureVal == 1.0)
1588       thickness = m_rasThickness.getValue().first;
1589 
1590     /*-- Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる --*/
1591     if (!m_pencil.getValue()) thickness -= 1.0;
1592 
1593     TRectD invalidateRect;
1594     TThickPoint thickPoint(pos + rasCenter, thickness);
1595     std::vector<TThickPoint> pts;
1596     if (m_smooth.getValue() == 0) {
1597       pts.push_back(thickPoint);
1598     } else {
1599       m_smoothStroke.addPoint(thickPoint);
1600       m_smoothStroke.endStroke();
1601       m_smoothStroke.getSmoothPoints(pts);
1602     }
1603     for (size_t i = 0; i < pts.size(); ++i) {
1604       const TThickPoint &thickPoint = pts[i];
1605       m_rasterTrack->add(thickPoint);
1606       m_tileSaver->save(m_rasterTrack->getLastRect());
1607       m_rasterTrack->generateLastPieceOfStroke(m_pencil.getValue(), true);
1608 
1609       std::vector<TThickPoint> brushPoints = m_rasterTrack->getPointsSequence();
1610       int m                                = (int)brushPoints.size();
1611       std::vector<TThickPoint> points;
1612       if (m == 3) {
1613         points.push_back(brushPoints[0]);
1614         points.push_back(brushPoints[1]);
1615       } else {
1616         points.push_back(brushPoints[m - 4]);
1617         points.push_back(brushPoints[m - 3]);
1618         points.push_back(brushPoints[m - 2]);
1619       }
1620       int maxThickness = m_rasThickness.getValue().second;
1621       invalidateRect += ToolUtils::getBounds(points, maxThickness) - rasCenter;
1622     }
1623     invalidate(invalidateRect.enlarge(2));
1624 
1625     if (m_tileSet->getTileCount() > 0) {
1626       TUndoManager::manager()->add(new RasterBrushUndo(
1627           m_tileSet, m_rasterTrack->getPointsSequence(),
1628           m_rasterTrack->getStyleId(), m_rasterTrack->isSelective(),
1629           simLevel.getPointer(), frameId, m_pencil.getValue(), m_isFrameCreated,
1630           m_isLevelCreated, m_rasterTrack->isPaletteOrder()));
1631     }
1632     delete m_rasterTrack;
1633     m_rasterTrack = 0;
1634   } else {
1635     double maxThickness = m_rasThickness.getValue().second;
1636     double thickness    = (m_pressure.getValue())
1637                            ? computeThickness(pressureVal, m_rasThickness)
1638                            : maxThickness;
1639     TPointD rasCenter = ti->getRaster()->getCenterD();
1640     TRectD invalidateRect;
1641     TThickPoint thickPoint(pos + rasCenter, thickness);
1642     std::vector<TThickPoint> pts;
1643     if (m_smooth.getValue() == 0) {
1644       pts.push_back(thickPoint);
1645     } else {
1646       m_smoothStroke.addPoint(thickPoint);
1647       m_smoothStroke.endStroke();
1648       m_smoothStroke.getSmoothPoints(pts);
1649     }
1650     // we need to skip the for-loop here if pts.size() == 0 or else
1651     // (pts.size() - 1) becomes ULLONG_MAX since size_t is unsigned
1652     if (pts.size() > 0) {
1653       for (size_t i = 0; i < pts.size() - 1; ++i) {
1654         TThickPoint old = m_points.back();
1655 
1656         const TThickPoint &point = pts[i];
1657         TThickPoint mid((old + point) * 0.5, (point.thick + old.thick) * 0.5);
1658         m_points.push_back(mid);
1659         m_points.push_back(point);
1660 
1661         TRect bbox;
1662         int m = (int)m_points.size();
1663         std::vector<TThickPoint> points;
1664         if (m == 3) {
1665           // ho appena cominciato. devo disegnare un segmento
1666           TThickPoint pa = m_points.front();
1667           points.push_back(pa);
1668           points.push_back(mid);
1669           bbox = m_bluredBrush->getBoundFromPoints(points);
1670           updateWorkAndBackupRasters(bbox + m_lastRect);
1671           m_tileSaver->save(bbox);
1672           m_bluredBrush->addArc(pa, (mid + pa) * 0.5, mid, 1, 1);
1673           m_lastRect += bbox;
1674         } else {
1675           points.push_back(m_points[m - 4]);
1676           points.push_back(old);
1677           points.push_back(mid);
1678           bbox = m_bluredBrush->getBoundFromPoints(points);
1679           updateWorkAndBackupRasters(bbox + m_lastRect);
1680           m_tileSaver->save(bbox);
1681           m_bluredBrush->addArc(m_points[m - 4], old, mid, 1, 1);
1682           m_lastRect += bbox;
1683         }
1684 
1685         invalidateRect +=
1686             ToolUtils::getBounds(points, maxThickness) - rasCenter;
1687 
1688         m_bluredBrush->updateDrawing(ti->getRaster(), m_backupRas, bbox,
1689                                      m_styleId, m_drawOrder.getIndex());
1690         m_strokeRect += bbox;
1691       }
1692       TThickPoint point = pts.back();
1693       m_points.push_back(point);
1694       int m = m_points.size();
1695       std::vector<TThickPoint> points;
1696       points.push_back(m_points[m - 3]);
1697       points.push_back(m_points[m - 2]);
1698       points.push_back(m_points[m - 1]);
1699       TRect bbox = m_bluredBrush->getBoundFromPoints(points);
1700       updateWorkAndBackupRasters(bbox);
1701       m_tileSaver->save(bbox);
1702       m_bluredBrush->addArc(points[0], points[1], points[2], 1, 1);
1703       m_bluredBrush->updateDrawing(ti->getRaster(), m_backupRas, bbox,
1704                                    m_styleId, m_drawOrder.getIndex());
1705 
1706       invalidateRect += ToolUtils::getBounds(points, maxThickness) - rasCenter;
1707 
1708       m_lastRect += bbox;
1709       m_strokeRect += bbox;
1710     }
1711     if (!invalidateRect.isEmpty()) invalidate(invalidateRect.enlarge(2));
1712     m_lastRect.empty();
1713 
1714     delete m_bluredBrush;
1715     m_bluredBrush = 0;
1716 
1717     if (m_tileSet->getTileCount() > 0) {
1718       TUndoManager::manager()->add(new RasterBluredBrushUndo(
1719           m_tileSet, m_points, m_styleId, (DrawOrder)m_drawOrder.getIndex(),
1720           simLevel.getPointer(), frameId, m_rasThickness.getValue().second,
1721           m_hardness.getValue() * 0.01, m_isFrameCreated, m_isLevelCreated));
1722     }
1723   }
1724   delete m_tileSaver;
1725 
1726   m_tileSaver = 0;
1727 
1728   /*-- FIdを指定して、描画中にフレームが動いても、
1729     描画開始時のFidのサムネイルが更新されるようにする。--*/
1730   notifyImageChanged(frameId);
1731 
1732   m_strokeRect.empty();
1733 
1734   ToolUtils::updateSaveBox();
1735 
1736   /*-- 作業中のフレームをリセット --*/
1737   m_workingFrameId = TFrameId();
1738 }
1739 //---------------------------------------------------------------------------------------------------------------
1740 // 明日はここをMyPaintのときにカーソルを消せるように修正する!!!!!!
mouseMove(const TPointD & pos,const TMouseEvent & e)1741 void ToonzRasterBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
1742   qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
1743 
1744   struct Locals {
1745     ToonzRasterBrushTool *m_this;
1746 
1747     void setValue(TDoublePairProperty &prop,
1748                   const TDoublePairProperty::Value &value) {
1749       prop.setValue(value);
1750 
1751       m_this->onPropertyChanged(prop.getName());
1752       TTool::getApplication()->getCurrentTool()->notifyToolChanged();
1753     }
1754 
1755     void addMinMax(TDoublePairProperty &prop, double add) {
1756       if (add == 0.0) return;
1757       const TDoublePairProperty::Range &range = prop.getRange();
1758 
1759       TDoublePairProperty::Value value = prop.getValue();
1760       value.first  = tcrop(value.first + add, range.first, range.second);
1761       value.second = tcrop(value.second + add, range.first, range.second);
1762 
1763       setValue(prop, value);
1764     }
1765 
1766     void addMinMaxSeparate(TDoublePairProperty &prop, double min, double max) {
1767       if (min == 0.0 && max == 0.0) return;
1768       const TDoublePairProperty::Range &range = prop.getRange();
1769 
1770       TDoublePairProperty::Value value = prop.getValue();
1771       value.first += min;
1772       value.second += max;
1773       if (value.first > value.second) value.first = value.second;
1774       value.first  = tcrop(value.first, range.first, range.second);
1775       value.second = tcrop(value.second, range.first, range.second);
1776 
1777       setValue(prop, value);
1778     }
1779 
1780   } locals = {this};
1781 
1782   // if (e.isAltPressed() && !e.isCtrlPressed()) {
1783   // const TPointD &diff = pos - m_mousePos;
1784   // double add = (fabs(diff.x) > fabs(diff.y)) ? diff.x : diff.y;
1785 
1786   // locals.addMinMax(
1787   //  TToonzImageP(getImage(false, 1)) ? m_rasThickness : m_thickness, add);
1788   //} else
1789 
1790   double thickness =
1791       (m_isMyPaintStyleSelected) ? (double)(m_maxCursorThick + 1) : m_maxThick;
1792   TPointD halfThick(thickness * 0.5, thickness * 0.5);
1793   TRectD invalidateRect(m_brushPos - halfThick, m_brushPos + halfThick);
1794 
1795   if (e.isCtrlPressed() && e.isAltPressed() && !e.isShiftPressed() &&
1796       Preferences::instance()->useCtrlAltToResizeBrushEnabled()) {
1797     // Resize the brush if CTRL+ALT is pressed and the preference is enabled.
1798     const TPointD &diff = pos - m_mousePos;
1799     double max          = diff.x / 2;
1800     double min          = diff.y / 2;
1801 
1802     locals.addMinMaxSeparate(m_rasThickness, min, max);
1803 
1804     double radius = m_rasThickness.getValue().second * 0.5;
1805     invalidateRect += TRectD(m_brushPos - TPointD(radius, radius),
1806                              m_brushPos + TPointD(radius, radius));
1807 
1808   } else {
1809     m_mousePos = pos;
1810     m_brushPos = getCenteredCursorPos(pos);
1811 
1812     invalidateRect += TRectD(pos - halfThick, pos + halfThick);
1813   }
1814 
1815   invalidate(invalidateRect.enlarge(2));
1816 
1817   if (m_minThick == 0 && m_maxThick == 0) {
1818     m_minThick = m_rasThickness.getValue().first;
1819     m_maxThick = m_rasThickness.getValue().second;
1820   }
1821 }
1822 
1823 //-------------------------------------------------------------------------------------------------------------
1824 
draw()1825 void ToonzRasterBrushTool::draw() {
1826   /*--ショートカットでのツール切り替え時に赤点が描かれるのを防止する--*/
1827   if (m_minThick == 0 && m_maxThick == 0 &&
1828       !Preferences::instance()->getShow0ThickLines())
1829     return;
1830 
1831   TImageP img = getImage(false, 1);
1832 
1833   if (getApplication()->getCurrentObject()->isSpline()) return;
1834 
1835   // If toggled off, don't draw brush outline
1836   if (!Preferences::instance()->isCursorOutlineEnabled()) return;
1837 
1838   // Draw the brush outline - change color when the Ink / Paint check is
1839   // activated
1840   if ((ToonzCheck::instance()->getChecks() & ToonzCheck::eInk) ||
1841       (ToonzCheck::instance()->getChecks() & ToonzCheck::ePaint) ||
1842       (ToonzCheck::instance()->getChecks() & ToonzCheck::eInk1))
1843     glColor3d(0.5, 0.8, 0.8);
1844   // normally draw in red
1845   else
1846     glColor3d(1.0, 0.0, 0.0);
1847 
1848   if (m_isMyPaintStyleSelected) {
1849     tglDrawCircle(m_brushPos, (m_minCursorThick + 1) * 0.5);
1850     tglDrawCircle(m_brushPos, (m_maxCursorThick + 1) * 0.5);
1851     return;
1852   }
1853 
1854   if (TToonzImageP ti = img) {
1855     TRasterP ras = ti->getRaster();
1856     int lx       = ras->getLx();
1857     int ly       = ras->getLy();
1858     drawEmptyCircle(m_brushPos, tround(m_minThick), lx % 2 == 0, ly % 2 == 0,
1859                     m_pencil.getValue());
1860     drawEmptyCircle(m_brushPos, tround(m_maxThick), lx % 2 == 0, ly % 2 == 0,
1861                     m_pencil.getValue());
1862   } else {
1863     drawEmptyCircle(m_brushPos, tround(m_minThick), true, true,
1864                     m_pencil.getValue());
1865     drawEmptyCircle(m_brushPos, tround(m_maxThick), true, true,
1866                     m_pencil.getValue());
1867   }
1868 }
1869 
1870 //--------------------------------------------------------------------------------------------------------------
1871 
onEnter()1872 void ToonzRasterBrushTool::onEnter() {
1873   TImageP img = getImage(false);
1874 
1875   m_minThick = m_rasThickness.getValue().first;
1876   m_maxThick = m_rasThickness.getValue().second;
1877   updateCurrentStyle();
1878 
1879   Application *app = getApplication();
1880 
1881   m_styleId       = app->getCurrentLevelStyleIndex();
1882   TColorStyle *cs = app->getCurrentLevelStyle();
1883   if (cs) {
1884     TRasterStyleFx *rfx = cs->getRasterStyleFx();
1885     m_active            = cs->isStrokeStyle() || (rfx && rfx->isInkStyle());
1886     m_currentColor      = cs->getAverageColor();
1887     m_currentColor.m    = 255;
1888   } else {
1889     m_currentColor = TPixel32::Black;
1890   }
1891   m_active = img;
1892 }
1893 
1894 //----------------------------------------------------------------------------------------------------------
1895 
onLeave()1896 void ToonzRasterBrushTool::onLeave() {
1897   m_minThick       = 0;
1898   m_maxThick       = 0;
1899   m_minCursorThick = 0;
1900   m_maxCursorThick = 0;
1901 }
1902 
1903 //----------------------------------------------------------------------------------------------------------
1904 
getProperties(int idx)1905 TPropertyGroup *ToonzRasterBrushTool::getProperties(int idx) {
1906   if (!m_presetsLoaded) initPresets();
1907 
1908   return &m_prop[idx];
1909 }
1910 
1911 //----------------------------------------------------------------------------------------------------------
1912 
onImageChanged()1913 void ToonzRasterBrushTool::onImageChanged() {
1914   if (!isEnabled()) return;
1915 
1916   setWorkAndBackupImages();
1917 }
1918 
1919 //----------------------------------------------------------------------------------------------------------
1920 
setWorkAndBackupImages()1921 void ToonzRasterBrushTool::setWorkAndBackupImages() {
1922   TToonzImageP ti = (TToonzImageP)getImage(false, 1);
1923   if (!ti) return;
1924   TRasterP ras   = ti->getRaster();
1925   TDimension dim = ras->getSize();
1926 
1927   double hardness = m_hardness.getValue() * 0.01;
1928   if (!m_isMyPaintStyleSelected && hardness == 1.0 &&
1929       ras->getPixelSize() == 4) {
1930     m_workRas   = TRaster32P();
1931     m_backupRas = TRasterCM32P();
1932   } else {
1933     if (!m_workRas || m_workRas->getLx() > dim.lx ||
1934         m_workRas->getLy() > dim.ly)
1935       m_workRas = TRaster32P(dim);
1936     if (!m_backupRas || m_backupRas->getLx() > dim.lx ||
1937         m_backupRas->getLy() > dim.ly)
1938       m_backupRas = TRasterCM32P(dim);
1939 
1940     m_strokeRect.empty();
1941     m_lastRect.empty();
1942   }
1943 }
1944 
1945 //------------------------------------------------------------------
1946 
onPropertyChanged(std::string propertyName)1947 bool ToonzRasterBrushTool::onPropertyChanged(std::string propertyName) {
1948   if (m_propertyUpdating) return true;
1949 
1950   if (propertyName == m_preset.getName()) {
1951     if (m_preset.getValue() != CUSTOM_WSTR)
1952       loadPreset();
1953     else  // Chose <custom>, go back to last saved brush settings
1954       loadLastBrush();
1955 
1956     RasterBrushPreset  = m_preset.getValueAsString();
1957     m_propertyUpdating = true;
1958     getApplication()->getCurrentTool()->notifyToolChanged();
1959     m_propertyUpdating = false;
1960     return true;
1961   }
1962 
1963   RasterBrushMinSize       = m_rasThickness.getValue().first;
1964   RasterBrushMaxSize       = m_rasThickness.getValue().second;
1965   BrushSmooth              = m_smooth.getValue();
1966   BrushDrawOrder           = m_drawOrder.getIndex();
1967   RasterBrushPencilMode    = m_pencil.getValue();
1968   BrushPressureSensitivity = m_pressure.getValue();
1969   RasterBrushHardness      = m_hardness.getValue();
1970   RasterBrushModifierSize  = m_modifierSize.getValue();
1971 
1972   // Recalculate/reset based on changed settings
1973   if (propertyName == m_rasThickness.getName()) {
1974     m_minThick = m_rasThickness.getValue().first;
1975     m_maxThick = m_rasThickness.getValue().second;
1976   }
1977 
1978   if (propertyName == m_hardness.getName()) setWorkAndBackupImages();
1979 
1980   if (propertyName == m_hardness.getName() ||
1981       propertyName == m_rasThickness.getName()) {
1982     m_brushPad = getBrushPad(m_rasThickness.getValue().second,
1983                              m_hardness.getValue() * 0.01);
1984     TRectD rect(m_mousePos - TPointD(m_maxThick + 2, m_maxThick + 2),
1985                 m_mousePos + TPointD(m_maxThick + 2, m_maxThick + 2));
1986     invalidate(rect);
1987   }
1988 
1989   if (m_preset.getValue() != CUSTOM_WSTR) {
1990     m_preset.setValue(CUSTOM_WSTR);
1991     RasterBrushPreset  = m_preset.getValueAsString();
1992     m_propertyUpdating = true;
1993     getApplication()->getCurrentTool()->notifyToolChanged();
1994     m_propertyUpdating = false;
1995   }
1996 
1997   return true;
1998 }
1999 
2000 //------------------------------------------------------------------
2001 
initPresets()2002 void ToonzRasterBrushTool::initPresets() {
2003   if (!m_presetsLoaded) {
2004     // If necessary, load the presets from file
2005     m_presetsLoaded = true;
2006     m_presetsManager.load(TEnv::getConfigDir() + "brush_toonzraster.txt");
2007   }
2008 
2009   // Rebuild the presets property entries
2010   const std::set<BrushData> &presets = m_presetsManager.presets();
2011 
2012   m_preset.deleteAllValues();
2013   m_preset.addValue(CUSTOM_WSTR);
2014   m_preset.setItemUIName(CUSTOM_WSTR, tr("<custom>"));
2015 
2016   std::set<BrushData>::const_iterator it, end = presets.end();
2017   for (it = presets.begin(); it != end; ++it) m_preset.addValue(it->m_name);
2018 }
2019 
2020 //----------------------------------------------------------------------------------------------------------
2021 
loadPreset()2022 void ToonzRasterBrushTool::loadPreset() {
2023   const std::set<BrushData> &presets = m_presetsManager.presets();
2024   std::set<BrushData>::const_iterator it;
2025 
2026   it = presets.find(BrushData(m_preset.getValue()));
2027   if (it == presets.end()) return;
2028 
2029   const BrushData &preset = *it;
2030 
2031   try  // Don't bother with RangeErrors
2032   {
2033     m_rasThickness.setValue(
2034         TDoublePairProperty::Value(std::max(preset.m_min, 1.0), preset.m_max));
2035     m_hardness.setValue(preset.m_hardness, true);
2036     m_smooth.setValue(preset.m_smooth, true);
2037     m_drawOrder.setIndex(preset.m_drawOrder);
2038     m_pencil.setValue(preset.m_pencil);
2039     m_pressure.setValue(preset.m_pressure);
2040     m_modifierSize.setValue(preset.m_modifierSize);
2041 
2042     // Recalculate based on updated presets
2043     m_minThick = m_rasThickness.getValue().first;
2044     m_maxThick = m_rasThickness.getValue().second;
2045 
2046     setWorkAndBackupImages();
2047 
2048     m_brushPad = ToolUtils::getBrushPad(preset.m_max, preset.m_hardness * 0.01);
2049   } catch (...) {
2050   }
2051 }
2052 
2053 //------------------------------------------------------------------
2054 
addPreset(QString name)2055 void ToonzRasterBrushTool::addPreset(QString name) {
2056   // Build the preset
2057   BrushData preset(name.toStdWString());
2058 
2059   preset.m_min = m_rasThickness.getValue().first;
2060   preset.m_max = m_rasThickness.getValue().second;
2061 
2062   preset.m_smooth       = m_smooth.getValue();
2063   preset.m_hardness     = m_hardness.getValue();
2064   preset.m_drawOrder    = m_drawOrder.getIndex();
2065   preset.m_pencil       = m_pencil.getValue();
2066   preset.m_pressure     = m_pressure.getValue();
2067   preset.m_modifierSize = m_modifierSize.getValue();
2068 
2069   // Pass the preset to the manager
2070   m_presetsManager.addPreset(preset);
2071 
2072   // Reinitialize the associated preset enum
2073   initPresets();
2074 
2075   // Set the value to the specified one
2076   m_preset.setValue(preset.m_name);
2077   RasterBrushPreset = m_preset.getValueAsString();
2078 }
2079 
2080 //------------------------------------------------------------------
2081 
removePreset()2082 void ToonzRasterBrushTool::removePreset() {
2083   std::wstring name(m_preset.getValue());
2084   if (name == CUSTOM_WSTR) return;
2085 
2086   m_presetsManager.removePreset(name);
2087   initPresets();
2088 
2089   // No parameter change, and set the preset value to custom
2090   m_preset.setValue(CUSTOM_WSTR);
2091   RasterBrushPreset = m_preset.getValueAsString();
2092 }
2093 
2094 //------------------------------------------------------------------
2095 
loadLastBrush()2096 void ToonzRasterBrushTool::loadLastBrush() {
2097   m_rasThickness.setValue(
2098       TDoublePairProperty::Value(RasterBrushMinSize, RasterBrushMaxSize));
2099   m_drawOrder.setIndex(BrushDrawOrder);
2100   m_pencil.setValue(RasterBrushPencilMode ? 1 : 0);
2101   m_hardness.setValue(RasterBrushHardness);
2102   m_pressure.setValue(BrushPressureSensitivity ? 1 : 0);
2103   m_smooth.setValue(BrushSmooth);
2104   m_modifierSize.setValue(RasterBrushModifierSize);
2105 
2106   // Recalculate based on prior values
2107   m_minThick = m_rasThickness.getValue().first;
2108   m_maxThick = m_rasThickness.getValue().second;
2109 
2110   setWorkAndBackupImages();
2111 
2112   m_brushPad = getBrushPad(m_rasThickness.getValue().second,
2113                            m_hardness.getValue() * 0.01);
2114 }
2115 
2116 //------------------------------------------------------------------
2117 /*!	Brush、PaintBrush、EraserToolがPencilModeのときにTrueを返す
2118  */
isPencilModeActive()2119 bool ToonzRasterBrushTool::isPencilModeActive() {
2120   return getTargetType() == TTool::ToonzImage && m_pencil.getValue();
2121 }
2122 
2123 //------------------------------------------------------------------
2124 
onColorStyleChanged()2125 void ToonzRasterBrushTool::onColorStyleChanged() {
2126   // in case the style switched while drawing
2127   if (m_tileSaver) {
2128     bool isValid = m_enabled && m_active;
2129     m_enabled    = false;
2130     if (isValid) {
2131       finishRasterBrush(m_mousePos, 1);
2132     }
2133   }
2134 
2135   TTool::Application *app = getApplication();
2136   TMyPaintBrushStyle *mpbs =
2137       dynamic_cast<TMyPaintBrushStyle *>(app->getCurrentLevelStyle());
2138   m_isMyPaintStyleSelected = (mpbs) ? true : false;
2139   setWorkAndBackupImages();
2140   getApplication()->getCurrentTool()->notifyToolChanged();
2141 }
2142 
2143 //------------------------------------------------------------------
2144 
restartBrushTimer()2145 double ToonzRasterBrushTool::restartBrushTimer() {
2146   double dtime = m_brushTimer.nsecsElapsed() * 1e-9;
2147   m_brushTimer.restart();
2148   return dtime;
2149 }
2150 
2151 //------------------------------------------------------------------
2152 
updateCurrentStyle()2153 void ToonzRasterBrushTool::updateCurrentStyle() {
2154   if (m_isMyPaintStyleSelected) {
2155     TTool::Application *app = TTool::getApplication();
2156     TMyPaintBrushStyle *brushStyle =
2157         dynamic_cast<TMyPaintBrushStyle *>(app->getCurrentLevelStyle());
2158     if (!brushStyle) {
2159       // brush changed to normal abnormally. Complete color style change.
2160       onColorStyleChanged();
2161       return;
2162     }
2163     double radiusLog = brushStyle->getBrush().getBaseValue(
2164                            MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC) +
2165                        m_modifierSize.getValue() * log(2.0);
2166     double radius    = exp(radiusLog);
2167     m_minCursorThick = m_maxCursorThick = (int)std::round(2.0 * radius);
2168   }
2169 }
2170 //==========================================================================================================
2171 
ToonzRasterBrushToolNotifier(ToonzRasterBrushTool * tool)2172 ToonzRasterBrushToolNotifier::ToonzRasterBrushToolNotifier(
2173     ToonzRasterBrushTool *tool)
2174     : m_tool(tool) {
2175   if (TTool::Application *app = m_tool->getApplication()) {
2176     if (TPaletteHandle *paletteHandle = app->getCurrentPalette()) {
2177       bool ret;
2178       ret = connect(paletteHandle, SIGNAL(colorStyleChanged(bool)), this,
2179                     SLOT(onColorStyleChanged()));
2180       ret = ret && connect(paletteHandle, SIGNAL(colorStyleSwitched()), this,
2181                            SLOT(onColorStyleChanged()));
2182       ret = ret && connect(paletteHandle, SIGNAL(paletteSwitched()), this,
2183                            SLOT(onColorStyleChanged()));
2184       assert(ret);
2185     }
2186   }
2187   onColorStyleChanged();
2188 }
2189 
2190 //==========================================================================================================
2191 
2192 // Tools instantiation
2193 
2194 ToonzRasterBrushTool toonzPencil("T_Brush",
2195                                  TTool::ToonzImage | TTool::EmptyTarget);
2196 
2197 //*******************************************************************************
2198 //    Brush Data implementation
2199 //*******************************************************************************
2200 
BrushData()2201 BrushData::BrushData()
2202     : m_name()
2203     , m_min(0.0)
2204     , m_max(0.0)
2205     , m_smooth(0.0)
2206     , m_hardness(0.0)
2207     , m_opacityMin(0.0)
2208     , m_opacityMax(0.0)
2209     , m_drawOrder(0)
2210     , m_pencil(false)
2211     , m_pressure(false)
2212     , m_modifierSize(0.0)
2213     , m_modifierOpacity(0.0)
2214     , m_modifierEraser(0.0)
2215     , m_modifierLockAlpha(0.0) {}
2216 
2217 //----------------------------------------------------------------------------------------------------------
2218 
BrushData(const std::wstring & name)2219 BrushData::BrushData(const std::wstring &name)
2220     : m_name(name)
2221     , m_min(0.0)
2222     , m_max(0.0)
2223     , m_smooth(0.0)
2224     , m_hardness(0.0)
2225     , m_opacityMin(0.0)
2226     , m_opacityMax(0.0)
2227     , m_drawOrder(0)
2228     , m_pencil(false)
2229     , m_pressure(false)
2230     , m_modifierSize(0.0)
2231     , m_modifierOpacity(0.0)
2232     , m_modifierEraser(0.0)
2233     , m_modifierLockAlpha(0.0) {}
2234 
2235 //----------------------------------------------------------------------------------------------------------
2236 
saveData(TOStream & os)2237 void BrushData::saveData(TOStream &os) {
2238   os.openChild("Name");
2239   os << m_name;
2240   os.closeChild();
2241   os.openChild("Thickness");
2242   os << m_min << m_max;
2243   os.closeChild();
2244   os.openChild("Smooth");
2245   os << m_smooth;
2246   os.closeChild();
2247   os.openChild("Hardness");
2248   os << m_hardness;
2249   os.closeChild();
2250   os.openChild("Opacity");
2251   os << m_opacityMin << m_opacityMax;
2252   os.closeChild();
2253   os.openChild("Draw_Order");
2254   os << m_drawOrder;
2255   os.closeChild();
2256   os.openChild("Pencil");
2257   os << (int)m_pencil;
2258   os.closeChild();
2259   os.openChild("Pressure_Sensitivity");
2260   os << (int)m_pressure;
2261   os.closeChild();
2262   os.openChild("Modifier_Size");
2263   os << m_modifierSize;
2264   os.closeChild();
2265   os.openChild("Modifier_Opacity");
2266   os << m_modifierOpacity;
2267   os.closeChild();
2268   os.openChild("Modifier_Eraser");
2269   os << (int)m_modifierEraser;
2270   os.closeChild();
2271   os.openChild("Modifier_LockAlpha");
2272   os << (int)m_modifierLockAlpha;
2273   os.closeChild();
2274 }
2275 
2276 //----------------------------------------------------------------------------------------------------------
2277 
loadData(TIStream & is)2278 void BrushData::loadData(TIStream &is) {
2279   std::string tagName;
2280   int val;
2281 
2282   while (is.matchTag(tagName)) {
2283     if (tagName == "Name")
2284       is >> m_name, is.matchEndTag();
2285     else if (tagName == "Thickness")
2286       is >> m_min >> m_max, is.matchEndTag();
2287     else if (tagName == "Smooth")
2288       is >> m_smooth, is.matchEndTag();
2289     else if (tagName == "Hardness")
2290       is >> m_hardness, is.matchEndTag();
2291     else if (tagName == "Opacity")
2292       is >> m_opacityMin >> m_opacityMax, is.matchEndTag();
2293     else if (tagName == "Selective" ||
2294              tagName == "Draw_Order")  // "Selective" is left to keep backward
2295                                        // compatibility
2296       is >> m_drawOrder, is.matchEndTag();
2297     else if (tagName == "Pencil")
2298       is >> val, m_pencil = val, is.matchEndTag();
2299     else if (tagName == "Pressure_Sensitivity")
2300       is >> val, m_pressure = val, is.matchEndTag();
2301     else if (tagName == "Modifier_Size")
2302       is >> m_modifierSize, is.matchEndTag();
2303     else if (tagName == "Modifier_Opacity")
2304       is >> m_modifierOpacity, is.matchEndTag();
2305     else if (tagName == "Modifier_Eraser")
2306       is >> val, m_modifierEraser = val, is.matchEndTag();
2307     else if (tagName == "Modifier_LockAlpha")
2308       is >> val, m_modifierLockAlpha = val, is.matchEndTag();
2309     else
2310       is.skipCurrentTag();
2311   }
2312 }
2313 
2314 //----------------------------------------------------------------------------------------------------------
2315 
2316 PERSIST_IDENTIFIER(BrushData, "BrushData");
2317 
2318 //*******************************************************************************
2319 //    Brush Preset Manager implementation
2320 //*******************************************************************************
2321 
load(const TFilePath & fp)2322 void BrushPresetManager::load(const TFilePath &fp) {
2323   m_fp = fp;
2324 
2325   std::string tagName;
2326   BrushData data;
2327 
2328   TIStream is(m_fp);
2329   try {
2330     while (is.matchTag(tagName)) {
2331       if (tagName == "version") {
2332         VersionNumber version;
2333         is >> version.first >> version.second;
2334 
2335         is.setVersion(version);
2336         is.matchEndTag();
2337       } else if (tagName == "brushes") {
2338         while (is.matchTag(tagName)) {
2339           if (tagName == "brush") {
2340             is >> data, m_presets.insert(data);
2341             is.matchEndTag();
2342           } else
2343             is.skipCurrentTag();
2344         }
2345 
2346         is.matchEndTag();
2347       } else
2348         is.skipCurrentTag();
2349     }
2350   } catch (...) {
2351   }
2352 }
2353 
2354 //------------------------------------------------------------------
2355 
save()2356 void BrushPresetManager::save() {
2357   TOStream os(m_fp);
2358 
2359   os.openChild("version");
2360   os << 1 << 19;
2361   os.closeChild();
2362 
2363   os.openChild("brushes");
2364 
2365   std::set<BrushData>::iterator it, end = m_presets.end();
2366   for (it = m_presets.begin(); it != end; ++it) {
2367     os.openChild("brush");
2368     os << (TPersist &)*it;
2369     os.closeChild();
2370   }
2371 
2372   os.closeChild();
2373 }
2374 
2375 //------------------------------------------------------------------
2376 
addPreset(const BrushData & data)2377 void BrushPresetManager::addPreset(const BrushData &data) {
2378   m_presets.erase(data);  // Overwriting insertion
2379   m_presets.insert(data);
2380   save();
2381 }
2382 
2383 //------------------------------------------------------------------
2384 
removePreset(const std::wstring & name)2385 void BrushPresetManager::removePreset(const std::wstring &name) {
2386   m_presets.erase(BrushData(name));
2387   save();
2388 }
2389