1 /*
2 * Copyright 2016 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 "modules/svg/include/SkSVGRenderContext.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkImageFilter.h"
12 #include "include/core/SkPath.h"
13 #include "include/effects/SkDashPathEffect.h"
14 #include "include/private/SkTo.h"
15 #include "modules/svg/include/SkSVGAttribute.h"
16 #include "modules/svg/include/SkSVGClipPath.h"
17 #include "modules/svg/include/SkSVGFilter.h"
18 #include "modules/svg/include/SkSVGMask.h"
19 #include "modules/svg/include/SkSVGNode.h"
20 #include "modules/svg/include/SkSVGTypes.h"
21
22 namespace {
23
length_size_for_type(const SkSize & viewport,SkSVGLengthContext::LengthType t)24 SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::LengthType t) {
25 switch (t) {
26 case SkSVGLengthContext::LengthType::kHorizontal:
27 return viewport.width();
28 case SkSVGLengthContext::LengthType::kVertical:
29 return viewport.height();
30 case SkSVGLengthContext::LengthType::kOther: {
31 // https://www.w3.org/TR/SVG11/coords.html#Units_viewport_percentage
32 constexpr SkScalar rsqrt2 = 1.0f / SK_ScalarSqrt2;
33 const SkScalar w = viewport.width(), h = viewport.height();
34 return rsqrt2 * SkScalarSqrt(w * w + h * h);
35 }
36 }
37
38 SkASSERT(false); // Not reached.
39 return 0;
40 }
41
42 // Multipliers for DPI-relative units.
43 constexpr SkScalar kINMultiplier = 1.00f;
44 constexpr SkScalar kPTMultiplier = kINMultiplier / 72.272f;
45 constexpr SkScalar kPCMultiplier = kPTMultiplier * 12;
46 constexpr SkScalar kMMMultiplier = kINMultiplier / 25.4f;
47 constexpr SkScalar kCMMultiplier = kMMMultiplier * 10;
48
49 } // namespace
50
resolveForSVG(const SkSVGLength & l,LengthType t) const51 SkScalar SkSVGLengthContext::resolveForSVG(const SkSVGLength& l, LengthType t) const {
52 switch (l.unit()) {
53 case SkSVGLength::Unit::kNumber:
54 // Fall through.
55 case SkSVGLength::Unit::kPX:
56 return l.value() * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
57 case SkSVGLength::Unit::kPercentage:
58 return l.value() * length_size_for_type(fViewport, t) / 100 * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
59 case SkSVGLength::Unit::kCM:
60 return l.value() * fDPI * kCMMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
61 case SkSVGLength::Unit::kMM:
62 return l.value() * fDPI * kMMMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
63 case SkSVGLength::Unit::kIN:
64 return l.value() * fDPI * kINMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
65 case SkSVGLength::Unit::kPT:
66 return l.value() * fDPI * kPTMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
67 case SkSVGLength::Unit::kPC:
68 return l.value() * fDPI * kPCMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
69 default:
70 SkDebugf("unsupported unit type: <%d>\n", (int)l.unit());
71 return 0;
72 }
73 }
74
resolveRectForSVG(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const75 SkRect SkSVGLengthContext::resolveRectForSVG(const SkSVGLength& x, const SkSVGLength& y,
76 const SkSVGLength& w, const SkSVGLength& h) const {
77 return SkRect::MakeXYWH(
78 this->resolveForSVG(x, SkSVGLengthContext::LengthType::kHorizontal),
79 this->resolveForSVG(y, SkSVGLengthContext::LengthType::kVertical),
80 this->resolveForSVG(w, SkSVGLengthContext::LengthType::kHorizontal),
81 this->resolveForSVG(h, SkSVGLengthContext::LengthType::kVertical));
82 }
83
resolve(const SkSVGLength & l,LengthType t) const84 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
85 switch (l.unit()) {
86 case SkSVGLength::Unit::kNumber:
87 // Fall through.
88 case SkSVGLength::Unit::kPX:
89 return l.value();
90 case SkSVGLength::Unit::kPercentage:
91 return l.value() * length_size_for_type(fViewport, t) / 100;
92 case SkSVGLength::Unit::kCM:
93 return l.value() * fDPI * kCMMultiplier;
94 case SkSVGLength::Unit::kMM:
95 return l.value() * fDPI * kMMMultiplier;
96 case SkSVGLength::Unit::kIN:
97 return l.value() * fDPI * kINMultiplier;
98 case SkSVGLength::Unit::kPT:
99 return l.value() * fDPI * kPTMultiplier;
100 case SkSVGLength::Unit::kPC:
101 return l.value() * fDPI * kPCMultiplier;
102 default:
103 SkDebugf("unsupported unit type: <%d>\n", (int)l.unit());
104 return 0;
105 }
106 }
107
resolveRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const108 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
109 const SkSVGLength& w, const SkSVGLength& h) const {
110 return SkRect::MakeXYWH(
111 this->resolve(x, SkSVGLengthContext::LengthType::kHorizontal),
112 this->resolve(y, SkSVGLengthContext::LengthType::kVertical),
113 this->resolve(w, SkSVGLengthContext::LengthType::kHorizontal),
114 this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
115 }
116
117 namespace {
118
toSkCap(const SkSVGLineCap & cap)119 SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
120 switch (cap) {
121 case SkSVGLineCap::kButt:
122 return SkPaint::kButt_Cap;
123 case SkSVGLineCap::kRound:
124 return SkPaint::kRound_Cap;
125 case SkSVGLineCap::kSquare:
126 return SkPaint::kSquare_Cap;
127 }
128 SkUNREACHABLE;
129 }
130
toSkJoin(const SkSVGLineJoin & join)131 SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
132 switch (join.type()) {
133 case SkSVGLineJoin::Type::kMiter:
134 return SkPaint::kMiter_Join;
135 case SkSVGLineJoin::Type::kRound:
136 return SkPaint::kRound_Join;
137 case SkSVGLineJoin::Type::kBevel:
138 return SkPaint::kBevel_Join;
139 default:
140 SkASSERT(false);
141 return SkPaint::kMiter_Join;
142 }
143 }
144
dash_effect(const SkSVGPresentationAttributes & props,const SkSVGLengthContext & lctx)145 static sk_sp<SkPathEffect> dash_effect(const SkSVGPresentationAttributes& props,
146 const SkSVGLengthContext& lctx) {
147 if (props.fStrokeDashArray->type() != SkSVGDashArray::Type::kDashArray) {
148 return nullptr;
149 }
150
151 const auto& da = *props.fStrokeDashArray;
152 const auto count = da.dashArray().count();
153 SkSTArray<128, SkScalar, true> intervals(count);
154 for (const auto& dash : da.dashArray()) {
155 intervals.push_back(lctx.resolve(dash, SkSVGLengthContext::LengthType::kOther));
156 }
157
158 if (count & 1) {
159 // If an odd number of values is provided, then the list of values
160 // is repeated to yield an even number of values.
161 intervals.push_back_n(count);
162 memcpy(intervals.begin() + count, intervals.begin(), count * sizeof(SkScalar));
163 }
164
165 SkASSERT((intervals.count() & 1) == 0);
166
167 const auto phase = lctx.resolve(*props.fStrokeDashOffset,
168 SkSVGLengthContext::LengthType::kOther);
169
170 return SkDashPathEffect::Make(intervals.begin(), intervals.count(), phase);
171 }
172
173 } // namespace
174
SkSVGPresentationContext()175 SkSVGPresentationContext::SkSVGPresentationContext()
176 : fInherited(SkSVGPresentationAttributes::MakeInitial())
177 {}
178
SkSVGRenderContext(SkCanvas * canvas,const sk_sp<SkFontMgr> & fmgr,const sk_sp<skresources::ResourceProvider> & rp,const SkSVGIDMapper & mapper,const SkSVGLengthContext & lctx,const SkSVGPresentationContext & pctx,const OBBScope & obbs)179 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
180 const sk_sp<SkFontMgr>& fmgr,
181 const sk_sp<skresources::ResourceProvider>& rp,
182 const SkSVGIDMapper& mapper,
183 const SkSVGLengthContext& lctx,
184 const SkSVGPresentationContext& pctx,
185 const OBBScope& obbs)
186 : fFontMgr(fmgr)
187 , fResourceProvider(rp)
188 , fIDMapper(mapper)
189 , fLengthContext(lctx)
190 , fPresentationContext(pctx)
191 , fCanvas(canvas)
192 , fCanvasSaveCount(canvas->getSaveCount())
193 , fOBBScope(obbs) {}
194
SkSVGRenderContext(const SkSVGRenderContext & other)195 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
196 : SkSVGRenderContext(other.fCanvas,
197 other.fFontMgr,
198 other.fResourceProvider,
199 other.fIDMapper,
200 *other.fLengthContext,
201 *other.fPresentationContext,
202 other.fOBBScope) {}
203
SkSVGRenderContext(const SkSVGRenderContext & other,SkCanvas * canvas)204 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkCanvas* canvas)
205 : SkSVGRenderContext(canvas,
206 other.fFontMgr,
207 other.fResourceProvider,
208 other.fIDMapper,
209 *other.fLengthContext,
210 *other.fPresentationContext,
211 other.fOBBScope) {}
212
SkSVGRenderContext(const SkSVGRenderContext & other,const SkSVGNode * node)213 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, const SkSVGNode* node)
214 : SkSVGRenderContext(other.fCanvas,
215 other.fFontMgr,
216 other.fResourceProvider,
217 other.fIDMapper,
218 *other.fLengthContext,
219 *other.fPresentationContext,
220 OBBScope{node, this}) {}
221
~SkSVGRenderContext()222 SkSVGRenderContext::~SkSVGRenderContext() {
223 fCanvas->restoreToCount(fCanvasSaveCount);
224 }
225
findNodeById(const SkSVGIRI & iri) const226 SkSVGRenderContext::BorrowedNode SkSVGRenderContext::findNodeById(const SkSVGIRI& iri) const {
227 if (iri.type() != SkSVGIRI::Type::kLocal) {
228 SkDebugf("non-local iri references not currently supported");
229 return BorrowedNode(nullptr);
230 }
231 return BorrowedNode(fIDMapper.find(iri.iri()));
232 }
233
applyPresentationAttributes(const SkSVGPresentationAttributes & attrs,uint32_t flags)234 void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs,
235 uint32_t flags) {
236
237 #define ApplyLazyInheritedAttribute(ATTR) \
238 do { \
239 /* All attributes should be defined on the inherited context. */ \
240 SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValue()); \
241 const auto& attr = attrs.f ## ATTR; \
242 if (attr.isValue() && *attr != *fPresentationContext->fInherited.f ## ATTR) { \
243 /* Update the local attribute value */ \
244 fPresentationContext.writable()->fInherited.f ## ATTR.set(*attr); \
245 } \
246 } while (false)
247
248 ApplyLazyInheritedAttribute(Fill);
249 ApplyLazyInheritedAttribute(FillOpacity);
250 ApplyLazyInheritedAttribute(FillRule);
251 ApplyLazyInheritedAttribute(FontFamily);
252 ApplyLazyInheritedAttribute(FontSize);
253 ApplyLazyInheritedAttribute(FontStyle);
254 ApplyLazyInheritedAttribute(FontWeight);
255 ApplyLazyInheritedAttribute(ClipRule);
256 ApplyLazyInheritedAttribute(Stroke);
257 ApplyLazyInheritedAttribute(StrokeDashOffset);
258 ApplyLazyInheritedAttribute(StrokeDashArray);
259 ApplyLazyInheritedAttribute(StrokeLineCap);
260 ApplyLazyInheritedAttribute(StrokeLineJoin);
261 ApplyLazyInheritedAttribute(StrokeMiterLimit);
262 ApplyLazyInheritedAttribute(StrokeOpacity);
263 ApplyLazyInheritedAttribute(StrokeWidth);
264 ApplyLazyInheritedAttribute(TextAnchor);
265 ApplyLazyInheritedAttribute(Visibility);
266 ApplyLazyInheritedAttribute(Color);
267 ApplyLazyInheritedAttribute(ColorInterpolation);
268 ApplyLazyInheritedAttribute(ColorInterpolationFilters);
269
270 #undef ApplyLazyInheritedAttribute
271
272 // Uninherited attributes. Only apply to the current context.
273
274 const bool hasFilter = attrs.fFilter.isValue();
275 if (attrs.fOpacity.isValue()) {
276 this->applyOpacity(*attrs.fOpacity, flags, hasFilter);
277 }
278
279 if (attrs.fClipPath.isValue()) {
280 this->applyClip(*attrs.fClipPath);
281 }
282
283 if (attrs.fMask.isValue()) {
284 this->applyMask(*attrs.fMask);
285 }
286
287 // TODO: when both a filter and opacity are present, we can apply both with a single layer
288 if (hasFilter) {
289 this->applyFilter(*attrs.fFilter);
290 }
291
292 // Remaining uninherited presentation attributes are accessed as SkSVGNode fields, not via
293 // the render context.
294 // TODO: resolve these in a pre-render styling pass and assert here that they are values.
295 // - stop-color
296 // - stop-opacity
297 // - flood-color
298 // - flood-opacity
299 // - lighting-color
300 }
301
applyOpacity(SkScalar opacity,uint32_t flags,bool hasFilter)302 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags, bool hasFilter) {
303 if (opacity >= 1) {
304 return;
305 }
306
307 const auto& props = fPresentationContext->fInherited;
308 const bool hasFill = props.fFill ->type() != SkSVGPaint::Type::kNone,
309 hasStroke = props.fStroke->type() != SkSVGPaint::Type::kNone;
310
311 // We can apply the opacity as paint alpha if it only affects one atomic draw.
312 // For now, this means all of the following must be true:
313 // - the target node doesn't have any descendants;
314 // - it only has a stroke or a fill (but not both);
315 // - it does not have a filter.
316 // Going forward, we may needto refine this heuristic (e.g. to accommodate markers).
317 if ((flags & kLeaf) && (hasFill ^ hasStroke) && !hasFilter) {
318 fDeferredPaintOpacity *= opacity;
319 } else {
320 // Expensive, layer-based fall back.
321 SkPaint opacityPaint;
322 opacityPaint.setAlphaf(SkTPin(opacity, 0.0f, 1.0f));
323 // Balanced in the destructor, via restoreToCount().
324 fCanvas->saveLayer(nullptr, &opacityPaint);
325 }
326 }
327
applyFilter(const SkSVGFuncIRI & filter)328 void SkSVGRenderContext::applyFilter(const SkSVGFuncIRI& filter) {
329 if (filter.type() != SkSVGFuncIRI::Type::kIRI) {
330 return;
331 }
332
333 const auto node = this->findNodeById(filter.iri());
334 if (!node || node->tag() != SkSVGTag::kFilter) {
335 return;
336 }
337
338 const SkSVGFilter* filterNode = reinterpret_cast<const SkSVGFilter*>(node.get());
339 sk_sp<SkImageFilter> imageFilter = filterNode->buildFilterDAG(*this);
340 if (imageFilter) {
341 SkPaint filterPaint;
342 filterPaint.setImageFilter(imageFilter);
343 // Balanced in the destructor, via restoreToCount().
344 fCanvas->saveLayer(nullptr, &filterPaint);
345 }
346 }
347
saveOnce()348 void SkSVGRenderContext::saveOnce() {
349 // The canvas only needs to be saved once, per local SkSVGRenderContext.
350 if (fCanvas->getSaveCount() == fCanvasSaveCount) {
351 fCanvas->save();
352 }
353
354 SkASSERT(fCanvas->getSaveCount() > fCanvasSaveCount);
355 }
356
applyClip(const SkSVGFuncIRI & clip)357 void SkSVGRenderContext::applyClip(const SkSVGFuncIRI& clip) {
358 if (clip.type() != SkSVGFuncIRI::Type::kIRI) {
359 return;
360 }
361
362 const auto clipNode = this->findNodeById(clip.iri());
363 if (!clipNode || clipNode->tag() != SkSVGTag::kClipPath) {
364 return;
365 }
366
367 const SkPath clipPath = static_cast<const SkSVGClipPath*>(clipNode.get())->resolveClip(*this);
368
369 // We use the computed clip path in two ways:
370 //
371 // - apply to the current canvas, for drawing
372 // - track in the presentation context, for asPath() composition
373 //
374 // TODO: the two uses are exclusive, avoid canvas churn when non needed.
375
376 this->saveOnce();
377
378 fCanvas->clipPath(clipPath, true);
379 fClipPath.set(clipPath);
380 }
381
applyMask(const SkSVGFuncIRI & mask)382 void SkSVGRenderContext::applyMask(const SkSVGFuncIRI& mask) {
383 if (mask.type() != SkSVGFuncIRI::Type::kIRI) {
384 return;
385 }
386
387 const auto node = this->findNodeById(mask.iri());
388 if (!node || node->tag() != SkSVGTag::kMask) {
389 return;
390 }
391
392 const auto* mask_node = static_cast<const SkSVGMask*>(node.get());
393 const auto mask_bounds = mask_node->bounds(*this);
394
395 // Isolation/mask layer.
396 fCanvas->saveLayer(mask_bounds, nullptr);
397
398 // Render and filter mask content.
399 mask_node->renderMask(*this);
400
401 // Content layer
402 SkPaint masking_paint;
403 masking_paint.setBlendMode(SkBlendMode::kSrcIn);
404 fCanvas->saveLayer(mask_bounds, &masking_paint);
405
406 // Content is also clipped to the specified mask bounds.
407 fCanvas->clipRect(mask_bounds, true);
408
409 // At this point we're set up for content rendering.
410 // The pending layers are restored in the destructor (render context scope exit).
411 // Restoring triggers srcIn-compositing the content against the mask.
412 }
413
commonPaint(const SkSVGPaint & paint_selector,float paint_opacity) const414 SkTLazy<SkPaint> SkSVGRenderContext::commonPaint(const SkSVGPaint& paint_selector,
415 float paint_opacity) const {
416 if (paint_selector.type() == SkSVGPaint::Type::kNone) {
417 return SkTLazy<SkPaint>();
418 }
419
420 SkTLazy<SkPaint> p;
421 p.init();
422
423 switch (paint_selector.type()) {
424 case SkSVGPaint::Type::kColor:
425 p->setColor(this->resolveSvgColor(paint_selector.color()));
426 break;
427 case SkSVGPaint::Type::kIRI: {
428 // Our property inheritance is borked as it follows the render path and not the tree
429 // hierarchy. To avoid gross transgressions like leaf node presentation attributes
430 // leaking into the paint server context, use a pristine presentation context when
431 // following hrefs.
432 //
433 // Preserve the OBB scope because some paints use object bounding box coords
434 // (e.g. gradient control points), which requires access to the render context
435 // and node being rendered.
436 SkSVGPresentationContext pctx;
437 SkSVGRenderContext local_ctx(fCanvas,
438 fFontMgr,
439 fResourceProvider,
440 fIDMapper,
441 *fLengthContext,
442 pctx,
443 fOBBScope);
444
445 const auto node = this->findNodeById(paint_selector.iri());
446 if (!node || !node->asPaint(local_ctx, p.get())) {
447 // Use the fallback color.
448 p->setColor(this->resolveSvgColor(paint_selector.color()));
449 }
450 } break;
451 default:
452 SkUNREACHABLE;
453 }
454
455 p->setAntiAlias(true); // TODO: shape-rendering support
456
457 // We observe 3 opacity components:
458 // - initial paint server opacity (e.g. color stop opacity)
459 // - paint-specific opacity (e.g. 'fill-opacity', 'stroke-opacity')
460 // - deferred opacity override (optimization for leaf nodes 'opacity')
461 p->setAlphaf(SkTPin(p->getAlphaf() * paint_opacity * fDeferredPaintOpacity, 0.0f, 1.0f));
462
463 return p;
464 }
465
fillPaint() const466 SkTLazy<SkPaint> SkSVGRenderContext::fillPaint() const {
467 const auto& props = fPresentationContext->fInherited;
468 auto p = this->commonPaint(*props.fFill, *props.fFillOpacity);
469
470 if (p.isValid()) {
471 p->setStyle(SkPaint::kFill_Style);
472 }
473
474 return p;
475 }
476
strokePaint() const477 SkTLazy<SkPaint> SkSVGRenderContext::strokePaint() const {
478 const auto& props = fPresentationContext->fInherited;
479 auto p = this->commonPaint(*props.fStroke, *props.fStrokeOpacity);
480
481 if (p.isValid()) {
482 p->setStyle(SkPaint::kStroke_Style);
483 p->setStrokeWidth(fLengthContext->resolve(*props.fStrokeWidth,
484 SkSVGLengthContext::LengthType::kOther));
485 p->setStrokeCap(toSkCap(*props.fStrokeLineCap));
486 p->setStrokeJoin(toSkJoin(*props.fStrokeLineJoin));
487 p->setStrokeMiter(*props.fStrokeMiterLimit);
488 p->setPathEffect(dash_effect(props, *fLengthContext));
489 }
490
491 return p;
492 }
493
resolveSvgColor(const SkSVGColor & color) const494 SkSVGColorType SkSVGRenderContext::resolveSvgColor(const SkSVGColor& color) const {
495 switch (color.type()) {
496 case SkSVGColor::Type::kColor:
497 return color.color();
498 case SkSVGColor::Type::kCurrentColor:
499 return *fPresentationContext->fInherited.fColor;
500 case SkSVGColor::Type::kICCColor:
501 SkDebugf("ICC color unimplemented");
502 return SK_ColorBLACK;
503 }
504 SkUNREACHABLE;
505 }
506
507 SkSVGRenderContext::OBBTransform
transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const508 SkSVGRenderContext::transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const {
509 if (!fOBBScope.fNode || u.type() == SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse) {
510 return {{0,0},{1,1}};
511 }
512 SkASSERT(fOBBScope.fCtx);
513
514 const auto obb = fOBBScope.fNode->objectBoundingBox(*fOBBScope.fCtx);
515 return {{obb.x(), obb.y()}, {obb.width(), obb.height()}};
516 }
517
resolveOBBRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h,SkSVGObjectBoundingBoxUnits obbu) const518 SkRect SkSVGRenderContext::resolveOBBRect(const SkSVGLength& x, const SkSVGLength& y,
519 const SkSVGLength& w, const SkSVGLength& h,
520 SkSVGObjectBoundingBoxUnits obbu) const {
521 SkTCopyOnFirstWrite<SkSVGLengthContext> lctx(fLengthContext);
522
523 if (obbu.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
524 *lctx.writable() = SkSVGLengthContext({1,1});
525 }
526
527 auto r = lctx->resolveRect(x, y, w, h);
528 const auto obbt = this->transformForCurrentOBB(obbu);
529
530 return SkRect::MakeXYWH(obbt.scale.x * r.x() + obbt.offset.x,
531 obbt.scale.y * r.y() + obbt.offset.y,
532 obbt.scale.x * r.width(),
533 obbt.scale.y * r.height());
534 }
535