OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2014 Google Inc. | 2 * Copyright 2014 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrDashingEffect.h" | 8 #include "GrDashingEffect.h" |
9 | 9 |
| 10 #include "../GrAARectRenderer.h" |
| 11 |
| 12 #include "effects/GrVertexEffect.h" |
10 #include "gl/GrGLEffect.h" | 13 #include "gl/GrGLEffect.h" |
| 14 #include "gl/GrGLVertexEffect.h" |
11 #include "gl/GrGLSL.h" | 15 #include "gl/GrGLSL.h" |
12 #include "GrContext.h" | 16 #include "GrContext.h" |
13 #include "GrCoordTransform.h" | 17 #include "GrCoordTransform.h" |
| 18 #include "GrDrawTarget.h" |
14 #include "GrDrawTargetCaps.h" | 19 #include "GrDrawTargetCaps.h" |
15 #include "GrEffect.h" | 20 #include "GrEffect.h" |
| 21 #include "GrGpu.h" |
| 22 #include "GrStrokeInfo.h" |
16 #include "GrTBackendEffectFactory.h" | 23 #include "GrTBackendEffectFactory.h" |
17 #include "SkGr.h" | 24 #include "SkGr.h" |
18 | 25 |
19 /////////////////////////////////////////////////////////////////////////////// | 26 /////////////////////////////////////////////////////////////////////////////// |
20 | 27 |
| 28 // Returns whether or not the gpu can fast path the dash line effect. |
| 29 static bool can_fast_path_dash(const SkPoint pts[2], const GrStrokeInfo& strokeI
nfo, |
| 30 const GrDrawTarget& target, const SkMatrix& viewM
atrix) { |
| 31 if (target.getDrawState().getRenderTarget()->isMultisampled()) { |
| 32 return false; |
| 33 } |
| 34 |
| 35 // Pts must be either horizontal or vertical in src space |
| 36 if (pts[0].fX != pts[1].fX && pts[0].fY != pts[1].fY) { |
| 37 return false; |
| 38 } |
| 39 |
| 40 // May be able to relax this to include skew. As of now cannot do perspectiv
e |
| 41 // because of the non uniform scaling of bloating a rect |
| 42 if (!viewMatrix.preservesRightAngles()) { |
| 43 return false; |
| 44 } |
| 45 |
| 46 if (!strokeInfo.isDashed() || 2 != strokeInfo.dashCount()) { |
| 47 return false; |
| 48 } |
| 49 |
| 50 SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap(); |
| 51 // Current we do don't handle Round or Square cap dashes |
| 52 if (SkPaint::kRound_Cap == cap) { |
| 53 return false; |
| 54 } |
| 55 |
| 56 return true; |
| 57 } |
| 58 |
| 59 namespace { |
| 60 |
| 61 struct DashLineVertex { |
| 62 SkPoint fPos; |
| 63 SkPoint fDashPos; |
| 64 }; |
| 65 |
| 66 extern const GrVertexAttrib gDashLineVertexAttribs[] = { |
| 67 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBind
ing }, |
| 68 { kVec2f_GrVertexAttribType, sizeof(SkPoint), kEffect_GrVertexAttribBindin
g }, |
| 69 }; |
| 70 |
| 71 }; |
21 static void calc_dash_scaling(SkScalar* parallelScale, SkScalar* perpScale, | 72 static void calc_dash_scaling(SkScalar* parallelScale, SkScalar* perpScale, |
22 const SkMatrix& viewMatrix, const SkPoint pts[2]) { | 73 const SkMatrix& viewMatrix, const SkPoint pts[2]) { |
23 SkVector vecSrc = pts[1] - pts[0]; | 74 SkVector vecSrc = pts[1] - pts[0]; |
24 SkScalar magSrc = vecSrc.length(); | 75 SkScalar magSrc = vecSrc.length(); |
25 SkScalar invSrc = magSrc ? SkScalarInvert(magSrc) : 0; | 76 SkScalar invSrc = magSrc ? SkScalarInvert(magSrc) : 0; |
26 vecSrc.scale(invSrc); | 77 vecSrc.scale(invSrc); |
27 | 78 |
28 SkVector vecSrcPerp; | 79 SkVector vecSrcPerp; |
29 vecSrc.rotateCW(&vecSrcPerp); | 80 vecSrc.rotateCW(&vecSrcPerp); |
30 viewMatrix.mapVectors(&vecSrc, 1); | 81 viewMatrix.mapVectors(&vecSrc, 1); |
(...skipping 24 matching lines...) Expand all Loading... |
55 // Assumes phase < sum of all intervals | 106 // Assumes phase < sum of all intervals |
56 static SkScalar calc_start_adjustment(const SkPathEffect::DashInfo& info) { | 107 static SkScalar calc_start_adjustment(const SkPathEffect::DashInfo& info) { |
57 SkASSERT(info.fPhase < info.fIntervals[0] + info.fIntervals[1]); | 108 SkASSERT(info.fPhase < info.fIntervals[0] + info.fIntervals[1]); |
58 if (info.fPhase >= info.fIntervals[0] && info.fPhase != 0) { | 109 if (info.fPhase >= info.fIntervals[0] && info.fPhase != 0) { |
59 SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1]; | 110 SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1]; |
60 return srcIntervalLen - info.fPhase; | 111 return srcIntervalLen - info.fPhase; |
61 } | 112 } |
62 return 0; | 113 return 0; |
63 } | 114 } |
64 | 115 |
65 static SkScalar calc_end_adjustment(const SkPathEffect::DashInfo& info, const Sk
Point pts[2], SkScalar* endingInt) { | 116 static SkScalar calc_end_adjustment(const SkPathEffect::DashInfo& info, const Sk
Point pts[2], |
| 117 SkScalar phase, SkScalar* endingInt) { |
66 if (pts[1].fX <= pts[0].fX) { | 118 if (pts[1].fX <= pts[0].fX) { |
67 return 0; | 119 return 0; |
68 } | 120 } |
69 SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1]; | 121 SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1]; |
70 SkScalar totalLen = pts[1].fX - pts[0].fX; | 122 SkScalar totalLen = pts[1].fX - pts[0].fX; |
71 SkScalar temp = SkScalarDiv(totalLen, srcIntervalLen); | 123 SkScalar temp = SkScalarDiv(totalLen, srcIntervalLen); |
72 SkScalar numFullIntervals = SkScalarFloorToScalar(temp); | 124 SkScalar numFullIntervals = SkScalarFloorToScalar(temp); |
73 *endingInt = totalLen - numFullIntervals * srcIntervalLen + info.fPhase; | 125 *endingInt = totalLen - numFullIntervals * srcIntervalLen + phase; |
74 temp = SkScalarDiv(*endingInt, srcIntervalLen); | 126 temp = SkScalarDiv(*endingInt, srcIntervalLen); |
75 *endingInt = *endingInt - SkScalarFloorToScalar(temp) * srcIntervalLen; | 127 *endingInt = *endingInt - SkScalarFloorToScalar(temp) * srcIntervalLen; |
76 if (0 == *endingInt) { | 128 if (0 == *endingInt) { |
77 *endingInt = srcIntervalLen; | 129 *endingInt = srcIntervalLen; |
78 } | 130 } |
79 if (*endingInt > info.fIntervals[0]) { | 131 if (*endingInt > info.fIntervals[0]) { |
80 if (0 == info.fIntervals[0]) { | 132 if (0 == info.fIntervals[0]) { |
81 *endingInt -= 0.01f; // make sure we capture the last zero size pnt
(used if has caps) | 133 *endingInt -= 0.01f; // make sure we capture the last zero size pnt
(used if has caps) |
82 } | 134 } |
83 return *endingInt - info.fIntervals[0]; | 135 return *endingInt - info.fIntervals[0]; |
84 } | 136 } |
85 return 0; | 137 return 0; |
86 } | 138 } |
87 | 139 |
88 | 140 static void setup_dashed_rect(const SkRect& rect, DashLineVertex* verts, int idx
, const SkMatrix& matrix, |
89 bool GrDashingEffect::DrawDashLine(const SkPoint pts[2], const SkPaint& paint, G
rContext* context) { | 141 SkScalar offset, SkScalar bloat, SkScalar len, SkScalar s
troke) { |
90 if (context->getRenderTarget()->isMultisampled()) { | 142 |
| 143 SkScalar startDashX = offset - bloat; |
| 144 SkScalar endDashX = offset + len + bloat; |
| 145 SkScalar startDashY = -stroke - bloat; |
| 146 SkScalar endDashY = stroke + bloat; |
| 147 verts[idx].fDashPos = SkPoint::Make(startDashX , startDashY); |
| 148 verts[idx + 1].fDashPos = SkPoint::Make(startDashX, endDashY); |
| 149 verts[idx + 2].fDashPos = SkPoint::Make(endDashX, endDashY); |
| 150 verts[idx + 3].fDashPos = SkPoint::Make(endDashX, startDashY); |
| 151 |
| 152 verts[idx].fPos = SkPoint::Make(rect.fLeft, rect.fTop); |
| 153 verts[idx + 1].fPos = SkPoint::Make(rect.fLeft, rect.fBottom); |
| 154 verts[idx + 2].fPos = SkPoint::Make(rect.fRight, rect.fBottom); |
| 155 verts[idx + 3].fPos = SkPoint::Make(rect.fRight, rect.fTop); |
| 156 |
| 157 matrix.mapPointsWithStride(&verts[idx].fPos, sizeof(DashLineVertex), 4); |
| 158 } |
| 159 |
| 160 |
| 161 bool GrDashingEffect::DrawDashLine(const SkPoint pts[2], const GrPaint& paint, |
| 162 const GrStrokeInfo& strokeInfo, GrGpu* gpu, |
| 163 GrDrawTarget* target, const SkMatrix& vm) { |
| 164 |
| 165 if (!can_fast_path_dash(pts, strokeInfo, *target, vm)) { |
91 return false; | 166 return false; |
92 } | 167 } |
93 | 168 |
94 const SkMatrix& viewMatrix = context->getMatrix(); | 169 const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo(); |
95 if (!viewMatrix.preservesRightAngles()) { | 170 |
96 return false; | 171 SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap(); |
97 } | 172 |
98 | 173 SkScalar srcStrokeWidth = strokeInfo.getStrokeRec().getWidth(); |
99 const SkPathEffect* pe = paint.getPathEffect(); | |
100 SkPathEffect::DashInfo info; | |
101 SkPathEffect::DashType dashType = pe->asADash(&info); | |
102 // Must be a dash effect with 2 intervals (1 on and 1 off) | |
103 if (SkPathEffect::kDash_DashType != dashType || 2 != info.fCount) { | |
104 return false; | |
105 } | |
106 | |
107 SkPaint::Cap cap = paint.getStrokeCap(); | |
108 // Current we do don't handle Round or Square cap dashes | |
109 if (SkPaint::kRound_Cap == cap) { | |
110 return false; | |
111 } | |
112 | |
113 SkScalar srcStrokeWidth = paint.getStrokeWidth(); | |
114 | |
115 // Get all info about the dash effect | |
116 SkAutoTArray<SkScalar> intervals(info.fCount); | |
117 info.fIntervals = intervals.get(); | |
118 pe->asADash(&info); | |
119 | 174 |
120 // the phase should be normalized to be [0, sum of all intervals) | 175 // the phase should be normalized to be [0, sum of all intervals) |
121 SkASSERT(info.fPhase >= 0 && info.fPhase < info.fIntervals[0] + info.fInterv
als[1]); | 176 SkASSERT(info.fPhase >= 0 && info.fPhase < info.fIntervals[0] + info.fInterv
als[1]); |
122 | 177 |
123 SkMatrix coordTrans; | 178 SkScalar srcPhase = info.fPhase; |
124 | 179 |
125 // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[
1].fX | 180 // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[
1].fX |
126 SkMatrix srcRotInv; | 181 SkMatrix srcRotInv; |
127 SkPoint ptsRot[2]; | 182 SkPoint ptsRot[2]; |
128 if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) { | 183 if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) { |
129 align_to_x_axis(pts, &coordTrans, ptsRot); | 184 SkMatrix rotMatrix; |
130 if(!coordTrans.invert(&srcRotInv)) { | 185 align_to_x_axis(pts, &rotMatrix, ptsRot); |
| 186 if(!rotMatrix.invert(&srcRotInv)) { |
| 187 GrPrintf("Failed to create invertible rotation matrix!\n"); |
131 return false; | 188 return false; |
132 } | 189 } |
133 } else { | 190 } else { |
134 coordTrans.reset(); | |
135 srcRotInv.reset(); | 191 srcRotInv.reset(); |
136 memcpy(ptsRot, pts, 2 * sizeof(SkPoint)); | 192 memcpy(ptsRot, pts, 2 * sizeof(SkPoint)); |
137 } | 193 } |
138 | 194 |
139 GrPaint grPaint; | |
140 SkPaint2GrPaintShader(context, paint, true, &grPaint); | |
141 | |
142 bool useAA = paint.isAntiAlias(); | 195 bool useAA = paint.isAntiAlias(); |
143 | 196 |
144 // Scale corrections of intervals and stroke from view matrix | 197 // Scale corrections of intervals and stroke from view matrix |
145 SkScalar parallelScale; | 198 SkScalar parallelScale; |
146 SkScalar perpScale; | 199 SkScalar perpScale; |
147 calc_dash_scaling(¶llelScale, &perpScale, viewMatrix, ptsRot); | 200 calc_dash_scaling(¶llelScale, &perpScale, vm, ptsRot); |
148 | 201 |
149 bool hasCap = SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth; | 202 bool hasCap = SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth; |
150 | 203 |
151 // We always want to at least stroke out half a pixel on each side in device
space | 204 // We always want to at least stroke out half a pixel on each side in device
space |
152 // so 0.5f / perpScale gives us this min in src space | 205 // so 0.5f / perpScale gives us this min in src space |
153 SkScalar halfStroke = SkMaxScalar(srcStrokeWidth * 0.5f, 0.5f / perpScale); | 206 SkScalar halfSrcStroke = SkMaxScalar(srcStrokeWidth * 0.5f, 0.5f / perpScale
); |
154 | 207 |
155 SkScalar xStroke; | 208 SkScalar strokeAdj; |
156 if (!hasCap) { | 209 if (!hasCap) { |
157 xStroke = 0.f; | 210 strokeAdj = 0.f; |
158 } else { | 211 } else { |
159 xStroke = halfStroke; | 212 strokeAdj = halfSrcStroke; |
160 } | 213 } |
161 | 214 |
| 215 SkScalar startAdj = 0; |
| 216 |
| 217 SkMatrix combinedMatrix = srcRotInv; |
| 218 combinedMatrix.postConcat(vm); |
| 219 |
| 220 bool lineDone = false; |
| 221 SkRect startRect; |
| 222 bool hasStartRect = false; |
162 // If we are using AA, check to see if we are drawing a partial dash at the
start. If so | 223 // If we are using AA, check to see if we are drawing a partial dash at the
start. If so |
163 // draw it separately here and adjust our start point accordingly | 224 // draw it separately here and adjust our start point accordingly |
164 if (useAA) { | 225 if (useAA) { |
165 if (info.fPhase > 0 && info.fPhase < info.fIntervals[0]) { | 226 if (srcPhase > 0 && srcPhase < info.fIntervals[0]) { |
166 SkPoint startPts[2]; | 227 SkPoint startPts[2]; |
167 startPts[0] = ptsRot[0]; | 228 startPts[0] = ptsRot[0]; |
168 startPts[1].fY = startPts[0].fY; | 229 startPts[1].fY = startPts[0].fY; |
169 startPts[1].fX = SkMinScalar(startPts[0].fX + info.fIntervals[0] - i
nfo.fPhase, | 230 startPts[1].fX = SkMinScalar(startPts[0].fX + info.fIntervals[0] - s
rcPhase, |
170 ptsRot[1].fX); | 231 ptsRot[1].fX); |
171 SkRect startRect; | |
172 startRect.set(startPts, 2); | 232 startRect.set(startPts, 2); |
173 startRect.outset(xStroke, halfStroke); | 233 startRect.outset(strokeAdj, halfSrcStroke); |
174 context->drawRect(grPaint, startRect, NULL, &srcRotInv); | 234 |
175 | 235 hasStartRect = true; |
176 ptsRot[0].fX += info.fIntervals[0] + info.fIntervals[1] - info.fPhas
e; | 236 startAdj = info.fIntervals[0] + info.fIntervals[1] - srcPhase; |
177 info.fPhase = 0; | |
178 } | 237 } |
179 } | 238 } |
180 | 239 |
181 // adjustments for start and end of bounding rect so we only draw dash inter
vals | 240 // adjustments for start and end of bounding rect so we only draw dash inter
vals |
182 // contained in the original line segment. | 241 // contained in the original line segment. |
183 SkScalar startAdj = calc_start_adjustment(info); | 242 startAdj += calc_start_adjustment(info); |
| 243 if (startAdj != 0) { |
| 244 ptsRot[0].fX += startAdj; |
| 245 srcPhase = 0; |
| 246 } |
184 SkScalar endingInterval = 0; | 247 SkScalar endingInterval = 0; |
185 SkScalar endAdj = calc_end_adjustment(info, ptsRot, &endingInterval); | 248 SkScalar endAdj = calc_end_adjustment(info, ptsRot, srcPhase, &endingInterva
l); |
186 if (ptsRot[0].fX + startAdj >= ptsRot[1].fX - endAdj) { | 249 ptsRot[1].fX -= endAdj; |
187 // Nothing left to draw so just return | 250 if (ptsRot[0].fX >= ptsRot[1].fX) { |
188 return true; | 251 lineDone = true; |
189 } | 252 } |
190 | 253 |
| 254 SkRect endRect; |
| 255 bool hasEndRect = false; |
191 // If we are using AA, check to see if we are drawing a partial dash at then
end. If so | 256 // If we are using AA, check to see if we are drawing a partial dash at then
end. If so |
192 // draw it separately here and adjust our end point accordingly | 257 // draw it separately here and adjust our end point accordingly |
193 if (useAA) { | 258 if (useAA && !lineDone) { |
194 // If we adjusted the end then we will not be drawing a partial dash at
the end. | 259 // If we adjusted the end then we will not be drawing a partial dash at
the end. |
195 // If we didn't adjust the end point then we just need to make sure the
ending | 260 // If we didn't adjust the end point then we just need to make sure the
ending |
196 // dash isn't a full dash | 261 // dash isn't a full dash |
197 if (0 == endAdj && endingInterval != info.fIntervals[0]) { | 262 if (0 == endAdj && endingInterval != info.fIntervals[0]) { |
198 | |
199 SkPoint endPts[2]; | 263 SkPoint endPts[2]; |
200 endPts[1] = ptsRot[1]; | 264 endPts[1] = ptsRot[1]; |
201 endPts[0].fY = endPts[1].fY; | 265 endPts[0].fY = endPts[1].fY; |
202 endPts[0].fX = endPts[1].fX - endingInterval; | 266 endPts[0].fX = endPts[1].fX - endingInterval; |
203 | 267 |
204 SkRect endRect; | |
205 endRect.set(endPts, 2); | 268 endRect.set(endPts, 2); |
206 endRect.outset(xStroke, halfStroke); | 269 endRect.outset(strokeAdj, halfSrcStroke); |
207 context->drawRect(grPaint, endRect, NULL, &srcRotInv); | 270 |
208 | 271 hasEndRect = true; |
209 ptsRot[1].fX -= endingInterval + info.fIntervals[1]; | 272 endAdj = endingInterval + info.fIntervals[1]; |
| 273 |
| 274 ptsRot[1].fX -= endAdj; |
210 if (ptsRot[0].fX >= ptsRot[1].fX) { | 275 if (ptsRot[0].fX >= ptsRot[1].fX) { |
211 // Nothing left to draw so just return | 276 lineDone = true; |
212 return true; | |
213 } | 277 } |
214 } | 278 } |
215 } | 279 } |
216 coordTrans.postConcat(viewMatrix); | 280 |
217 | 281 if (startAdj != 0) { |
218 SkPoint devicePts[2]; | 282 srcPhase = 0; |
219 viewMatrix.mapPoints(devicePts, ptsRot, 2); | 283 } |
220 | 284 |
221 info.fIntervals[0] *= parallelScale; | 285 // Change the dashing info from src space into device space |
222 info.fIntervals[1] *= parallelScale; | 286 SkScalar devIntervals[2]; |
223 info.fPhase *= parallelScale; | 287 devIntervals[0] = info.fIntervals[0] * parallelScale; |
| 288 devIntervals[1] = info.fIntervals[1] * parallelScale; |
| 289 SkScalar devPhase = srcPhase * parallelScale; |
224 SkScalar strokeWidth = srcStrokeWidth * perpScale; | 290 SkScalar strokeWidth = srcStrokeWidth * perpScale; |
225 | 291 |
226 if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) { | 292 if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) { |
227 strokeWidth = 1.f; | 293 strokeWidth = 1.f; |
228 } | 294 } |
229 | 295 |
230 // Set up coordTransform for device space transforms | 296 SkScalar halfDevStroke = strokeWidth * 0.5f; |
231 // We rotate the dashed line such that it is horizontal with the start point
at smaller x | |
232 // then we translate the start point to the origin | |
233 if (devicePts[0].fY != devicePts[1].fY || devicePts[0].fX > devicePts[1].fX)
{ | |
234 SkMatrix rot; | |
235 align_to_x_axis(devicePts, &rot); | |
236 coordTrans.postConcat(rot); | |
237 } | |
238 coordTrans.postTranslate(-devicePts[0].fX, -devicePts[0].fY); | |
239 coordTrans.postTranslate(info.fIntervals[1] * 0.5f + info.fPhase, 0); | |
240 | 297 |
241 if (SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth) { | 298 if (SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth) { |
242 // add cap to on interveal and remove from off interval | 299 // add cap to on interveal and remove from off interval |
243 info.fIntervals[0] += strokeWidth; | 300 devIntervals[0] += strokeWidth; |
244 info.fIntervals[1] -= strokeWidth; | 301 devIntervals[1] -= strokeWidth; |
245 } | 302 } |
246 | 303 SkScalar startOffset = devIntervals[1] * 0.5 + devPhase; |
247 if (info.fIntervals[1] > 0.f) { | 304 |
| 305 SkScalar bloatX = useAA ? 0.5f / parallelScale : 0.f; |
| 306 SkScalar bloatY = useAA ? 0.5f / perpScale : 0.f; |
| 307 |
| 308 SkScalar devBloat = useAA ? 0.5f : 0.f; |
| 309 |
| 310 GrDrawState* drawState = target->drawState(); |
| 311 if (devIntervals[1] <= 0.f && useAA) { |
| 312 // Case when we end up drawing a solid AA rect |
| 313 // Reset the start rect to draw this single solid rect |
| 314 // but it requires to upload a new intervals uniform so we can mimic |
| 315 // one giant dash |
| 316 ptsRot[0].fX -= hasStartRect ? startAdj : 0; |
| 317 ptsRot[1].fX += hasEndRect ? endAdj : 0; |
| 318 startRect.set(ptsRot, 2); |
| 319 startRect.outset(strokeAdj, halfSrcStroke); |
| 320 hasStartRect = true; |
| 321 hasEndRect = false; |
| 322 lineDone = true; |
| 323 |
| 324 SkPoint devicePts[2]; |
| 325 vm.mapPoints(devicePts, ptsRot, 2); |
| 326 SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); |
| 327 if (hasCap) { |
| 328 lineLength += 2.f * halfDevStroke; |
| 329 } |
| 330 devIntervals[0] = lineLength; |
| 331 } |
| 332 if (devIntervals[1] > 0.f || useAA) { |
| 333 SkPathEffect::DashInfo devInfo; |
| 334 devInfo.fPhase = devPhase; |
| 335 devInfo.fCount = 2; |
| 336 devInfo.fIntervals = devIntervals; |
248 GrEffectEdgeType edgeType= useAA ? kFillAA_GrEffectEdgeType : | 337 GrEffectEdgeType edgeType= useAA ? kFillAA_GrEffectEdgeType : |
249 kFillBW_GrEffectEdgeType; | 338 kFillBW_GrEffectEdgeType; |
250 grPaint.addCoverageEffect( | 339 drawState->addCoverageEffect( |
251 GrDashingEffect::Create(edgeType, info, coordTrans, strokeWidth))->u
nref(); | 340 GrDashingEffect::Create(edgeType, devInfo, strokeWidth), 1)->unref()
; |
252 grPaint.setAntiAlias(false); | 341 } |
253 } | 342 |
254 | 343 // Set up the vertex data for the line and start/end dashes |
255 SkRect rect; | 344 drawState->setVertexAttribs<gDashLineVertexAttribs>(SK_ARRAY_COUNT(gDashLine
VertexAttribs)); |
256 bool bloat = useAA && info.fIntervals[1] > 0.f; | 345 |
257 SkScalar bloatX = bloat ? 0.5f / parallelScale : 0.f; | 346 int totalRectCnt = 0; |
258 SkScalar bloatY = bloat ? 0.5f / perpScale : 0.f; | 347 |
259 ptsRot[0].fX += startAdj; | 348 totalRectCnt += !lineDone ? 1 : 0; |
260 ptsRot[1].fX -= endAdj; | 349 totalRectCnt += hasStartRect ? 1 : 0; |
261 if (!hasCap) { | 350 totalRectCnt += hasEndRect ? 1 : 0; |
262 xStroke = 0.f; | 351 |
263 } else { | 352 GrDrawTarget::AutoReleaseGeometry geo(target, totalRectCnt * 4, 0); |
264 xStroke = halfStroke; | 353 if (!geo.succeeded()) { |
265 } | 354 GrPrintf("Failed to get space for vertices!\n"); |
266 rect.set(ptsRot, 2); | 355 return false; |
267 rect.outset(bloatX + xStroke, bloatY + halfStroke); | 356 } |
268 context->drawRect(grPaint, rect, NULL, &srcRotInv); | 357 |
269 | 358 DashLineVertex* verts = reinterpret_cast<DashLineVertex*>(geo.vertices()); |
| 359 |
| 360 int curVIdx = 0; |
| 361 |
| 362 // Draw interior part of dashed line |
| 363 if (!lineDone) { |
| 364 SkPoint devicePts[2]; |
| 365 vm.mapPoints(devicePts, ptsRot, 2); |
| 366 SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); |
| 367 if (hasCap) { |
| 368 lineLength += 2.f * halfDevStroke; |
| 369 } |
| 370 |
| 371 SkRect bounds; |
| 372 bounds.set(ptsRot[0].fX, ptsRot[0].fY, ptsRot[1].fX, ptsRot[1].fY); |
| 373 bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke); |
| 374 setup_dashed_rect(bounds, verts, curVIdx, combinedMatrix, startOffset, d
evBloat, |
| 375 lineLength, halfDevStroke); |
| 376 curVIdx += 4; |
| 377 } |
| 378 |
| 379 if (hasStartRect) { |
| 380 SkASSERT(useAA); // so that we know bloatX and bloatY have been set |
| 381 startRect.outset(bloatX, bloatY); |
| 382 setup_dashed_rect(startRect, verts, curVIdx, combinedMatrix, startOffset
, devBloat, |
| 383 devIntervals[0], halfDevStroke); |
| 384 curVIdx += 4; |
| 385 } |
| 386 |
| 387 if (hasEndRect) { |
| 388 SkASSERT(useAA); // so that we know bloatX and bloatY have been set |
| 389 endRect.outset(bloatX, bloatY); |
| 390 setup_dashed_rect(endRect, verts, curVIdx, combinedMatrix, startOffset,
devBloat, |
| 391 devIntervals[0], halfDevStroke); |
| 392 } |
| 393 |
| 394 target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer()); |
| 395 target->drawIndexedInstances(kTriangles_GrPrimitiveType, totalRectCnt, 4, 6)
; |
| 396 target->resetIndexSource(); |
270 return true; | 397 return true; |
271 } | 398 } |
272 | 399 |
273 ////////////////////////////////////////////////////////////////////////////// | 400 ////////////////////////////////////////////////////////////////////////////// |
274 | 401 |
275 class GLDashingLineEffect; | 402 class GLDashingLineEffect; |
276 | 403 |
277 class DashingLineEffect : public GrEffect { | 404 class DashingLineEffect : public GrVertexEffect { |
278 public: | 405 public: |
279 typedef SkPathEffect::DashInfo DashInfo; | 406 typedef SkPathEffect::DashInfo DashInfo; |
280 | 407 |
281 /** | 408 /** |
282 * The effect calculates the coverage for the case of a horizontal line in d
evice space. | 409 * The effect calculates the coverage for the case of a horizontal line in d
evice space. |
283 * The matrix that is passed in should be able to convert a line in source s
pace to a | 410 * The matrix that is passed in should be able to convert a line in source s
pace to a |
284 * horizontal line in device space. Additionally, the coord transform matrix
should translate | 411 * horizontal line in device space. Additionally, the coord transform matrix
should translate |
285 * the the start of line to origin, and the shift it along the positive x-ax
is by the phase | 412 * the the start of line to origin, and the shift it along the positive x-ax
is by the phase |
286 * and half the off interval. | 413 * and half the off interval. |
287 */ | 414 */ |
288 static GrEffectRef* Create(GrEffectEdgeType edgeType, const DashInfo& info, | 415 static GrEffectRef* Create(GrEffectEdgeType edgeType, const DashInfo& info, |
289 const SkMatrix& matrix, SkScalar strokeWidth); | 416 SkScalar strokeWidth); |
290 | 417 |
291 virtual ~DashingLineEffect(); | 418 virtual ~DashingLineEffect(); |
292 | 419 |
293 static const char* Name() { return "DashingEffect"; } | 420 static const char* Name() { return "DashingEffect"; } |
294 | 421 |
295 GrEffectEdgeType getEdgeType() const { return fEdgeType; } | 422 GrEffectEdgeType getEdgeType() const { return fEdgeType; } |
296 | 423 |
297 const SkRect& getRect() const { return fRect; } | 424 const SkRect& getRect() const { return fRect; } |
298 | 425 |
299 SkScalar getIntervalLength() const { return fIntervalLength; } | 426 SkScalar getIntervalLength() const { return fIntervalLength; } |
300 | 427 |
301 typedef GLDashingLineEffect GLEffect; | 428 typedef GLDashingLineEffect GLEffect; |
302 | 429 |
303 virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags
) const SK_OVERRIDE; | 430 virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags
) const SK_OVERRIDE; |
304 | 431 |
305 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE; | 432 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE; |
306 | 433 |
307 private: | 434 private: |
308 DashingLineEffect(GrEffectEdgeType edgeType, const DashInfo& info, const SkM
atrix& matrix, | 435 DashingLineEffect(GrEffectEdgeType edgeType, const DashInfo& info, SkScalar
strokeWidth); |
309 SkScalar strokeWidth); | |
310 | 436 |
311 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE; | 437 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE; |
312 | 438 |
313 GrEffectEdgeType fEdgeType; | 439 GrEffectEdgeType fEdgeType; |
314 GrCoordTransform fCoordTransform; | |
315 SkRect fRect; | 440 SkRect fRect; |
316 SkScalar fIntervalLength; | 441 SkScalar fIntervalLength; |
317 | 442 |
318 GR_DECLARE_EFFECT_TEST; | 443 GR_DECLARE_EFFECT_TEST; |
319 | 444 |
320 typedef GrEffect INHERITED; | 445 typedef GrEffect INHERITED; |
321 }; | 446 }; |
322 | 447 |
323 ////////////////////////////////////////////////////////////////////////////// | 448 ////////////////////////////////////////////////////////////////////////////// |
324 | 449 |
325 class GLDashingLineEffect : public GrGLEffect { | 450 class GLDashingLineEffect : public GrGLVertexEffect { |
326 public: | 451 public: |
327 GLDashingLineEffect(const GrBackendEffectFactory&, const GrDrawEffect&); | 452 GLDashingLineEffect(const GrBackendEffectFactory&, const GrDrawEffect&); |
328 | 453 |
329 virtual void emitCode(GrGLShaderBuilder* builder, | 454 virtual void emitCode(GrGLFullShaderBuilder* builder, |
330 const GrDrawEffect& drawEffect, | 455 const GrDrawEffect& drawEffect, |
331 EffectKey key, | 456 EffectKey key, |
332 const char* outputColor, | 457 const char* outputColor, |
333 const char* inputColor, | 458 const char* inputColor, |
334 const TransformedCoordsArray&, | 459 const TransformedCoordsArray&, |
335 const TextureSamplerArray&) SK_OVERRIDE; | 460 const TextureSamplerArray&) SK_OVERRIDE; |
336 | 461 |
337 static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&); | 462 static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&); |
338 | 463 |
339 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVER
RIDE; | 464 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVER
RIDE; |
340 | 465 |
341 private: | 466 private: |
342 GrGLUniformManager::UniformHandle fRectUniform; | 467 GrGLUniformManager::UniformHandle fRectUniform; |
343 GrGLUniformManager::UniformHandle fIntervalUniform; | 468 GrGLUniformManager::UniformHandle fIntervalUniform; |
344 SkRect fPrevRect; | 469 SkRect fPrevRect; |
345 SkScalar fPrevIntervalLength; | 470 SkScalar fPrevIntervalLength; |
346 typedef GrGLEffect INHERITED; | 471 typedef GrGLVertexEffect INHERITED; |
347 }; | 472 }; |
348 | 473 |
349 GLDashingLineEffect::GLDashingLineEffect(const GrBackendEffectFactory& factory, | 474 GLDashingLineEffect::GLDashingLineEffect(const GrBackendEffectFactory& factory, |
350 const GrDrawEffect& drawEffect) | 475 const GrDrawEffect& drawEffect) |
351 : INHERITED (factory) { | 476 : INHERITED (factory) { |
352 fPrevRect.fLeft = SK_ScalarNaN; | 477 fPrevRect.fLeft = SK_ScalarNaN; |
353 fPrevIntervalLength = SK_ScalarMax; | 478 fPrevIntervalLength = SK_ScalarMax; |
354 | 479 |
355 } | 480 } |
356 | 481 |
357 void GLDashingLineEffect::emitCode(GrGLShaderBuilder* builder, | 482 void GLDashingLineEffect::emitCode(GrGLFullShaderBuilder* builder, |
358 const GrDrawEffect& drawEffect, | 483 const GrDrawEffect& drawEffect, |
359 EffectKey key, | 484 EffectKey key, |
360 const char* outputColor, | 485 const char* outputColor, |
361 const char* inputColor, | 486 const char* inputColor, |
362 const TransformedCoordsArray& coords, | 487 const TransformedCoordsArray&, |
363 const TextureSamplerArray& samplers) { | 488 const TextureSamplerArray& samplers) { |
364 const DashingLineEffect& de = drawEffect.castEffect<DashingLineEffect>(); | 489 const DashingLineEffect& de = drawEffect.castEffect<DashingLineEffect>(); |
365 const char *rectName; | 490 const char *rectName; |
366 // The rect uniform's xyzw refer to (left + 0.5, top + 0.5, right - 0.5, bot
tom - 0.5), | 491 // The rect uniform's xyzw refer to (left + 0.5, top + 0.5, right - 0.5, bot
tom - 0.5), |
367 // respectively. | 492 // respectively. |
368 fRectUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, | 493 fRectUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility, |
369 kVec4f_GrSLType, | 494 kVec4f_GrSLType, |
370 "rect", | 495 "rect", |
371 &rectName); | 496 &rectName); |
372 const char *intervalName; | 497 const char *intervalName; |
373 // The interval uniform's refers to the total length of the interval (on + o
ff) | 498 // The interval uniform's refers to the total length of the interval (on + o
ff) |
374 fIntervalUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibili
ty, | 499 fIntervalUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibili
ty, |
375 kFloat_GrSLType, | 500 kFloat_GrSLType, |
376 "interval", | 501 "interval", |
377 &intervalName); | 502 &intervalName); |
| 503 |
| 504 const char *vsCoordName, *fsCoordName; |
| 505 builder->addVarying(kVec2f_GrSLType, "Coord", &vsCoordName, &fsCoordName); |
| 506 const SkString* attr0Name = |
| 507 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]); |
| 508 builder->vsCodeAppendf("\t%s = %s;\n", vsCoordName, attr0Name->c_str()); |
| 509 |
378 // transforms all points so that we can compare them to our test rect | 510 // transforms all points so that we can compare them to our test rect |
379 builder->fsCodeAppendf("\t\tfloat xShifted = %s.x - floor(%s.x / %s) * %s;\n
", | 511 builder->fsCodeAppendf("\t\tfloat xShifted = %s.x - floor(%s.x / %s) * %s;\n
", |
380 coords[0].c_str(), coords[0].c_str(), intervalName, i
ntervalName); | 512 fsCoordName, fsCoordName, intervalName, intervalName)
; |
381 builder->fsCodeAppendf("\t\tvec2 fragPosShifted = vec2(xShifted, %s.y);\n",
coords[0].c_str()); | 513 builder->fsCodeAppendf("\t\tvec2 fragPosShifted = vec2(xShifted, %s.y);\n",
fsCoordName); |
382 if (GrEffectEdgeTypeIsAA(de.getEdgeType())) { | 514 if (GrEffectEdgeTypeIsAA(de.getEdgeType())) { |
383 // The amount of coverage removed in x and y by the edges is computed as
a pair of negative | 515 // The amount of coverage removed in x and y by the edges is computed as
a pair of negative |
384 // numbers, xSub and ySub. | 516 // numbers, xSub and ySub. |
385 builder->fsCodeAppend("\t\tfloat xSub, ySub;\n"); | 517 builder->fsCodeAppend("\t\tfloat xSub, ySub;\n"); |
386 builder->fsCodeAppendf("\t\txSub = min(fragPosShifted.x - %s.x, 0.0);\n"
, rectName); | 518 builder->fsCodeAppendf("\t\txSub = min(fragPosShifted.x - %s.x, 0.0);\n"
, rectName); |
387 builder->fsCodeAppendf("\t\txSub += min(%s.z - fragPosShifted.x, 0.0);\n
", rectName); | 519 builder->fsCodeAppendf("\t\txSub += min(%s.z - fragPosShifted.x, 0.0);\n
", rectName); |
388 builder->fsCodeAppendf("\t\tySub = min(fragPosShifted.y - %s.y, 0.0);\n"
, rectName); | 520 builder->fsCodeAppendf("\t\tySub = min(fragPosShifted.y - %s.y, 0.0);\n"
, rectName); |
389 builder->fsCodeAppendf("\t\tySub += min(%s.w - fragPosShifted.y, 0.0);\n
", rectName); | 521 builder->fsCodeAppendf("\t\tySub += min(%s.w - fragPosShifted.y, 0.0);\n
", rectName); |
390 // Now compute coverage in x and y and multiply them to get the fraction
of the pixel | 522 // Now compute coverage in x and y and multiply them to get the fraction
of the pixel |
391 // covered. | 523 // covered. |
(...skipping 23 matching lines...) Expand all Loading... |
415 | 547 |
416 GrGLEffect::EffectKey GLDashingLineEffect::GenKey(const GrDrawEffect& drawEffect
, | 548 GrGLEffect::EffectKey GLDashingLineEffect::GenKey(const GrDrawEffect& drawEffect
, |
417 const GrGLCaps&) { | 549 const GrGLCaps&) { |
418 const DashingLineEffect& de = drawEffect.castEffect<DashingLineEffect>(); | 550 const DashingLineEffect& de = drawEffect.castEffect<DashingLineEffect>(); |
419 return de.getEdgeType(); | 551 return de.getEdgeType(); |
420 } | 552 } |
421 | 553 |
422 ////////////////////////////////////////////////////////////////////////////// | 554 ////////////////////////////////////////////////////////////////////////////// |
423 | 555 |
424 GrEffectRef* DashingLineEffect::Create(GrEffectEdgeType edgeType, const DashInfo
& info, | 556 GrEffectRef* DashingLineEffect::Create(GrEffectEdgeType edgeType, const DashInfo
& info, |
425 const SkMatrix& matrix, SkScalar strokeWidt
h) { | 557 SkScalar strokeWidth) { |
426 if (info.fCount != 2) { | 558 if (info.fCount != 2) { |
427 return NULL; | 559 return NULL; |
428 } | 560 } |
429 | 561 |
430 return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(DashingLineEffect, | 562 return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(DashingLineEffect, |
431 (edgeType, info, matrix, s
trokeWidth)))); | 563 (edgeType, info, strokeWid
th)))); |
432 } | 564 } |
433 | 565 |
434 DashingLineEffect::~DashingLineEffect() {} | 566 DashingLineEffect::~DashingLineEffect() {} |
435 | 567 |
436 void DashingLineEffect::getConstantColorComponents(GrColor* color, uint32_t* val
idFlags) const { | 568 void DashingLineEffect::getConstantColorComponents(GrColor* color, uint32_t* val
idFlags) const { |
437 *validFlags = 0; | 569 *validFlags = 0; |
438 } | 570 } |
439 | 571 |
440 const GrBackendEffectFactory& DashingLineEffect::getFactory() const { | 572 const GrBackendEffectFactory& DashingLineEffect::getFactory() const { |
441 return GrTBackendEffectFactory<DashingLineEffect>::getInstance(); | 573 return GrTBackendEffectFactory<DashingLineEffect>::getInstance(); |
442 } | 574 } |
443 | 575 |
444 DashingLineEffect::DashingLineEffect(GrEffectEdgeType edgeType, const DashInfo&
info, | 576 DashingLineEffect::DashingLineEffect(GrEffectEdgeType edgeType, const DashInfo&
info, |
445 const SkMatrix& matrix, SkScalar strokeWidth) | 577 SkScalar strokeWidth) |
446 : fEdgeType(edgeType) | 578 : fEdgeType(edgeType) { |
447 , fCoordTransform(kLocal_GrCoordSet, matrix) { | |
448 SkScalar onLen = info.fIntervals[0]; | 579 SkScalar onLen = info.fIntervals[0]; |
449 SkScalar offLen = info.fIntervals[1]; | 580 SkScalar offLen = info.fIntervals[1]; |
450 SkScalar halfOffLen = SkScalarHalf(offLen); | 581 SkScalar halfOffLen = SkScalarHalf(offLen); |
451 SkScalar halfStroke = SkScalarHalf(strokeWidth); | 582 SkScalar halfStroke = SkScalarHalf(strokeWidth); |
452 fIntervalLength = onLen + offLen; | 583 fIntervalLength = onLen + offLen; |
453 fRect.set(halfOffLen, -halfStroke, halfOffLen + onLen, halfStroke); | 584 fRect.set(halfOffLen, -halfStroke, halfOffLen + onLen, halfStroke); |
454 | 585 |
455 addCoordTransform(&fCoordTransform); | 586 this->addVertexAttrib(kVec2f_GrSLType); |
456 } | 587 } |
457 | 588 |
458 bool DashingLineEffect::onIsEqual(const GrEffect& other) const { | 589 bool DashingLineEffect::onIsEqual(const GrEffect& other) const { |
459 const DashingLineEffect& de = CastEffect<DashingLineEffect>(other); | 590 const DashingLineEffect& de = CastEffect<DashingLineEffect>(other); |
460 return (fEdgeType == de.fEdgeType && | 591 return (fEdgeType == de.fEdgeType && |
461 fCoordTransform == de.fCoordTransform && | |
462 fRect == de.fRect && | 592 fRect == de.fRect && |
463 fIntervalLength == de.fIntervalLength); | 593 fIntervalLength == de.fIntervalLength); |
464 } | 594 } |
465 | 595 |
466 GR_DEFINE_EFFECT_TEST(DashingLineEffect); | 596 GR_DEFINE_EFFECT_TEST(DashingLineEffect); |
467 | 597 |
468 GrEffectRef* DashingLineEffect::TestCreate(SkRandom* random, | 598 GrEffectRef* DashingLineEffect::TestCreate(SkRandom* random, |
469 GrContext*, | 599 GrContext*, |
470 const GrDrawTargetCaps& caps, | 600 const GrDrawTargetCaps& caps, |
471 GrTexture*[]) { | 601 GrTexture*[]) { |
472 GrEffectRef* effect; | 602 GrEffectRef* effect; |
473 SkMatrix m; | |
474 m.reset(); | |
475 GrEffectEdgeType edgeType = static_cast<GrEffectEdgeType>(random->nextULessT
han( | 603 GrEffectEdgeType edgeType = static_cast<GrEffectEdgeType>(random->nextULessT
han( |
476 kGrEffectEdgeTypeCnt)); | 604 kGrEffectEdgeTypeCnt)); |
477 SkScalar strokeWidth = random->nextRangeScalar(0, 100.f); | 605 SkScalar strokeWidth = random->nextRangeScalar(0, 100.f); |
478 DashInfo info; | 606 DashInfo info; |
479 info.fCount = 2; | 607 info.fCount = 2; |
480 SkAutoTArray<SkScalar> intervals(info.fCount); | 608 SkAutoTArray<SkScalar> intervals(info.fCount); |
481 info.fIntervals = intervals.get(); | 609 info.fIntervals = intervals.get(); |
482 info.fIntervals[0] = random->nextRangeScalar(0, 10.f); | 610 info.fIntervals[0] = random->nextRangeScalar(0, 10.f); |
483 info.fIntervals[1] = random->nextRangeScalar(0, 10.f); | 611 info.fIntervals[1] = random->nextRangeScalar(0, 10.f); |
484 info.fPhase = random->nextRangeScalar(0, info.fIntervals[0] + info.fInterval
s[1]); | 612 info.fPhase = random->nextRangeScalar(0, info.fIntervals[0] + info.fInterval
s[1]); |
485 | 613 |
486 effect = DashingLineEffect::Create(edgeType, info, m, strokeWidth); | 614 effect = DashingLineEffect::Create(edgeType, info, strokeWidth); |
487 return effect; | 615 return effect; |
488 } | 616 } |
489 | 617 |
490 ////////////////////////////////////////////////////////////////////////////// | 618 ////////////////////////////////////////////////////////////////////////////// |
491 | 619 |
492 GrEffectRef* GrDashingEffect::Create(GrEffectEdgeType edgeType, const SkPathEffe
ct::DashInfo& info, | 620 GrEffectRef* GrDashingEffect::Create(GrEffectEdgeType edgeType, const SkPathEffe
ct::DashInfo& info, |
493 const SkMatrix& matrix, SkScalar strokeWidt
h) { | 621 SkScalar strokeWidth) { |
494 return DashingLineEffect::Create(edgeType, info, matrix, strokeWidth); | 622 return DashingLineEffect::Create(edgeType, info, strokeWidth); |
495 } | 623 } |
OLD | NEW |