OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "modules/canvas2d/BaseRenderingContext2D.h" |
| 6 |
| 7 #include "core/css/parser/CSSParser.h" |
| 8 #include "core/frame/ImageBitmap.h" |
| 9 #include "core/html/HTMLCanvasElement.h" |
| 10 #include "core/html/HTMLImageElement.h" |
| 11 #include "core/html/HTMLVideoElement.h" |
| 12 #include "modules/canvas2d/CanvasGradient.h" |
| 13 #include "modules/canvas2d/CanvasPattern.h" |
| 14 #include "modules/canvas2d/CanvasStyle.h" |
| 15 #include "modules/canvas2d/Path2D.h" |
| 16 #include "platform/geometry/FloatQuad.h" |
| 17 #include "platform/graphics/Color.h" |
| 18 #include "platform/graphics/ExpensiveCanvasHeuristicParameters.h" |
| 19 #include "platform/graphics/Image.h" |
| 20 #include "platform/graphics/ImageBuffer.h" |
| 21 #include "platform/graphics/StrokeData.h" |
| 22 #include "platform/graphics/skia/SkiaUtils.h" |
| 23 #include "third_party/skia/include/core/SkImageFilter.h" |
| 24 |
| 25 namespace blink { |
| 26 |
| 27 static const double cDeviceScaleFactor = 1.0; // Canvas is device independent |
| 28 |
| 29 BaseRenderingContext2D::BaseRenderingContext2D() |
| 30 : m_clipAntialiasing(NotAntiAliased) |
| 31 { |
| 32 m_stateStack.append(CanvasRenderingContext2DState::create()); |
| 33 } |
| 34 |
| 35 BaseRenderingContext2D::~BaseRenderingContext2D() |
| 36 { |
| 37 } |
| 38 |
| 39 CanvasRenderingContext2DState& BaseRenderingContext2D::modifiableState() |
| 40 { |
| 41 realizeSaves(); |
| 42 return *m_stateStack.last(); |
| 43 } |
| 44 |
| 45 void BaseRenderingContext2D::realizeSaves() |
| 46 { |
| 47 validateStateStack(); |
| 48 if (state().hasUnrealizedSaves()) { |
| 49 ASSERT(m_stateStack.size() >= 1); |
| 50 // Reduce the current state's unrealized count by one now, |
| 51 // to reflect the fact we are saving one state. |
| 52 m_stateStack.last()->restore(); |
| 53 m_stateStack.append(CanvasRenderingContext2DState::create(state(), Canva
sRenderingContext2DState::DontCopyClipList)); |
| 54 // Set the new state's unrealized count to 0, because it has no outstand
ing saves. |
| 55 // We need to do this explicitly because the copy constructor and operat
or= used |
| 56 // by the Vector operations copy the unrealized count from the previous
state (in |
| 57 // turn necessary to support correct resizing and unwinding of the stack
). |
| 58 m_stateStack.last()->resetUnrealizedSaveCount(); |
| 59 SkCanvas* canvas = drawingCanvas(); |
| 60 if (canvas) |
| 61 canvas->save(); |
| 62 validateStateStack(); |
| 63 } |
| 64 } |
| 65 |
| 66 void BaseRenderingContext2D::save() |
| 67 { |
| 68 m_stateStack.last()->save(); |
| 69 } |
| 70 |
| 71 void BaseRenderingContext2D::restore() |
| 72 { |
| 73 validateStateStack(); |
| 74 if (state().hasUnrealizedSaves()) { |
| 75 // We never realized the save, so just record that it was unnecessary. |
| 76 m_stateStack.last()->restore(); |
| 77 return; |
| 78 } |
| 79 ASSERT(m_stateStack.size() >= 1); |
| 80 if (m_stateStack.size() <= 1) |
| 81 return; |
| 82 m_path.transform(state().transform()); |
| 83 m_stateStack.removeLast(); |
| 84 m_stateStack.last()->clearResolvedFilter(); |
| 85 m_path.transform(state().transform().inverse()); |
| 86 SkCanvas* c = drawingCanvas(); |
| 87 if (c) |
| 88 c->restore(); |
| 89 |
| 90 validateStateStack(); |
| 91 } |
| 92 |
| 93 static inline void convertCanvasStyleToUnionType(CanvasStyle* style, StringOrCan
vasGradientOrCanvasPattern& returnValue) |
| 94 { |
| 95 if (CanvasGradient* gradient = style->canvasGradient()) { |
| 96 returnValue.setCanvasGradient(gradient); |
| 97 return; |
| 98 } |
| 99 if (CanvasPattern* pattern = style->canvasPattern()) { |
| 100 returnValue.setCanvasPattern(pattern); |
| 101 return; |
| 102 } |
| 103 returnValue.setString(style->color()); |
| 104 } |
| 105 |
| 106 void BaseRenderingContext2D::strokeStyle(StringOrCanvasGradientOrCanvasPattern&
returnValue) const |
| 107 { |
| 108 convertCanvasStyleToUnionType(state().strokeStyle(), returnValue); |
| 109 } |
| 110 |
| 111 void BaseRenderingContext2D::setStrokeStyle(const StringOrCanvasGradientOrCanvas
Pattern& style) |
| 112 { |
| 113 ASSERT(!style.isNull()); |
| 114 |
| 115 String colorString; |
| 116 CanvasStyle* canvasStyle = nullptr; |
| 117 if (style.isString()) { |
| 118 colorString = style.getAsString(); |
| 119 if (colorString == state().unparsedStrokeColor()) |
| 120 return; |
| 121 Color parsedColor = 0; |
| 122 if (!parseColorOrCurrentColor(parsedColor, colorString)) |
| 123 return; |
| 124 if (state().strokeStyle()->isEquivalentRGBA(parsedColor.rgb())) { |
| 125 modifiableState().setUnparsedStrokeColor(colorString); |
| 126 return; |
| 127 } |
| 128 canvasStyle = CanvasStyle::createFromRGBA(parsedColor.rgb()); |
| 129 } else if (style.isCanvasGradient()) { |
| 130 canvasStyle = CanvasStyle::createFromGradient(style.getAsCanvasGradient(
)); |
| 131 } else if (style.isCanvasPattern()) { |
| 132 CanvasPattern* canvasPattern = style.getAsCanvasPattern(); |
| 133 |
| 134 if (originClean() && !canvasPattern->originClean()) |
| 135 setOriginTainted(); |
| 136 |
| 137 canvasStyle = CanvasStyle::createFromPattern(canvasPattern); |
| 138 } |
| 139 |
| 140 ASSERT(canvasStyle); |
| 141 |
| 142 modifiableState().setStrokeStyle(canvasStyle); |
| 143 modifiableState().setUnparsedStrokeColor(colorString); |
| 144 } |
| 145 |
| 146 void BaseRenderingContext2D::fillStyle(StringOrCanvasGradientOrCanvasPattern& re
turnValue) const |
| 147 { |
| 148 convertCanvasStyleToUnionType(state().fillStyle(), returnValue); |
| 149 } |
| 150 |
| 151 void BaseRenderingContext2D::setFillStyle(const StringOrCanvasGradientOrCanvasPa
ttern& style) |
| 152 { |
| 153 ASSERT(!style.isNull()); |
| 154 validateStateStack(); |
| 155 String colorString; |
| 156 CanvasStyle* canvasStyle = nullptr; |
| 157 if (style.isString()) { |
| 158 colorString = style.getAsString(); |
| 159 if (colorString == state().unparsedFillColor()) |
| 160 return; |
| 161 Color parsedColor = 0; |
| 162 if (!parseColorOrCurrentColor(parsedColor, colorString)) |
| 163 return; |
| 164 if (state().fillStyle()->isEquivalentRGBA(parsedColor.rgb())) { |
| 165 modifiableState().setUnparsedFillColor(colorString); |
| 166 return; |
| 167 } |
| 168 canvasStyle = CanvasStyle::createFromRGBA(parsedColor.rgb()); |
| 169 } else if (style.isCanvasGradient()) { |
| 170 canvasStyle = CanvasStyle::createFromGradient(style.getAsCanvasGradient(
)); |
| 171 } else if (style.isCanvasPattern()) { |
| 172 CanvasPattern* canvasPattern = style.getAsCanvasPattern(); |
| 173 |
| 174 if (originClean() && !canvasPattern->originClean()) |
| 175 setOriginTainted(); |
| 176 if (canvasPattern->pattern()->isTextureBacked()) |
| 177 disableDeferral(DisableDeferralReasonUsingTextureBackedPattern); |
| 178 canvasStyle = CanvasStyle::createFromPattern(canvasPattern); |
| 179 } |
| 180 |
| 181 ASSERT(canvasStyle); |
| 182 modifiableState().setFillStyle(canvasStyle); |
| 183 modifiableState().setUnparsedFillColor(colorString); |
| 184 } |
| 185 |
| 186 double BaseRenderingContext2D::lineWidth() const |
| 187 { |
| 188 return state().lineWidth(); |
| 189 } |
| 190 |
| 191 void BaseRenderingContext2D::setLineWidth(double width) |
| 192 { |
| 193 if (!std::isfinite(width) || width <= 0) |
| 194 return; |
| 195 if (state().lineWidth() == width) |
| 196 return; |
| 197 modifiableState().setLineWidth(width); |
| 198 } |
| 199 |
| 200 String BaseRenderingContext2D::lineCap() const |
| 201 { |
| 202 return lineCapName(state().lineCap()); |
| 203 } |
| 204 |
| 205 void BaseRenderingContext2D::setLineCap(const String& s) |
| 206 { |
| 207 LineCap cap; |
| 208 if (!parseLineCap(s, cap)) |
| 209 return; |
| 210 if (state().lineCap() == cap) |
| 211 return; |
| 212 modifiableState().setLineCap(cap); |
| 213 } |
| 214 |
| 215 String BaseRenderingContext2D::lineJoin() const |
| 216 { |
| 217 return lineJoinName(state().lineJoin()); |
| 218 } |
| 219 |
| 220 void BaseRenderingContext2D::setLineJoin(const String& s) |
| 221 { |
| 222 LineJoin join; |
| 223 if (!parseLineJoin(s, join)) |
| 224 return; |
| 225 if (state().lineJoin() == join) |
| 226 return; |
| 227 modifiableState().setLineJoin(join); |
| 228 } |
| 229 |
| 230 double BaseRenderingContext2D::miterLimit() const |
| 231 { |
| 232 return state().miterLimit(); |
| 233 } |
| 234 |
| 235 void BaseRenderingContext2D::setMiterLimit(double limit) |
| 236 { |
| 237 if (!std::isfinite(limit) || limit <= 0) |
| 238 return; |
| 239 if (state().miterLimit() == limit) |
| 240 return; |
| 241 modifiableState().setMiterLimit(limit); |
| 242 } |
| 243 |
| 244 double BaseRenderingContext2D::shadowOffsetX() const |
| 245 { |
| 246 return state().shadowOffset().width(); |
| 247 } |
| 248 |
| 249 void BaseRenderingContext2D::setShadowOffsetX(double x) |
| 250 { |
| 251 if (!std::isfinite(x)) |
| 252 return; |
| 253 if (state().shadowOffset().width() == x) |
| 254 return; |
| 255 modifiableState().setShadowOffsetX(x); |
| 256 } |
| 257 |
| 258 double BaseRenderingContext2D::shadowOffsetY() const |
| 259 { |
| 260 return state().shadowOffset().height(); |
| 261 } |
| 262 |
| 263 void BaseRenderingContext2D::setShadowOffsetY(double y) |
| 264 { |
| 265 if (!std::isfinite(y)) |
| 266 return; |
| 267 if (state().shadowOffset().height() == y) |
| 268 return; |
| 269 modifiableState().setShadowOffsetY(y); |
| 270 } |
| 271 |
| 272 double BaseRenderingContext2D::shadowBlur() const |
| 273 { |
| 274 return state().shadowBlur(); |
| 275 } |
| 276 |
| 277 void BaseRenderingContext2D::setShadowBlur(double blur) |
| 278 { |
| 279 if (!std::isfinite(blur) || blur < 0) |
| 280 return; |
| 281 if (state().shadowBlur() == blur) |
| 282 return; |
| 283 modifiableState().setShadowBlur(blur); |
| 284 } |
| 285 |
| 286 String BaseRenderingContext2D::shadowColor() const |
| 287 { |
| 288 return Color(state().shadowColor()).serialized(); |
| 289 } |
| 290 |
| 291 void BaseRenderingContext2D::setShadowColor(const String& colorString) |
| 292 { |
| 293 Color color; |
| 294 if (!parseColorOrCurrentColor(color, colorString)) |
| 295 return; |
| 296 if (state().shadowColor() == color) |
| 297 return; |
| 298 modifiableState().setShadowColor(color.rgb()); |
| 299 } |
| 300 |
| 301 const Vector<double>& BaseRenderingContext2D::getLineDash() const |
| 302 { |
| 303 return state().lineDash(); |
| 304 } |
| 305 |
| 306 static bool lineDashSequenceIsValid(const Vector<double>& dash) |
| 307 { |
| 308 for (size_t i = 0; i < dash.size(); i++) { |
| 309 if (!std::isfinite(dash[i]) || dash[i] < 0) |
| 310 return false; |
| 311 } |
| 312 return true; |
| 313 } |
| 314 |
| 315 void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) |
| 316 { |
| 317 if (!lineDashSequenceIsValid(dash)) |
| 318 return; |
| 319 modifiableState().setLineDash(dash); |
| 320 } |
| 321 |
| 322 double BaseRenderingContext2D::lineDashOffset() const |
| 323 { |
| 324 return state().lineDashOffset(); |
| 325 } |
| 326 |
| 327 void BaseRenderingContext2D::setLineDashOffset(double offset) |
| 328 { |
| 329 if (!std::isfinite(offset) || state().lineDashOffset() == offset) |
| 330 return; |
| 331 modifiableState().setLineDashOffset(offset); |
| 332 } |
| 333 |
| 334 double BaseRenderingContext2D::globalAlpha() const |
| 335 { |
| 336 return state().globalAlpha(); |
| 337 } |
| 338 |
| 339 void BaseRenderingContext2D::setGlobalAlpha(double alpha) |
| 340 { |
| 341 if (!(alpha >= 0 && alpha <= 1)) |
| 342 return; |
| 343 if (state().globalAlpha() == alpha) |
| 344 return; |
| 345 modifiableState().setGlobalAlpha(alpha); |
| 346 } |
| 347 |
| 348 String BaseRenderingContext2D::globalCompositeOperation() const |
| 349 { |
| 350 return compositeOperatorName(compositeOperatorFromSkia(state().globalComposi
te()), blendModeFromSkia(state().globalComposite())); |
| 351 } |
| 352 |
| 353 void BaseRenderingContext2D::setGlobalCompositeOperation(const String& operation
) |
| 354 { |
| 355 CompositeOperator op = CompositeSourceOver; |
| 356 WebBlendMode blendMode = WebBlendModeNormal; |
| 357 if (!parseCompositeAndBlendOperator(operation, op, blendMode)) |
| 358 return; |
| 359 SkXfermode::Mode xfermode = WebCoreCompositeToSkiaComposite(op, blendMode); |
| 360 if (state().globalComposite() == xfermode) |
| 361 return; |
| 362 modifiableState().setGlobalComposite(xfermode); |
| 363 } |
| 364 |
| 365 String BaseRenderingContext2D::filter() const |
| 366 { |
| 367 return state().unparsedFilter(); |
| 368 } |
| 369 |
| 370 void BaseRenderingContext2D::setFilter(const String& filterString) |
| 371 { |
| 372 if (filterString == state().unparsedFilter()) |
| 373 return; |
| 374 |
| 375 RefPtrWillBeRawPtr<CSSValue> filterValue = CSSParser::parseSingleValue(CSSPr
opertyWebkitFilter, filterString, CSSParserContext(HTMLStandardMode, 0)); |
| 376 |
| 377 if (!filterValue || filterValue->isInitialValue() || filterValue->isInherite
dValue()) |
| 378 return; |
| 379 |
| 380 modifiableState().setUnparsedFilter(filterString); |
| 381 modifiableState().setFilter(filterValue.release()); |
| 382 } |
| 383 |
| 384 PassRefPtrWillBeRawPtr<SVGMatrixTearOff> BaseRenderingContext2D::currentTransfor
m() const |
| 385 { |
| 386 return SVGMatrixTearOff::create(state().transform()); |
| 387 } |
| 388 |
| 389 void BaseRenderingContext2D::setCurrentTransform(PassRefPtrWillBeRawPtr<SVGMatri
xTearOff> passMatrixTearOff) |
| 390 { |
| 391 RefPtrWillBeRawPtr<SVGMatrixTearOff> matrixTearOff = passMatrixTearOff; |
| 392 const AffineTransform& transform = matrixTearOff->value(); |
| 393 setTransform(transform.a(), transform.b(), transform.c(), transform.d(), tra
nsform.e(), transform.f()); |
| 394 } |
| 395 |
| 396 void BaseRenderingContext2D::scale(double sx, double sy) |
| 397 { |
| 398 SkCanvas* c = drawingCanvas(); |
| 399 if (!c) |
| 400 return; |
| 401 |
| 402 if (!std::isfinite(sx) || !std::isfinite(sy)) |
| 403 return; |
| 404 |
| 405 AffineTransform newTransform = state().transform(); |
| 406 newTransform.scaleNonUniform(sx, sy); |
| 407 if (state().transform() == newTransform) |
| 408 return; |
| 409 |
| 410 modifiableState().setTransform(newTransform); |
| 411 if (!state().isTransformInvertible()) |
| 412 return; |
| 413 |
| 414 c->scale(sx, sy); |
| 415 m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy)); |
| 416 } |
| 417 |
| 418 void BaseRenderingContext2D::rotate(double angleInRadians) |
| 419 { |
| 420 SkCanvas* c = drawingCanvas(); |
| 421 if (!c) |
| 422 return; |
| 423 |
| 424 if (!std::isfinite(angleInRadians)) |
| 425 return; |
| 426 |
| 427 AffineTransform newTransform = state().transform(); |
| 428 newTransform.rotateRadians(angleInRadians); |
| 429 if (state().transform() == newTransform) |
| 430 return; |
| 431 |
| 432 modifiableState().setTransform(newTransform); |
| 433 if (!state().isTransformInvertible()) |
| 434 return; |
| 435 c->rotate(angleInRadians * (180.0 / piFloat)); |
| 436 m_path.transform(AffineTransform().rotateRadians(-angleInRadians)); |
| 437 } |
| 438 |
| 439 void BaseRenderingContext2D::translate(double tx, double ty) |
| 440 { |
| 441 SkCanvas* c = drawingCanvas(); |
| 442 if (!c) |
| 443 return; |
| 444 if (!state().isTransformInvertible()) |
| 445 return; |
| 446 |
| 447 if (!std::isfinite(tx) || !std::isfinite(ty)) |
| 448 return; |
| 449 |
| 450 AffineTransform newTransform = state().transform(); |
| 451 newTransform.translate(tx, ty); |
| 452 if (state().transform() == newTransform) |
| 453 return; |
| 454 |
| 455 modifiableState().setTransform(newTransform); |
| 456 if (!state().isTransformInvertible()) |
| 457 return; |
| 458 c->translate(tx, ty); |
| 459 m_path.transform(AffineTransform().translate(-tx, -ty)); |
| 460 } |
| 461 |
| 462 void BaseRenderingContext2D::transform(double m11, double m12, double m21, doubl
e m22, double dx, double dy) |
| 463 { |
| 464 SkCanvas* c = drawingCanvas(); |
| 465 if (!c) |
| 466 return; |
| 467 |
| 468 if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || !std
::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy)) |
| 469 return; |
| 470 |
| 471 AffineTransform transform(m11, m12, m21, m22, dx, dy); |
| 472 AffineTransform newTransform = state().transform() * transform; |
| 473 if (state().transform() == newTransform) |
| 474 return; |
| 475 |
| 476 modifiableState().setTransform(newTransform); |
| 477 if (!state().isTransformInvertible()) |
| 478 return; |
| 479 |
| 480 c->concat(affineTransformToSkMatrix(transform)); |
| 481 m_path.transform(transform.inverse()); |
| 482 } |
| 483 |
| 484 void BaseRenderingContext2D::resetTransform() |
| 485 { |
| 486 SkCanvas* c = drawingCanvas(); |
| 487 if (!c) |
| 488 return; |
| 489 |
| 490 AffineTransform ctm = state().transform(); |
| 491 bool invertibleCTM = state().isTransformInvertible(); |
| 492 // It is possible that CTM is identity while CTM is not invertible. |
| 493 // When CTM becomes non-invertible, realizeSaves() can make CTM identity. |
| 494 if (ctm.isIdentity() && invertibleCTM) |
| 495 return; |
| 496 |
| 497 // resetTransform() resolves the non-invertible CTM state. |
| 498 modifiableState().resetTransform(); |
| 499 c->setMatrix(affineTransformToSkMatrix(baseTransform())); |
| 500 |
| 501 if (invertibleCTM) |
| 502 m_path.transform(ctm); |
| 503 // When else, do nothing because all transform methods didn't update m_path
when CTM became non-invertible. |
| 504 // It means that resetTransform() restores m_path just before CTM became non
-invertible. |
| 505 } |
| 506 |
| 507 void BaseRenderingContext2D::setTransform(double m11, double m12, double m21, do
uble m22, double dx, double dy) |
| 508 { |
| 509 SkCanvas* c = drawingCanvas(); |
| 510 if (!c) |
| 511 return; |
| 512 |
| 513 if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || !std
::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy)) |
| 514 return; |
| 515 |
| 516 resetTransform(); |
| 517 transform(m11, m12, m21, m22, dx, dy); |
| 518 } |
| 519 |
| 520 void BaseRenderingContext2D::beginPath() |
| 521 { |
| 522 m_path.clear(); |
| 523 } |
| 524 |
| 525 static bool validateRectForCanvas(double& x, double& y, double& width, double& h
eight) |
| 526 { |
| 527 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) || !std:
:isfinite(height)) |
| 528 return false; |
| 529 |
| 530 if (!width && !height) |
| 531 return false; |
| 532 |
| 533 if (width < 0) { |
| 534 width = -width; |
| 535 x -= width; |
| 536 } |
| 537 |
| 538 if (height < 0) { |
| 539 height = -height; |
| 540 y -= height; |
| 541 } |
| 542 |
| 543 return true; |
| 544 } |
| 545 |
| 546 void BaseRenderingContext2D::drawForText(const Font& font, const TextRunPaintInf
o& textRunPaintInfo, const FloatPoint& location, CanvasRenderingContext2DState::
PaintType paintType) |
| 547 { |
| 548 draw( |
| 549 [&font, this, &textRunPaintInfo, &location](SkCanvas* c, const SkPaint*
paint) // draw lambda |
| 550 { |
| 551 font.drawBidiText(c, textRunPaintInfo, location, Font::UseFallbackIf
FontNotReady, cDeviceScaleFactor, *paint); |
| 552 }, |
| 553 [](const SkIRect& rect) // overdraw test lambda |
| 554 { |
| 555 return false; |
| 556 }, |
| 557 textRunPaintInfo.bounds, paintType); |
| 558 } |
| 559 |
| 560 static bool isFullCanvasCompositeMode(SkXfermode::Mode op) |
| 561 { |
| 562 // See 4.8.11.1.3 Compositing |
| 563 // CompositeSourceAtop and CompositeDestinationOut are not listed here as th
e platforms already |
| 564 // implement the specification's behavior. |
| 565 return op == SkXfermode::kSrcIn_Mode || op == SkXfermode::kSrcOut_Mode || op
== SkXfermode::kDstIn_Mode || op == SkXfermode::kDstATop_Mode; |
| 566 } |
| 567 |
| 568 template<typename DrawFunc> |
| 569 void BaseRenderingContext2D::compositedDraw(const DrawFunc& drawFunc, SkCanvas*
c, CanvasRenderingContext2DState::PaintType paintType, CanvasRenderingContext2DS
tate::ImageType imageType) |
| 570 { |
| 571 SkImageFilter* filter = stateGetFilter(); |
| 572 ASSERT(isFullCanvasCompositeMode(state().globalComposite()) || filter); |
| 573 SkMatrix ctm = c->getTotalMatrix(); |
| 574 c->resetMatrix(); |
| 575 SkPaint compositePaint; |
| 576 compositePaint.setXfermodeMode(state().globalComposite()); |
| 577 if (state().shouldDrawShadows()) { |
| 578 // unroll into two independently composited passes if drawing shadows |
| 579 SkPaint shadowPaint = *state().getPaint(paintType, DrawShadowOnly, image
Type); |
| 580 int saveCount = c->getSaveCount(); |
| 581 if (filter) { |
| 582 SkPaint filterPaint; |
| 583 filterPaint.setImageFilter(filter); |
| 584 // TODO(junov): crbug.com/502921 We could use primitive bounds if we
knew that the filter |
| 585 // does not affect transparent black regions. |
| 586 c->saveLayer(nullptr, &shadowPaint); |
| 587 c->saveLayer(nullptr, &filterPaint); |
| 588 SkPaint foregroundPaint = *state().getPaint(paintType, DrawForegroun
dOnly, imageType); |
| 589 c->setMatrix(ctm); |
| 590 drawFunc(c, &foregroundPaint); |
| 591 } else { |
| 592 ASSERT(isFullCanvasCompositeMode(state().globalComposite())); |
| 593 c->saveLayer(nullptr, &compositePaint); |
| 594 shadowPaint.setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| 595 c->setMatrix(ctm); |
| 596 drawFunc(c, &shadowPaint); |
| 597 } |
| 598 c->restoreToCount(saveCount); |
| 599 } |
| 600 |
| 601 compositePaint.setImageFilter(filter); |
| 602 // TODO(junov): crbug.com/502921 We could use primitive bounds if we knew th
at the filter |
| 603 // does not affect transparent black regions *and* !isFullCanvasCompositeMod
e |
| 604 c->saveLayer(nullptr, &compositePaint); |
| 605 SkPaint foregroundPaint = *state().getPaint(paintType, DrawForegroundOnly, i
mageType); |
| 606 foregroundPaint.setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| 607 c->setMatrix(ctm); |
| 608 drawFunc(c, &foregroundPaint); |
| 609 c->restore(); |
| 610 c->setMatrix(ctm); |
| 611 } |
| 612 |
| 613 template<typename DrawFunc, typename ContainsFunc> |
| 614 bool BaseRenderingContext2D::draw(const DrawFunc& drawFunc, const ContainsFunc&
drawCoversClipBounds, const SkRect& bounds, CanvasRenderingContext2DState::Paint
Type paintType, CanvasRenderingContext2DState::ImageType imageType) |
| 615 { |
| 616 if (!state().isTransformInvertible()) |
| 617 return false; |
| 618 |
| 619 SkIRect clipBounds; |
| 620 if (!drawingCanvas() || !drawingCanvas()->getClipDeviceBounds(&clipBounds)) |
| 621 return false; |
| 622 |
| 623 // If gradient size is zero, then paint nothing. |
| 624 CanvasStyle* style = state().style(paintType); |
| 625 if (style) { |
| 626 CanvasGradient* gradient = style->canvasGradient(); |
| 627 if (gradient && gradient->gradient()->isZeroSize()) |
| 628 return false; |
| 629 } |
| 630 |
| 631 if (isFullCanvasCompositeMode(state().globalComposite()) || stateHasFilter()
) { |
| 632 compositedDraw(drawFunc, drawingCanvas(), paintType, imageType); |
| 633 didDraw(clipBounds); |
| 634 } else if (state().globalComposite() == SkXfermode::kSrc_Mode) { |
| 635 clearCanvas(); // takes care of checkOverdraw() |
| 636 const SkPaint* paint = state().getPaint(paintType, DrawForegroundOnly, i
mageType); |
| 637 drawFunc(drawingCanvas(), paint); |
| 638 didDraw(clipBounds); |
| 639 } else { |
| 640 SkIRect dirtyRect; |
| 641 if (computeDirtyRect(bounds, clipBounds, &dirtyRect)) { |
| 642 const SkPaint* paint = state().getPaint(paintType, DrawShadowAndFore
ground, imageType); |
| 643 if (paintType != CanvasRenderingContext2DState::StrokePaintType && d
rawCoversClipBounds(clipBounds)) |
| 644 checkOverdraw(bounds, paint, imageType, ClipFill); |
| 645 drawFunc(drawingCanvas(), paint); |
| 646 didDraw(dirtyRect); |
| 647 } |
| 648 } |
| 649 return true; |
| 650 } |
| 651 |
| 652 static bool isPathExpensive(const Path& path) |
| 653 { |
| 654 const SkPath& skPath = path.skPath(); |
| 655 if (ExpensiveCanvasHeuristicParameters::ConcavePathsAreExpensive && !skPath.
isConvex()) |
| 656 return true; |
| 657 |
| 658 if (skPath.countPoints() > ExpensiveCanvasHeuristicParameters::ExpensivePath
PointCount) |
| 659 return true; |
| 660 |
| 661 return false; |
| 662 } |
| 663 |
| 664 void BaseRenderingContext2D::drawPathInternal(const Path& path, CanvasRenderingC
ontext2DState::PaintType paintType, SkPath::FillType fillType) |
| 665 { |
| 666 if (path.isEmpty()) |
| 667 return; |
| 668 |
| 669 SkPath skPath = path.skPath(); |
| 670 FloatRect bounds = path.boundingRect(); |
| 671 skPath.setFillType(fillType); |
| 672 |
| 673 if (paintType == CanvasRenderingContext2DState::StrokePaintType) |
| 674 inflateStrokeRect(bounds); |
| 675 |
| 676 if (!drawingCanvas()) |
| 677 return; |
| 678 |
| 679 if (draw( |
| 680 [&skPath, this](SkCanvas* c, const SkPaint* paint) // draw lambda |
| 681 { |
| 682 c->drawPath(skPath, *paint); |
| 683 }, |
| 684 [](const SkIRect& rect) // overdraw test lambda |
| 685 { |
| 686 return false; |
| 687 }, bounds, paintType)) { |
| 688 if (isPathExpensive(path)) { |
| 689 ImageBuffer* buffer = imageBuffer(); |
| 690 if (buffer) |
| 691 buffer->setHasExpensiveOp(); |
| 692 } |
| 693 } |
| 694 } |
| 695 |
| 696 static SkPath::FillType parseWinding(const String& windingRuleString) |
| 697 { |
| 698 if (windingRuleString == "nonzero") |
| 699 return SkPath::kWinding_FillType; |
| 700 if (windingRuleString == "evenodd") |
| 701 return SkPath::kEvenOdd_FillType; |
| 702 |
| 703 ASSERT_NOT_REACHED(); |
| 704 return SkPath::kEvenOdd_FillType; |
| 705 } |
| 706 |
| 707 void BaseRenderingContext2D::fill(const String& windingRuleString) |
| 708 { |
| 709 drawPathInternal(m_path, CanvasRenderingContext2DState::FillPaintType, parse
Winding(windingRuleString)); |
| 710 } |
| 711 |
| 712 void BaseRenderingContext2D::fill(Path2D* domPath, const String& windingRuleStri
ng) |
| 713 { |
| 714 drawPathInternal(domPath->path(), CanvasRenderingContext2DState::FillPaintTy
pe, parseWinding(windingRuleString)); |
| 715 } |
| 716 |
| 717 void BaseRenderingContext2D::stroke() |
| 718 { |
| 719 drawPathInternal(m_path, CanvasRenderingContext2DState::StrokePaintType); |
| 720 } |
| 721 |
| 722 void BaseRenderingContext2D::stroke(Path2D* domPath) |
| 723 { |
| 724 drawPathInternal(domPath->path(), CanvasRenderingContext2DState::StrokePaint
Type); |
| 725 } |
| 726 |
| 727 void BaseRenderingContext2D::fillRect(double x, double y, double width, double h
eight) |
| 728 { |
| 729 if (!validateRectForCanvas(x, y, width, height)) |
| 730 return; |
| 731 |
| 732 if (!drawingCanvas()) |
| 733 return; |
| 734 |
| 735 SkRect rect = SkRect::MakeXYWH(x, y, width, height); |
| 736 draw( |
| 737 [&rect, this](SkCanvas* c, const SkPaint* paint) // draw lambda |
| 738 { |
| 739 c->drawRect(rect, *paint); |
| 740 }, |
| 741 [&rect, this](const SkIRect& clipBounds) // overdraw test lambda |
| 742 { |
| 743 return rectContainsTransformedRect(rect, clipBounds); |
| 744 }, rect, CanvasRenderingContext2DState::FillPaintType); |
| 745 } |
| 746 |
| 747 static void strokeRectOnCanvas(const FloatRect& rect, SkCanvas* canvas, const Sk
Paint* paint) |
| 748 { |
| 749 ASSERT(paint->getStyle() == SkPaint::kStroke_Style); |
| 750 if ((rect.width() > 0) != (rect.height() > 0)) { |
| 751 // When stroking, we must skip the zero-dimension segments |
| 752 SkPath path; |
| 753 path.moveTo(rect.x(), rect.y()); |
| 754 path.lineTo(rect.maxX(), rect.maxY()); |
| 755 path.close(); |
| 756 canvas->drawPath(path, *paint); |
| 757 return; |
| 758 } |
| 759 canvas->drawRect(rect, *paint); |
| 760 } |
| 761 |
| 762 void BaseRenderingContext2D::strokeRect(double x, double y, double width, double
height) |
| 763 { |
| 764 if (!validateRectForCanvas(x, y, width, height)) |
| 765 return; |
| 766 |
| 767 if (!drawingCanvas()) |
| 768 return; |
| 769 |
| 770 SkRect rect = SkRect::MakeXYWH(x, y, width, height); |
| 771 FloatRect bounds = rect; |
| 772 inflateStrokeRect(bounds); |
| 773 draw( |
| 774 [&rect, this](SkCanvas* c, const SkPaint* paint) // draw lambda |
| 775 { |
| 776 strokeRectOnCanvas(rect, c, paint); |
| 777 }, |
| 778 [](const SkIRect& clipBounds) // overdraw test lambda |
| 779 { |
| 780 return false; |
| 781 }, bounds, CanvasRenderingContext2DState::StrokePaintType); |
| 782 } |
| 783 |
| 784 void BaseRenderingContext2D::clipInternal(const Path& path, const String& windin
gRuleString) |
| 785 { |
| 786 SkCanvas* c = drawingCanvas(); |
| 787 if (!c) { |
| 788 return; |
| 789 } |
| 790 if (!state().isTransformInvertible()) { |
| 791 return; |
| 792 } |
| 793 |
| 794 SkPath skPath = path.skPath(); |
| 795 skPath.setFillType(parseWinding(windingRuleString)); |
| 796 modifiableState().clipPath(skPath, m_clipAntialiasing); |
| 797 c->clipPath(skPath, SkRegion::kIntersect_Op, m_clipAntialiasing == AntiAlias
ed); |
| 798 if (ExpensiveCanvasHeuristicParameters::ComplexClipsAreExpensive && !skPath.
isRect(0) && hasImageBuffer()) { |
| 799 imageBuffer()->setHasExpensiveOp(); |
| 800 } |
| 801 } |
| 802 |
| 803 void BaseRenderingContext2D::clip(const String& windingRuleString) |
| 804 { |
| 805 clipInternal(m_path, windingRuleString); |
| 806 } |
| 807 |
| 808 void BaseRenderingContext2D::clip(Path2D* domPath, const String& windingRuleStri
ng) |
| 809 { |
| 810 clipInternal(domPath->path(), windingRuleString); |
| 811 } |
| 812 |
| 813 bool BaseRenderingContext2D::isPointInPath(const double x, const double y, const
String& windingRuleString) |
| 814 { |
| 815 return isPointInPathInternal(m_path, x, y, windingRuleString); |
| 816 } |
| 817 |
| 818 bool BaseRenderingContext2D::isPointInPath(Path2D* domPath, const double x, cons
t double y, const String& windingRuleString) |
| 819 { |
| 820 return isPointInPathInternal(domPath->path(), x, y, windingRuleString); |
| 821 } |
| 822 |
| 823 bool BaseRenderingContext2D::isPointInPathInternal(const Path& path, const doubl
e x, const double y, const String& windingRuleString) |
| 824 { |
| 825 SkCanvas* c = drawingCanvas(); |
| 826 if (!c) |
| 827 return false; |
| 828 if (!state().isTransformInvertible()) |
| 829 return false; |
| 830 |
| 831 FloatPoint point(x, y); |
| 832 if (!std::isfinite(point.x()) || !std::isfinite(point.y())) |
| 833 return false; |
| 834 AffineTransform ctm = state().transform(); |
| 835 FloatPoint transformedPoint = ctm.inverse().mapPoint(point); |
| 836 |
| 837 return path.contains(transformedPoint, SkFillTypeToWindRule(parseWinding(win
dingRuleString))); |
| 838 } |
| 839 |
| 840 bool BaseRenderingContext2D::isPointInStroke(const double x, const double y) |
| 841 { |
| 842 return isPointInStrokeInternal(m_path, x, y); |
| 843 } |
| 844 |
| 845 bool BaseRenderingContext2D::isPointInStroke(Path2D* domPath, const double x, co
nst double y) |
| 846 { |
| 847 return isPointInStrokeInternal(domPath->path(), x, y); |
| 848 } |
| 849 |
| 850 bool BaseRenderingContext2D::isPointInStrokeInternal(const Path& path, const dou
ble x, const double y) |
| 851 { |
| 852 SkCanvas* c = drawingCanvas(); |
| 853 if (!c) |
| 854 return false; |
| 855 if (!state().isTransformInvertible()) |
| 856 return false; |
| 857 |
| 858 FloatPoint point(x, y); |
| 859 if (!std::isfinite(point.x()) || !std::isfinite(point.y())) |
| 860 return false; |
| 861 AffineTransform ctm = state().transform(); |
| 862 FloatPoint transformedPoint = ctm.inverse().mapPoint(point); |
| 863 |
| 864 StrokeData strokeData; |
| 865 strokeData.setThickness(state().lineWidth()); |
| 866 strokeData.setLineCap(state().lineCap()); |
| 867 strokeData.setLineJoin(state().lineJoin()); |
| 868 strokeData.setMiterLimit(state().miterLimit()); |
| 869 Vector<float> lineDash(state().lineDash().size()); |
| 870 std::copy(state().lineDash().begin(), state().lineDash().end(), lineDash.beg
in()); |
| 871 strokeData.setLineDash(lineDash, state().lineDashOffset()); |
| 872 return path.strokeContains(transformedPoint, strokeData); |
| 873 } |
| 874 |
| 875 void BaseRenderingContext2D::clearRect(double x, double y, double width, double
height) |
| 876 { |
| 877 if (!validateRectForCanvas(x, y, width, height)) |
| 878 return; |
| 879 |
| 880 SkCanvas* c = drawingCanvas(); |
| 881 if (!c) |
| 882 return; |
| 883 if (!state().isTransformInvertible()) |
| 884 return; |
| 885 |
| 886 SkIRect clipBounds; |
| 887 if (!c->getClipDeviceBounds(&clipBounds)) |
| 888 return; |
| 889 |
| 890 SkPaint clearPaint; |
| 891 clearPaint.setXfermodeMode(SkXfermode::kClear_Mode); |
| 892 clearPaint.setStyle(SkPaint::kFill_Style); |
| 893 FloatRect rect(x, y, width, height); |
| 894 |
| 895 if (rectContainsTransformedRect(rect, clipBounds)) { |
| 896 checkOverdraw(rect, &clearPaint, CanvasRenderingContext2DState::NoImage,
ClipFill); |
| 897 if (drawingCanvas()) |
| 898 drawingCanvas()->drawRect(rect, clearPaint); |
| 899 didDraw(clipBounds); |
| 900 } else { |
| 901 SkIRect dirtyRect; |
| 902 if (computeDirtyRect(rect, clipBounds, &dirtyRect)) { |
| 903 c->drawRect(rect, clearPaint); |
| 904 didDraw(dirtyRect); |
| 905 } |
| 906 } |
| 907 } |
| 908 |
| 909 static inline FloatRect normalizeRect(const FloatRect& rect) |
| 910 { |
| 911 return FloatRect(std::min(rect.x(), rect.maxX()), |
| 912 std::min(rect.y(), rect.maxY()), |
| 913 std::max(rect.width(), -rect.width()), |
| 914 std::max(rect.height(), -rect.height())); |
| 915 } |
| 916 |
| 917 static inline void clipRectsToImageRect(const FloatRect& imageRect, FloatRect* s
rcRect, FloatRect* dstRect) |
| 918 { |
| 919 if (imageRect.contains(*srcRect)) |
| 920 return; |
| 921 |
| 922 // Compute the src to dst transform |
| 923 FloatSize scale(dstRect->size().width() / srcRect->size().width(), dstRect->
size().height() / srcRect->size().height()); |
| 924 FloatPoint scaledSrcLocation = srcRect->location(); |
| 925 scaledSrcLocation.scale(scale.width(), scale.height()); |
| 926 FloatSize offset = dstRect->location() - scaledSrcLocation; |
| 927 |
| 928 srcRect->intersect(imageRect); |
| 929 |
| 930 // To clip the destination rectangle in the same proportion, transform the c
lipped src rect |
| 931 *dstRect = *srcRect; |
| 932 dstRect->scale(scale.width(), scale.height()); |
| 933 dstRect->move(offset); |
| 934 } |
| 935 |
| 936 static inline CanvasImageSource* toImageSourceInternal(const CanvasImageSourceUn
ion& value) |
| 937 { |
| 938 if (value.isHTMLImageElement()) |
| 939 return value.getAsHTMLImageElement().get(); |
| 940 if (value.isHTMLVideoElement()) |
| 941 return value.getAsHTMLVideoElement().get(); |
| 942 if (value.isHTMLCanvasElement()) |
| 943 return value.getAsHTMLCanvasElement().get(); |
| 944 if (value.isImageBitmap()) |
| 945 return value.getAsImageBitmap().get(); |
| 946 ASSERT_NOT_REACHED(); |
| 947 return nullptr; |
| 948 } |
| 949 |
| 950 void BaseRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource
, double x, double y, ExceptionState& exceptionState) |
| 951 { |
| 952 CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
| 953 FloatSize sourceRectSize = imageSourceInternal->elementSize(); |
| 954 FloatSize destRectSize = imageSourceInternal->defaultDestinationSize(); |
| 955 drawImage(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.
height(), x, y, destRectSize.width(), destRectSize.height(), exceptionState); |
| 956 } |
| 957 |
| 958 void BaseRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource
, |
| 959 double x, double y, double width, double height, ExceptionState& exceptionSt
ate) |
| 960 { |
| 961 CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
| 962 FloatSize sourceRectSize = imageSourceInternal->elementSize(); |
| 963 drawImage(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.
height(), x, y, width, height, exceptionState); |
| 964 } |
| 965 |
| 966 void BaseRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource
, |
| 967 double sx, double sy, double sw, double sh, |
| 968 double dx, double dy, double dw, double dh, ExceptionState& exceptionState) |
| 969 { |
| 970 CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
| 971 drawImage(imageSourceInternal, sx, sy, sw, sh, dx, dy, dw, dh, exceptionStat
e); |
| 972 } |
| 973 |
| 974 bool BaseRenderingContext2D::shouldDrawImageAntialiased(const FloatRect& destRec
t) const |
| 975 { |
| 976 if (!state().shouldAntialias()) |
| 977 return false; |
| 978 SkCanvas* c = drawingCanvas(); |
| 979 ASSERT(c); |
| 980 |
| 981 const SkMatrix &ctm = c->getTotalMatrix(); |
| 982 // Don't disable anti-aliasing if we're rotated or skewed. |
| 983 if (!ctm.rectStaysRect()) |
| 984 return true; |
| 985 // Check if the dimensions of the destination are "small" (less than one |
| 986 // device pixel). To prevent sudden drop-outs. Since we know that |
| 987 // kRectStaysRect_Mask is set, the matrix either has scale and no skew or |
| 988 // vice versa. We can query the kAffine_Mask flag to determine which case |
| 989 // it is. |
| 990 // FIXME: This queries the CTM while drawing, which is generally |
| 991 // discouraged. Always drawing with AA can negatively impact performance |
| 992 // though - that's why it's not always on. |
| 993 SkScalar widthExpansion, heightExpansion; |
| 994 if (ctm.getType() & SkMatrix::kAffine_Mask) |
| 995 widthExpansion = ctm[SkMatrix::kMSkewY], heightExpansion = ctm[SkMatrix:
:kMSkewX]; |
| 996 else |
| 997 widthExpansion = ctm[SkMatrix::kMScaleX], heightExpansion = ctm[SkMatrix
::kMScaleY]; |
| 998 return destRect.width() * fabs(widthExpansion) < 1 || destRect.height() * fa
bs(heightExpansion) < 1; |
| 999 } |
| 1000 |
| 1001 void BaseRenderingContext2D::drawImageInternal(SkCanvas* c, CanvasImageSource* i
mageSource, Image* image, const FloatRect& srcRect, const FloatRect& dstRect, co
nst SkPaint* paint) |
| 1002 { |
| 1003 int initialSaveCount = c->getSaveCount(); |
| 1004 SkPaint imagePaint = *paint; |
| 1005 |
| 1006 if (paint->getImageFilter()) { |
| 1007 SkMatrix invCtm; |
| 1008 if (!c->getTotalMatrix().invert(&invCtm)) { |
| 1009 // There is an earlier check for invertibility, but the arithmetic |
| 1010 // in AffineTransform is not exactly identical, so it is possible |
| 1011 // for SkMatrix to find the transform to be non-invertible at this s
tage. |
| 1012 // crbug.com/504687 |
| 1013 return; |
| 1014 } |
| 1015 SkRect bounds = dstRect; |
| 1016 SkPaint layerPaint; |
| 1017 layerPaint.setXfermode(paint->getXfermode()); |
| 1018 SkAutoTUnref<SkImageFilter> localFilter(paint->getImageFilter()->newWith
LocalMatrix(invCtm)); |
| 1019 layerPaint.setImageFilter(localFilter); |
| 1020 c->saveLayer(&bounds, &layerPaint); |
| 1021 imagePaint.setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| 1022 imagePaint.setImageFilter(nullptr); |
| 1023 } |
| 1024 |
| 1025 if (!imageSource->isVideoElement()) { |
| 1026 imagePaint.setAntiAlias(shouldDrawImageAntialiased(dstRect)); |
| 1027 image->draw(c, imagePaint, dstRect, srcRect, DoNotRespectImageOrientatio
n, Image::DoNotClampImageToSourceRect); |
| 1028 } else { |
| 1029 c->save(); |
| 1030 c->clipRect(dstRect); |
| 1031 c->translate(dstRect.x(), dstRect.y()); |
| 1032 c->scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.h
eight()); |
| 1033 c->translate(-srcRect.x(), -srcRect.y()); |
| 1034 HTMLVideoElement* video = static_cast<HTMLVideoElement*>(imageSource); |
| 1035 video->paintCurrentFrame(c, IntRect(IntPoint(), IntSize(video->videoWidt
h(), video->videoHeight())), &imagePaint); |
| 1036 } |
| 1037 |
| 1038 c->restoreToCount(initialSaveCount); |
| 1039 } |
| 1040 |
| 1041 bool shouldDisableDeferral(CanvasImageSource* imageSource, DisableDeferralReason
* reason) |
| 1042 { |
| 1043 ASSERT(reason); |
| 1044 ASSERT(*reason == DisableDeferralReasonUnknown); |
| 1045 |
| 1046 if (imageSource->isVideoElement()) { |
| 1047 *reason = DisableDeferralReasonDrawImageOfVideo; |
| 1048 return true; |
| 1049 } |
| 1050 if (imageSource->isCanvasElement()) { |
| 1051 HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(imageSource)
; |
| 1052 if (canvas->isAnimated2D()) { |
| 1053 *reason = DisableDeferralReasonDrawImageOfAnimated2dCanvas; |
| 1054 return true; |
| 1055 } |
| 1056 } |
| 1057 return false; |
| 1058 } |
| 1059 |
| 1060 void BaseRenderingContext2D::drawImage(CanvasImageSource* imageSource, |
| 1061 double sx, double sy, double sw, double sh, |
| 1062 double dx, double dy, double dw, double dh, ExceptionState& exceptionState) |
| 1063 { |
| 1064 if (!drawingCanvas()) |
| 1065 return; |
| 1066 |
| 1067 RefPtr<Image> image; |
| 1068 SourceImageStatus sourceImageStatus = InvalidSourceImageStatus; |
| 1069 if (!imageSource->isVideoElement()) { |
| 1070 AccelerationHint hint = imageBuffer()->isAccelerated() ? PreferAccelerat
ion : PreferNoAcceleration; |
| 1071 image = imageSource->getSourceImageForCanvas(&sourceImageStatus, hint, S
napshotReasonDrawImage); |
| 1072 if (sourceImageStatus == UndecodableSourceImageStatus) |
| 1073 exceptionState.throwDOMException(InvalidStateError, "The HTMLImageEl
ement provided is in the 'broken' state."); |
| 1074 if (!image || !image->width() || !image->height()) |
| 1075 return; |
| 1076 } else { |
| 1077 if (!static_cast<HTMLVideoElement*>(imageSource)->hasAvailableVideoFrame
()) |
| 1078 return; |
| 1079 } |
| 1080 |
| 1081 if (!std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dw) || !std::
isfinite(dh) |
| 1082 || !std::isfinite(sx) || !std::isfinite(sy) || !std::isfinite(sw) || !st
d::isfinite(sh) |
| 1083 || !dw || !dh || !sw || !sh) |
| 1084 return; |
| 1085 |
| 1086 FloatRect srcRect = normalizeRect(FloatRect(sx, sy, sw, sh)); |
| 1087 FloatRect dstRect = normalizeRect(FloatRect(dx, dy, dw, dh)); |
| 1088 |
| 1089 clipRectsToImageRect(FloatRect(FloatPoint(), imageSource->elementSize()), &s
rcRect, &dstRect); |
| 1090 |
| 1091 imageSource->adjustDrawRects(&srcRect, &dstRect); |
| 1092 |
| 1093 if (srcRect.isEmpty()) |
| 1094 return; |
| 1095 |
| 1096 DisableDeferralReason reason = DisableDeferralReasonUnknown; |
| 1097 if (shouldDisableDeferral(imageSource, &reason) || image->isTextureBacked()) |
| 1098 disableDeferral(reason); |
| 1099 |
| 1100 validateStateStack(); |
| 1101 |
| 1102 draw( |
| 1103 [this, &imageSource, &image, &srcRect, dstRect](SkCanvas* c, const SkPai
nt* paint) // draw lambda |
| 1104 { |
| 1105 drawImageInternal(c, imageSource, image.get(), srcRect, dstRect, pai
nt); |
| 1106 }, |
| 1107 [this, &dstRect](const SkIRect& clipBounds) // overdraw test lambda |
| 1108 { |
| 1109 return rectContainsTransformedRect(dstRect, clipBounds); |
| 1110 }, dstRect, CanvasRenderingContext2DState::ImagePaintType, |
| 1111 imageSource->isOpaque() ? CanvasRenderingContext2DState::OpaqueImage : C
anvasRenderingContext2DState::NonOpaqueImage); |
| 1112 |
| 1113 validateStateStack(); |
| 1114 |
| 1115 bool isExpensive = false; |
| 1116 |
| 1117 if (ExpensiveCanvasHeuristicParameters::SVGImageSourcesAreExpensive && image
Source->isSVGSource()) |
| 1118 isExpensive = true; |
| 1119 |
| 1120 if (imageSource->elementSize().width() * imageSource->elementSize().height()
> width() * height() * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRa
tio) |
| 1121 isExpensive = true; |
| 1122 |
| 1123 if (isExpensive) { |
| 1124 ImageBuffer* buffer = imageBuffer(); |
| 1125 if (buffer) |
| 1126 buffer->setHasExpensiveOp(); |
| 1127 } |
| 1128 |
| 1129 if (imageSource->isCanvasElement() && static_cast<HTMLCanvasElement*>(imageS
ource)->is3D()) { |
| 1130 // WebGL to 2D canvas: must flush graphics context to prevent a race |
| 1131 // FIXME: crbug.com/516331 Fix the underlying synchronization issue so t
his flush can be eliminated. |
| 1132 imageBuffer()->flushGpu(FlushReasonDrawImageOfWebGL); |
| 1133 } |
| 1134 |
| 1135 if (originClean() && wouldTaintOrigin(imageSource)) |
| 1136 setOriginTainted(); |
| 1137 } |
| 1138 |
| 1139 void BaseRenderingContext2D::clearCanvas() |
| 1140 { |
| 1141 FloatRect canvasRect(0, 0, width(), height()); |
| 1142 checkOverdraw(canvasRect, 0, CanvasRenderingContext2DState::NoImage, ClipFil
l); |
| 1143 SkCanvas* c = drawingCanvas(); |
| 1144 if (c) |
| 1145 c->clear(hasAlpha() ? SK_ColorTRANSPARENT : SK_ColorBLACK); |
| 1146 } |
| 1147 |
| 1148 bool BaseRenderingContext2D::rectContainsTransformedRect(const FloatRect& rect,
const SkIRect& transformedRect) const |
| 1149 { |
| 1150 FloatQuad quad(rect); |
| 1151 FloatQuad transformedQuad(FloatRect(transformedRect.x(), transformedRect.y()
, transformedRect.width(), transformedRect.height())); |
| 1152 return state().transform().mapQuad(quad).containsQuad(transformedQuad); |
| 1153 } |
| 1154 |
| 1155 CanvasGradient* BaseRenderingContext2D::createLinearGradient(double x0, double y
0, double x1, double y1) |
| 1156 { |
| 1157 CanvasGradient* gradient = CanvasGradient::create(FloatPoint(x0, y0), FloatP
oint(x1, y1)); |
| 1158 return gradient; |
| 1159 } |
| 1160 |
| 1161 CanvasGradient* BaseRenderingContext2D::createRadialGradient(double x0, double y
0, double r0, double x1, double y1, double r1, ExceptionState& exceptionState) |
| 1162 { |
| 1163 if (r0 < 0 || r1 < 0) { |
| 1164 exceptionState.throwDOMException(IndexSizeError, String::format("The %s
provided is less than 0.", r0 < 0 ? "r0" : "r1")); |
| 1165 return nullptr; |
| 1166 } |
| 1167 |
| 1168 CanvasGradient* gradient = CanvasGradient::create(FloatPoint(x0, y0), r0, Fl
oatPoint(x1, y1), r1); |
| 1169 return gradient; |
| 1170 } |
| 1171 |
| 1172 CanvasPattern* BaseRenderingContext2D::createPattern(const CanvasImageSourceUnio
n& imageSource, const String& repetitionType, ExceptionState& exceptionState) |
| 1173 { |
| 1174 Pattern::RepeatMode repeatMode = CanvasPattern::parseRepetitionType(repetiti
onType, exceptionState); |
| 1175 if (exceptionState.hadException()) |
| 1176 return nullptr; |
| 1177 |
| 1178 SourceImageStatus status; |
| 1179 CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource); |
| 1180 RefPtr<Image> imageForRendering = imageSourceInternal->getSourceImageForCanv
as(&status, PreferNoAcceleration, SnapshotReasonCreatePattern); |
| 1181 |
| 1182 switch (status) { |
| 1183 case NormalSourceImageStatus: |
| 1184 break; |
| 1185 case ZeroSizeCanvasSourceImageStatus: |
| 1186 exceptionState.throwDOMException(InvalidStateError, String::format("The
canvas %s is 0.", imageSourceInternal->elementSize().width() ? "height" : "width
")); |
| 1187 return nullptr; |
| 1188 case UndecodableSourceImageStatus: |
| 1189 exceptionState.throwDOMException(InvalidStateError, "Source image is in
the 'broken' state."); |
| 1190 return nullptr; |
| 1191 case InvalidSourceImageStatus: |
| 1192 imageForRendering = Image::nullImage(); |
| 1193 break; |
| 1194 case IncompleteSourceImageStatus: |
| 1195 return nullptr; |
| 1196 default: |
| 1197 ASSERT_NOT_REACHED(); |
| 1198 return nullptr; |
| 1199 } |
| 1200 ASSERT(imageForRendering); |
| 1201 |
| 1202 bool originClean = !wouldTaintOrigin(imageSourceInternal); |
| 1203 |
| 1204 return CanvasPattern::create(imageForRendering.release(), repeatMode, origin
Clean); |
| 1205 } |
| 1206 |
| 1207 bool BaseRenderingContext2D::computeDirtyRect(const FloatRect& localRect, SkIRec
t* dirtyRect) |
| 1208 { |
| 1209 SkIRect clipBounds; |
| 1210 if (!drawingCanvas()->getClipDeviceBounds(&clipBounds)) |
| 1211 return false; |
| 1212 return computeDirtyRect(localRect, clipBounds, dirtyRect); |
| 1213 } |
| 1214 |
| 1215 bool BaseRenderingContext2D::computeDirtyRect(const FloatRect& localRect, const
SkIRect& transformedClipBounds, SkIRect* dirtyRect) |
| 1216 { |
| 1217 FloatRect canvasRect = state().transform().mapRect(localRect); |
| 1218 |
| 1219 if (alphaChannel(state().shadowColor())) { |
| 1220 FloatRect shadowRect(canvasRect); |
| 1221 shadowRect.move(state().shadowOffset()); |
| 1222 shadowRect.inflate(state().shadowBlur()); |
| 1223 canvasRect.unite(shadowRect); |
| 1224 } |
| 1225 |
| 1226 SkIRect canvasIRect; |
| 1227 static_cast<SkRect>(canvasRect).roundOut(&canvasIRect); |
| 1228 if (!canvasIRect.intersect(transformedClipBounds)) |
| 1229 return false; |
| 1230 |
| 1231 if (dirtyRect) |
| 1232 *dirtyRect = canvasIRect; |
| 1233 |
| 1234 return true; |
| 1235 } |
| 1236 |
| 1237 void BaseRenderingContext2D::inflateStrokeRect(FloatRect& rect) const |
| 1238 { |
| 1239 // Fast approximation of the stroke's bounding rect. |
| 1240 // This yields a slightly oversized rect but is very fast |
| 1241 // compared to Path::strokeBoundingRect(). |
| 1242 static const double root2 = sqrtf(2); |
| 1243 double delta = state().lineWidth() / 2; |
| 1244 if (state().lineJoin() == MiterJoin) |
| 1245 delta *= state().miterLimit(); |
| 1246 else if (state().lineCap() == SquareCap) |
| 1247 delta *= root2; |
| 1248 |
| 1249 rect.inflate(delta); |
| 1250 } |
| 1251 |
| 1252 bool BaseRenderingContext2D::imageSmoothingEnabled() const |
| 1253 { |
| 1254 return state().imageSmoothingEnabled(); |
| 1255 } |
| 1256 |
| 1257 void BaseRenderingContext2D::setImageSmoothingEnabled(bool enabled) |
| 1258 { |
| 1259 if (enabled == state().imageSmoothingEnabled()) |
| 1260 return; |
| 1261 |
| 1262 modifiableState().setImageSmoothingEnabled(enabled); |
| 1263 } |
| 1264 |
| 1265 String BaseRenderingContext2D::imageSmoothingQuality() const |
| 1266 { |
| 1267 return state().imageSmoothingQuality(); |
| 1268 } |
| 1269 |
| 1270 void BaseRenderingContext2D::setImageSmoothingQuality(const String& quality) |
| 1271 { |
| 1272 if (quality == state().imageSmoothingQuality()) |
| 1273 return; |
| 1274 |
| 1275 modifiableState().setImageSmoothingQuality(quality); |
| 1276 } |
| 1277 |
| 1278 void BaseRenderingContext2D::checkOverdraw(const SkRect& rect, const SkPaint* pa
int, CanvasRenderingContext2DState::ImageType imageType, DrawType drawType) |
| 1279 { |
| 1280 SkCanvas* c = drawingCanvas(); |
| 1281 if (!c || !imageBuffer()->isRecording()) |
| 1282 return; |
| 1283 |
| 1284 SkRect deviceRect; |
| 1285 if (drawType == UntransformedUnclippedFill) { |
| 1286 deviceRect = rect; |
| 1287 } else { |
| 1288 ASSERT(drawType == ClipFill); |
| 1289 if (state().hasComplexClip()) |
| 1290 return; |
| 1291 |
| 1292 SkIRect skIBounds; |
| 1293 if (!c->getClipDeviceBounds(&skIBounds)) |
| 1294 return; |
| 1295 deviceRect = SkRect::Make(skIBounds); |
| 1296 } |
| 1297 |
| 1298 const SkImageInfo& imageInfo = c->imageInfo(); |
| 1299 if (!deviceRect.contains(SkRect::MakeWH(imageInfo.width(), imageInfo.height(
)))) |
| 1300 return; |
| 1301 |
| 1302 bool isSourceOver = true; |
| 1303 unsigned alpha = 0xFF; |
| 1304 if (paint) { |
| 1305 if (paint->getLooper() || paint->getImageFilter() || paint->getMaskFilte
r()) |
| 1306 return; |
| 1307 |
| 1308 SkXfermode* xfermode = paint->getXfermode(); |
| 1309 if (xfermode) { |
| 1310 SkXfermode::Mode mode; |
| 1311 if (xfermode->asMode(&mode)) { |
| 1312 isSourceOver = mode == SkXfermode::kSrcOver_Mode; |
| 1313 if (!isSourceOver && mode != SkXfermode::kSrc_Mode && mode != Sk
Xfermode::kClear_Mode) |
| 1314 return; // The code below only knows how to handle Src, SrcO
ver, and Clear |
| 1315 } else { |
| 1316 // unknown xfermode |
| 1317 ASSERT_NOT_REACHED(); |
| 1318 return; |
| 1319 } |
| 1320 } |
| 1321 |
| 1322 alpha = paint->getAlpha(); |
| 1323 |
| 1324 if (isSourceOver && imageType == CanvasRenderingContext2DState::NoImage)
{ |
| 1325 SkShader* shader = paint->getShader(); |
| 1326 if (shader) { |
| 1327 if (shader->isOpaque() && alpha == 0xFF) |
| 1328 imageBuffer()->willOverwriteCanvas(); |
| 1329 return; |
| 1330 } |
| 1331 } |
| 1332 } |
| 1333 |
| 1334 if (isSourceOver) { |
| 1335 // With source over, we need to certify that alpha == 0xFF for all pixel
s |
| 1336 if (imageType == CanvasRenderingContext2DState::NonOpaqueImage) |
| 1337 return; |
| 1338 if (alpha < 0xFF) |
| 1339 return; |
| 1340 } |
| 1341 |
| 1342 imageBuffer()->willOverwriteCanvas(); |
| 1343 } |
| 1344 |
| 1345 DEFINE_TRACE(BaseRenderingContext2D) |
| 1346 { |
| 1347 visitor->trace(m_stateStack); |
| 1348 } |
| 1349 |
| 1350 } // namespace blink |
OLD | NEW |