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> ¶meterValues,
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> ¶meterValues) {
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