OLD | NEW |
| (Empty) |
1 | |
2 /* | |
3 * Copyright 2012 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 #include "SkBBoxRecord.h" | |
10 #include "SkPatchUtils.h" | |
11 | |
12 #include "SkTextBlob.h" | |
13 | |
14 SkBBoxRecord::~SkBBoxRecord() { | |
15 fSaveStack.deleteAll(); | |
16 } | |
17 | |
18 void SkBBoxRecord::drawOval(const SkRect& rect, const SkPaint& paint) { | |
19 if (this->transformBounds(rect, &paint)) { | |
20 INHERITED::drawOval(rect, paint); | |
21 } | |
22 } | |
23 | |
24 void SkBBoxRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) { | |
25 if (this->transformBounds(rrect.rect(), &paint)) { | |
26 INHERITED::drawRRect(rrect, paint); | |
27 } | |
28 } | |
29 | |
30 void SkBBoxRecord::drawRect(const SkRect& rect, const SkPaint& paint) { | |
31 if (this->transformBounds(rect, &paint)) { | |
32 INHERITED::drawRect(rect, paint); | |
33 } | |
34 } | |
35 | |
36 void SkBBoxRecord::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, | |
37 const SkPaint& paint) { | |
38 if (this->transformBounds(outer.rect(), &paint)) { | |
39 this->INHERITED::onDrawDRRect(outer, inner, paint); | |
40 } | |
41 } | |
42 | |
43 void SkBBoxRecord::drawPath(const SkPath& path, const SkPaint& paint) { | |
44 if (path.isInverseFillType()) { | |
45 // If path is inverse filled, use the current clip bounds as the | |
46 // path's device-space bounding box. | |
47 SkIRect clipBounds; | |
48 if (this->getClipDeviceBounds(&clipBounds)) { | |
49 this->handleBBox(SkRect::Make(clipBounds)); | |
50 INHERITED::drawPath(path, paint); | |
51 } | |
52 } else if (this->transformBounds(path.getBounds(), &paint)) { | |
53 INHERITED::drawPath(path, paint); | |
54 } | |
55 } | |
56 | |
57 void SkBBoxRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts[], | |
58 const SkPaint& paint) { | |
59 SkRect bbox; | |
60 bbox.set(pts, SkToInt(count)); | |
61 // Small min width value, just to ensure hairline point bounding boxes aren'
t empty. | |
62 // Even though we know hairline primitives are drawn one pixel wide, we do n
ot use a | |
63 // minimum of 1 because the playback scale factor is unknown at record time.
Later | |
64 // outsets will take care of adding additional padding for antialiasing and
rounding out | |
65 // to integer device coordinates, guaranteeing that the rasterized pixels wi
ll be included | |
66 // in the computed bounds. | |
67 // Note: The device coordinate outset in SkBBoxHierarchyRecord::handleBBox i
s currently | |
68 // done in the recording coordinate space, which is wrong. | |
69 // http://code.google.com/p/skia/issues/detail?id=1021 | |
70 static const SkScalar kMinWidth = 0.01f; | |
71 SkScalar halfStrokeWidth = SkMaxScalar(paint.getStrokeWidth(), kMinWidth) /
2; | |
72 bbox.outset(halfStrokeWidth, halfStrokeWidth); | |
73 if (this->transformBounds(bbox, &paint)) { | |
74 INHERITED::drawPoints(mode, count, pts, paint); | |
75 } | |
76 } | |
77 | |
78 void SkBBoxRecord::drawPaint(const SkPaint& paint) { | |
79 SkRect bbox; | |
80 if (this->getClipBounds(&bbox)) { | |
81 if (this->transformBounds(bbox, &paint)) { | |
82 INHERITED::drawPaint(paint); | |
83 } | |
84 } | |
85 } | |
86 | |
87 void SkBBoxRecord::clear(SkColor color) { | |
88 SkISize size = this->getDeviceSize(); | |
89 SkRect bbox = {0, 0, SkIntToScalar(size.width()), SkIntToScalar(size.height(
))}; | |
90 this->handleBBox(bbox); | |
91 INHERITED::clear(color); | |
92 } | |
93 | |
94 void SkBBoxRecord::onDrawText(const void* text, size_t byteLength, SkScalar x, S
kScalar y, | |
95 const SkPaint& paint) { | |
96 SkRect bbox; | |
97 paint.measureText(text, byteLength, &bbox); | |
98 SkPaint::FontMetrics metrics; | |
99 paint.getFontMetrics(&metrics); | |
100 | |
101 // Vertical and aligned text need to be offset | |
102 if (paint.isVerticalText()) { | |
103 SkScalar h = bbox.fBottom - bbox.fTop; | |
104 if (paint.getTextAlign() == SkPaint::kCenter_Align) { | |
105 bbox.fTop -= h / 2; | |
106 bbox.fBottom -= h / 2; | |
107 } | |
108 // Pad top and bottom with max extents from FontMetrics | |
109 bbox.fBottom += metrics.fBottom; | |
110 bbox.fTop += metrics.fTop; | |
111 } else { | |
112 SkScalar w = bbox.fRight - bbox.fLeft; | |
113 if (paint.getTextAlign() == SkPaint::kCenter_Align) { | |
114 bbox.fLeft -= w / 2; | |
115 bbox.fRight -= w / 2; | |
116 } else if (paint.getTextAlign() == SkPaint::kRight_Align) { | |
117 bbox.fLeft -= w; | |
118 bbox.fRight -= w; | |
119 } | |
120 // Set vertical bounds to max extents from font metrics | |
121 bbox.fTop = metrics.fTop; | |
122 bbox.fBottom = metrics.fBottom; | |
123 } | |
124 | |
125 // Pad horizontal bounds on each side by half of max vertical extents (this
is sort of | |
126 // arbitrary, but seems to produce reasonable results, if there were a way o
f getting max | |
127 // glyph X-extents to pad by, that may be better here, but FontMetrics fXMin
and fXMax seem | |
128 // incorrect on most platforms (too small in Linux, never even set in Window
s). | |
129 SkScalar pad = (metrics.fBottom - metrics.fTop) / 2; | |
130 bbox.fLeft -= pad; | |
131 bbox.fRight += pad; | |
132 | |
133 bbox.fLeft += x; | |
134 bbox.fRight += x; | |
135 bbox.fTop += y; | |
136 bbox.fBottom += y; | |
137 if (this->transformBounds(bbox, &paint)) { | |
138 INHERITED::onDrawText(text, byteLength, x, y, paint); | |
139 } | |
140 } | |
141 | |
142 void SkBBoxRecord::drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar to
p, | |
143 const SkPaint* paint) { | |
144 SkRect bbox = {left, top, left + bitmap.width(), top + bitmap.height()}; | |
145 if (this->transformBounds(bbox, paint)) { | |
146 INHERITED::drawBitmap(bitmap, left, top, paint); | |
147 } | |
148 } | |
149 | |
150 void SkBBoxRecord::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* sr
c, | |
151 const SkRect& dst, const SkPaint* paint, | |
152 DrawBitmapRectFlags flags) { | |
153 if (this->transformBounds(dst, paint)) { | |
154 INHERITED::drawBitmapRectToRect(bitmap, src, dst, paint, flags); | |
155 } | |
156 } | |
157 | |
158 void SkBBoxRecord::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& mat, | |
159 const SkPaint* paint) { | |
160 SkMatrix m = mat; | |
161 SkRect bbox = {0, 0, SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.hei
ght())}; | |
162 m.mapRect(&bbox); | |
163 if (this->transformBounds(bbox, paint)) { | |
164 INHERITED::drawBitmapMatrix(bitmap, mat, paint); | |
165 } | |
166 } | |
167 | |
168 void SkBBoxRecord::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, | |
169 const SkRect& dst, const SkPaint* paint) { | |
170 if (this->transformBounds(dst, paint)) { | |
171 INHERITED::drawBitmapNine(bitmap, center, dst, paint); | |
172 } | |
173 } | |
174 | |
175 // Hack to work-around https://code.google.com/p/chromium/issues/detail?id=37378
5 | |
176 // This logic assums that 'pad' is enough to add to the left and right to accoun
t for | |
177 // big glyphs. For the font in question (a logo font) the glyphs is much wider t
han just | |
178 // the pointsize (approx 3x wider). | |
179 // As a temp work-around, we scale-up pad. | |
180 // A more correct fix might be to add fontmetrics.fMaxX, but we don't have that
value in hand | |
181 // at the moment, and (possibly) the value in the font may not be accurate (but
who knows). | |
182 // | |
183 static SkScalar hack_373785_amend_pad(SkScalar pad) { | |
184 return pad * 4; | |
185 } | |
186 | |
187 void SkBBoxRecord::onDrawPosText(const void* text, size_t byteLength, const SkPo
int pos[], | |
188 const SkPaint& paint) { | |
189 SkRect bbox; | |
190 bbox.set(pos, paint.countText(text, byteLength)); | |
191 SkPaint::FontMetrics metrics; | |
192 paint.getFontMetrics(&metrics); | |
193 bbox.fTop += metrics.fTop; | |
194 bbox.fBottom += metrics.fBottom; | |
195 | |
196 // pad on left and right by half of max vertical glyph extents | |
197 SkScalar pad = (metrics.fTop - metrics.fBottom) / 2; | |
198 pad = hack_373785_amend_pad(pad); | |
199 bbox.fLeft += pad; | |
200 bbox.fRight -= pad; | |
201 | |
202 if (this->transformBounds(bbox, &paint)) { | |
203 INHERITED::onDrawPosText(text, byteLength, pos, paint); | |
204 } | |
205 } | |
206 | |
207 void SkBBoxRecord::onDrawPosTextH(const void* text, size_t byteLength, const SkS
calar xpos[], | |
208 SkScalar constY, const SkPaint& paint) { | |
209 size_t numChars = paint.countText(text, byteLength); | |
210 if (numChars == 0) { | |
211 return; | |
212 } | |
213 | |
214 const SkFlatData* flatPaintData = this->getFlatPaintData(paint); | |
215 WriteTopBot(paint, *flatPaintData); | |
216 | |
217 SkScalar top = flatPaintData->topBot()[0]; | |
218 SkScalar bottom = flatPaintData->topBot()[1]; | |
219 SkScalar pad = top - bottom; | |
220 | |
221 SkRect bbox; | |
222 bbox.fLeft = SK_ScalarMax; | |
223 bbox.fRight = SK_ScalarMin; | |
224 | |
225 for (size_t i = 0; i < numChars; ++i) { | |
226 if (xpos[i] < bbox.fLeft) { | |
227 bbox.fLeft = xpos[i]; | |
228 } | |
229 if (xpos[i] > bbox.fRight) { | |
230 bbox.fRight = xpos[i]; | |
231 } | |
232 } | |
233 | |
234 // pad horizontally by max glyph height | |
235 pad = hack_373785_amend_pad(pad); | |
236 bbox.fLeft += pad; | |
237 bbox.fRight -= pad; | |
238 | |
239 bbox.fTop = top + constY; | |
240 bbox.fBottom = bottom + constY; | |
241 | |
242 if (!this->transformBounds(bbox, &paint)) { | |
243 return; | |
244 } | |
245 // This is the equivalent of calling: | |
246 // INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); | |
247 // but we filled our flat paint beforehand so that we could get font metrics
. | |
248 drawPosTextHImpl(text, byteLength, xpos, constY, paint, flatPaintData); | |
249 } | |
250 | |
251 void SkBBoxRecord::drawSprite(const SkBitmap& bitmap, int left, int top, | |
252 const SkPaint* paint) { | |
253 SkRect bbox; | |
254 bbox.set(SkIRect::MakeXYWH(left, top, bitmap.width(), bitmap.height())); | |
255 this->handleBBox(bbox); // directly call handleBBox, matrix is ignored | |
256 INHERITED::drawSprite(bitmap, left, top, paint); | |
257 } | |
258 | |
259 void SkBBoxRecord::onDrawTextOnPath(const void* text, size_t byteLength, const S
kPath& path, | |
260 const SkMatrix* matrix, const SkPaint& paint
) { | |
261 SkRect bbox = path.getBounds(); | |
262 SkPaint::FontMetrics metrics; | |
263 paint.getFontMetrics(&metrics); | |
264 | |
265 // pad out all sides by the max glyph height above baseline | |
266 SkScalar pad = metrics.fTop; | |
267 bbox.fLeft += pad; | |
268 bbox.fRight -= pad; | |
269 bbox.fTop += pad; | |
270 bbox.fBottom -= pad; | |
271 | |
272 if (this->transformBounds(bbox, &paint)) { | |
273 INHERITED::onDrawTextOnPath(text, byteLength, path, matrix, paint); | |
274 } | |
275 } | |
276 | |
277 void SkBBoxRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y
, | |
278 const SkPaint& paint) { | |
279 SkRect bbox = blob->bounds(); | |
280 bbox.offset(x, y); | |
281 // FIXME: implement implicit blob bounds! | |
282 if (bbox.isEmpty()) { | |
283 this->getClipBounds(&bbox); | |
284 } | |
285 | |
286 if (this->transformBounds(bbox, &paint)) { | |
287 INHERITED::onDrawTextBlob(blob, x, y, paint); | |
288 } | |
289 } | |
290 | |
291 void SkBBoxRecord::drawVertices(VertexMode mode, int vertexCount, | |
292 const SkPoint vertices[], const SkPoint texs[], | |
293 const SkColor colors[], SkXfermode* xfer, | |
294 const uint16_t indices[], int indexCount, | |
295 const SkPaint& paint) { | |
296 SkRect bbox; | |
297 bbox.set(vertices, vertexCount); | |
298 if (this->transformBounds(bbox, &paint)) { | |
299 INHERITED::drawVertices(mode, vertexCount, vertices, texs, | |
300 colors, xfer, indices, indexCount, paint); | |
301 } | |
302 } | |
303 | |
304 void SkBBoxRecord::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4]
, | |
305 const SkPoint texCoords[4], SkXfermode* xmode, | |
306 const SkPaint& paint) { | |
307 SkRect bbox; | |
308 bbox.set(cubics, SkPatchUtils::kNumCtrlPts); | |
309 if (this->transformBounds(bbox, &paint)) { | |
310 INHERITED::onDrawPatch(cubics, colors, texCoords, xmode, paint); | |
311 } | |
312 } | |
313 | |
314 void SkBBoxRecord::onDrawPicture(const SkPicture* picture, const SkMatrix* matri
x, | |
315 const SkPaint* paint) { | |
316 SkRect bounds = picture->cullRect(); | |
317 // todo: wonder if we should allow passing an optional matrix to transformBo
unds so we don't | |
318 // end up transforming the rect twice. | |
319 if (matrix) { | |
320 matrix->mapRect(&bounds); | |
321 } | |
322 if (this->transformBounds(bounds, paint)) { | |
323 this->INHERITED::onDrawPicture(picture, matrix, paint); | |
324 } | |
325 } | |
326 | |
327 void SkBBoxRecord::willSave() { | |
328 fSaveStack.push(NULL); | |
329 this->INHERITED::willSave(); | |
330 } | |
331 | |
332 SkCanvas::SaveLayerStrategy SkBBoxRecord::willSaveLayer(const SkRect* bounds, | |
333 const SkPaint* paint, | |
334 SaveFlags flags) { | |
335 // Image filters can affect the effective bounds of primitives drawn inside
saveLayer(). | |
336 // Copy the paint so we can compute the modified bounds in transformBounds()
. | |
337 fSaveStack.push(paint && paint->getImageFilter() ? new SkPaint(*paint) : NUL
L); | |
338 return this->INHERITED::willSaveLayer(bounds, paint, flags); | |
339 } | |
340 | |
341 void SkBBoxRecord::willRestore() { | |
342 delete fSaveStack.top(); | |
343 fSaveStack.pop(); | |
344 this->INHERITED::willRestore(); | |
345 } | |
346 | |
347 bool SkBBoxRecord::transformBounds(const SkRect& bounds, const SkPaint* paint) { | |
348 SkRect outBounds = bounds; | |
349 outBounds.sort(); | |
350 | |
351 if (paint) { | |
352 // account for stroking, path effects, shadows, etc | |
353 if (paint->canComputeFastBounds()) { | |
354 SkRect temp; | |
355 outBounds = paint->computeFastBounds(outBounds, &temp); | |
356 } else { | |
357 // set bounds to current clip | |
358 if (!this->getClipBounds(&outBounds)) { | |
359 // current clip is empty | |
360 return false; | |
361 } | |
362 } | |
363 } | |
364 | |
365 for (int i = fSaveStack.count() - 1; i >= 0; --i) { | |
366 const SkPaint* paint = fSaveStack.getAt(i); | |
367 if (paint && paint->canComputeFastBounds()) { | |
368 SkRect temp; | |
369 outBounds = paint->computeFastBounds(outBounds, &temp); | |
370 } | |
371 } | |
372 | |
373 if (!outBounds.isEmpty() && !this->quickReject(outBounds)) { | |
374 this->getTotalMatrix().mapRect(&outBounds); | |
375 this->handleBBox(outBounds); | |
376 return true; | |
377 } | |
378 | |
379 return false; | |
380 } | |
OLD | NEW |