1
2 /*
3 * Copyright 2013 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9 // This test only works with the GPU backend.
10
11 #include "gm.h"
12
13 #if SK_SUPPORT_GPU
14
15 #include "GrDrawContext.h"
16 #include "GrContext.h"
17 #include "GrPathUtils.h"
18 #include "GrTest.h"
19 #include "SkColorPriv.h"
20 #include "SkDevice.h"
21 #include "SkGeometry.h"
22
23 #include "batches/GrTestBatch.h"
24
25 #include "effects/GrBezierEffect.h"
26
eval_line(const SkPoint & p,const SkScalar lineEq[3],SkScalar sign)27 static inline SkScalar eval_line(const SkPoint& p, const SkScalar lineEq[3], SkScalar sign) {
28 return sign * (lineEq[0] * p.fX + lineEq[1] * p.fY + lineEq[2]);
29 }
30
31 namespace skiagm {
32
33 class BezierCubicOrConicTestBatch : public GrTestBatch {
34 public:
35 DEFINE_BATCH_CLASS_ID
36 struct Geometry : public GrTestBatch::Geometry {
37 SkRect fBounds;
38 };
39
name() const40 const char* name() const override { return "BezierCubicOrConicTestBatch"; }
41
Create(const GrGeometryProcessor * gp,const Geometry & geo,const SkScalar klmEqs[9],SkScalar sign)42 static GrDrawBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo,
43 const SkScalar klmEqs[9], SkScalar sign) {
44 return new BezierCubicOrConicTestBatch(gp, geo, klmEqs, sign);
45 }
46
47 private:
BezierCubicOrConicTestBatch(const GrGeometryProcessor * gp,const Geometry & geo,const SkScalar klmEqs[9],SkScalar sign)48 BezierCubicOrConicTestBatch(const GrGeometryProcessor* gp, const Geometry& geo,
49 const SkScalar klmEqs[9], SkScalar sign)
50 : INHERITED(ClassID(), gp, geo.fBounds) {
51 for (int i = 0; i < 9; i++) {
52 fKlmEqs[i] = klmEqs[i];
53 }
54
55 fGeometry = geo;
56 fSign = sign;
57 }
58
59 struct Vertex {
60 SkPoint fPosition;
61 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
62 };
63
geoData(int index)64 Geometry* geoData(int index) override {
65 SkASSERT(0 == index);
66 return &fGeometry;
67 }
68
geoData(int index) const69 const Geometry* geoData(int index) const override {
70 SkASSERT(0 == index);
71 return &fGeometry;
72 }
73
generateGeometry(Target * target) const74 void generateGeometry(Target* target) const override {
75 QuadHelper helper;
76 size_t vertexStride = this->geometryProcessor()->getVertexStride();
77 SkASSERT(vertexStride == sizeof(Vertex));
78 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(target, vertexStride, 1));
79 if (!verts) {
80 return;
81 }
82
83 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop,
84 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom,
85 sizeof(Vertex));
86 for (int v = 0; v < 4; ++v) {
87 verts[v].fKLM[0] = eval_line(verts[v].fPosition, fKlmEqs + 0, fSign);
88 verts[v].fKLM[1] = eval_line(verts[v].fPosition, fKlmEqs + 3, fSign);
89 verts[v].fKLM[2] = eval_line(verts[v].fPosition, fKlmEqs + 6, 1.f);
90 }
91 helper.recordDraw(target);
92 }
93
94 Geometry fGeometry;
95 SkScalar fKlmEqs[9];
96 SkScalar fSign;
97
98 static const int kVertsPerCubic = 4;
99 static const int kIndicesPerCubic = 6;
100
101 typedef GrTestBatch INHERITED;
102 };
103
104 /**
105 * This GM directly exercises effects that draw Bezier curves in the GPU backend.
106 */
107 class BezierCubicEffects : public GM {
108 public:
BezierCubicEffects()109 BezierCubicEffects() {
110 this->setBGColor(0xFFFFFFFF);
111 }
112
113 protected:
onShortName()114 SkString onShortName() override {
115 return SkString("bezier_cubic_effects");
116 }
117
onISize()118 SkISize onISize() override {
119 return SkISize::Make(800, 800);
120 }
121
onDraw(SkCanvas * canvas)122 void onDraw(SkCanvas* canvas) override {
123 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget();
124 if (nullptr == rt) {
125 skiagm::GM::DrawGpuOnlyMessage(canvas);
126 return;
127 }
128 GrContext* context = rt->getContext();
129 if (nullptr == context) {
130 return;
131 }
132
133 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(rt));
134 if (!drawContext) {
135 return;
136 }
137
138 struct Vertex {
139 SkPoint fPosition;
140 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
141 };
142
143 static const int kNumCubics = 15;
144 SkRandom rand;
145
146 // Mult by 3 for each edge effect type
147 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumCubics*3)));
148 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumCubics*3) / numCols);
149 SkScalar w = SkIntToScalar(rt->width()) / numCols;
150 SkScalar h = SkIntToScalar(rt->height()) / numRows;
151 int row = 0;
152 int col = 0;
153 static const GrColor color = 0xff000000;
154
155 for (int i = 0; i < kNumCubics; ++i) {
156 SkPoint baseControlPts[] = {
157 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
158 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
159 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
160 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}
161 };
162 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) {
163 SkAutoTUnref<GrGeometryProcessor> gp;
164 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType;
165 gp.reset(GrCubicEffect::Create(color, SkMatrix::I(), et,
166 *context->caps()));
167 if (!gp) {
168 continue;
169 }
170 SkScalar x = SkScalarMul(col, w);
171 SkScalar y = SkScalarMul(row, h);
172 SkPoint controlPts[] = {
173 {x + baseControlPts[0].fX, y + baseControlPts[0].fY},
174 {x + baseControlPts[1].fX, y + baseControlPts[1].fY},
175 {x + baseControlPts[2].fX, y + baseControlPts[2].fY},
176 {x + baseControlPts[3].fX, y + baseControlPts[3].fY}
177 };
178 SkPoint chopped[10];
179 SkScalar klmEqs[9];
180 SkScalar klmSigns[3];
181 int cnt = GrPathUtils::chopCubicAtLoopIntersection(controlPts,
182 chopped,
183 klmEqs,
184 klmSigns);
185
186 SkPaint ctrlPtPaint;
187 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
188 for (int i = 0; i < 4; ++i) {
189 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint);
190 }
191
192 SkPaint polyPaint;
193 polyPaint.setColor(0xffA0A0A0);
194 polyPaint.setStrokeWidth(0);
195 polyPaint.setStyle(SkPaint::kStroke_Style);
196 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, controlPts, polyPaint);
197
198 SkPaint choppedPtPaint;
199 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
200
201 for (int c = 0; c < cnt; ++c) {
202 SkPoint* pts = chopped + 3 * c;
203
204 for (int i = 0; i < 4; ++i) {
205 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint);
206 }
207
208 SkRect bounds;
209 bounds.set(pts, 4);
210
211 SkPaint boundsPaint;
212 boundsPaint.setColor(0xff808080);
213 boundsPaint.setStrokeWidth(0);
214 boundsPaint.setStyle(SkPaint::kStroke_Style);
215 canvas->drawRect(bounds, boundsPaint);
216
217 GrPipelineBuilder pipelineBuilder;
218 pipelineBuilder.setXPFactory(
219 GrPorterDuffXPFactory::Create(SkXfermode::kSrc_Mode))->unref();
220 pipelineBuilder.setRenderTarget(rt);
221
222 BezierCubicOrConicTestBatch::Geometry geometry;
223 geometry.fColor = color;
224 geometry.fBounds = bounds;
225
226 SkAutoTUnref<GrDrawBatch> batch(
227 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, klmSigns[c]));
228
229 drawContext->internal_drawBatch(pipelineBuilder, batch);
230 }
231 ++col;
232 if (numCols == col) {
233 col = 0;
234 ++row;
235 }
236 }
237 }
238 }
239
240 private:
241 typedef GM INHERITED;
242 };
243
244 //////////////////////////////////////////////////////////////////////////////
245
246 /**
247 * This GM directly exercises effects that draw Bezier curves in the GPU backend.
248 */
249 class BezierConicEffects : public GM {
250 public:
BezierConicEffects()251 BezierConicEffects() {
252 this->setBGColor(0xFFFFFFFF);
253 }
254
255 protected:
onShortName()256 SkString onShortName() override {
257 return SkString("bezier_conic_effects");
258 }
259
onISize()260 SkISize onISize() override {
261 return SkISize::Make(800, 800);
262 }
263
264
onDraw(SkCanvas * canvas)265 void onDraw(SkCanvas* canvas) override {
266 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget();
267 if (nullptr == rt) {
268 skiagm::GM::DrawGpuOnlyMessage(canvas);
269 return;
270 }
271 GrContext* context = rt->getContext();
272 if (nullptr == context) {
273 return;
274 }
275
276 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(rt));
277 if (!drawContext) {
278 return;
279 }
280
281 struct Vertex {
282 SkPoint fPosition;
283 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
284 };
285
286 static const int kNumConics = 10;
287 SkRandom rand;
288
289 // Mult by 3 for each edge effect type
290 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumConics*3)));
291 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumConics*3) / numCols);
292 SkScalar w = SkIntToScalar(rt->width()) / numCols;
293 SkScalar h = SkIntToScalar(rt->height()) / numRows;
294 int row = 0;
295 int col = 0;
296 static const GrColor color = 0xff000000;
297
298 for (int i = 0; i < kNumConics; ++i) {
299 SkPoint baseControlPts[] = {
300 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
301 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
302 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}
303 };
304 SkScalar weight = rand.nextRangeF(0.f, 2.f);
305 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) {
306 SkAutoTUnref<GrGeometryProcessor> gp;
307 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType;
308 gp.reset(GrConicEffect::Create(color, SkMatrix::I(), et,
309 *context->caps(), SkMatrix::I(), false));
310 if (!gp) {
311 continue;
312 }
313
314 SkScalar x = SkScalarMul(col, w);
315 SkScalar y = SkScalarMul(row, h);
316 SkPoint controlPts[] = {
317 {x + baseControlPts[0].fX, y + baseControlPts[0].fY},
318 {x + baseControlPts[1].fX, y + baseControlPts[1].fY},
319 {x + baseControlPts[2].fX, y + baseControlPts[2].fY}
320 };
321 SkConic dst[4];
322 SkScalar klmEqs[9];
323 int cnt = chop_conic(controlPts, dst, weight);
324 GrPathUtils::getConicKLM(controlPts, weight, klmEqs);
325
326 SkPaint ctrlPtPaint;
327 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
328 for (int i = 0; i < 3; ++i) {
329 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint);
330 }
331
332 SkPaint polyPaint;
333 polyPaint.setColor(0xffA0A0A0);
334 polyPaint.setStrokeWidth(0);
335 polyPaint.setStyle(SkPaint::kStroke_Style);
336 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint);
337
338 SkPaint choppedPtPaint;
339 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
340
341 for (int c = 0; c < cnt; ++c) {
342 SkPoint* pts = dst[c].fPts;
343 for (int i = 0; i < 3; ++i) {
344 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint);
345 }
346
347 SkRect bounds;
348 //SkPoint bPts[] = {{0.f, 0.f}, {800.f, 800.f}};
349 //bounds.set(bPts, 2);
350 bounds.set(pts, 3);
351
352 SkPaint boundsPaint;
353 boundsPaint.setColor(0xff808080);
354 boundsPaint.setStrokeWidth(0);
355 boundsPaint.setStyle(SkPaint::kStroke_Style);
356 canvas->drawRect(bounds, boundsPaint);
357
358 GrPipelineBuilder pipelineBuilder;
359 pipelineBuilder.setXPFactory(
360 GrPorterDuffXPFactory::Create(SkXfermode::kSrc_Mode))->unref();
361 pipelineBuilder.setRenderTarget(rt);
362
363 BezierCubicOrConicTestBatch::Geometry geometry;
364 geometry.fColor = color;
365 geometry.fBounds = bounds;
366
367 SkAutoTUnref<GrDrawBatch> batch(
368 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, 1.f));
369
370 drawContext->internal_drawBatch(pipelineBuilder, batch);
371 }
372 ++col;
373 if (numCols == col) {
374 col = 0;
375 ++row;
376 }
377 }
378 }
379 }
380
381 private:
382 // Uses the max curvature function for quads to estimate
383 // where to chop the conic. If the max curvature is not
384 // found along the curve segment it will return 1 and
385 // dst[0] is the original conic. If it returns 2 the dst[0]
386 // and dst[1] are the two new conics.
split_conic(const SkPoint src[3],SkConic dst[2],const SkScalar weight)387 int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
388 SkScalar t = SkFindQuadMaxCurvature(src);
389 if (t == 0) {
390 if (dst) {
391 dst[0].set(src, weight);
392 }
393 return 1;
394 } else {
395 if (dst) {
396 SkConic conic;
397 conic.set(src, weight);
398 conic.chopAt(t, dst);
399 }
400 return 2;
401 }
402 }
403
404 // Calls split_conic on the entire conic and then once more on each subsection.
405 // Most cases will result in either 1 conic (chop point is not within t range)
406 // or 3 points (split once and then one subsection is split again).
chop_conic(const SkPoint src[3],SkConic dst[4],const SkScalar weight)407 int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
408 SkConic dstTemp[2];
409 int conicCnt = split_conic(src, dstTemp, weight);
410 if (2 == conicCnt) {
411 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
412 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
413 } else {
414 dst[0] = dstTemp[0];
415 }
416 return conicCnt;
417 }
418
419 typedef GM INHERITED;
420 };
421
422 //////////////////////////////////////////////////////////////////////////////
423
424 class BezierQuadTestBatch : public GrTestBatch {
425 public:
426 DEFINE_BATCH_CLASS_ID
427 struct Geometry : public GrTestBatch::Geometry {
428 SkRect fBounds;
429 };
430
name() const431 const char* name() const override { return "BezierQuadTestBatch"; }
432
Create(const GrGeometryProcessor * gp,const Geometry & geo,const GrPathUtils::QuadUVMatrix & devToUV)433 static GrDrawBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo,
434 const GrPathUtils::QuadUVMatrix& devToUV) {
435 return new BezierQuadTestBatch(gp, geo, devToUV);
436 }
437
438 private:
BezierQuadTestBatch(const GrGeometryProcessor * gp,const Geometry & geo,const GrPathUtils::QuadUVMatrix & devToUV)439 BezierQuadTestBatch(const GrGeometryProcessor* gp, const Geometry& geo,
440 const GrPathUtils::QuadUVMatrix& devToUV)
441 : INHERITED(ClassID(), gp, geo.fBounds)
442 , fGeometry(geo)
443 , fDevToUV(devToUV) {
444 }
445
446 struct Vertex {
447 SkPoint fPosition;
448 float fKLM[4]; // The last value is ignored. The effect expects a vec4f.
449 };
450
geoData(int index)451 Geometry* geoData(int index) override {
452 SkASSERT(0 == index);
453 return &fGeometry;
454 }
455
geoData(int index) const456 const Geometry* geoData(int index) const override {
457 SkASSERT(0 == index);
458 return &fGeometry;
459 }
460
generateGeometry(Target * target) const461 void generateGeometry(Target* target) const override {
462 QuadHelper helper;
463 size_t vertexStride = this->geometryProcessor()->getVertexStride();
464 SkASSERT(vertexStride == sizeof(Vertex));
465 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(target, vertexStride, 1));
466 if (!verts) {
467 return;
468 }
469 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop,
470 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom,
471 sizeof(Vertex));
472 fDevToUV.apply<4, sizeof(Vertex), sizeof(SkPoint)>(verts);
473 helper.recordDraw(target);
474 }
475
476 Geometry fGeometry;
477 GrPathUtils::QuadUVMatrix fDevToUV;
478
479 static const int kVertsPerCubic = 4;
480 static const int kIndicesPerCubic = 6;
481
482 typedef GrTestBatch INHERITED;
483 };
484
485 /**
486 * This GM directly exercises effects that draw Bezier quad curves in the GPU backend.
487 */
488 class BezierQuadEffects : public GM {
489 public:
BezierQuadEffects()490 BezierQuadEffects() {
491 this->setBGColor(0xFFFFFFFF);
492 }
493
494 protected:
onShortName()495 SkString onShortName() override {
496 return SkString("bezier_quad_effects");
497 }
498
onISize()499 SkISize onISize() override {
500 return SkISize::Make(800, 800);
501 }
502
503
onDraw(SkCanvas * canvas)504 void onDraw(SkCanvas* canvas) override {
505 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget();
506 if (nullptr == rt) {
507 skiagm::GM::DrawGpuOnlyMessage(canvas);
508 return;
509 }
510 GrContext* context = rt->getContext();
511 if (nullptr == context) {
512 return;
513 }
514
515 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(rt));
516 if (!drawContext) {
517 return;
518 }
519
520 struct Vertex {
521 SkPoint fPosition;
522 float fUV[4]; // The last two values are ignored. The effect expects a vec4f.
523 };
524
525 static const int kNumQuads = 5;
526 SkRandom rand;
527
528 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumQuads*3)));
529 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumQuads*3) / numCols);
530 SkScalar w = SkIntToScalar(rt->width()) / numCols;
531 SkScalar h = SkIntToScalar(rt->height()) / numRows;
532 int row = 0;
533 int col = 0;
534 static const GrColor color = 0xff000000;
535
536 for (int i = 0; i < kNumQuads; ++i) {
537 SkPoint baseControlPts[] = {
538 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
539 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)},
540 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}
541 };
542 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) {
543 SkAutoTUnref<GrGeometryProcessor> gp;
544 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType;
545 gp.reset(GrQuadEffect::Create(color, SkMatrix::I(), et,
546 *context->caps(), SkMatrix::I(), false));
547 if (!gp) {
548 continue;
549 }
550
551 SkScalar x = SkScalarMul(col, w);
552 SkScalar y = SkScalarMul(row, h);
553 SkPoint controlPts[] = {
554 {x + baseControlPts[0].fX, y + baseControlPts[0].fY},
555 {x + baseControlPts[1].fX, y + baseControlPts[1].fY},
556 {x + baseControlPts[2].fX, y + baseControlPts[2].fY}
557 };
558 SkPoint chopped[5];
559 int cnt = SkChopQuadAtMaxCurvature(controlPts, chopped);
560
561 SkPaint ctrlPtPaint;
562 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
563 for (int i = 0; i < 3; ++i) {
564 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint);
565 }
566
567 SkPaint polyPaint;
568 polyPaint.setColor(0xffA0A0A0);
569 polyPaint.setStrokeWidth(0);
570 polyPaint.setStyle(SkPaint::kStroke_Style);
571 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint);
572
573 SkPaint choppedPtPaint;
574 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
575
576 for (int c = 0; c < cnt; ++c) {
577 SkPoint* pts = chopped + 2 * c;
578
579 for (int i = 0; i < 3; ++i) {
580 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint);
581 }
582
583 SkRect bounds;
584 bounds.set(pts, 3);
585
586 SkPaint boundsPaint;
587 boundsPaint.setColor(0xff808080);
588 boundsPaint.setStrokeWidth(0);
589 boundsPaint.setStyle(SkPaint::kStroke_Style);
590 canvas->drawRect(bounds, boundsPaint);
591
592 GrPipelineBuilder pipelineBuilder;
593 pipelineBuilder.setXPFactory(
594 GrPorterDuffXPFactory::Create(SkXfermode::kSrc_Mode))->unref();
595 pipelineBuilder.setRenderTarget(rt);
596
597 GrPathUtils::QuadUVMatrix DevToUV(pts);
598
599 BezierQuadTestBatch::Geometry geometry;
600 geometry.fColor = color;
601 geometry.fBounds = bounds;
602
603 SkAutoTUnref<GrDrawBatch> batch(BezierQuadTestBatch::Create(gp, geometry,
604 DevToUV));
605
606 drawContext->internal_drawBatch(pipelineBuilder, batch);
607 }
608 ++col;
609 if (numCols == col) {
610 col = 0;
611 ++row;
612 }
613 }
614 }
615 }
616
617 private:
618 typedef GM INHERITED;
619 };
620
621 DEF_GM(return new BezierCubicEffects;)
622 DEF_GM(return new BezierConicEffects;)
623 DEF_GM(return new BezierQuadEffects;)
624 }
625
626 #endif
627