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