aboutsummaryrefslogtreecommitdiffhomepage
path: root/samplecode/SampleQuadStroker.cpp
diff options
context:
space:
mode:
authorGravatar caryclark <caryclark@google.com>2015-02-20 06:33:57 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2015-02-20 06:33:57 -0800
commit04e4d08556750ff6be4576a4cd4925964c63376f (patch)
treea311cbe718948f35a5408ed175cb93f3be34316b /samplecode/SampleQuadStroker.cpp
parenta1f1ee98a1f6d0770f6243270ca2f0e6c92efaba (diff)
This uses quad approximations of the outer and inner paths describing a stroke. Cubics and conics' thick strokes are approximated with quads as well.
The approximation uses a similar error term as the fill scan converter to determine the number of quads to use. This also updates SampleApp QuadStroker test with conics, ovals, and stroked text. Review URL: https://codereview.chromium.org/932113002
Diffstat (limited to 'samplecode/SampleQuadStroker.cpp')
-rw-r--r--samplecode/SampleQuadStroker.cpp175
1 files changed, 138 insertions, 37 deletions
diff --git a/samplecode/SampleQuadStroker.cpp b/samplecode/SampleQuadStroker.cpp
index 4fd6e7434b..a3863a8991 100644
--- a/samplecode/SampleQuadStroker.cpp
+++ b/samplecode/SampleQuadStroker.cpp
@@ -85,6 +85,10 @@ struct StrokeTypeButton {
bool fEnabled;
};
+struct CircleTypeButton : public StrokeTypeButton {
+ bool fFill;
+};
+
class QuadStrokerView : public SampleView {
enum {
SKELETON_COLOR = 0xFF0000FF,
@@ -92,9 +96,10 @@ class QuadStrokerView : public SampleView {
};
enum {
- kCount = 10
+ kCount = 15
};
SkPoint fPts[kCount];
+ SkRect fWeightControl;
SkRect fErrorControl;
SkRect fWidthControl;
SkRect fBounds;
@@ -103,11 +108,14 @@ class QuadStrokerView : public SampleView {
SkAutoTUnref<SkSurface> fMinSurface;
SkAutoTUnref<SkSurface> fMaxSurface;
StrokeTypeButton fCubicButton;
+ StrokeTypeButton fConicButton;
StrokeTypeButton fQuadButton;
StrokeTypeButton fRRectButton;
+ CircleTypeButton fCircleButton;
StrokeTypeButton fTextButton;
SkString fText;
SkScalar fTextSize;
+ SkScalar fWeight;
SkScalar fWidth, fDWidth;
SkScalar fWidthScale;
int fW, fH, fZoom;
@@ -124,32 +132,44 @@ public:
QuadStrokerView() {
this->setBGColor(SK_ColorLTGRAY);
- fPts[0].set(50, 200);
+ fPts[0].set(50, 200); // cubic
fPts[1].set(50, 100);
fPts[2].set(150, 50);
fPts[3].set(300, 50);
- fPts[4].set(350, 200);
+ fPts[4].set(350, 200); // conic
fPts[5].set(350, 100);
fPts[6].set(450, 50);
- fPts[7].set(200, 200);
- fPts[8].set(400, 400);
+ fPts[7].set(150, 300); // quad
+ fPts[8].set(150, 200);
+ fPts[9].set(250, 150);
+
+ fPts[10].set(200, 200); // rrect
+ fPts[11].set(400, 400);
+
+ fPts[12].set(250, 250); // oval
+ fPts[13].set(450, 450);
- fPts[9].set(250, 800);
fText = "a";
fTextSize = 12;
fWidth = 50;
fDWidth = 0.25f;
+ fWeight = 1;
fCubicButton.fLabel = 'C';
fCubicButton.fEnabled = false;
+ fConicButton.fLabel = 'K';
+ fConicButton.fEnabled = true;
fQuadButton.fLabel = 'Q';
fQuadButton.fEnabled = false;
fRRectButton.fLabel = 'R';
fRRectButton.fEnabled = false;
+ fCircleButton.fLabel = 'O';
+ fCircleButton.fEnabled = false;
+ fCircleButton.fFill = false;
fTextButton.fLabel = 'T';
- fTextButton.fEnabled = true;
+ fTextButton.fEnabled = false;
fAnimate = true;
setAsNeeded();
}
@@ -183,12 +203,21 @@ protected:
}
void onSizeChange() SK_OVERRIDE {
+ fWeightControl.setXYWH(this->width() - 150, 30, 30, 400);
fErrorControl.setXYWH(this->width() - 100, 30, 30, 400);
fWidthControl.setXYWH(this->width() - 50, 30, 30, 400);
- fCubicButton.fBounds.setXYWH(this->width() - 50, 450, 30, 30);
- fQuadButton.fBounds.setXYWH(this->width() - 50, 500, 30, 30);
- fRRectButton.fBounds.setXYWH(this->width() - 50, 550, 30, 30);
- fTextButton.fBounds.setXYWH(this->width() - 50, 600, 30, 30);
+ int buttonOffset = 450;
+ fCubicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
+ buttonOffset += 50;
+ fConicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
+ buttonOffset += 50;
+ fQuadButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
+ buttonOffset += 50;
+ fRRectButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
+ buttonOffset += 50;
+ fCircleButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
+ buttonOffset += 50;
+ fTextButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
this->INHERITED::onSizeChange();
}
@@ -273,25 +302,26 @@ protected:
}
}
- void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, bool drawText) {
+ void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale,
+ bool drawText) {
SkRect bounds = path.getBounds();
if (bounds.isEmpty()) {
return;
}
- this->setWHZ(SkScalarCeilToInt(bounds.right()), SkScalarRoundToInt(fTextSize * 3 / 2),
- SkScalarRoundToInt(950.0f / fTextSize));
+ this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText
+ ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale),
+ SkScalarRoundToInt(950.0f / scale));
erase(fMinSurface);
SkPaint paint;
paint.setColor(0x1f1f0f0f);
- fMinSurface->getCanvas()->drawPath(path, paint);
paint.setStyle(SkPaint::kStroke_Style);
- paint.setStrokeWidth(width * fTextSize * fTextSize);
+ paint.setStrokeWidth(width * scale * scale);
paint.setColor(0x3f0f1f3f);
- fMinSurface->getCanvas()->drawPath(path, paint);
-
- this->copyMinToMax();
- fMaxSurface->draw(canvas, 0, 0, NULL);
-
+ if (drawText) {
+ fMinSurface->getCanvas()->drawPath(path, paint);
+ this->copyMinToMax();
+ fMaxSurface->draw(canvas, 0, 0, NULL);
+ }
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(1);
@@ -300,7 +330,7 @@ protected:
SkPath scaled;
SkMatrix matrix;
matrix.reset();
- matrix.setScale(950 / fTextSize, 950 / fTextSize);
+ matrix.setScale(950 / scale, 950 / scale);
if (drawText) {
path.transform(matrix, &scaled);
} else {
@@ -317,8 +347,11 @@ protected:
SkPaint p;
p.setStyle(SkPaint::kStroke_Style);
- p.setStrokeWidth(width * fTextSize * fTextSize);
-
+ if (drawText) {
+ p.setStrokeWidth(width * scale * scale);
+ } else {
+ p.setStrokeWidth(width);
+ }
p.getFillPath(path, &fill);
SkPath scaledFill;
if (drawText) {
@@ -331,6 +364,33 @@ protected:
draw_points(canvas, scaledFill, WIREFRAME_COLOR, false);
}
+ void draw_fill(SkCanvas* canvas, const SkRect& rect, SkScalar width) {
+ if (rect.isEmpty()) {
+ return;
+ }
+ SkPaint paint;
+ paint.setColor(0x1f1f0f0f);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(width);
+ SkPath path;
+ SkScalar maxSide = SkTMax(rect.width(), rect.height()) / 2;
+ SkPoint center = { rect.fLeft + maxSide, rect.fTop + maxSide };
+ path.addCircle(center.fX, center.fY, maxSide);
+ canvas->drawPath(path, paint);
+ paint.setStyle(SkPaint::kFill_Style);
+ path.reset();
+ path.addCircle(center.fX, center.fY, maxSide - width / 2);
+ paint.setColor(0x3f0f1f3f);
+ canvas->drawPath(path, paint);
+ path.reset();
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.addCircle(center.fX, center.fY, maxSide + width / 2);
+ SkRect outside = SkRect::MakeXYWH(center.fX - maxSide - width, center.fY - maxSide - width,
+ (maxSide + width) * 2, (maxSide + width) * 2);
+ path.addRect(outside);
+ canvas->drawPath(path, paint);
+ }
+
void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) {
SkPaint paint;
paint.setAntiAlias(true);
@@ -377,7 +437,8 @@ protected:
}
void setAsNeeded() {
- if (fCubicButton.fEnabled || fQuadButton.fEnabled || fRRectButton.fEnabled) {
+ if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled
+ || fRRectButton.fEnabled || fCircleButton.fEnabled) {
setForGeometry();
} else {
setForText();
@@ -392,27 +453,34 @@ protected:
path.moveTo(fPts[0]);
path.cubicTo(fPts[1], fPts[2], fPts[3]);
setForGeometry();
- draw_stroke(canvas, path, width, false);
+ draw_stroke(canvas, path, width, 950, false);
+ }
+
+ if (fConicButton.fEnabled) {
+ path.moveTo(fPts[4]);
+ path.conicTo(fPts[5], fPts[6], fWeight);
+ setForGeometry();
+ draw_stroke(canvas, path, width, 950, false);
}
if (fQuadButton.fEnabled) {
path.reset();
- path.moveTo(fPts[4]);
- path.quadTo(fPts[5], fPts[6]);
+ path.moveTo(fPts[7]);
+ path.quadTo(fPts[8], fPts[9]);
setForGeometry();
- draw_stroke(canvas, path, width, false);
+ draw_stroke(canvas, path, width, 950, false);
}
if (fRRectButton.fEnabled) {
SkScalar rad = 32;
SkRect r;
- r.set(&fPts[7], 2);
+ r.set(&fPts[10], 2);
path.reset();
SkRRect rr;
rr.setRectXY(r, rad, rad);
path.addRRect(rr);
setForGeometry();
- draw_stroke(canvas, path, width, false);
+ draw_stroke(canvas, path, width, 950, false);
path.reset();
SkRRect rr2;
@@ -426,6 +494,19 @@ protected:
canvas->drawPath(path, paint);
}
+ if (fCircleButton.fEnabled) {
+ path.reset();
+ SkRect r;
+ r.set(&fPts[12], 2);
+ path.addOval(r);
+ setForGeometry();
+ if (fCircleButton.fFill) {
+ draw_fill(canvas, r, width);
+ } else {
+ draw_stroke(canvas, path, width, 950, false);
+ }
+ }
+
if (fTextButton.fEnabled) {
path.reset();
SkPaint paint;
@@ -433,7 +514,7 @@ protected:
paint.setTextSize(fTextSize);
paint.getTextPath(fText.c_str(), fText.size(), 0, fTextSize, &path);
setForText();
- draw_stroke(canvas, path, width * fWidthScale / fTextSize, true);
+ draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true);
}
if (fAnimate) {
@@ -445,6 +526,9 @@ protected:
}
}
setAsNeeded();
+ if (fConicButton.fEnabled) {
+ draw_control(canvas, fWeightControl, fWeight, 0, 5, "weight");
+ }
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax,
"error");
@@ -453,7 +537,9 @@ protected:
kWidthMax * fWidthScale, "width");
draw_button(canvas, fQuadButton);
draw_button(canvas, fCubicButton);
+ draw_button(canvas, fConicButton);
draw_button(canvas, fRRectButton);
+ draw_button(canvas, fCircleButton);
draw_button(canvas, fTextButton);
this->inval(NULL);
}
@@ -472,9 +558,12 @@ protected:
}
}
const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
+ if (fWeightControl.contains(rectPt)) {
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1);
+ }
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
if (fErrorControl.contains(rectPt)) {
- return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1);
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 2);
}
#endif
if (fWidthControl.contains(rectPt)) {
@@ -484,17 +573,27 @@ protected:
fCubicButton.fEnabled ^= true;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4);
}
+ if (fConicButton.fBounds.contains(rectPt)) {
+ fConicButton.fEnabled ^= true;
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5);
+ }
if (fQuadButton.fBounds.contains(rectPt)) {
fQuadButton.fEnabled ^= true;
- return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5);
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6);
}
if (fRRectButton.fBounds.contains(rectPt)) {
fRRectButton.fEnabled ^= true;
- return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6);
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7);
+ }
+ if (fCircleButton.fBounds.contains(rectPt)) {
+ bool wasEnabled = fCircleButton.fEnabled;
+ fCircleButton.fEnabled = !fCircleButton.fFill;
+ fCircleButton.fFill = wasEnabled && !fCircleButton.fFill;
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 8);
}
if (fTextButton.fBounds.contains(rectPt)) {
fTextButton.fEnabled ^= true;
- return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7);
+ return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 9);
}
return this->INHERITED::onFindClickHandler(x, y, modi);
}
@@ -510,9 +609,11 @@ protected:
fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
this->inval(NULL);
+ } else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) {
+ fWeight = MapScreenYtoValue(click->fICurr.fY, fWeightControl, 0, 5);
}
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
- else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) {
+ else if (index == (int) SK_ARRAY_COUNT(fPts) + 2) {
gDebugStrokerError = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY,
fErrorControl, kStrokerErrorMin, kStrokerErrorMax));
gDebugStrokerErrorSet = true;