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