commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2013 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | #include "GrOvalRenderer.h" |
| 9 | |
| 10 | #include "effects/GrCircleEdgeEffect.h" |
| 11 | #include "effects/GrEllipseEdgeEffect.h" |
| 12 | |
| 13 | #include "GrDrawState.h" |
| 14 | #include "GrDrawTarget.h" |
| 15 | #include "SkStrokeRec.h" |
| 16 | |
| 17 | SK_DEFINE_INST_COUNT(GrOvalRenderer) |
| 18 | |
| 19 | namespace { |
| 20 | |
| 21 | struct CircleVertex { |
| 22 | GrPoint fPos; |
| 23 | GrPoint fCenter; |
| 24 | SkScalar fOuterRadius; |
| 25 | SkScalar fInnerRadius; |
| 26 | }; |
| 27 | |
| 28 | struct EllipseVertex { |
| 29 | GrPoint fPos; |
| 30 | GrPoint fCenter; |
| 31 | SkScalar fOuterXRadius; |
| 32 | SkScalar fOuterXYRatio; |
| 33 | SkScalar fInnerXRadius; |
| 34 | SkScalar fInnerXYRatio; |
| 35 | }; |
| 36 | |
| 37 | inline bool circle_stays_circle(const SkMatrix& m) { |
| 38 | return m.isSimilarity(); |
| 39 | } |
| 40 | |
| 41 | } |
| 42 | |
skia.committer@gmail.com | 7e32851 | 2013-03-23 07:01:28 +0000 | [diff] [blame] | 43 | bool GrOvalRenderer::drawOval(GrDrawTarget* target, const GrContext* context, const GrPaint& paint, |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 44 | const GrRect& oval, const SkStrokeRec& stroke) |
| 45 | { |
| 46 | if (!paint.isAntiAlias()) { |
| 47 | return false; |
| 48 | } |
| 49 | |
| 50 | const SkMatrix& vm = context->getMatrix(); |
| 51 | |
skia.committer@gmail.com | 7e32851 | 2013-03-23 07:01:28 +0000 | [diff] [blame] | 52 | // we can draw circles |
| 53 | if (SkScalarNearlyEqual(oval.width(), oval.height()) |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 54 | && circle_stays_circle(vm)) { |
| 55 | drawCircle(target, paint, oval, stroke); |
| 56 | |
| 57 | // and axis-aligned ellipses only |
| 58 | } else if (vm.rectStaysRect()) { |
| 59 | drawEllipse(target, paint, oval, stroke); |
| 60 | |
| 61 | } else { |
| 62 | return false; |
| 63 | } |
| 64 | |
| 65 | return true; |
| 66 | } |
| 67 | |
skia.committer@gmail.com | 7e32851 | 2013-03-23 07:01:28 +0000 | [diff] [blame] | 68 | void GrOvalRenderer::drawCircle(GrDrawTarget* target, |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 69 | const GrPaint& paint, |
| 70 | const GrRect& circle, |
| 71 | const SkStrokeRec& stroke) |
| 72 | { |
| 73 | GrDrawState* drawState = target->drawState(); |
| 74 | |
| 75 | const SkMatrix& vm = drawState->getViewMatrix(); |
| 76 | GrPoint center = GrPoint::Make(circle.centerX(), circle.centerY()); |
| 77 | vm.mapPoints(¢er, 1); |
| 78 | SkScalar radius = vm.mapRadius(SkScalarHalf(circle.width())); |
| 79 | SkScalar strokeWidth = vm.mapRadius(stroke.getWidth()); |
| 80 | |
| 81 | GrDrawState::AutoDeviceCoordDraw adcd(drawState); |
| 82 | if (!adcd.succeeded()) { |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | // position + edge |
| 87 | static const GrVertexAttrib kVertexAttribs[] = { |
| 88 | {kVec2f_GrVertexAttribType, 0}, |
| 89 | {kVec4f_GrVertexAttribType, sizeof(GrPoint)} |
| 90 | }; |
| 91 | drawState->setVertexAttribs(kVertexAttribs, SK_ARRAY_COUNT(kVertexAttribs)); |
| 92 | drawState->setAttribIndex(GrDrawState::kPosition_AttribIndex, 0); |
| 93 | GrAssert(sizeof(CircleVertex) == drawState->getVertexSize()); |
| 94 | |
| 95 | GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0); |
| 96 | if (!geo.succeeded()) { |
| 97 | GrPrintf("Failed to get space for vertices!\n"); |
| 98 | return; |
| 99 | } |
| 100 | |
| 101 | CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices()); |
| 102 | |
| 103 | SkStrokeRec::Style style = stroke.getStyle(); |
| 104 | bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style); |
| 105 | enum { |
| 106 | // the edge effects share this stage with glyph rendering |
| 107 | // (kGlyphMaskStage in GrTextContext) && SW path rendering |
| 108 | // (kPathMaskStage in GrSWMaskHelper) |
| 109 | kEdgeEffectStage = GrPaint::kTotalStages, |
| 110 | }; |
| 111 | drawState->setAttribBindings(GrDrawState::kDefault_AttribBindings); |
skia.committer@gmail.com | 7e32851 | 2013-03-23 07:01:28 +0000 | [diff] [blame] | 112 | |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 113 | GrEffectRef* effect = GrCircleEdgeEffect::Create(isStroked); |
| 114 | static const int kCircleEdgeAttrIndex = 1; |
| 115 | drawState->setEffect(kEdgeEffectStage, effect, kCircleEdgeAttrIndex)->unref(); |
| 116 | |
| 117 | SkScalar innerRadius = 0.0f; |
| 118 | SkScalar outerRadius = radius; |
| 119 | SkScalar halfWidth = 0; |
| 120 | if (style != SkStrokeRec::kFill_Style) { |
| 121 | if (SkScalarNearlyZero(strokeWidth)) { |
| 122 | halfWidth = SK_ScalarHalf; |
| 123 | } else { |
| 124 | halfWidth = SkScalarHalf(strokeWidth); |
| 125 | } |
| 126 | |
| 127 | outerRadius += halfWidth; |
| 128 | if (isStroked) { |
| 129 | innerRadius = SkMaxScalar(0, radius - halfWidth); |
| 130 | } |
| 131 | } |
| 132 | |
bsalomon@google.com | 58e30fe | 2013-04-01 19:01:20 +0000 | [diff] [blame^] | 133 | // The radii are outset for two reasons. First, it allows the shader to simply perform |
| 134 | // clamp(distance-to-center - radius, 0, 1). Second, the outer radius is used to compute the |
| 135 | // verts of the bounding box that is rendered and the outset ensures the box will cover all |
| 136 | // pixels partially covered by the circle. |
| 137 | outerRadius += SK_ScalarHalf; |
| 138 | innerRadius -= SK_ScalarHalf; |
| 139 | |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 140 | for (int i = 0; i < 4; ++i) { |
| 141 | verts[i].fCenter = center; |
bsalomon@google.com | 58e30fe | 2013-04-01 19:01:20 +0000 | [diff] [blame^] | 142 | verts[i].fOuterRadius = outerRadius; |
| 143 | verts[i].fInnerRadius = innerRadius; |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 144 | } |
| 145 | |
commit-bot@chromium.org | bb5c465 | 2013-04-01 12:49:31 +0000 | [diff] [blame] | 146 | SkRect bounds = SkRect::MakeLTRB( |
bsalomon@google.com | 58e30fe | 2013-04-01 19:01:20 +0000 | [diff] [blame^] | 147 | center.fX - outerRadius, |
| 148 | center.fY - outerRadius, |
| 149 | center.fX + outerRadius, |
| 150 | center.fY + outerRadius |
commit-bot@chromium.org | bb5c465 | 2013-04-01 12:49:31 +0000 | [diff] [blame] | 151 | ); |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 152 | |
commit-bot@chromium.org | bb5c465 | 2013-04-01 12:49:31 +0000 | [diff] [blame] | 153 | verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop); |
| 154 | verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); |
| 155 | verts[2].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom); |
| 156 | verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom); |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 157 | |
commit-bot@chromium.org | bb5c465 | 2013-04-01 12:49:31 +0000 | [diff] [blame] | 158 | target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds); |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 159 | } |
| 160 | |
skia.committer@gmail.com | 7e32851 | 2013-03-23 07:01:28 +0000 | [diff] [blame] | 161 | void GrOvalRenderer::drawEllipse(GrDrawTarget* target, |
commit-bot@chromium.org | 8131283 | 2013-03-22 18:34:09 +0000 | [diff] [blame] | 162 | const GrPaint& paint, |
| 163 | const GrRect& ellipse, |
| 164 | const SkStrokeRec& stroke) |
| 165 | { |
| 166 | GrDrawState* drawState = target->drawState(); |
| 167 | #ifdef SK_DEBUG |
| 168 | { |
| 169 | // we should have checked for this previously |
| 170 | bool isAxisAlignedEllipse = drawState->getViewMatrix().rectStaysRect(); |
| 171 | SkASSERT(paint.isAntiAlias() && isAxisAlignedEllipse); |
| 172 | } |
| 173 | #endif |
| 174 | |
| 175 | const SkMatrix& vm = drawState->getViewMatrix(); |
| 176 | GrPoint center = GrPoint::Make(ellipse.centerX(), ellipse.centerY()); |
| 177 | vm.mapPoints(¢er, 1); |
| 178 | SkRect xformedRect; |
| 179 | vm.mapRect(&xformedRect, ellipse); |
| 180 | |
| 181 | GrDrawState::AutoDeviceCoordDraw adcd(drawState); |
| 182 | if (!adcd.succeeded()) { |
| 183 | return; |
| 184 | } |
| 185 | |
| 186 | // position + edge |
| 187 | static const GrVertexAttrib kVertexAttribs[] = { |
| 188 | {kVec2f_GrVertexAttribType, 0}, |
| 189 | {kVec2f_GrVertexAttribType, sizeof(GrPoint)}, |
| 190 | {kVec4f_GrVertexAttribType, 2*sizeof(GrPoint)} |
| 191 | }; |
| 192 | drawState->setVertexAttribs(kVertexAttribs, SK_ARRAY_COUNT(kVertexAttribs)); |
| 193 | drawState->setAttribIndex(GrDrawState::kPosition_AttribIndex, 0); |
| 194 | GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize()); |
| 195 | |
| 196 | GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0); |
| 197 | if (!geo.succeeded()) { |
| 198 | GrPrintf("Failed to get space for vertices!\n"); |
| 199 | return; |
| 200 | } |
| 201 | |
| 202 | EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices()); |
| 203 | |
| 204 | SkStrokeRec::Style style = stroke.getStyle(); |
| 205 | bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style); |
| 206 | enum { |
| 207 | // the edge effects share this stage with glyph rendering |
| 208 | // (kGlyphMaskStage in GrTextContext) && SW path rendering |
| 209 | // (kPathMaskStage in GrSWMaskHelper) |
| 210 | kEdgeEffectStage = GrPaint::kTotalStages, |
| 211 | }; |
| 212 | drawState->setAttribBindings(GrDrawState::kDefault_AttribBindings); |
| 213 | |
| 214 | GrEffectRef* effect = GrEllipseEdgeEffect::Create(isStroked); |
| 215 | static const int kEllipseCenterAttrIndex = 1; |
| 216 | static const int kEllipseEdgeAttrIndex = 2; |
| 217 | drawState->setEffect(kEdgeEffectStage, effect, |
| 218 | kEllipseCenterAttrIndex, kEllipseEdgeAttrIndex)->unref(); |
| 219 | |
| 220 | SkScalar xRadius = SkScalarHalf(xformedRect.width()); |
| 221 | SkScalar yRadius = SkScalarHalf(xformedRect.height()); |
| 222 | SkScalar innerXRadius = 0.0f; |
| 223 | SkScalar innerRatio = 1.0f; |
| 224 | |
| 225 | if (SkStrokeRec::kFill_Style != style) { |
| 226 | SkScalar strokeWidth = stroke.getWidth(); |
| 227 | |
| 228 | // do (potentially) anisotropic mapping |
| 229 | SkVector scaledStroke; |
| 230 | scaledStroke.set(strokeWidth, strokeWidth); |
| 231 | vm.mapVectors(&scaledStroke, 1); |
| 232 | |
| 233 | if (SkScalarNearlyZero(scaledStroke.length())) { |
| 234 | scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); |
| 235 | } else { |
| 236 | scaledStroke.scale(0.5f); |
| 237 | } |
| 238 | |
| 239 | // this is legit only if scale & translation (which should be the case at the moment) |
| 240 | if (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style) { |
| 241 | SkScalar innerYRadius = SkMaxScalar(0, yRadius - scaledStroke.fY); |
| 242 | if (innerYRadius > SK_ScalarNearlyZero) { |
| 243 | innerXRadius = SkMaxScalar(0, xRadius - scaledStroke.fX); |
| 244 | innerRatio = innerXRadius/innerYRadius; |
| 245 | } |
| 246 | } |
| 247 | xRadius += scaledStroke.fX; |
| 248 | yRadius += scaledStroke.fY; |
| 249 | } |
| 250 | |
| 251 | SkScalar outerRatio = SkScalarDiv(xRadius, yRadius); |
| 252 | |
| 253 | for (int i = 0; i < 4; ++i) { |
| 254 | verts[i].fCenter = center; |
| 255 | verts[i].fOuterXRadius = xRadius + 0.5f; |
| 256 | verts[i].fOuterXYRatio = outerRatio; |
| 257 | verts[i].fInnerXRadius = innerXRadius - 0.5f; |
| 258 | verts[i].fInnerXYRatio = innerRatio; |
| 259 | } |
| 260 | |
| 261 | SkScalar L = -xRadius; |
| 262 | SkScalar R = +xRadius; |
| 263 | SkScalar T = -yRadius; |
| 264 | SkScalar B = +yRadius; |
| 265 | |
| 266 | // We've extended the outer x radius out half a pixel to antialias. |
| 267 | // Expand the drawn rect here so all the pixels will be captured. |
| 268 | L += center.fX - SK_ScalarHalf; |
| 269 | R += center.fX + SK_ScalarHalf; |
| 270 | T += center.fY - SK_ScalarHalf; |
| 271 | B += center.fY + SK_ScalarHalf; |
| 272 | |
| 273 | verts[0].fPos = SkPoint::Make(L, T); |
| 274 | verts[1].fPos = SkPoint::Make(R, T); |
| 275 | verts[2].fPos = SkPoint::Make(L, B); |
| 276 | verts[3].fPos = SkPoint::Make(R, B); |
| 277 | |
| 278 | target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4); |
| 279 | } |