1 /* 2 * Copyright 2006, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 #include "config.h" 26 #include "GraphicsContext.h" 27 28 #include "AffineTransform.h" 29 #include "Gradient.h" 30 #include "NotImplemented.h" 31 #include "Path.h" 32 #include "Pattern.h" 33 #include "PlatformGraphicsContext.h" 34 #include "SkBitmapRef.h" 35 #include "SkBlurDrawLooper.h" 36 #include "SkBlurMaskFilter.h" 37 #include "SkCanvas.h" 38 #include "SkColorPriv.h" 39 #include "SkDashPathEffect.h" 40 #include "SkDevice.h" 41 #include "SkGradientShader.h" 42 #include "SkPaint.h" 43 #include "SkString.h" 44 #include "SkiaUtils.h" 45 #include "TransformationMatrix.h" 46 #include "android_graphics.h" 47 48 using namespace std; 49 50 #define GC2CANVAS(ctx) (ctx)->m_data->getPlatformGfxCtx()->mCanvas 51 52 namespace WebCore { 53 54 static int RoundToInt(float x) 55 { 56 return (int)roundf(x); 57 } 58 59 template <typename T> T* deepCopyPtr(const T* src) 60 { 61 return src ? new T(*src) : 0; 62 } 63 64 // Set a bitmap shader that mimics dashing by width-on, width-off. 65 // Returns false if it could not succeed (e.g. there was an existing shader) 66 static bool setBitmapDash(SkPaint* paint, int width) { 67 if (width <= 0 || paint->getShader()) 68 return false; 69 70 SkColor c = paint->getColor(); 71 72 SkBitmap bm; 73 bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 1); 74 bm.allocPixels(); 75 bm.lockPixels(); 76 77 // set the ON pixel 78 *bm.getAddr32(0, 0) = SkPreMultiplyARGB(0xFF, SkColorGetR(c), 79 SkColorGetG(c), SkColorGetB(c)); 80 // set the OFF pixel 81 *bm.getAddr32(1, 0) = 0; 82 bm.unlockPixels(); 83 84 SkMatrix matrix; 85 matrix.setScale(SkIntToScalar(width), SK_Scalar1); 86 87 SkShader* s = SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode, 88 SkShader::kClamp_TileMode); 89 s->setLocalMatrix(matrix); 90 91 paint->setShader(s)->unref(); 92 return true; 93 } 94 95 // TODO / questions 96 97 // alpha: how does this interact with the alpha in Color? multiply them together? 98 // mode: do I always respect this? If so, then 99 // the rgb() & 0xFF000000 check will abort drawing too often 100 // Is Color premultiplied or not? If it is, then I can't blindly pass it to paint.setColor() 101 102 struct ShadowRec { 103 SkScalar blur; 104 SkScalar dx; 105 SkScalar dy; 106 SkColor color; // alpha>0 means valid shadow 107 ShadowRec(SkScalar b = 0, 108 SkScalar x = 0, 109 SkScalar y = 0, 110 SkColor c = 0) // by default, alpha=0, so no shadow 111 : blur(b), dx(x), dy(y), color(c) 112 {}; 113 }; 114 115 class GraphicsContextPlatformPrivate { 116 public: 117 struct State { 118 SkPathEffect* pathEffect; 119 float miterLimit; 120 float alpha; 121 float strokeThickness; 122 SkPaint::Cap lineCap; 123 SkPaint::Join lineJoin; 124 SkXfermode::Mode mode; 125 int dashRatio; // Ratio of the length of a dash to its width 126 ShadowRec shadow; 127 SkColor fillColor; 128 SkColor strokeColor; 129 bool useAA; 130 // This is a list of clipping paths which are currently active, in the 131 // order in which they were pushed. 132 WTF::Vector<SkPath> antiAliasClipPaths; 133 134 State() 135 : pathEffect(0) 136 , miterLimit(4) 137 , alpha(1) 138 , strokeThickness(0) // Same as default in GraphicsContextPrivate.h 139 , lineCap(SkPaint::kDefault_Cap) 140 , lineJoin(SkPaint::kDefault_Join) 141 , mode(SkXfermode::kSrcOver_Mode) 142 , dashRatio(3) 143 , fillColor(SK_ColorBLACK) 144 , strokeColor(SK_ColorBLACK) 145 , useAA(true) 146 { 147 } 148 149 State(const State& other) 150 : pathEffect(other.pathEffect) 151 , miterLimit(other.miterLimit) 152 , alpha(other.alpha) 153 , strokeThickness(other.strokeThickness) 154 , lineCap(other.lineCap) 155 , lineJoin(other.lineJoin) 156 , mode(other.mode) 157 , dashRatio(other.dashRatio) 158 , shadow(other.shadow) 159 , fillColor(other.fillColor) 160 , strokeColor(other.strokeColor) 161 , useAA(other.useAA) 162 { 163 SkSafeRef(pathEffect); 164 } 165 166 ~State() 167 { 168 SkSafeUnref(pathEffect); 169 } 170 171 void setShadow(int radius, int dx, int dy, SkColor c) 172 { 173 // Cut the radius in half, to visually match the effect seen in 174 // safari browser 175 shadow.blur = SkScalarHalf(SkIntToScalar(radius)); 176 shadow.dx = SkIntToScalar(dx); 177 shadow.dy = SkIntToScalar(dy); 178 shadow.color = c; 179 } 180 181 bool setupShadowPaint(GraphicsContext* ctx, SkPaint* paint, SkPoint* offset) 182 { 183 paint->setAntiAlias(true); 184 paint->setDither(true); 185 paint->setXfermodeMode(mode); 186 paint->setColor(shadow.color); 187 offset->set(shadow.dx, shadow.dy); 188 189 // Currently, only GraphicsContexts associated with the 190 // HTMLCanvasElement have shadows ignore transforms set. This 191 // allows us to distinguish between CSS and Canvas shadows which 192 // have different rendering specifications. 193 uint32_t flags = SkBlurMaskFilter::kHighQuality_BlurFlag; 194 if (ctx->shadowsIgnoreTransforms()) { 195 offset->fY = -offset->fY; 196 flags |= SkBlurMaskFilter::kIgnoreTransform_BlurFlag; 197 } 198 199 if (shadow.blur > 0) { 200 paint->setMaskFilter(SkBlurMaskFilter::Create(shadow.blur, 201 SkBlurMaskFilter::kNormal_BlurStyle))->unref(); 202 } 203 return SkColorGetA(shadow.color) && (shadow.blur || shadow.dx || shadow.dy); 204 } 205 206 SkColor applyAlpha(SkColor c) const 207 { 208 int s = RoundToInt(alpha * 256); 209 if (s >= 256) 210 return c; 211 if (s < 0) 212 return 0; 213 214 int a = SkAlphaMul(SkColorGetA(c), s); 215 return (c & 0x00FFFFFF) | (a << 24); 216 } 217 }; 218 219 GraphicsContextPlatformPrivate(GraphicsContext* gfxCtx, PlatformGraphicsContext* platformGfxCtx) 220 : m_parentGfxCtx(gfxCtx) 221 , m_platformGfxCtx(platformGfxCtx) 222 , m_stateStack(sizeof(State)) 223 { 224 State* state = static_cast<State*>(m_stateStack.push_back()); 225 new (state) State(); 226 m_state = state; 227 } 228 229 ~GraphicsContextPlatformPrivate() 230 { 231 // We force restores so we don't leak any subobjects owned by our 232 // stack of State records. 233 while (m_stateStack.count() > 0) 234 this->restore(); 235 236 if (m_platformGfxCtx && m_platformGfxCtx->deleteUs()) 237 delete m_platformGfxCtx; 238 } 239 240 void save() 241 { 242 State* newState = static_cast<State*>(m_stateStack.push_back()); 243 new (newState) State(*m_state); 244 m_state = newState; 245 } 246 247 void restore() 248 { 249 if (!m_state->antiAliasClipPaths.isEmpty()) 250 applyAntiAliasedClipPaths(m_state->antiAliasClipPaths); 251 252 m_state->~State(); 253 m_stateStack.pop_back(); 254 m_state = static_cast<State*>(m_stateStack.back()); 255 } 256 257 void setFillColor(const Color& c) 258 { 259 m_state->fillColor = c.rgb(); 260 } 261 262 void setStrokeColor(const Color& c) 263 { 264 m_state->strokeColor = c.rgb(); 265 } 266 267 void setStrokeThickness(float f) 268 { 269 m_state->strokeThickness = f; 270 } 271 272 void setupPaintCommon(SkPaint* paint) const 273 { 274 paint->setAntiAlias(m_state->useAA); 275 paint->setDither(true); 276 paint->setXfermodeMode(m_state->mode); 277 if (SkColorGetA(m_state->shadow.color) > 0) { 278 279 // Currently, only GraphicsContexts associated with the 280 // HTMLCanvasElement have shadows ignore transforms set. This 281 // allows us to distinguish between CSS and Canvas shadows which 282 // have different rendering specifications. 283 SkScalar dy = m_state->shadow.dy; 284 uint32_t flags = SkBlurDrawLooper::kHighQuality_BlurFlag; 285 if (m_parentGfxCtx->shadowsIgnoreTransforms()) { 286 dy = -dy; 287 flags |= SkBlurDrawLooper::kIgnoreTransform_BlurFlag; 288 flags |= SkBlurDrawLooper::kOverrideColor_BlurFlag; 289 } 290 291 SkDrawLooper* looper = new SkBlurDrawLooper(m_state->shadow.blur, 292 m_state->shadow.dx, 293 dy, 294 m_state->shadow.color, 295 flags); 296 paint->setLooper(looper)->unref(); 297 } 298 } 299 300 void setupPaintFill(SkPaint* paint) const 301 { 302 this->setupPaintCommon(paint); 303 paint->setColor(m_state->applyAlpha(m_state->fillColor)); 304 } 305 306 void setupPaintBitmap(SkPaint* paint) const 307 { 308 this->setupPaintCommon(paint); 309 // We only want the global alpha for bitmaps, 310 // so just give applyAlpha opaque black 311 paint->setColor(m_state->applyAlpha(0xFF000000)); 312 } 313 314 // Sets up the paint for stroking. Returns true if the style is really 315 // just a dash of squares (the size of the paint's stroke-width. 316 bool setupPaintStroke(SkPaint* paint, SkRect* rect, bool isHLine = false) 317 { 318 this->setupPaintCommon(paint); 319 paint->setColor(m_state->applyAlpha(m_state->strokeColor)); 320 321 float width = m_state->strokeThickness; 322 323 // This allows dashing and dotting to work properly for hairline strokes 324 // FIXME: Should we only do this for dashed and dotted strokes? 325 if (!width) 326 width = 1; 327 328 paint->setStyle(SkPaint::kStroke_Style); 329 paint->setStrokeWidth(SkFloatToScalar(width)); 330 paint->setStrokeCap(m_state->lineCap); 331 paint->setStrokeJoin(m_state->lineJoin); 332 paint->setStrokeMiter(SkFloatToScalar(m_state->miterLimit)); 333 334 if (rect && (RoundToInt(width) & 1)) 335 rect->inset(-SK_ScalarHalf, -SK_ScalarHalf); 336 337 SkPathEffect* pe = m_state->pathEffect; 338 if (pe) { 339 paint->setPathEffect(pe); 340 return false; 341 } 342 switch (m_parentGfxCtx->strokeStyle()) { 343 case NoStroke: 344 case SolidStroke: 345 width = 0; 346 break; 347 case DashedStroke: 348 width = m_state->dashRatio * width; 349 break; 350 // No break 351 case DottedStroke: 352 break; 353 } 354 355 if (width > 0) { 356 // Return true if we're basically a dotted dash of squares 357 bool justSqrs = RoundToInt(width) == RoundToInt(paint->getStrokeWidth()); 358 359 if (justSqrs || !isHLine || !setBitmapDash(paint, width)) { 360 #if 0 361 // this is slow enough that we just skip it for now 362 // see http://b/issue?id=4163023 363 SkScalar intervals[] = { width, width }; 364 pe = new SkDashPathEffect(intervals, 2, 0); 365 paint->setPathEffect(pe)->unref(); 366 #endif 367 } 368 return justSqrs; 369 } 370 return false; 371 } 372 373 void clipPathAntiAliased(const SkPath& clipPath) 374 { 375 // If we are currently tracking any anti-alias clip paths, then we already 376 // have a layer in place and don't need to add another. 377 bool haveLayerOutstanding = m_state->antiAliasClipPaths.size(); 378 379 // See comments in applyAntiAliasedClipPaths about how this works. 380 m_state->antiAliasClipPaths.append(clipPath); 381 if (!haveLayerOutstanding) { 382 SkRect bounds = clipPath.getBounds(); 383 if (m_platformGfxCtx && m_platformGfxCtx->mCanvas) { 384 m_platformGfxCtx->mCanvas->saveLayerAlpha(&bounds, 255, 385 static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag 386 | SkCanvas::kFullColorLayer_SaveFlag 387 | SkCanvas::kClipToLayer_SaveFlag)); 388 m_platformGfxCtx->mCanvas->save(); 389 } else 390 ASSERT(0); 391 } 392 } 393 394 void applyAntiAliasedClipPaths(WTF::Vector<SkPath>& paths) 395 { 396 // Anti-aliased clipping: 397 // 398 // Refer to PlatformContextSkia.cpp's applyAntiAliasedClipPaths() for more details 399 400 if (m_platformGfxCtx && m_platformGfxCtx->mCanvas) 401 m_platformGfxCtx->mCanvas->restore(); 402 403 SkPaint paint; 404 paint.setXfermodeMode(SkXfermode::kClear_Mode); 405 paint.setAntiAlias(true); 406 paint.setStyle(SkPaint::kFill_Style); 407 408 if (m_platformGfxCtx && m_platformGfxCtx->mCanvas) { 409 for (size_t i = paths.size() - 1; i < paths.size(); --i) { 410 paths[i].setFillType(SkPath::kInverseWinding_FillType); 411 m_platformGfxCtx->mCanvas->drawPath(paths[i], paint); 412 } 413 m_platformGfxCtx->mCanvas->restore(); 414 } else 415 ASSERT(0); 416 } 417 418 PlatformGraphicsContext* getPlatformGfxCtx() 419 { 420 return m_platformGfxCtx; 421 } 422 423 State* getState() 424 { 425 return m_state; 426 } 427 private: 428 State* m_state; 429 GraphicsContext* m_parentGfxCtx; // Back-ptr to our parent 430 PlatformGraphicsContext* m_platformGfxCtx; 431 SkDeque m_stateStack; 432 // Not supported yet 433 State& operator=(const State&); 434 }; 435 436 static SkShader::TileMode SpreadMethod2TileMode(GradientSpreadMethod sm) 437 { 438 SkShader::TileMode mode = SkShader::kClamp_TileMode; 439 440 switch (sm) { 441 case SpreadMethodPad: 442 mode = SkShader::kClamp_TileMode; 443 break; 444 case SpreadMethodReflect: 445 mode = SkShader::kMirror_TileMode; 446 break; 447 case SpreadMethodRepeat: 448 mode = SkShader::kRepeat_TileMode; 449 break; 450 } 451 return mode; 452 } 453 454 static void extactShader(SkPaint* paint, Pattern* pat, Gradient* grad) 455 { 456 if (pat) { 457 // platformPattern() returns a cached obj 458 paint->setShader(pat->platformPattern(AffineTransform())); 459 } else if (grad) { 460 // grad->getShader() returns a cached obj 461 GradientSpreadMethod sm = grad->spreadMethod(); 462 paint->setShader(grad->getShader(SpreadMethod2TileMode(sm))); 463 } 464 } 465 466 //////////////////////////////////////////////////////////////////////////////////////////////// 467 468 GraphicsContext* GraphicsContext::createOffscreenContext(int width, int height) 469 { 470 PlatformGraphicsContext* pgc = new PlatformGraphicsContext(); 471 472 SkBitmap bitmap; 473 474 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); 475 bitmap.allocPixels(); 476 bitmap.eraseColor(0); 477 pgc->mCanvas->setBitmapDevice(bitmap); 478 479 GraphicsContext* ctx = new GraphicsContext(pgc); 480 return ctx; 481 } 482 483 //////////////////////////////////////////////////////////////////////////////////////////////// 484 485 void GraphicsContext::platformInit(PlatformGraphicsContext* gc) 486 { 487 m_data = new GraphicsContextPlatformPrivate(this, gc); 488 setPaintingDisabled(!gc || !gc->mCanvas); 489 } 490 491 void GraphicsContext::platformDestroy() 492 { 493 delete m_data; 494 } 495 496 void GraphicsContext::savePlatformState() 497 { 498 // Save our private State 499 m_data->save(); 500 // Save our native canvas 501 GC2CANVAS(this)->save(); 502 } 503 504 void GraphicsContext::restorePlatformState() 505 { 506 // Restore our private State 507 m_data->restore(); 508 // Restore our native canvas 509 GC2CANVAS(this)->restore(); 510 } 511 512 bool GraphicsContext::willFill() const 513 { 514 return m_data->getState()->fillColor; 515 } 516 517 bool GraphicsContext::willStroke() const 518 { 519 return m_data->getState()->strokeColor; 520 } 521 522 // Draws a filled rectangle with a stroked border. 523 void GraphicsContext::drawRect(const IntRect& rect) 524 { 525 if (paintingDisabled()) 526 return; 527 528 SkPaint paint; 529 SkRect r(rect); 530 531 if (fillColor().alpha()) { 532 m_data->setupPaintFill(&paint); 533 GC2CANVAS(this)->drawRect(r, paint); 534 } 535 536 // According to GraphicsContext.h, stroking inside drawRect always means 537 // a stroke of 1 inside the rect. 538 if (strokeStyle() != NoStroke && strokeColor().alpha()) { 539 paint.reset(); 540 m_data->setupPaintStroke(&paint, &r); 541 paint.setPathEffect(0); // No dashing please 542 paint.setStrokeWidth(SK_Scalar1); // Always just 1.0 width 543 r.inset(SK_ScalarHalf, SK_ScalarHalf); // Ensure we're "inside" 544 GC2CANVAS(this)->drawRect(r, paint); 545 } 546 } 547 548 // This is only used to draw borders. 549 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) 550 { 551 if (paintingDisabled()) 552 return; 553 554 StrokeStyle style = strokeStyle(); 555 if (style == NoStroke) 556 return; 557 558 SkPaint paint; 559 SkCanvas* canvas = GC2CANVAS(this); 560 const int idx = SkAbs32(point2.x() - point1.x()); 561 const int idy = SkAbs32(point2.y() - point1.y()); 562 563 // Special-case horizontal and vertical lines that are really just dots 564 if (m_data->setupPaintStroke(&paint, 0, !idy) && (!idx || !idy)) { 565 const SkScalar diameter = paint.getStrokeWidth(); 566 const SkScalar radius = SkScalarHalf(diameter); 567 SkScalar x = SkIntToScalar(SkMin32(point1.x(), point2.x())); 568 SkScalar y = SkIntToScalar(SkMin32(point1.y(), point2.y())); 569 SkScalar dx, dy; 570 int count; 571 SkRect bounds; 572 573 if (!idy) { // Horizontal 574 bounds.set(x, y - radius, x + SkIntToScalar(idx), y + radius); 575 x += radius; 576 dx = diameter * 2; 577 dy = 0; 578 count = idx; 579 } else { // Vertical 580 bounds.set(x - radius, y, x + radius, y + SkIntToScalar(idy)); 581 y += radius; 582 dx = 0; 583 dy = diameter * 2; 584 count = idy; 585 } 586 587 // The actual count is the number of ONs we hit alternating 588 // ON(diameter), OFF(diameter), ... 589 { 590 SkScalar width = SkScalarDiv(SkIntToScalar(count), diameter); 591 // Now compute the number of cells (ON and OFF) 592 count = SkScalarRound(width); 593 // Now compute the number of ONs 594 count = (count + 1) >> 1; 595 } 596 597 SkAutoMalloc storage(count * sizeof(SkPoint)); 598 SkPoint* verts = (SkPoint*)storage.get(); 599 // Now build the array of vertices to past to drawPoints 600 for (int i = 0; i < count; i++) { 601 verts[i].set(x, y); 602 x += dx; 603 y += dy; 604 } 605 606 paint.setStyle(SkPaint::kFill_Style); 607 paint.setPathEffect(0); 608 609 // Clipping to bounds is not required for correctness, but it does 610 // allow us to reject the entire array of points if we are completely 611 // offscreen. This is common in a webpage for android, where most of 612 // the content is clipped out. If drawPoints took an (optional) bounds 613 // parameter, that might even be better, as we would *just* use it for 614 // culling, and not both wacking the canvas' save/restore stack. 615 canvas->save(SkCanvas::kClip_SaveFlag); 616 canvas->clipRect(bounds); 617 canvas->drawPoints(SkCanvas::kPoints_PointMode, count, verts, paint); 618 canvas->restore(); 619 } else { 620 SkPoint pts[2] = { point1, point2 }; 621 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); 622 } 623 } 624 625 static void setrectForUnderline(SkRect* r, GraphicsContext* context, const FloatPoint& point, int yOffset, float width) 626 { 627 float lineThickness = context->strokeThickness(); 628 #if 0 629 if (lineThickness < 1) // Do we really need/want this? 630 lineThickness = 1; 631 #endif 632 r->fLeft = point.x(); 633 r->fTop = point.y() + yOffset; 634 r->fRight = r->fLeft + width; 635 r->fBottom = r->fTop + lineThickness; 636 } 637 638 void GraphicsContext::drawLineForText(const FloatPoint& pt, float width, bool) 639 { 640 if (paintingDisabled()) 641 return; 642 643 SkRect r; 644 setrectForUnderline(&r, this, pt, 0, width); 645 646 SkPaint paint; 647 paint.setAntiAlias(true); 648 paint.setColor(this->strokeColor().rgb()); 649 650 GC2CANVAS(this)->drawRect(r, paint); 651 } 652 653 // TODO: Should we draw different based on TextCheckingLineStyle? 654 void GraphicsContext::drawLineForTextChecking(const FloatPoint& pt, float width, TextCheckingLineStyle) 655 { 656 if (paintingDisabled()) 657 return; 658 659 SkRect r; 660 setrectForUnderline(&r, this, pt, 0, width); 661 662 SkPaint paint; 663 paint.setAntiAlias(true); 664 paint.setColor(SK_ColorRED); // Is this specified somewhere? 665 666 GC2CANVAS(this)->drawRect(r, paint); 667 } 668 669 // This method is only used to draw the little circles used in lists. 670 void GraphicsContext::drawEllipse(const IntRect& rect) 671 { 672 if (paintingDisabled()) 673 return; 674 675 SkPaint paint; 676 SkRect oval(rect); 677 678 if (fillColor().rgb() & 0xFF000000) { 679 m_data->setupPaintFill(&paint); 680 GC2CANVAS(this)->drawOval(oval, paint); 681 } 682 if (strokeStyle() != NoStroke) { 683 paint.reset(); 684 m_data->setupPaintStroke(&paint, &oval); 685 GC2CANVAS(this)->drawOval(oval, paint); 686 } 687 } 688 689 static inline int fastMod(int value, int max) 690 { 691 int sign = SkExtractSign(value); 692 693 value = SkApplySign(value, sign); 694 if (value >= max) 695 value %= max; 696 return SkApplySign(value, sign); 697 } 698 699 void GraphicsContext::strokeArc(const IntRect& r, int startAngle, int angleSpan) 700 { 701 if (paintingDisabled()) 702 return; 703 704 SkPath path; 705 SkPaint paint; 706 SkRect oval(r); 707 708 if (strokeStyle() == NoStroke) { 709 m_data->setupPaintFill(&paint); // We want the fill color 710 paint.setStyle(SkPaint::kStroke_Style); 711 paint.setStrokeWidth(SkFloatToScalar(this->strokeThickness())); 712 } else 713 m_data->setupPaintStroke(&paint, 0); 714 715 // We do this before converting to scalar, so we don't overflow SkFixed 716 startAngle = fastMod(startAngle, 360); 717 angleSpan = fastMod(angleSpan, 360); 718 719 path.addArc(oval, SkIntToScalar(-startAngle), SkIntToScalar(-angleSpan)); 720 GC2CANVAS(this)->drawPath(path, paint); 721 } 722 723 void GraphicsContext::drawConvexPolygon(size_t numPoints, const FloatPoint* points, bool shouldAntialias) 724 { 725 if (paintingDisabled()) 726 return; 727 728 if (numPoints <= 1) 729 return; 730 731 SkPaint paint; 732 SkPath path; 733 734 path.incReserve(numPoints); 735 path.moveTo(SkFloatToScalar(points[0].x()), SkFloatToScalar(points[0].y())); 736 for (size_t i = 1; i < numPoints; i++) 737 path.lineTo(SkFloatToScalar(points[i].x()), SkFloatToScalar(points[i].y())); 738 739 if (GC2CANVAS(this)->quickReject(path, shouldAntialias ? 740 SkCanvas::kAA_EdgeType : SkCanvas::kBW_EdgeType)) { 741 return; 742 } 743 744 if (fillColor().rgb() & 0xFF000000) { 745 m_data->setupPaintFill(&paint); 746 paint.setAntiAlias(shouldAntialias); 747 GC2CANVAS(this)->drawPath(path, paint); 748 } 749 750 if (strokeStyle() != NoStroke) { 751 paint.reset(); 752 m_data->setupPaintStroke(&paint, 0); 753 paint.setAntiAlias(shouldAntialias); 754 GC2CANVAS(this)->drawPath(path, paint); 755 } 756 } 757 758 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, 759 const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace) 760 { 761 if (paintingDisabled()) 762 return; 763 764 SkPaint paint; 765 SkPath path; 766 SkScalar radii[8]; 767 768 radii[0] = SkIntToScalar(topLeft.width()); 769 radii[1] = SkIntToScalar(topLeft.height()); 770 radii[2] = SkIntToScalar(topRight.width()); 771 radii[3] = SkIntToScalar(topRight.height()); 772 radii[4] = SkIntToScalar(bottomRight.width()); 773 radii[5] = SkIntToScalar(bottomRight.height()); 774 radii[6] = SkIntToScalar(bottomLeft.width()); 775 radii[7] = SkIntToScalar(bottomLeft.height()); 776 path.addRoundRect(rect, radii); 777 778 m_data->setupPaintFill(&paint); 779 paint.setColor(color.rgb()); 780 GC2CANVAS(this)->drawPath(path, paint); 781 } 782 783 void GraphicsContext::fillRect(const FloatRect& rect) 784 { 785 SkPaint paint; 786 787 m_data->setupPaintFill(&paint); 788 789 extactShader(&paint, 790 m_state.fillPattern.get(), 791 m_state.fillGradient.get()); 792 793 GC2CANVAS(this)->drawRect(rect, paint); 794 } 795 796 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace) 797 { 798 if (paintingDisabled()) 799 return; 800 801 if (color.rgb() & 0xFF000000) { 802 SkPaint paint; 803 804 m_data->setupPaintCommon(&paint); 805 paint.setColor(color.rgb()); // Punch in the specified color 806 paint.setShader(0); // In case we had one set 807 808 // Sometimes we record and draw portions of the page, using clips 809 // for each portion. The problem with this is that webkit, sometimes, 810 // sees that we're only recording a portion, and they adjust some of 811 // their rectangle coordinates accordingly (e.g. 812 // RenderBoxModelObject::paintFillLayerExtended() which calls 813 // rect.intersect(paintInfo.rect) and then draws the bg with that 814 // rect. The result is that we end up drawing rects that are meant to 815 // seam together (one for each portion), but if the rects have 816 // fractional coordinates (e.g. we are zoomed by a fractional amount) 817 // we will double-draw those edges, resulting in visual cracks or 818 // artifacts. 819 820 // The fix seems to be to just turn off antialasing for rects (this 821 // entry-point in GraphicsContext seems to have been sufficient, 822 // though perhaps we'll find we need to do this as well in fillRect(r) 823 // as well.) Currently setupPaintCommon() enables antialiasing. 824 825 // Since we never show the page rotated at a funny angle, disabling 826 // antialiasing seems to have no real down-side, and it does fix the 827 // bug when we're zoomed (and drawing portions that need to seam). 828 paint.setAntiAlias(false); 829 830 GC2CANVAS(this)->drawRect(rect, paint); 831 } 832 } 833 834 void GraphicsContext::clip(const FloatRect& rect) 835 { 836 if (paintingDisabled()) 837 return; 838 839 GC2CANVAS(this)->clipRect(rect); 840 } 841 842 void GraphicsContext::clip(const Path& path) 843 { 844 if (paintingDisabled()) 845 return; 846 847 m_data->clipPathAntiAliased(*path.platformPath()); 848 } 849 850 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness) 851 { 852 if (paintingDisabled()) 853 return; 854 855 SkPath path; 856 SkRect r(rect); 857 858 path.addOval(r, SkPath::kCW_Direction); 859 // Only perform the inset if we won't invert r 860 if (2 * thickness < rect.width() && 2 * thickness < rect.height()) { 861 // Adding one to the thickness doesn't make the border too thick as 862 // it's painted over afterwards. But without this adjustment the 863 // border appears a little anemic after anti-aliasing. 864 r.inset(SkIntToScalar(thickness + 1), SkIntToScalar(thickness + 1)); 865 path.addOval(r, SkPath::kCCW_Direction); 866 } 867 m_data->clipPathAntiAliased(path); 868 } 869 870 void GraphicsContext::canvasClip(const Path& path) 871 { 872 clip(path); 873 } 874 875 void GraphicsContext::clipOut(const IntRect& r) 876 { 877 if (paintingDisabled()) 878 return; 879 880 GC2CANVAS(this)->clipRect(r, SkRegion::kDifference_Op); 881 } 882 883 #if ENABLE(SVG) 884 void GraphicsContext::clipPath(const Path& pathToClip, WindRule clipRule) 885 { 886 if (paintingDisabled()) 887 return; 888 889 SkPath path = *pathToClip.platformPath(); 890 path.setFillType(clipRule == RULE_EVENODD ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType); 891 GC2CANVAS(this)->clipPath(path); 892 } 893 #endif 894 895 void GraphicsContext::clipOut(const Path& p) 896 { 897 if (paintingDisabled()) 898 return; 899 900 GC2CANVAS(this)->clipPath(*p.platformPath(), SkRegion::kDifference_Op); 901 } 902 903 ////////////////////////////////////////////////////////////////////////////////////////////////// 904 905 #if SVG_SUPPORT 906 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext() 907 { 908 return new KRenderingDeviceContextQuartz(platformContext()); 909 } 910 #endif 911 912 // These are the flags we need when we call saveLayer for transparency. 913 // Since it does not appear that webkit intends this to also save/restore 914 // the matrix or clip, I do not give those flags (for performance) 915 #define TRANSPARENCY_SAVEFLAGS \ 916 (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | \ 917 SkCanvas::kFullColorLayer_SaveFlag) 918 919 void GraphicsContext::beginTransparencyLayer(float opacity) 920 { 921 if (paintingDisabled()) 922 return; 923 924 SkCanvas* canvas = GC2CANVAS(this); 925 canvas->saveLayerAlpha(0, (int)(opacity * 255), TRANSPARENCY_SAVEFLAGS); 926 } 927 928 void GraphicsContext::endTransparencyLayer() 929 { 930 if (paintingDisabled()) 931 return; 932 933 GC2CANVAS(this)->restore(); 934 } 935 936 /////////////////////////////////////////////////////////////////////////// 937 938 void GraphicsContext::setupBitmapPaint(SkPaint* paint) 939 { 940 m_data->setupPaintBitmap(paint); 941 } 942 943 void GraphicsContext::setupFillPaint(SkPaint* paint) 944 { 945 m_data->setupPaintFill(paint); 946 } 947 948 void GraphicsContext::setupStrokePaint(SkPaint* paint) 949 { 950 m_data->setupPaintStroke(paint, 0); 951 } 952 953 bool GraphicsContext::setupShadowPaint(SkPaint* paint, SkPoint* offset) 954 { 955 return m_data->getState()->setupShadowPaint(this, paint, offset); 956 } 957 958 void GraphicsContext::setPlatformStrokeColor(const Color& c, ColorSpace) 959 { 960 m_data->setStrokeColor(c); 961 } 962 963 void GraphicsContext::setPlatformStrokeThickness(float f) 964 { 965 m_data->setStrokeThickness(f); 966 } 967 968 void GraphicsContext::setPlatformFillColor(const Color& c, ColorSpace) 969 { 970 m_data->setFillColor(c); 971 } 972 973 void GraphicsContext::setPlatformShadow(const FloatSize& size, float blur, const Color& color, ColorSpace) 974 { 975 if (paintingDisabled()) 976 return; 977 978 if (blur <= 0) 979 this->clearPlatformShadow(); 980 981 SkColor c; 982 if (color.isValid()) 983 c = color.rgb(); 984 else 985 c = SkColorSetARGB(0xFF / 3, 0, 0, 0); // "std" Apple shadow color 986 m_data->getState()->setShadow(blur, size.width(), size.height(), c); 987 } 988 989 void GraphicsContext::clearPlatformShadow() 990 { 991 if (paintingDisabled()) 992 return; 993 994 m_data->getState()->setShadow(0, 0, 0, 0); 995 } 996 997 /////////////////////////////////////////////////////////////////////////////// 998 999 void GraphicsContext::drawFocusRing(const Vector<IntRect>&, int, int, const Color&) 1000 { 1001 // Do nothing, since we draw the focus ring independently. 1002 } 1003 1004 void GraphicsContext::drawFocusRing(const Path&, int, int, const Color&) 1005 { 1006 // Do nothing, since we draw the focus ring independently. 1007 } 1008 1009 PlatformGraphicsContext* GraphicsContext::platformContext() const 1010 { 1011 ASSERT(!paintingDisabled()); 1012 return m_data->getPlatformGfxCtx(); 1013 } 1014 1015 void GraphicsContext::setMiterLimit(float limit) 1016 { 1017 m_data->getState()->miterLimit = limit; 1018 } 1019 1020 void GraphicsContext::setAlpha(float alpha) 1021 { 1022 m_data->getState()->alpha = alpha; 1023 } 1024 1025 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op) 1026 { 1027 m_data->getState()->mode = WebCoreCompositeToSkiaComposite(op); 1028 } 1029 1030 void GraphicsContext::clearRect(const FloatRect& rect) 1031 { 1032 if (paintingDisabled()) 1033 return; 1034 1035 SkPaint paint; 1036 1037 m_data->setupPaintFill(&paint); 1038 paint.setXfermodeMode(SkXfermode::kClear_Mode); 1039 GC2CANVAS(this)->drawRect(rect, paint); 1040 } 1041 1042 void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth) 1043 { 1044 if (paintingDisabled()) 1045 return; 1046 1047 SkPaint paint; 1048 1049 m_data->setupPaintStroke(&paint, 0); 1050 paint.setStrokeWidth(SkFloatToScalar(lineWidth)); 1051 GC2CANVAS(this)->drawRect(rect, paint); 1052 } 1053 1054 void GraphicsContext::setLineCap(LineCap cap) 1055 { 1056 switch (cap) { 1057 case ButtCap: 1058 m_data->getState()->lineCap = SkPaint::kButt_Cap; 1059 break; 1060 case RoundCap: 1061 m_data->getState()->lineCap = SkPaint::kRound_Cap; 1062 break; 1063 case SquareCap: 1064 m_data->getState()->lineCap = SkPaint::kSquare_Cap; 1065 break; 1066 default: 1067 SkDEBUGF(("GraphicsContext::setLineCap: unknown LineCap %d\n", cap)); 1068 break; 1069 } 1070 } 1071 1072 #if ENABLE(SVG) 1073 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) 1074 { 1075 if (paintingDisabled()) 1076 return; 1077 1078 size_t dashLength = dashes.size(); 1079 if (!dashLength) 1080 return; 1081 1082 size_t count = !(dashLength % 2) ? dashLength : dashLength * 2; 1083 SkScalar* intervals = new SkScalar[count]; 1084 1085 for (unsigned int i = 0; i < count; i++) 1086 intervals[i] = SkFloatToScalar(dashes[i % dashLength]); 1087 SkPathEffect **effectPtr = &m_data->getState()->pathEffect; 1088 SkSafeUnref(*effectPtr); 1089 *effectPtr = new SkDashPathEffect(intervals, count, SkFloatToScalar(dashOffset)); 1090 1091 delete[] intervals; 1092 } 1093 #endif 1094 1095 void GraphicsContext::setLineJoin(LineJoin join) 1096 { 1097 switch (join) { 1098 case MiterJoin: 1099 m_data->getState()->lineJoin = SkPaint::kMiter_Join; 1100 break; 1101 case RoundJoin: 1102 m_data->getState()->lineJoin = SkPaint::kRound_Join; 1103 break; 1104 case BevelJoin: 1105 m_data->getState()->lineJoin = SkPaint::kBevel_Join; 1106 break; 1107 default: 1108 SkDEBUGF(("GraphicsContext::setLineJoin: unknown LineJoin %d\n", join)); 1109 break; 1110 } 1111 } 1112 1113 void GraphicsContext::scale(const FloatSize& size) 1114 { 1115 if (paintingDisabled()) 1116 return; 1117 GC2CANVAS(this)->scale(SkFloatToScalar(size.width()), SkFloatToScalar(size.height())); 1118 } 1119 1120 void GraphicsContext::rotate(float angleInRadians) 1121 { 1122 if (paintingDisabled()) 1123 return; 1124 GC2CANVAS(this)->rotate(SkFloatToScalar(angleInRadians * (180.0f / 3.14159265f))); 1125 } 1126 1127 void GraphicsContext::translate(float x, float y) 1128 { 1129 if (paintingDisabled()) 1130 return; 1131 GC2CANVAS(this)->translate(SkFloatToScalar(x), SkFloatToScalar(y)); 1132 } 1133 1134 void GraphicsContext::concatCTM(const AffineTransform& affine) 1135 { 1136 if (paintingDisabled()) 1137 return; 1138 GC2CANVAS(this)->concat(affine); 1139 } 1140 1141 // This is intended to round the rect to device pixels (through the CTM) 1142 // and then invert the result back into source space, with the hope that when 1143 // it is drawn (through the matrix), it will land in the "right" place (i.e. 1144 // on pixel boundaries). 1145 1146 // For android, we record this geometry once and then draw it though various 1147 // scale factors as the user zooms, without re-recording. Thus this routine 1148 // should just leave the original geometry alone. 1149 1150 // If we instead draw into bitmap tiles, we should then perform this 1151 // transform -> round -> inverse step. 1152 1153 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode) 1154 { 1155 return rect; 1156 } 1157 1158 ////////////////////////////////////////////////////////////////////////////////////////////////// 1159 1160 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) 1161 { 1162 // Appears to be PDF specific, so we ignore it 1163 #if 0 1164 if (paintingDisabled()) 1165 return; 1166 1167 CFURLRef urlRef = link.createCFURL(); 1168 if (urlRef) { 1169 CGContextRef context = platformContext(); 1170 1171 // Get the bounding box to handle clipping. 1172 CGRect box = CGContextGetClipBoundingBox(context); 1173 1174 IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height); 1175 IntRect rect = destRect; 1176 rect.intersect(intBox); 1177 1178 CGPDFContextSetURLForRect(context, urlRef, 1179 CGRectApplyAffineTransform(rect, CGContextGetCTM(context))); 1180 1181 CFRelease(urlRef); 1182 } 1183 #endif 1184 } 1185 1186 void GraphicsContext::setPlatformShouldAntialias(bool useAA) 1187 { 1188 if (paintingDisabled()) 1189 return; 1190 m_data->getState()->useAA = useAA; 1191 } 1192 1193 AffineTransform GraphicsContext::getCTM() const 1194 { 1195 const SkMatrix& m = GC2CANVAS(this)->getTotalMatrix(); 1196 return AffineTransform(SkScalarToDouble(m.getScaleX()), // a 1197 SkScalarToDouble(m.getSkewY()), // b 1198 SkScalarToDouble(m.getSkewX()), // c 1199 SkScalarToDouble(m.getScaleY()), // d 1200 SkScalarToDouble(m.getTranslateX()), // e 1201 SkScalarToDouble(m.getTranslateY())); // f 1202 } 1203 1204 void GraphicsContext::setCTM(const AffineTransform& transform) 1205 { 1206 if (paintingDisabled()) 1207 return; 1208 1209 GC2CANVAS(this)->setMatrix(transform); 1210 } 1211 1212 /////////////////////////////////////////////////////////////////////////////// 1213 1214 void GraphicsContext::fillPath(const Path& pathToFill) 1215 { 1216 SkPath* path = pathToFill.platformPath(); 1217 if (paintingDisabled() || !path) 1218 return; 1219 1220 switch (this->fillRule()) { 1221 case RULE_NONZERO: 1222 path->setFillType(SkPath::kWinding_FillType); 1223 break; 1224 case RULE_EVENODD: 1225 path->setFillType(SkPath::kEvenOdd_FillType); 1226 break; 1227 } 1228 1229 SkPaint paint; 1230 m_data->setupPaintFill(&paint); 1231 1232 extactShader(&paint, 1233 m_state.fillPattern.get(), 1234 m_state.fillGradient.get()); 1235 1236 GC2CANVAS(this)->drawPath(*path, paint); 1237 } 1238 1239 void GraphicsContext::strokePath(const Path& pathToStroke) 1240 { 1241 const SkPath* path = pathToStroke.platformPath(); 1242 if (paintingDisabled() || !path) 1243 return; 1244 1245 SkPaint paint; 1246 m_data->setupPaintStroke(&paint, 0); 1247 1248 extactShader(&paint, 1249 m_state.strokePattern.get(), 1250 m_state.strokeGradient.get()); 1251 1252 GC2CANVAS(this)->drawPath(*path, paint); 1253 } 1254 1255 InterpolationQuality GraphicsContext::imageInterpolationQuality() const 1256 { 1257 notImplemented(); 1258 return InterpolationDefault; 1259 } 1260 1261 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode) 1262 { 1263 #if 0 1264 enum InterpolationQuality { 1265 InterpolationDefault, 1266 InterpolationNone, 1267 InterpolationLow, 1268 InterpolationMedium, 1269 InterpolationHigh 1270 }; 1271 #endif 1272 // TODO: record this, so we can know when to use bitmap-filtering when we draw 1273 // ... not sure how meaningful this will be given our playback model. 1274 1275 // Certainly safe to do nothing for the present. 1276 } 1277 1278 void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint*, bool antialias) 1279 { 1280 if (paintingDisabled()) 1281 return; 1282 1283 if (numPoints <= 1) 1284 return; 1285 1286 // FIXME: IMPLEMENT! 1287 } 1288 1289 } // namespace WebCore 1290 1291 /////////////////////////////////////////////////////////////////////////////// 1292 1293 SkCanvas* android_gc2canvas(WebCore::GraphicsContext* gc) 1294 { 1295 return gc->platformContext()->mCanvas; 1296 } 1297