1 
2 
3 #include "toonzvectorbrushtool.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/tonionskinmaskhandle.h"
32 
33 // TnzCore includes
34 #include "tstream.h"
35 #include "tcolorstyles.h"
36 #include "tvectorimage.h"
37 #include "tenv.h"
38 #include "tregion.h"
39 #include "tinbetween.h"
40 
41 #include "tgl.h"
42 #include "trop.h"
43 
44 // Qt includes
45 #include <QPainter>
46 
47 using namespace ToolUtils;
48 
49 TEnv::DoubleVar V_VectorBrushMinSize("InknpaintVectorBrushMinSize", 1);
50 TEnv::DoubleVar V_VectorBrushMaxSize("InknpaintVectorBrushMaxSize", 5);
51 TEnv::IntVar V_VectorCapStyle("InknpaintVectorCapStyle", 1);
52 TEnv::IntVar V_VectorJoinStyle("InknpaintVectorJoinStyle", 1);
53 TEnv::IntVar V_VectorMiterValue("InknpaintVectorMiterValue", 4);
54 TEnv::DoubleVar V_BrushAccuracy("InknpaintBrushAccuracy", 20);
55 TEnv::DoubleVar V_BrushSmooth("InknpaintBrushSmooth", 0);
56 TEnv::IntVar V_BrushBreakSharpAngles("InknpaintBrushBreakSharpAngles", 0);
57 TEnv::IntVar V_BrushPressureSensitivity("InknpaintBrushPressureSensitivity", 1);
58 TEnv::IntVar V_VectorBrushFrameRange("VectorBrushFrameRange", 0);
59 TEnv::IntVar V_VectorBrushSnap("VectorBrushSnap", 0);
60 TEnv::IntVar V_VectorBrushSnapSensitivity("VectorBrushSnapSensitivity", 0);
61 TEnv::StringVar V_VectorBrushPreset("VectorBrushPreset", "<custom>");
62 
63 //-------------------------------------------------------------------
64 
65 #define ROUNDC_WSTR L"round_cap"
66 #define BUTT_WSTR L"butt_cap"
67 #define PROJECTING_WSTR L"projecting_cap"
68 #define ROUNDJ_WSTR L"round_join"
69 #define BEVEL_WSTR L"bevel_join"
70 #define MITER_WSTR L"miter_join"
71 #define CUSTOM_WSTR L"<custom>"
72 
73 #define LINEAR_WSTR L"Linear"
74 #define EASEIN_WSTR L"In"
75 #define EASEOUT_WSTR L"Out"
76 #define EASEINOUT_WSTR L"In&Out"
77 
78 #define LOW_WSTR L"Low"
79 #define MEDIUM_WSTR L"Med"
80 #define HIGH_WSTR L"High"
81 
82 const double SNAPPING_LOW    = 5.0;
83 const double SNAPPING_MEDIUM = 25.0;
84 const double SNAPPING_HIGH   = 100.0;
85 //-------------------------------------------------------------------
86 //
87 // (Da mettere in libreria) : funzioni che spezzano una stroke
88 // nei suoi punti angolosi. Lo facciamo specialmente per limitare
89 // i problemi di fill.
90 //
91 //-------------------------------------------------------------------
92 
93 //
94 // Split a stroke in n+1 parts, according to n parameter values
95 // Input:
96 //      stroke            = stroke to split
97 //      parameterValues[] = vector of parameters where I want to split the
98 //      stroke
99 //                          assert: 0<a[0]<a[1]<...<a[n-1]<1
100 // Output:
101 //      strokes[]         = the split strokes
102 //
103 // note: stroke is unchanged
104 //
105 
split(TStroke * stroke,const std::vector<double> & parameterValues,std::vector<TStroke * > & strokes)106 static void split(TStroke *stroke, const std::vector<double> &parameterValues,
107                   std::vector<TStroke *> &strokes) {
108   TThickPoint p2;
109   std::vector<TThickPoint> points;
110   TThickPoint lastPoint = stroke->getControlPoint(0);
111   int n                 = parameterValues.size();
112   int chunk;
113   double t;
114   int last_chunk = -1, startPoint = 0;
115   double lastLocT = 0;
116 
117   for (int i = 0; i < n; i++) {
118     points.push_back(lastPoint);  // Add first point of the stroke
119     double w =
120         parameterValues[i];  // Global parameter. along the stroke 0<=w<=1
121     stroke->getChunkAndT(w, chunk,
122                          t);  // t: local parameter in the chunk-th quadratic
123 
124     if (i == 0)
125       startPoint = 1;
126     else {
127       int indexAfterLastT =
128           stroke->getControlPointIndexAfterParameter(parameterValues[i - 1]);
129       startPoint = indexAfterLastT;
130       if ((indexAfterLastT & 1) && lastLocT != 1) startPoint++;
131     }
132     int endPoint = 2 * chunk + 1;
133     if (lastLocT != 1 && i > 0) {
134       if (last_chunk != chunk || t == 1)
135         points.push_back(p2);  // If the last local t is not an extreme
136                                // add the point p2
137     }
138 
139     for (int j = startPoint; j < endPoint; j++)
140       points.push_back(stroke->getControlPoint(j));
141 
142     TThickPoint p, A, B, C;
143     p       = stroke->getPoint(w);
144     C       = stroke->getControlPoint(2 * chunk + 2);
145     B       = stroke->getControlPoint(2 * chunk + 1);
146     A       = stroke->getControlPoint(2 * chunk);
147     p.thick = A.thick;
148 
149     if (last_chunk != chunk) {
150       TThickPoint p1 = (1 - t) * A + t * B;
151       points.push_back(p1);
152       p.thick = p1.thick;
153     } else {
154       if (t != 1) {
155         // If the i-th cut point belong to the same chunk of the (i-1)-th cut
156         // point.
157         double tInters  = lastLocT / t;
158         TThickPoint p11 = (1 - t) * A + t * B;
159         TThickPoint p1  = (1 - tInters) * p11 + tInters * p;
160         points.push_back(p1);
161         p.thick = p1.thick;
162       }
163     }
164 
165     points.push_back(p);
166 
167     if (t != 1) p2 = (1 - t) * B + t * C;
168 
169     assert(points.size() & 1);
170 
171     // Add new stroke
172     TStroke *strokeAdd = new TStroke(points);
173     strokeAdd->setStyle(stroke->getStyle());
174     strokeAdd->outlineOptions() = stroke->outlineOptions();
175     strokes.push_back(strokeAdd);
176 
177     lastPoint  = p;
178     last_chunk = chunk;
179     lastLocT   = t;
180     points.clear();
181   }
182   // Add end stroke
183   points.push_back(lastPoint);
184 
185   if (lastLocT != 1) points.push_back(p2);
186 
187   startPoint =
188       stroke->getControlPointIndexAfterParameter(parameterValues[n - 1]);
189   if ((stroke->getControlPointIndexAfterParameter(parameterValues[n - 1]) &
190        1) &&
191       lastLocT != 1)
192     startPoint++;
193   for (int j = startPoint; j < stroke->getControlPointCount(); j++)
194     points.push_back(stroke->getControlPoint(j));
195 
196   assert(points.size() & 1);
197   TStroke *strokeAdd = new TStroke(points);
198   strokeAdd->setStyle(stroke->getStyle());
199   strokeAdd->outlineOptions() = stroke->outlineOptions();
200   strokes.push_back(strokeAdd);
201   points.clear();
202 }
203 
204 // Compute Parametric Curve Curvature
205 // By Formula:
206 // k(t)=(|p'(t) x p''(t)|)/Norm2(p')^3
207 // p(t) is parametric curve
208 // Input:
209 //      dp  = First Derivate.
210 //      ddp = Second Derivate
211 // Output:
212 //      return curvature value.
213 //      Note: if the curve is a single point (that's dp=0) or it is a straight
214 //      line (that's ddp=0) return 0
215 
curvature(TPointD dp,TPointD ddp)216 static double curvature(TPointD dp, TPointD ddp) {
217   if (dp == TPointD(0, 0))
218     return 0;
219   else
220     return fabs(cross(dp, ddp) / pow(norm2(dp), 1.5));
221 }
222 
223 // Find the max curvature points of a stroke.
224 // Input:
225 //      stroke.
226 //      angoloLim =  Value (radians) of the Corner between two tangent vector.
227 //                   Up this value the two corner can be considered angular.
228 //      curvMaxLim = Value of the max curvature.
229 //                   Up this value the point can be considered a max curvature
230 //                   point.
231 // Output:
232 //      parameterValues = vector of max curvature parameter points
233 
findMaxCurvPoints(TStroke * stroke,const float & angoloLim,const float & curvMaxLim,std::vector<double> & parameterValues)234 static void findMaxCurvPoints(TStroke *stroke, const float &angoloLim,
235                               const float &curvMaxLim,
236                               std::vector<double> &parameterValues) {
237   TPointD tg1, tg2;  // Tangent vectors
238 
239   TPointD dp, ddp;  // First and Second derivate.
240 
241   parameterValues.clear();
242   int cpn = stroke ? stroke->getControlPointCount() : 0;
243   for (int j = 2; j < cpn; j += 2) {
244     TPointD p0 = stroke->getControlPoint(j - 2);
245     TPointD p1 = stroke->getControlPoint(j - 1);
246     TPointD p2 = stroke->getControlPoint(j);
247 
248     TPointD q = p1 - (p0 + p2) * 0.5;
249 
250     // Search corner point
251     if (j > 2) {
252       tg2 = -p0 + p2 + 2 * q;  // Tangent vector to this chunk at t=0
253       double prod_scal =
254           tg2 * tg1;  // Inner product between tangent vectors at t=0.
255       assert(tg1 != TPointD(0, 0) || tg2 != TPointD(0, 0));
256       // Compute corner between two tangent vectors
257       double angolo =
258           acos(prod_scal / (pow(norm2(tg2), 0.5) * pow(norm2(tg1), 0.5)));
259 
260       // Add corner point
261       if (angolo > angoloLim) {
262         double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)),
263                                      0);  //  transform lacal t to global t
264         parameterValues.push_back(w);
265       }
266     }
267     tg1 = -p0 + p2 - 2 * q;  // Tangent vector to this chunk at t=1
268 
269     // End search corner point
270 
271     // Search max curvature point
272     // Value of t where the curvature function has got an extreme.
273     // (Point where first derivate is null)
274     double estremo_int = 0;
275     double t           = -1;
276     if (q != TPointD(0, 0)) {
277       t = 0.25 *
278           (2 * q.x * q.x + 2 * q.y * q.y - q.x * p0.x + q.x * p2.x -
279            q.y * p0.y + q.y * p2.y) /
280           (q.x * q.x + q.y * q.y);
281 
282       dp  = -p0 + p2 + 2 * q - 4 * t * q;  // First derivate of the curve
283       ddp = -4 * q;                        // Second derivate of the curve
284       estremo_int = curvature(dp, ddp);
285 
286       double h    = 0.01;
287       dp          = -p0 + p2 + 2 * q - 4 * (t + h) * q;
288       double c_dx = curvature(dp, ddp);
289       dp          = -p0 + p2 + 2 * q - 4 * (t - h) * q;
290       double c_sx = curvature(dp, ddp);
291       // Check the point is a max and not a minimum
292       if (estremo_int < c_dx && estremo_int < c_sx) {
293         estremo_int = 0;
294       }
295     }
296     double curv_max = estremo_int;
297 
298     // Compute curvature at the extreme of interval [0,1]
299     // Compute curvature at t=0 (Left extreme)
300     dp                = -p0 + p2 + 2 * q;
301     double estremo_sx = curvature(dp, ddp);
302 
303     // Compute curvature at t=1 (Right extreme)
304     dp                = -p0 + p2 - 2 * q;
305     double estremo_dx = curvature(dp, ddp);
306 
307     // Compare curvature at the extreme of interval [0,1] with the internal
308     // value
309     double t_ext;
310     if (estremo_sx >= estremo_dx)
311       t_ext = 0;
312     else
313       t_ext = 1;
314     double maxEstremi = std::max(estremo_dx, estremo_sx);
315     if (maxEstremi > estremo_int) {
316       t        = t_ext;
317       curv_max = maxEstremi;
318     }
319 
320     // Add max curvature point
321     if (t >= 0 && t <= 1 && curv_max > curvMaxLim) {
322       double w = getWfromChunkAndT(stroke, (UINT)(0.5 * (j - 2)),
323                                    t);  // transform local t to global t
324       parameterValues.push_back(w);
325     }
326     // End search max curvature point
327   }
328   // Delete duplicate of parameterValues
329   // Because some max cuvature point can coincide with the corner point
330   if ((int)parameterValues.size() > 1) {
331     std::sort(parameterValues.begin(), parameterValues.end());
332     parameterValues.erase(
333         std::unique(parameterValues.begin(), parameterValues.end()),
334         parameterValues.end());
335   }
336 }
337 
addStroke(TTool::Application * application,const TVectorImageP & vi,TStroke * stroke,bool breakAngles,bool autoGroup,bool autoFill,bool frameCreated,bool levelCreated,TXshSimpleLevel * sLevel=NULL,TFrameId fid=TFrameId::NO_FRAME)338 static void addStroke(TTool::Application *application, const TVectorImageP &vi,
339                       TStroke *stroke, bool breakAngles, bool autoGroup,
340                       bool autoFill, bool frameCreated, bool levelCreated,
341                       TXshSimpleLevel *sLevel = NULL,
342                       TFrameId fid            = TFrameId::NO_FRAME) {
343   QMutexLocker lock(vi->getMutex());
344 
345   if (application->getCurrentObject()->isSpline()) {
346     application->getCurrentXsheet()->notifyXsheetChanged();
347     return;
348   }
349 
350   std::vector<double> corners;
351   std::vector<TStroke *> strokes;
352 
353   const float angoloLim =
354       1;  // Value (radians) of the Corner between two tangent vector.
355           // Up this value the two corner can be considered angular.
356   const float curvMaxLim = 0.8;  // Value of the max curvature.
357   // Up this value the point can be considered a max curvature point.
358 
359   findMaxCurvPoints(stroke, angoloLim, curvMaxLim, corners);
360   TXshSimpleLevel *sl;
361   if (!sLevel) {
362     sl = application->getCurrentLevel()->getSimpleLevel();
363   } else {
364     sl = sLevel;
365   }
366   TFrameId id = application->getCurrentTool()->getTool()->getCurrentFid();
367   if (id == TFrameId::NO_FRAME && fid != TFrameId::NO_FRAME) id = fid;
368   if (!corners.empty()) {
369     if (breakAngles)
370       split(stroke, corners, strokes);
371     else
372       strokes.push_back(new TStroke(*stroke));
373 
374     int n = strokes.size();
375 
376     TUndoManager::manager()->beginBlock();
377     for (int i = 0; i < n; i++) {
378       std::vector<TFilledRegionInf> *fillInformation =
379           new std::vector<TFilledRegionInf>;
380       ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation,
381                                                        stroke->getBBox());
382       TStroke *str = new TStroke(*strokes[i]);
383       vi->addStroke(str);
384       TUndoManager::manager()->add(new UndoPencil(str, fillInformation, sl, id,
385                                                   frameCreated, levelCreated,
386                                                   autoGroup, autoFill));
387     }
388     TUndoManager::manager()->endBlock();
389   } else {
390     std::vector<TFilledRegionInf> *fillInformation =
391         new std::vector<TFilledRegionInf>;
392     ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation,
393                                                      stroke->getBBox());
394     TStroke *str = new TStroke(*stroke);
395     vi->addStroke(str);
396     TUndoManager::manager()->add(new UndoPencil(str, fillInformation, sl, id,
397                                                 frameCreated, levelCreated,
398                                                 autoGroup, autoFill));
399   }
400 
401   if (autoGroup && stroke->isSelfLoop()) {
402     int index = vi->getStrokeCount() - 1;
403     vi->group(index, 1);
404     if (autoFill) {
405       // to avoid filling other strokes, I enter into the new stroke group
406       int currentGroup = vi->exitGroup();
407       vi->enterGroup(index);
408       vi->selectFill(stroke->getBBox().enlarge(1, 1), 0, stroke->getStyle(),
409                      false, true, false);
410       if (currentGroup != -1)
411         vi->enterGroup(currentGroup);
412       else
413         vi->exitGroup();
414     }
415   }
416 
417   // Update regions. It will call roundStroke() in
418   // TVectorImage::Imp::findIntersections().
419   // roundStroke() will slightly modify all the stroke positions.
420   // It is needed to update information for Fill Check.
421   vi->findRegions();
422 
423   for (int k = 0; k < (int)strokes.size(); k++) delete strokes[k];
424   strokes.clear();
425 
426   application->getCurrentTool()->getTool()->notifyImageChanged();
427 }
428 
429 //-------------------------------------------------------------------
430 //
431 // Gennaro: end
432 //
433 //-------------------------------------------------------------------
434 
435 //===================================================================
436 //
437 // Helper functions and classes
438 //
439 //-------------------------------------------------------------------
440 
441 namespace {
442 
443 //-------------------------------------------------------------------
444 
addStrokeToImage(TTool::Application * application,const TVectorImageP & vi,TStroke * stroke,bool breakAngles,bool autoGroup,bool autoFill,bool frameCreated,bool levelCreated,TXshSimpleLevel * sLevel=NULL,TFrameId id=TFrameId::NO_FRAME)445 void addStrokeToImage(TTool::Application *application, const TVectorImageP &vi,
446                       TStroke *stroke, bool breakAngles, bool autoGroup,
447                       bool autoFill, bool frameCreated, bool levelCreated,
448                       TXshSimpleLevel *sLevel = NULL,
449                       TFrameId id             = TFrameId::NO_FRAME) {
450   QMutexLocker lock(vi->getMutex());
451   addStroke(application, vi.getPointer(), stroke, breakAngles, autoGroup,
452             autoFill, frameCreated, levelCreated, sLevel, id);
453   // la notifica viene gia fatta da addStroke!
454   // getApplication()->getCurrentTool()->getTool()->notifyImageChanged();
455 }
456 
457 //---------------------------------------------------------------------------------------------------------
458 
459 enum DrawOrder { OverAll = 0, UnderAll, PaletteOrder };
460 
getAboveStyleIdSet(int styleId,TPaletteP palette,QSet<int> & aboveStyles)461 void getAboveStyleIdSet(int styleId, TPaletteP palette,
462                         QSet<int> &aboveStyles) {
463   if (!palette) return;
464   for (int p = 0; p < palette->getPageCount(); p++) {
465     TPalette::Page *page = palette->getPage(p);
466     for (int s = 0; s < page->getStyleCount(); s++) {
467       int tmpId = page->getStyleId(s);
468       if (tmpId == styleId) return;
469       if (tmpId != 0) aboveStyles.insert(tmpId);
470     }
471   }
472 }
473 
474 //=========================================================================================================
475 
computeThickness(double pressure,const TDoublePairProperty & property,bool isPath)476 double computeThickness(double pressure, const TDoublePairProperty &property,
477                         bool isPath) {
478   if (isPath) return 0.0;
479   double t      = pressure * pressure * pressure;
480   double thick0 = property.getValue().first;
481   double thick1 = property.getValue().second;
482   if (thick1 < 0.0001) thick0 = thick1 = 0.0;
483   return (thick0 + (thick1 - thick0) * t) * 0.5;
484 }
485 
486 }  // namespace
487 
488 //===================================================================
489 //
490 // ToonzVectorBrushTool
491 //
492 //-----------------------------------------------------------------------------
493 
ToonzVectorBrushTool(std::string name,int targetType)494 ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType)
495     : TTool(name)
496     , m_thickness("Size", 0, 1000, 0, 5)
497     , m_accuracy("Accuracy:", 1, 100, 20)
498     , m_smooth("Smooth:", 0, 50, 0)
499     , m_preset("Preset:")
500     , m_breakAngles("Break", true)
501     , m_pressure("Pressure", true)
502     , m_capStyle("Cap")
503     , m_joinStyle("Join")
504     , m_miterJoinLimit("Miter:", 0, 100, 4)
505     , m_rasterTrack(0)
506     , m_styleId(0)
507     , m_modifiedRegion()
508     , m_bluredBrush(0)
509     , m_active(false)
510     , m_enabled(false)
511     , m_isPrompting(false)
512     , m_firstTime(true)
513     , m_firstFrameRange(true)
514     , m_presetsLoaded(false)
515     , m_frameRange("Range:")
516     , m_snap("Snap", false)
517     , m_snapSensitivity("Sensitivity:")
518     , m_targetType(targetType)
519     , m_workingFrameId(TFrameId()) {
520   bind(targetType);
521 
522   m_thickness.setNonLinearSlider();
523 
524   m_prop[0].bind(m_thickness);
525   m_prop[0].bind(m_accuracy);
526   m_prop[0].bind(m_smooth);
527   m_prop[0].bind(m_breakAngles);
528   m_prop[0].bind(m_pressure);
529 
530   m_prop[0].bind(m_frameRange);
531   m_frameRange.addValue(L"Off");
532   m_frameRange.addValue(LINEAR_WSTR);
533   m_frameRange.addValue(EASEIN_WSTR);
534   m_frameRange.addValue(EASEOUT_WSTR);
535   m_frameRange.addValue(EASEINOUT_WSTR);
536 
537   m_prop[0].bind(m_snap);
538 
539   m_prop[0].bind(m_snapSensitivity);
540   m_snapSensitivity.addValue(LOW_WSTR);
541   m_snapSensitivity.addValue(MEDIUM_WSTR);
542   m_snapSensitivity.addValue(HIGH_WSTR);
543 
544   m_prop[0].bind(m_preset);
545   m_preset.addValue(CUSTOM_WSTR);
546 
547   m_prop[1].bind(m_capStyle);
548   m_capStyle.addValue(BUTT_WSTR, QString::fromStdWString(BUTT_WSTR));
549   m_capStyle.addValue(ROUNDC_WSTR, QString::fromStdWString(ROUNDC_WSTR));
550   m_capStyle.addValue(PROJECTING_WSTR,
551                       QString::fromStdWString(PROJECTING_WSTR));
552 
553   m_prop[1].bind(m_joinStyle);
554   m_joinStyle.addValue(MITER_WSTR, QString::fromStdWString(MITER_WSTR));
555   m_joinStyle.addValue(ROUNDJ_WSTR, QString::fromStdWString(ROUNDJ_WSTR));
556   m_joinStyle.addValue(BEVEL_WSTR, QString::fromStdWString(BEVEL_WSTR));
557 
558   m_prop[1].bind(m_miterJoinLimit);
559 
560   m_breakAngles.setId("BreakSharpAngles");
561   m_frameRange.setId("FrameRange");
562   m_snap.setId("Snap");
563   m_snapSensitivity.setId("SnapSensitivity");
564   m_preset.setId("BrushPreset");
565   m_pressure.setId("PressureSensitivity");
566   m_capStyle.setId("Cap");
567   m_joinStyle.setId("Join");
568   m_miterJoinLimit.setId("Miter");
569 }
570 
571 //-------------------------------------------------------------------------------------------------------
572 
createOptionsBox()573 ToolOptionsBox *ToonzVectorBrushTool::createOptionsBox() {
574   TPaletteHandle *currPalette =
575       TTool::getApplication()->getPaletteController()->getCurrentLevelPalette();
576   ToolHandle *currTool = TTool::getApplication()->getCurrentTool();
577   return new BrushToolOptionsBox(0, this, currPalette, currTool);
578 }
579 
580 //-------------------------------------------------------------------------------------------------------
581 
updateTranslation()582 void ToonzVectorBrushTool::updateTranslation() {
583   m_thickness.setQStringName(tr("Size"));
584   m_accuracy.setQStringName(tr("Accuracy:"));
585   m_smooth.setQStringName(tr("Smooth:"));
586   m_preset.setQStringName(tr("Preset:"));
587   m_preset.setItemUIName(CUSTOM_WSTR, tr("<custom>"));
588   m_breakAngles.setQStringName(tr("Break"));
589   m_pressure.setQStringName(tr("Pressure"));
590   m_capStyle.setQStringName(tr("Cap"));
591   m_joinStyle.setQStringName(tr("Join"));
592   m_miterJoinLimit.setQStringName(tr("Miter:"));
593   m_frameRange.setQStringName(tr("Range:"));
594   m_snap.setQStringName(tr("Snap"));
595   m_snapSensitivity.setQStringName("");
596   m_frameRange.setItemUIName(L"Off", tr("Off"));
597   m_frameRange.setItemUIName(LINEAR_WSTR, tr("Linear"));
598   m_frameRange.setItemUIName(EASEIN_WSTR, tr("In"));
599   m_frameRange.setItemUIName(EASEOUT_WSTR, tr("Out"));
600   m_frameRange.setItemUIName(EASEINOUT_WSTR, tr("In&Out"));
601   m_snapSensitivity.setItemUIName(LOW_WSTR, tr("Low"));
602   m_snapSensitivity.setItemUIName(MEDIUM_WSTR, tr("Med"));
603   m_snapSensitivity.setItemUIName(HIGH_WSTR, tr("High"));
604   m_capStyle.setItemUIName(BUTT_WSTR, tr("Butt cap"));
605   m_capStyle.setItemUIName(ROUNDC_WSTR, tr("Round cap"));
606   m_capStyle.setItemUIName(PROJECTING_WSTR, tr("Projecting cap"));
607   m_joinStyle.setItemUIName(MITER_WSTR, tr("Miter join"));
608   m_joinStyle.setItemUIName(ROUNDJ_WSTR, tr("Round join"));
609   m_joinStyle.setItemUIName(BEVEL_WSTR, tr("Bevel join"));
610 }
611 
612 //---------------------------------------------------------------------------------------------------
613 
onActivate()614 void ToonzVectorBrushTool::onActivate() {
615   if (m_firstTime) {
616     m_firstTime = false;
617 
618     std::wstring wpreset =
619         QString::fromStdString(V_VectorBrushPreset.getValue()).toStdWString();
620     if (wpreset != CUSTOM_WSTR) {
621       initPresets();
622       if (!m_preset.isValue(wpreset)) wpreset = CUSTOM_WSTR;
623       m_preset.setValue(wpreset);
624       V_VectorBrushPreset = m_preset.getValueAsString();
625       loadPreset();
626     } else
627       loadLastBrush();
628   }
629   resetFrameRange();
630   // TODO:app->editImageOrSpline();
631 }
632 
633 //--------------------------------------------------------------------------------------------------
634 
onDeactivate()635 void ToonzVectorBrushTool::onDeactivate() {
636   /*---
637    * ドラッグ中にツールが切り替わった場合に備え、onDeactivateにもMouseReleaseと同じ処理を行う
638    * ---*/
639 
640   // End current stroke.
641   if (m_active && m_enabled) {
642     leftButtonUp(m_lastDragPos, m_lastDragEvent);
643   }
644 
645   if (m_tileSaver && !m_isPath) {
646     m_enabled = false;
647   }
648   m_workRas   = TRaster32P();
649   m_backupRas = TRasterCM32P();
650   resetFrameRange();
651 }
652 
653 //--------------------------------------------------------------------------------------------------
654 
preLeftButtonDown()655 bool ToonzVectorBrushTool::preLeftButtonDown() {
656   if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
657 
658   touchImage();
659   if (m_isFrameCreated) {
660     // When the xsheet frame is selected, whole viewer will be updated from
661     // SceneViewer::onXsheetChanged() on adding a new frame.
662     // We need to take care of a case when the level frame is selected.
663     if (m_application->getCurrentFrame()->isEditingLevel()) invalidate();
664   }
665   return true;
666 }
667 
668 //--------------------------------------------------------------------------------------------------
669 
leftButtonDown(const TPointD & pos,const TMouseEvent & e)670 void ToonzVectorBrushTool::leftButtonDown(const TPointD &pos,
671                                           const TMouseEvent &e) {
672   TTool::Application *app = TTool::getApplication();
673   if (!app) return;
674 
675   if (getViewer() && getViewer()->getGuidedStrokePickerMode()) {
676     getViewer()->doPickGuideStroke(pos);
677     return;
678   }
679 
680   int col   = app->getCurrentColumn()->getColumnIndex();
681   m_isPath  = app->getCurrentObject()->isSpline();
682   m_enabled = col >= 0 || m_isPath || app->getCurrentFrame()->isEditingLevel();
683   // todo: gestire autoenable
684   if (!m_enabled) return;
685   if (!m_isPath) {
686     m_currentColor = TPixel32::Black;
687     m_active       = !!getImage(true);
688     if (!m_active) {
689       m_active = !!touchImage();
690     }
691     if (!m_active) return;
692 
693     if (m_active) {
694       // nel caso che il colore corrente sia un cleanup/studiopalette color
695       // oppure il colore di un colorfield
696       m_styleId       = app->getCurrentLevelStyleIndex();
697       TColorStyle *cs = app->getCurrentLevelStyle();
698       if (cs) {
699         TRasterStyleFx *rfx = cs ? cs->getRasterStyleFx() : 0;
700         m_active =
701             cs != 0 && (cs->isStrokeStyle() || (rfx && rfx->isInkStyle()));
702         m_currentColor   = cs->getAverageColor();
703         m_currentColor.m = 255;
704       } else {
705         m_styleId      = 1;
706         m_currentColor = TPixel32::Black;
707       }
708     }
709   } else {
710     m_currentColor = TPixel32::Red;
711     m_active       = true;
712   }
713   // assert(0<=m_styleId && m_styleId<2);
714   m_track.clear();
715   double thickness = (m_pressure.getValue() || m_isPath)
716                          ? computeThickness(e.m_pressure, m_thickness, m_isPath)
717                          : m_thickness.getValue().second * 0.5;
718 
719   /*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/
720   if (m_pressure.getValue() && e.m_pressure == 1.0)
721     thickness = m_thickness.getValue().first * 0.5;
722   m_currThickness = thickness;
723   m_smoothStroke.beginStroke(m_smooth.getValue());
724 
725   if (m_foundFirstSnap) {
726     addTrackPoint(TThickPoint(m_firstSnapPoint, thickness),
727                   getPixelSize() * getPixelSize());
728   } else
729     addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
730   TRectD invalidateRect = m_track.getLastModifiedRegion();
731   invalidate(invalidateRect.enlarge(2));
732 
733   // updating m_brushPos is needed to refresh viewer properly
734   m_brushPos = m_mousePos = pos;
735 }
736 
737 //-------------------------------------------------------------------------------------------------------------
738 
leftButtonDrag(const TPointD & pos,const TMouseEvent & e)739 void ToonzVectorBrushTool::leftButtonDrag(const TPointD &pos,
740                                           const TMouseEvent &e) {
741   if (!m_enabled || !m_active) {
742     m_brushPos = m_mousePos = pos;
743     return;
744   }
745 
746   m_lastDragPos   = pos;
747   m_lastDragEvent = e;
748 
749   double thickness = (m_pressure.getValue() || m_isPath)
750                          ? computeThickness(e.m_pressure, m_thickness, m_isPath)
751                          : m_thickness.getValue().second * 0.5;
752 
753   TRectD invalidateRect;
754   TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
755   TPointD snapThick(6.0 * m_pixelSize, 6.0 * m_pixelSize);
756 
757   // In order to clear the previous brush tip
758   invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
759 
760   // In order to clear the previous snap indicator
761   if (m_foundLastSnap)
762     invalidateRect +=
763         TRectD(m_lastSnapPoint - snapThick, m_lastSnapPoint + snapThick);
764 
765   m_currThickness = thickness;
766 
767   m_mousePos       = pos;
768   m_lastSnapPoint  = pos;
769   m_foundLastSnap  = false;
770   m_foundFirstSnap = false;
771   m_snapSelf       = false;
772   m_altPressed     = e.isAltPressed() && !e.isCtrlPressed();
773 
774   checkStrokeSnapping(false, m_altPressed);
775   checkGuideSnapping(false, m_altPressed);
776   m_brushPos = m_lastSnapPoint;
777 
778   if (m_foundLastSnap)
779     invalidateRect +=
780         TRectD(m_lastSnapPoint - snapThick, m_lastSnapPoint + snapThick);
781 
782   if (e.isShiftPressed()) {
783     m_smoothStroke.clearPoints();
784     m_track.add(TThickPoint(m_brushPos, thickness),
785                 getPixelSize() * getPixelSize());
786     m_track.removeMiddlePoints();
787     invalidateRect += m_track.getModifiedRegion();
788   } else if (m_dragDraw) {
789     addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
790     invalidateRect += m_track.getLastModifiedRegion();
791   }
792 
793   // In order to draw the current brush tip
794   invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
795 
796   if (!invalidateRect.isEmpty()) {
797     // for motion path, call the invalidate function directry to ignore dpi of
798     // the current level
799     if (m_isPath)
800       m_viewer->GLInvalidateRect(invalidateRect.enlarge(2));
801     else
802       invalidate(invalidateRect.enlarge(2));
803   }
804 }
805 
806 //---------------------------------------------------------------------------------------------------------------
807 
leftButtonUp(const TPointD & pos,const TMouseEvent & e)808 void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
809                                         const TMouseEvent &e) {
810   bool isValid = m_enabled && m_active;
811   m_enabled    = false;
812 
813   if (!isValid) {
814     // in case the current frame is moved to empty cell while dragging
815     if (!m_track.isEmpty()) {
816       m_track.clear();
817       invalidate();
818     }
819     return;
820   }
821 
822   if (m_isPath) {
823     double error = 20.0 * getPixelSize();
824 
825     TStroke *stroke;
826     if (e.isShiftPressed()) {
827       m_track.removeMiddlePoints();
828       stroke = m_track.makeStroke(0);
829     } else {
830       flushTrackPoint();
831       stroke = m_track.makeStroke(error);
832     }
833     int points = stroke->getControlPointCount();
834 
835     TVectorImageP vi = getImage(true);
836     struct Cleanup {
837       ToonzVectorBrushTool *m_this;
838       ~Cleanup() { m_this->m_track.clear(), m_this->invalidate(); }
839     } cleanup = {this};
840 
841     if (!isJustCreatedSpline(vi.getPointer())) {
842       m_isPrompting = true;
843 
844       QString question("Are you sure you want to replace the motion path?");
845       int ret =
846           DVGui::MsgBox(question, QObject::tr("Yes"), QObject::tr("No"), 0);
847 
848       m_isPrompting = false;
849 
850       if (ret == 2 || ret == 0) return;
851     }
852 
853     QMutexLocker lock(vi->getMutex());
854 
855     TUndo *undo =
856         new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline());
857 
858     while (vi->getStrokeCount() > 0) vi->deleteStroke(0);
859     vi->addStroke(stroke, false);
860 
861     notifyImageChanged();
862     TUndoManager::manager()->add(undo);
863 
864     return;
865   }
866 
867   TVectorImageP vi = getImage(true);
868   if (m_track.isEmpty()) {
869     m_styleId = 0;
870     m_track.clear();
871     return;
872   }
873 
874   if (vi && (m_snap.getValue() != m_altPressed) && m_foundLastSnap) {
875     addTrackPoint(TThickPoint(m_lastSnapPoint, m_currThickness),
876                   getPixelSize() * getPixelSize());
877   }
878   m_strokeIndex1   = -1;
879   m_strokeIndex2   = -1;
880   m_w1             = -1;
881   m_w2             = -2;
882   m_foundFirstSnap = false;
883   m_foundLastSnap  = false;
884 
885   m_track.filterPoints();
886   double error = 30.0 / (1 + 0.5 * m_accuracy.getValue());
887   error *= getPixelSize();
888 
889   TStroke *stroke;
890   if (e.isShiftPressed()) {
891     m_track.removeMiddlePoints();
892     stroke = m_track.makeStroke(0);
893   } else {
894     flushTrackPoint();
895     stroke = m_track.makeStroke(error);
896   }
897   stroke->setStyle(m_styleId);
898   {
899     TStroke::OutlineOptions &options = stroke->outlineOptions();
900     options.m_capStyle               = m_capStyle.getIndex();
901     options.m_joinStyle              = m_joinStyle.getIndex();
902     options.m_miterUpper             = m_miterJoinLimit.getValue();
903   }
904   m_styleId = 0;
905 
906   QMutexLocker lock(vi->getMutex());
907   if (stroke->getControlPointCount() == 3 &&
908       stroke->getControlPoint(0) !=
909           stroke->getControlPoint(2))  // gli stroke con solo 1 chunk vengono
910                                        // fatti dal tape tool...e devono venir
911                                        // riconosciuti come speciali di
912                                        // autoclose proprio dal fatto che
913                                        // hanno 1 solo chunk.
914     stroke->insertControlPoints(0.5);
915   if (m_frameRange.getIndex()) {
916     if (m_firstFrameId == -1) {
917       m_firstStroke                   = new TStroke(*stroke);
918       m_firstFrameId                  = getFrameId();
919       TTool::Application *application = TTool::getApplication();
920       if (application) {
921         m_col        = application->getCurrentColumn()->getColumnIndex();
922         m_firstFrame = application->getCurrentFrame()->getFrame();
923       }
924       m_rangeTrack = m_track;
925       if (m_firstFrameRange) {
926         m_veryFirstCol     = m_col;
927         m_veryFirstFrame   = m_firstFrame;
928         m_veryFirstFrameId = m_firstFrameId;
929       }
930     } else if (m_firstFrameId == getFrameId()) {
931       if (m_firstStroke) {
932         delete m_firstStroke;
933         m_firstStroke = 0;
934       }
935       m_firstStroke = new TStroke(*stroke);
936       m_rangeTrack  = m_track;
937     } else {
938       TFrameId currentId = getFrameId();
939       int curCol = 0, curFrame = 0;
940       TTool::Application *application = TTool::getApplication();
941       if (application) {
942         curCol   = application->getCurrentColumn()->getColumnIndex();
943         curFrame = application->getCurrentFrame()->getFrame();
944       }
945       bool success = doFrameRangeStrokes(
946           m_firstFrameId, m_firstStroke, getFrameId(), stroke,
947           m_frameRange.getIndex(), m_breakAngles.getValue(), false, false,
948           m_firstFrameRange);
949       if (e.isCtrlPressed()) {
950         if (application) {
951           if (m_firstFrameId > currentId) {
952             if (application->getCurrentFrame()->isEditingScene()) {
953               application->getCurrentColumn()->setColumnIndex(curCol);
954               application->getCurrentFrame()->setFrame(curFrame);
955             } else
956               application->getCurrentFrame()->setFid(currentId);
957           }
958         }
959         resetFrameRange();
960         m_firstStroke     = new TStroke(*stroke);
961         m_rangeTrack      = m_track;
962         m_firstFrameId    = currentId;
963         m_firstFrameRange = false;
964       }
965 
966       if (application && !e.isCtrlPressed()) {
967         if (application->getCurrentFrame()->isEditingScene()) {
968           application->getCurrentColumn()->setColumnIndex(m_veryFirstCol);
969           application->getCurrentFrame()->setFrame(m_veryFirstFrame);
970         } else
971           application->getCurrentFrame()->setFid(m_veryFirstFrameId);
972       }
973 
974       if (!e.isCtrlPressed()) {
975         resetFrameRange();
976       }
977     }
978     invalidate();
979   } else {
980     if (m_snapSelf) {
981       stroke->setSelfLoop(true);
982       m_snapSelf = false;
983     }
984 
985     addStrokeToImage(getApplication(), vi, stroke, m_breakAngles.getValue(),
986                      false, false, m_isFrameCreated, m_isLevelCreated);
987     TRectD bbox = stroke->getBBox().enlarge(2) + m_track.getModifiedRegion();
988 
989     invalidate();  // should use bbox?
990 
991     if ((Preferences::instance()->getGuidedDrawingType() == 1 ||
992          Preferences::instance()->getGuidedDrawingType() == 2) &&
993         Preferences::instance()->getGuidedAutoInbetween()) {
994       int fidx     = getApplication()->getCurrentFrame()->getFrameIndex();
995       TFrameId fId = getFrameId();
996 
997       doGuidedAutoInbetween(fId, vi, stroke, m_breakAngles.getValue(), false,
998                             false, false);
999 
1000       if (getApplication()->getCurrentFrame()->isEditingScene())
1001         getApplication()->getCurrentFrame()->setFrame(fidx);
1002       else
1003         getApplication()->getCurrentFrame()->setFid(fId);
1004     }
1005   }
1006   assert(stroke);
1007   m_track.clear();
1008   m_altPressed = false;
1009 }
1010 
1011 //--------------------------------------------------------------------------------------------------
1012 
keyDown(QKeyEvent * event)1013 bool ToonzVectorBrushTool::keyDown(QKeyEvent *event) {
1014   if (event->key() == Qt::Key_Escape) {
1015     resetFrameRange();
1016   }
1017   return false;
1018 }
1019 
1020 //--------------------------------------------------------------------------------------------------
1021 
doFrameRangeStrokes(TFrameId firstFrameId,TStroke * firstStroke,TFrameId lastFrameId,TStroke * lastStroke,int interpolationType,bool breakAngles,bool autoGroup,bool autoFill,bool drawFirstStroke,bool drawLastStroke,bool withUndo)1022 bool ToonzVectorBrushTool::doFrameRangeStrokes(
1023     TFrameId firstFrameId, TStroke *firstStroke, TFrameId lastFrameId,
1024     TStroke *lastStroke, int interpolationType, bool breakAngles,
1025     bool autoGroup, bool autoFill, bool drawFirstStroke, bool drawLastStroke,
1026     bool withUndo) {
1027   TXshSimpleLevel *sl =
1028       TTool::getApplication()->getCurrentLevel()->getLevel()->getSimpleLevel();
1029   TStroke *first           = new TStroke();
1030   TStroke *last            = new TStroke();
1031   TVectorImageP firstImage = new TVectorImage();
1032   TVectorImageP lastImage  = new TVectorImage();
1033 
1034   *first       = *firstStroke;
1035   *last        = *lastStroke;
1036   bool swapped = false;
1037   if (firstFrameId > lastFrameId) {
1038     std::swap(firstFrameId, lastFrameId);
1039     *first  = *lastStroke;
1040     *last   = *firstStroke;
1041     swapped = true;
1042   }
1043 
1044   firstImage->addStroke(first, false);
1045   lastImage->addStroke(last, false);
1046   assert(firstFrameId <= lastFrameId);
1047 
1048   std::vector<TFrameId> allFids;
1049   sl->getFids(allFids);
1050   std::vector<TFrameId>::iterator i0 = allFids.begin();
1051   while (i0 != allFids.end() && *i0 < firstFrameId) i0++;
1052   if (i0 == allFids.end()) return false;
1053   std::vector<TFrameId>::iterator i1 = i0;
1054   while (i1 != allFids.end() && *i1 <= lastFrameId) i1++;
1055   assert(i0 < i1);
1056   std::vector<TFrameId> fids(i0, i1);
1057   int m = fids.size();
1058   assert(m > 0);
1059 
1060   if (withUndo) TUndoManager::manager()->beginBlock();
1061   int row = getApplication()->getCurrentFrame()->isEditingScene()
1062                 ? getApplication()->getCurrentFrame()->getFrameIndex()
1063                 : -1;
1064   TFrameId cFid = getApplication()->getCurrentFrame()->getFid();
1065   for (int i = 0; i < m; ++i) {
1066     TFrameId fid = fids[i];
1067     assert(firstFrameId <= fid && fid <= lastFrameId);
1068 
1069     // This is an attempt to divide the tween evenly
1070     double t = m > 1 ? (double)i / (double)(m - 1) : 0.5;
1071     double s = t;
1072     switch (interpolationType) {
1073     case 1:  // LINEAR_WSTR
1074       break;
1075     case 2:  // EASEIN_WSTR
1076       s = t * (2 - t);
1077       break;  // s'(1) = 0
1078     case 3:   // EASEOUT_WSTR
1079       s = t * t;
1080       break;  // s'(0) = 0
1081     case 4:   // EASEINOUT_WSTR:
1082       s = t * t * (3 - 2 * t);
1083       break;  // s'(0) = s'(1) = 0
1084     }
1085 
1086     TTool::Application *app = TTool::getApplication();
1087     if (app) app->getCurrentFrame()->setFid(fid);
1088 
1089     TVectorImageP img = sl->getFrame(fid, true);
1090     if (t == 0) {
1091       if (!swapped && !drawFirstStroke) {
1092       } else
1093         addStrokeToImage(getApplication(), img, firstImage->getStroke(0),
1094                          breakAngles, autoGroup, autoFill, m_isFrameCreated,
1095                          m_isLevelCreated, sl, fid);
1096     } else if (t == 1) {
1097       if (swapped && !drawFirstStroke) {
1098       } else if (drawLastStroke)
1099         addStrokeToImage(getApplication(), img, lastImage->getStroke(0),
1100                          breakAngles, autoGroup, autoFill, m_isFrameCreated,
1101                          m_isLevelCreated, sl, fid);
1102     } else {
1103       assert(firstImage->getStrokeCount() == 1);
1104       assert(lastImage->getStrokeCount() == 1);
1105       TVectorImageP vi = TInbetween(firstImage, lastImage).tween(s);
1106       assert(vi->getStrokeCount() == 1);
1107       addStrokeToImage(getApplication(), img, vi->getStroke(0), breakAngles,
1108                        autoGroup, autoFill, m_isFrameCreated, m_isLevelCreated,
1109                        sl, fid);
1110     }
1111   }
1112   if (row != -1)
1113     getApplication()->getCurrentFrame()->setFrame(row);
1114   else
1115     getApplication()->getCurrentFrame()->setFid(cFid);
1116 
1117   if (withUndo) TUndoManager::manager()->endBlock();
1118   notifyImageChanged();
1119   return true;
1120 }
1121 
1122 //--------------------------------------------------------------------------------------------------
doGuidedAutoInbetween(TFrameId cFid,const TVectorImageP & cvi,TStroke * cStroke,bool breakAngles,bool autoGroup,bool autoFill,bool drawStroke)1123 bool ToonzVectorBrushTool::doGuidedAutoInbetween(
1124     TFrameId cFid, const TVectorImageP &cvi, TStroke *cStroke, bool breakAngles,
1125     bool autoGroup, bool autoFill, bool drawStroke) {
1126   TApplication *app = TTool::getApplication();
1127 
1128   if (cFid.isEmptyFrame() || cFid.isNoFrame() || !cvi || !cStroke) return false;
1129 
1130   TXshSimpleLevel *sl = app->getCurrentLevel()->getLevel()->getSimpleLevel();
1131   if (!sl) return false;
1132 
1133   int osBack  = -1;
1134   int osFront = -1;
1135 
1136   getViewer()->getGuidedFrameIdx(&osBack, &osFront);
1137 
1138   TFrameHandle *currentFrame = getApplication()->getCurrentFrame();
1139   bool resultBack            = false;
1140   bool resultFront           = false;
1141   TFrameId oFid;
1142   int cStrokeIdx = cvi->getStrokeCount();
1143   if (!drawStroke) cStrokeIdx--;
1144 
1145   TUndoManager::manager()->beginBlock();
1146   if (osBack != -1) {
1147     if (currentFrame->isEditingScene()) {
1148       TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
1149       int col      = app->getCurrentColumn()->getColumnIndex();
1150       if (xsh && col >= 0) {
1151         TXshCell cell = xsh->getCell(osBack, col);
1152         if (!cell.isEmpty()) oFid = cell.getFrameId();
1153       }
1154     } else
1155       oFid = sl->getFrameId(osBack);
1156 
1157     TVectorImageP fvi = sl->getFrame(oFid, false);
1158     int fStrokeCount  = fvi ? fvi->getStrokeCount() : 0;
1159 
1160     int strokeIdx = getViewer()->getGuidedBackStroke() != -1
1161                         ? getViewer()->getGuidedBackStroke()
1162                         : cStrokeIdx;
1163 
1164     if (!oFid.isEmptyFrame() && oFid != cFid && fvi && fStrokeCount &&
1165         strokeIdx < fStrokeCount) {
1166       TStroke *fStroke = fvi->getStroke(strokeIdx);
1167 
1168       bool frameCreated = m_isFrameCreated;
1169       m_isFrameCreated  = false;
1170       touchImage();
1171       resultBack        = doFrameRangeStrokes(
1172           oFid, fStroke, cFid, cStroke,
1173           Preferences::instance()->getGuidedInterpolation(), breakAngles,
1174           autoGroup, autoFill, false, drawStroke, false);
1175       m_isFrameCreated = frameCreated;
1176     }
1177   }
1178 
1179   if (osFront != -1) {
1180     bool drawFirstStroke = (osBack != -1 && resultBack) ? false : true;
1181 
1182     if (currentFrame->isEditingScene()) {
1183       TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
1184       int col      = app->getCurrentColumn()->getColumnIndex();
1185       if (xsh && col >= 0) {
1186         TXshCell cell = xsh->getCell(osFront, col);
1187         if (!cell.isEmpty()) oFid = cell.getFrameId();
1188       }
1189     } else
1190       oFid = sl->getFrameId(osFront);
1191 
1192     TVectorImageP fvi = sl->getFrame(oFid, false);
1193     int fStrokeCount  = fvi ? fvi->getStrokeCount() : 0;
1194 
1195     int strokeIdx = getViewer()->getGuidedFrontStroke() != -1
1196                         ? getViewer()->getGuidedFrontStroke()
1197                         : cStrokeIdx;
1198 
1199     if (!oFid.isEmptyFrame() && oFid != cFid && fvi && fStrokeCount &&
1200         strokeIdx < fStrokeCount) {
1201       TStroke *fStroke = fvi->getStroke(strokeIdx);
1202 
1203       bool frameCreated = m_isFrameCreated;
1204       m_isFrameCreated  = false;
1205       touchImage();
1206       resultFront       = doFrameRangeStrokes(
1207           cFid, cStroke, oFid, fStroke,
1208           Preferences::instance()->getGuidedInterpolation(), breakAngles,
1209           autoGroup, autoFill, drawFirstStroke, false, false);
1210       m_isFrameCreated = frameCreated;
1211     }
1212   }
1213   TUndoManager::manager()->endBlock();
1214 
1215   return resultBack || resultFront;
1216 }
1217 
1218 //--------------------------------------------------------------------------------------------------
1219 
addTrackPoint(const TThickPoint & point,double pixelSize2)1220 void ToonzVectorBrushTool::addTrackPoint(const TThickPoint &point,
1221                                          double pixelSize2) {
1222   m_smoothStroke.addPoint(point);
1223   std::vector<TThickPoint> pts;
1224   m_smoothStroke.getSmoothPoints(pts);
1225   for (size_t i = 0; i < pts.size(); ++i) {
1226     m_track.add(pts[i], pixelSize2);
1227   }
1228 }
1229 
1230 //--------------------------------------------------------------------------------------------------
1231 
flushTrackPoint()1232 void ToonzVectorBrushTool::flushTrackPoint() {
1233   m_smoothStroke.endStroke();
1234   std::vector<TThickPoint> pts;
1235   m_smoothStroke.getSmoothPoints(pts);
1236   double pixelSize2 = getPixelSize() * getPixelSize();
1237   for (size_t i = 0; i < pts.size(); ++i) {
1238     m_track.add(pts[i], pixelSize2);
1239   }
1240 }
1241 
1242 //---------------------------------------------------------------------------------------------------------------
1243 
mouseMove(const TPointD & pos,const TMouseEvent & e)1244 void ToonzVectorBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
1245   qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
1246 
1247   struct Locals {
1248     ToonzVectorBrushTool *m_this;
1249 
1250     void setValue(TDoublePairProperty &prop,
1251                   const TDoublePairProperty::Value &value) {
1252       prop.setValue(value);
1253 
1254       m_this->onPropertyChanged(prop.getName());
1255       TTool::getApplication()->getCurrentTool()->notifyToolChanged();
1256     }
1257 
1258     void addMinMax(TDoublePairProperty &prop, double add) {
1259       if (add == 0.0) return;
1260       const TDoublePairProperty::Range &range = prop.getRange();
1261 
1262       TDoublePairProperty::Value value = prop.getValue();
1263       value.first  = tcrop(value.first + add, range.first, range.second);
1264       value.second = tcrop(value.second + add, range.first, range.second);
1265 
1266       setValue(prop, value);
1267     }
1268 
1269     void addMinMaxSeparate(TDoublePairProperty &prop, double min, double max) {
1270       if (min == 0.0 && max == 0.0) return;
1271       const TDoublePairProperty::Range &range = prop.getRange();
1272 
1273       TDoublePairProperty::Value value = prop.getValue();
1274       value.first += min;
1275       value.second += max;
1276       if (value.first > value.second) value.first = value.second;
1277       value.first  = tcrop(value.first, range.first, range.second);
1278       value.second = tcrop(value.second, range.first, range.second);
1279 
1280       setValue(prop, value);
1281     }
1282 
1283   } locals = {this};
1284 
1285   TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
1286   TRectD invalidateRect(m_brushPos - halfThick, m_brushPos + halfThick);
1287 
1288   if (e.isCtrlPressed() && e.isAltPressed() && !e.isShiftPressed() &&
1289       Preferences::instance()->useCtrlAltToResizeBrushEnabled()) {
1290     // Resize the brush if CTRL+ALT is pressed and the preference is enabled.
1291     const TPointD &diff = pos - m_mousePos;
1292     double max          = diff.x / 2;
1293     double min          = diff.y / 2;
1294 
1295     locals.addMinMaxSeparate(m_thickness, min, max);
1296 
1297     double radius = m_thickness.getValue().second * 0.5;
1298     invalidateRect += TRectD(m_brushPos - TPointD(radius, radius),
1299                              m_brushPos + TPointD(radius, radius));
1300 
1301   } else {
1302     m_mousePos = pos;
1303     m_brushPos = pos;
1304 
1305     TPointD snapThick(6.0 * m_pixelSize, 6.0 * m_pixelSize);
1306     // In order to clear the previous snap indicator
1307     if (m_foundFirstSnap)
1308       invalidateRect +=
1309           TRectD(m_firstSnapPoint - snapThick, m_firstSnapPoint + snapThick);
1310 
1311     m_firstSnapPoint = pos;
1312     m_foundFirstSnap = false;
1313     m_altPressed     = e.isAltPressed() && !e.isCtrlPressed();
1314     checkStrokeSnapping(true, m_altPressed);
1315     checkGuideSnapping(true, m_altPressed);
1316     m_brushPos = m_firstSnapPoint;
1317     // In order to draw the snap indicator
1318     if (m_foundFirstSnap)
1319       invalidateRect +=
1320           TRectD(m_firstSnapPoint - snapThick, m_firstSnapPoint + snapThick);
1321 
1322     invalidateRect += TRectD(pos - halfThick, pos + halfThick);
1323   }
1324 
1325   invalidate(invalidateRect.enlarge(2));
1326 
1327   if (m_minThick == 0 && m_maxThick == 0) {
1328     m_minThick = m_thickness.getValue().first;
1329     m_maxThick = m_thickness.getValue().second;
1330   }
1331 }
1332 
1333 //-------------------------------------------------------------------------------------------------------------
1334 
checkStrokeSnapping(bool beforeMousePress,bool invertCheck)1335 void ToonzVectorBrushTool::checkStrokeSnapping(bool beforeMousePress,
1336                                                bool invertCheck) {
1337   if (Preferences::instance()->getVectorSnappingTarget() == 1) return;
1338 
1339   TVectorImageP vi(getImage(false));
1340   bool checkSnap = m_snap.getValue();
1341   if (invertCheck) checkSnap = !checkSnap;
1342   m_dragDraw = true;
1343   if (vi && checkSnap) {
1344     double minDistance2 = m_minDistance2;
1345     if (beforeMousePress)
1346       m_strokeIndex1 = -1;
1347     else
1348       m_strokeIndex2 = -1;
1349     int i, strokeNumber = vi->getStrokeCount();
1350     TStroke *stroke;
1351     double distance2, outW;
1352     bool snapFound = false;
1353     TThickPoint point1;
1354 
1355     for (i = 0; i < strokeNumber; i++) {
1356       stroke = vi->getStroke(i);
1357       if (stroke->getNearestW(m_mousePos, outW, distance2) &&
1358           distance2 < minDistance2) {
1359         minDistance2                      = distance2;
1360         beforeMousePress ? m_strokeIndex1 = i : m_strokeIndex2 = i;
1361         if (areAlmostEqual(outW, 0.0, 1e-3))
1362           beforeMousePress ? m_w1 = 0.0 : m_w2 = 0.0;
1363         else if (areAlmostEqual(outW, 1.0, 1e-3))
1364           beforeMousePress ? m_w1 = 1.0 : m_w2 = 1.0;
1365         else
1366           beforeMousePress ? m_w1 = outW : m_w2 = outW;
1367 
1368         beforeMousePress ? point1 = stroke->getPoint(m_w1)
1369                          : point1 = stroke->getPoint(m_w2);
1370         snapFound                 = true;
1371       }
1372     }
1373     // compare to first point of current stroke
1374     if (beforeMousePress && snapFound) {
1375       m_firstSnapPoint = TPointD(point1.x, point1.y);
1376       m_foundFirstSnap = true;
1377     } else if (!beforeMousePress) {
1378       if (!snapFound) {
1379         TPointD tempPoint        = m_track.getFirstPoint();
1380         double distanceFromStart = tdistance2(m_mousePos, tempPoint);
1381 
1382         if (distanceFromStart < m_minDistance2) {
1383           point1     = tempPoint;
1384           distance2  = distanceFromStart;
1385           snapFound  = true;
1386           m_snapSelf = true;
1387         }
1388       }
1389       if (snapFound) {
1390         m_lastSnapPoint = TPointD(point1.x, point1.y);
1391         m_foundLastSnap = true;
1392         if (distance2 < 2.0) m_dragDraw = false;
1393       }
1394     }
1395   }
1396 }
1397 
1398 //-------------------------------------------------------------------------------------------------------------
1399 
checkGuideSnapping(bool beforeMousePress,bool invertCheck)1400 void ToonzVectorBrushTool::checkGuideSnapping(bool beforeMousePress,
1401                                               bool invertCheck) {
1402   if (Preferences::instance()->getVectorSnappingTarget() == 0) return;
1403   bool foundSnap;
1404   TPointD snapPoint;
1405   beforeMousePress ? foundSnap = m_foundFirstSnap : foundSnap = m_foundLastSnap;
1406   beforeMousePress ? snapPoint = m_firstSnapPoint : snapPoint = m_lastSnapPoint;
1407 
1408   bool checkSnap = m_snap.getValue();
1409   if (invertCheck) checkSnap = !checkSnap;
1410 
1411   if (checkSnap) {
1412     // check guide snapping
1413     int vGuideCount = 0, hGuideCount = 0;
1414     double guideDistance  = sqrt(m_minDistance2);
1415     TTool::Viewer *viewer = getViewer();
1416     if (viewer) {
1417       vGuideCount = viewer->getVGuideCount();
1418       hGuideCount = viewer->getHGuideCount();
1419     }
1420     double distanceToVGuide = -1.0, distanceToHGuide = -1.0;
1421     double vGuide, hGuide;
1422     bool useGuides = false;
1423     if (vGuideCount) {
1424       for (int j = 0; j < vGuideCount; j++) {
1425         double guide        = viewer->getVGuide(j);
1426         double tempDistance = std::abs(guide - m_mousePos.y);
1427         if (tempDistance < guideDistance &&
1428             (distanceToVGuide < 0 || tempDistance < distanceToVGuide)) {
1429           distanceToVGuide = tempDistance;
1430           vGuide           = guide;
1431           useGuides        = true;
1432         }
1433       }
1434     }
1435     if (hGuideCount) {
1436       for (int j = 0; j < hGuideCount; j++) {
1437         double guide        = viewer->getHGuide(j);
1438         double tempDistance = std::abs(guide - m_mousePos.x);
1439         if (tempDistance < guideDistance &&
1440             (distanceToHGuide < 0 || tempDistance < distanceToHGuide)) {
1441           distanceToHGuide = tempDistance;
1442           hGuide           = guide;
1443           useGuides        = true;
1444         }
1445       }
1446     }
1447     if (useGuides && foundSnap) {
1448       double currYDistance = std::abs(snapPoint.y - m_mousePos.y);
1449       double currXDistance = std::abs(snapPoint.x - m_mousePos.x);
1450       double hypotenuse =
1451           sqrt(pow(currYDistance, 2.0) + pow(currXDistance, 2.0));
1452       if ((distanceToVGuide >= 0 && distanceToVGuide < hypotenuse) ||
1453           (distanceToHGuide >= 0 && distanceToHGuide < hypotenuse)) {
1454         useGuides  = true;
1455         m_snapSelf = false;
1456       } else
1457         useGuides = false;
1458     }
1459     if (useGuides) {
1460       assert(distanceToHGuide >= 0 || distanceToVGuide >= 0);
1461       if (distanceToHGuide < 0 ||
1462           (distanceToVGuide <= distanceToHGuide && distanceToVGuide >= 0)) {
1463         snapPoint.y = vGuide;
1464         snapPoint.x = m_mousePos.x;
1465 
1466       } else {
1467         snapPoint.y = m_mousePos.y;
1468         snapPoint.x = hGuide;
1469       }
1470       beforeMousePress ? m_foundFirstSnap = true : m_foundLastSnap = true;
1471       beforeMousePress ? m_firstSnapPoint                          = snapPoint
1472                        : m_lastSnapPoint                           = snapPoint;
1473     }
1474   }
1475 }
1476 
1477 //-------------------------------------------------------------------------------------------------------------
1478 
draw()1479 void ToonzVectorBrushTool::draw() {
1480   /*--ショートカットでのツール切り替え時に赤点が描かれるのを防止する--*/
1481   if (m_minThick == 0 && m_maxThick == 0 &&
1482       !Preferences::instance()->getShow0ThickLines())
1483     return;
1484 
1485   TImageP img = getImage(false, 1);
1486 
1487   // Draw track
1488   tglColor(m_isPrompting ? TPixel32::Green : m_currentColor);
1489   m_track.drawAllFragments();
1490 
1491   // snapping
1492   TVectorImageP vi = img;
1493   if (m_snap.getValue() != m_altPressed) {
1494     m_pixelSize  = getPixelSize();
1495     double thick = 6.0 * m_pixelSize;
1496     if (m_foundFirstSnap) {
1497       tglColor(TPixelD(0.1, 0.9, 0.1));
1498       tglDrawCircle(m_firstSnapPoint, thick);
1499     }
1500 
1501     TThickPoint point2;
1502 
1503     if (m_foundLastSnap) {
1504       tglColor(TPixelD(0.1, 0.9, 0.1));
1505       tglDrawCircle(m_lastSnapPoint, thick);
1506     }
1507   }
1508 
1509   // frame range
1510   if (m_firstStroke) {
1511     glColor3d(1.0, 0.0, 0.0);
1512     m_rangeTrack.drawAllFragments();
1513     glColor3d(0.0, 0.6, 0.0);
1514     TPointD firstPoint        = m_rangeTrack.getFirstPoint();
1515     TPointD topLeftCorner     = TPointD(firstPoint.x - 5, firstPoint.y - 5);
1516     TPointD topRightCorner    = TPointD(firstPoint.x + 5, firstPoint.y - 5);
1517     TPointD bottomLeftCorner  = TPointD(firstPoint.x - 5, firstPoint.y + 5);
1518     TPointD bottomRightCorner = TPointD(firstPoint.x + 5, firstPoint.y + 5);
1519     tglDrawSegment(topLeftCorner, bottomRightCorner);
1520     tglDrawSegment(topRightCorner, bottomLeftCorner);
1521   }
1522 
1523   if (getApplication()->getCurrentObject()->isSpline()) return;
1524 
1525   // If toggled off, don't draw brush outline
1526   if (!Preferences::instance()->isCursorOutlineEnabled()) return;
1527 
1528   // Don't draw brush outline if picking guiding stroke
1529   if (getViewer()->getGuidedStrokePickerMode()) return;
1530 
1531   // Draw the brush outline - change color when the Ink / Paint check is
1532   // activated
1533   if ((ToonzCheck::instance()->getChecks() & ToonzCheck::eInk) ||
1534       (ToonzCheck::instance()->getChecks() & ToonzCheck::ePaint) ||
1535       (ToonzCheck::instance()->getChecks() & ToonzCheck::eInk1))
1536     glColor3d(0.5, 0.8, 0.8);
1537   // normally draw in red
1538   else
1539     glColor3d(1.0, 0.0, 0.0);
1540 
1541   tglDrawCircle(m_brushPos, 0.5 * m_minThick);
1542   tglDrawCircle(m_brushPos, 0.5 * m_maxThick);
1543 }
1544 
1545 //--------------------------------------------------------------------------------------------------------------
1546 
onEnter()1547 void ToonzVectorBrushTool::onEnter() {
1548   TImageP img = getImage(false);
1549 
1550   m_minThick = m_thickness.getValue().first;
1551   m_maxThick = m_thickness.getValue().second;
1552 
1553   Application *app = getApplication();
1554 
1555   m_styleId       = app->getCurrentLevelStyleIndex();
1556   TColorStyle *cs = app->getCurrentLevelStyle();
1557   if (cs) {
1558     TRasterStyleFx *rfx = cs->getRasterStyleFx();
1559     m_active            = cs->isStrokeStyle() || (rfx && rfx->isInkStyle());
1560     m_currentColor      = cs->getAverageColor();
1561     m_currentColor.m    = 255;
1562   } else {
1563     m_currentColor = TPixel32::Black;
1564   }
1565   m_active = img;
1566 }
1567 
1568 //----------------------------------------------------------------------------------------------------------
1569 
onLeave()1570 void ToonzVectorBrushTool::onLeave() {
1571   m_minThick = 0;
1572   m_maxThick = 0;
1573 }
1574 
1575 //----------------------------------------------------------------------------------------------------------
1576 
getProperties(int idx)1577 TPropertyGroup *ToonzVectorBrushTool::getProperties(int idx) {
1578   if (!m_presetsLoaded) initPresets();
1579 
1580   return &m_prop[idx];
1581 }
1582 
1583 //------------------------------------------------------------------
1584 
resetFrameRange()1585 void ToonzVectorBrushTool::resetFrameRange() {
1586   m_rangeTrack.clear();
1587   m_firstFrameId = -1;
1588   if (m_firstStroke) {
1589     delete m_firstStroke;
1590     m_firstStroke = 0;
1591   }
1592   m_firstFrameRange = true;
1593 }
1594 
1595 //------------------------------------------------------------------
1596 
onPropertyChanged(std::string propertyName)1597 bool ToonzVectorBrushTool::onPropertyChanged(std::string propertyName) {
1598   if (m_propertyUpdating) return true;
1599 
1600   // Set the following to true whenever a different piece of interface must
1601   // be refreshed - done once at the end.
1602   bool notifyTool = false;
1603 
1604   if (propertyName == m_preset.getName()) {
1605     if (m_preset.getValue() != CUSTOM_WSTR)
1606       loadPreset();
1607     else  // Chose <custom>, go back to last saved brush settings
1608       loadLastBrush();
1609 
1610     V_VectorBrushPreset = m_preset.getValueAsString();
1611     m_propertyUpdating  = true;
1612     getApplication()->getCurrentTool()->notifyToolChanged();
1613     m_propertyUpdating = false;
1614     return true;
1615   }
1616 
1617   // Switch to <custom> only if it's a preset property change
1618   if (m_preset.getValue() != CUSTOM_WSTR &&
1619       (propertyName == m_thickness.getName() ||
1620        propertyName == m_accuracy.getName() ||
1621        propertyName == m_smooth.getName() ||
1622        propertyName == m_breakAngles.getName() ||
1623        propertyName == m_pressure.getName() ||
1624        propertyName == m_capStyle.getName() ||
1625        propertyName == m_joinStyle.getName() ||
1626        propertyName == m_miterJoinLimit.getName())) {
1627     m_preset.setValue(CUSTOM_WSTR);
1628     V_VectorBrushPreset = m_preset.getValueAsString();
1629     notifyTool          = true;
1630   }
1631 
1632   // Properties tracked with preset. Update only on <custom>
1633   if (m_preset.getValue() == CUSTOM_WSTR) {
1634     V_VectorBrushMinSize       = m_thickness.getValue().first;
1635     V_VectorBrushMaxSize       = m_thickness.getValue().second;
1636     V_BrushAccuracy            = m_accuracy.getValue();
1637     V_BrushSmooth              = m_smooth.getValue();
1638     V_BrushBreakSharpAngles    = m_breakAngles.getValue();
1639     V_BrushPressureSensitivity = m_pressure.getValue();
1640     V_VectorCapStyle           = m_capStyle.getIndex();
1641     V_VectorJoinStyle          = m_joinStyle.getIndex();
1642     V_VectorMiterValue         = m_miterJoinLimit.getValue();
1643   }
1644 
1645   // Properties not tracked with preset
1646   int frameIndex               = m_frameRange.getIndex();
1647   V_VectorBrushFrameRange      = frameIndex;
1648   V_VectorBrushSnap            = m_snap.getValue();
1649   int snapSensitivityIndex     = m_snapSensitivity.getIndex();
1650   V_VectorBrushSnapSensitivity = snapSensitivityIndex;
1651 
1652   // Recalculate/reset based on changed settings
1653   m_minThick = m_thickness.getValue().first;
1654   m_maxThick = m_thickness.getValue().second;
1655 
1656   if (frameIndex == 0) resetFrameRange();
1657 
1658   switch (snapSensitivityIndex) {
1659   case 0:
1660     m_minDistance2 = SNAPPING_LOW;
1661     break;
1662   case 1:
1663     m_minDistance2 = SNAPPING_MEDIUM;
1664     break;
1665   case 2:
1666     m_minDistance2 = SNAPPING_HIGH;
1667     break;
1668   }
1669 
1670   if (propertyName == m_joinStyle.getName()) notifyTool = true;
1671 
1672   if (notifyTool) {
1673     m_propertyUpdating = true;
1674     getApplication()->getCurrentTool()->notifyToolChanged();
1675     m_propertyUpdating = false;
1676   }
1677 
1678   return true;
1679 }
1680 
1681 //------------------------------------------------------------------
1682 
initPresets()1683 void ToonzVectorBrushTool::initPresets() {
1684   if (!m_presetsLoaded) {
1685     // If necessary, load the presets from file
1686     m_presetsLoaded = true;
1687     m_presetsManager.load(TEnv::getConfigDir() + "brush_vector.txt");
1688   }
1689 
1690   // Rebuild the presets property entries
1691   const std::set<VectorBrushData> &presets = m_presetsManager.presets();
1692 
1693   m_preset.deleteAllValues();
1694   m_preset.addValue(CUSTOM_WSTR);
1695   m_preset.setItemUIName(CUSTOM_WSTR, tr("<custom>"));
1696 
1697   std::set<VectorBrushData>::const_iterator it, end = presets.end();
1698   for (it = presets.begin(); it != end; ++it) m_preset.addValue(it->m_name);
1699 }
1700 
1701 //----------------------------------------------------------------------------------------------------------
1702 
loadPreset()1703 void ToonzVectorBrushTool::loadPreset() {
1704   const std::set<VectorBrushData> &presets = m_presetsManager.presets();
1705   std::set<VectorBrushData>::const_iterator it;
1706 
1707   it = presets.find(VectorBrushData(m_preset.getValue()));
1708   if (it == presets.end()) return;
1709 
1710   const VectorBrushData &preset = *it;
1711 
1712   try  // Don't bother with RangeErrors
1713   {
1714     m_thickness.setValue(
1715         TDoublePairProperty::Value(preset.m_min, preset.m_max));
1716     m_accuracy.setValue(preset.m_acc, true);
1717     m_smooth.setValue(preset.m_smooth, true);
1718     m_breakAngles.setValue(preset.m_breakAngles);
1719     m_pressure.setValue(preset.m_pressure);
1720     m_capStyle.setIndex(preset.m_cap);
1721     m_joinStyle.setIndex(preset.m_join);
1722     m_miterJoinLimit.setValue(preset.m_miter);
1723 
1724     // Recalculate based on updated presets
1725     m_minThick = m_thickness.getValue().first;
1726     m_maxThick = m_thickness.getValue().second;
1727   } catch (...) {
1728   }
1729 }
1730 
1731 //------------------------------------------------------------------
1732 
addPreset(QString name)1733 void ToonzVectorBrushTool::addPreset(QString name) {
1734   // Build the preset
1735   VectorBrushData preset(name.toStdWString());
1736 
1737   preset.m_min = m_thickness.getValue().first;
1738   preset.m_max = m_thickness.getValue().second;
1739 
1740   preset.m_acc         = m_accuracy.getValue();
1741   preset.m_smooth      = m_smooth.getValue();
1742   preset.m_breakAngles = m_breakAngles.getValue();
1743   preset.m_pressure    = m_pressure.getValue();
1744   preset.m_cap         = m_capStyle.getIndex();
1745   preset.m_join        = m_joinStyle.getIndex();
1746   preset.m_miter       = m_miterJoinLimit.getValue();
1747 
1748   // Pass the preset to the manager
1749   m_presetsManager.addPreset(preset);
1750 
1751   // Reinitialize the associated preset enum
1752   initPresets();
1753 
1754   // Set the value to the specified one
1755   m_preset.setValue(preset.m_name);
1756   V_VectorBrushPreset = m_preset.getValueAsString();
1757 }
1758 
1759 //------------------------------------------------------------------
1760 
removePreset()1761 void ToonzVectorBrushTool::removePreset() {
1762   std::wstring name(m_preset.getValue());
1763   if (name == CUSTOM_WSTR) return;
1764 
1765   m_presetsManager.removePreset(name);
1766   initPresets();
1767 
1768   // No parameter change, and set the preset value to custom
1769   m_preset.setValue(CUSTOM_WSTR);
1770   V_VectorBrushPreset = m_preset.getValueAsString();
1771 }
1772 
1773 //------------------------------------------------------------------
1774 
loadLastBrush()1775 void ToonzVectorBrushTool::loadLastBrush() {
1776   // Properties tracked with preset
1777   m_thickness.setValue(
1778       TDoublePairProperty::Value(V_VectorBrushMinSize, V_VectorBrushMaxSize));
1779   m_capStyle.setIndex(V_VectorCapStyle);
1780   m_joinStyle.setIndex(V_VectorJoinStyle);
1781   m_miterJoinLimit.setValue(V_VectorMiterValue);
1782   m_breakAngles.setValue(V_BrushBreakSharpAngles ? 1 : 0);
1783   m_accuracy.setValue(V_BrushAccuracy);
1784   m_pressure.setValue(V_BrushPressureSensitivity ? 1 : 0);
1785   m_smooth.setValue(V_BrushSmooth);
1786 
1787   // Properties not tracked with preset
1788   m_frameRange.setIndex(V_VectorBrushFrameRange);
1789   m_snap.setValue(V_VectorBrushSnap);
1790   m_snapSensitivity.setIndex(V_VectorBrushSnapSensitivity);
1791 
1792   // Recalculate based on prior values
1793   m_minThick = m_thickness.getValue().first;
1794   m_maxThick = m_thickness.getValue().second;
1795 
1796   switch (V_VectorBrushSnapSensitivity) {
1797   case 0:
1798     m_minDistance2 = SNAPPING_LOW;
1799     break;
1800   case 1:
1801     m_minDistance2 = SNAPPING_MEDIUM;
1802     break;
1803   case 2:
1804     m_minDistance2 = SNAPPING_HIGH;
1805     break;
1806   }
1807 }
1808 
1809 //------------------------------------------------------------------
1810 /*!	Brush、PaintBrush、EraserToolがPencilModeのときにTrueを返す
1811  */
isPencilModeActive()1812 bool ToonzVectorBrushTool::isPencilModeActive() { return false; }
1813 
1814 //==========================================================================================================
1815 
1816 // Tools instantiation
1817 
1818 ToonzVectorBrushTool vectorPencil("T_Brush",
1819                                   TTool::Vectors | TTool::EmptyTarget);
1820 
1821 //*******************************************************************************
1822 //    Brush Data implementation
1823 //*******************************************************************************
1824 
VectorBrushData()1825 VectorBrushData::VectorBrushData()
1826     : m_name()
1827     , m_min(0.0)
1828     , m_max(0.0)
1829     , m_acc(0.0)
1830     , m_smooth(0.0)
1831     , m_breakAngles(false)
1832     , m_pressure(false)
1833     , m_cap(0)
1834     , m_join(0)
1835     , m_miter(0) {}
1836 
1837 //----------------------------------------------------------------------------------------------------------
1838 
VectorBrushData(const std::wstring & name)1839 VectorBrushData::VectorBrushData(const std::wstring &name)
1840     : m_name(name)
1841     , m_min(0.0)
1842     , m_max(0.0)
1843     , m_acc(0.0)
1844     , m_smooth(0.0)
1845     , m_breakAngles(false)
1846     , m_pressure(false)
1847     , m_cap(0)
1848     , m_join(0)
1849     , m_miter(0) {}
1850 
1851 //----------------------------------------------------------------------------------------------------------
1852 
saveData(TOStream & os)1853 void VectorBrushData::saveData(TOStream &os) {
1854   os.openChild("Name");
1855   os << m_name;
1856   os.closeChild();
1857   os.openChild("Thickness");
1858   os << m_min << m_max;
1859   os.closeChild();
1860   os.openChild("Accuracy");
1861   os << m_acc;
1862   os.closeChild();
1863   os.openChild("Smooth");
1864   os << m_smooth;
1865   os.closeChild();
1866   os.openChild("Break_Sharp_Angles");
1867   os << (int)m_breakAngles;
1868   os.closeChild();
1869   os.openChild("Pressure_Sensitivity");
1870   os << (int)m_pressure;
1871   os.closeChild();
1872   os.openChild("Cap");
1873   os << m_cap;
1874   os.closeChild();
1875   os.openChild("Join");
1876   os << m_join;
1877   os.closeChild();
1878   os.openChild("Miter");
1879   os << m_miter;
1880   os.closeChild();
1881 }
1882 
1883 //----------------------------------------------------------------------------------------------------------
1884 
loadData(TIStream & is)1885 void VectorBrushData::loadData(TIStream &is) {
1886   std::string tagName;
1887   int val;
1888 
1889   while (is.matchTag(tagName)) {
1890     if (tagName == "Name")
1891       is >> m_name, is.matchEndTag();
1892     else if (tagName == "Thickness")
1893       is >> m_min >> m_max, is.matchEndTag();
1894     else if (tagName == "Accuracy")
1895       is >> m_acc, is.matchEndTag();
1896     else if (tagName == "Smooth")
1897       is >> m_smooth, is.matchEndTag();
1898     else if (tagName == "Break_Sharp_Angles")
1899       is >> val, m_breakAngles = val, is.matchEndTag();
1900     else if (tagName == "Pressure_Sensitivity")
1901       is >> val, m_pressure = val, is.matchEndTag();
1902     else if (tagName == "Cap")
1903       is >> m_cap, is.matchEndTag();
1904     else if (tagName == "Join")
1905       is >> m_join, is.matchEndTag();
1906     else if (tagName == "Miter")
1907       is >> m_miter, is.matchEndTag();
1908     else
1909       is.skipCurrentTag();
1910   }
1911 }
1912 
1913 //----------------------------------------------------------------------------------------------------------
1914 
1915 PERSIST_IDENTIFIER(VectorBrushData, "VectorBrushData");
1916 
1917 //*******************************************************************************
1918 //    Brush Preset Manager implementation
1919 //*******************************************************************************
1920 
load(const TFilePath & fp)1921 void VectorBrushPresetManager::load(const TFilePath &fp) {
1922   m_fp = fp;
1923 
1924   std::string tagName;
1925   VectorBrushData data;
1926 
1927   TIStream is(m_fp);
1928   try {
1929     while (is.matchTag(tagName)) {
1930       if (tagName == "version") {
1931         VersionNumber version;
1932         is >> version.first >> version.second;
1933 
1934         is.setVersion(version);
1935         is.matchEndTag();
1936       } else if (tagName == "brushes") {
1937         while (is.matchTag(tagName)) {
1938           if (tagName == "brush") {
1939             is >> data, m_presets.insert(data);
1940             is.matchEndTag();
1941           } else
1942             is.skipCurrentTag();
1943         }
1944 
1945         is.matchEndTag();
1946       } else
1947         is.skipCurrentTag();
1948     }
1949   } catch (...) {
1950   }
1951 }
1952 
1953 //------------------------------------------------------------------
1954 
save()1955 void VectorBrushPresetManager::save() {
1956   TOStream os(m_fp);
1957 
1958   os.openChild("version");
1959   os << 1 << 20;
1960   os.closeChild();
1961 
1962   os.openChild("brushes");
1963 
1964   std::set<VectorBrushData>::iterator it, end = m_presets.end();
1965   for (it = m_presets.begin(); it != end; ++it) {
1966     os.openChild("brush");
1967     os << (TPersist &)*it;
1968     os.closeChild();
1969   }
1970 
1971   os.closeChild();
1972 }
1973 
1974 //------------------------------------------------------------------
1975 
addPreset(const VectorBrushData & data)1976 void VectorBrushPresetManager::addPreset(const VectorBrushData &data) {
1977   m_presets.erase(data);  // Overwriting insertion
1978   m_presets.insert(data);
1979   save();
1980 }
1981 
1982 //------------------------------------------------------------------
1983 
removePreset(const std::wstring & name)1984 void VectorBrushPresetManager::removePreset(const std::wstring &name) {
1985   m_presets.erase(VectorBrushData(name));
1986   save();
1987 }
1988