1 /* 2 * Copyright (c) 2008, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 33 #include "GraphicsContext.h" 34 #include "ImageBuffer.h" 35 #include "NativeImageSkia.h" 36 #include "PlatformContextSkia.h" 37 #include "SkiaUtils.h" 38 39 #include "skia/ext/image_operations.h" 40 #include "skia/ext/platform_canvas.h" 41 42 #include "SkBitmap.h" 43 #include "SkColorPriv.h" 44 #include "SkShader.h" 45 #include "SkDashPathEffect.h" 46 47 #include <wtf/MathExtras.h> 48 #include <wtf/Vector.h> 49 50 namespace WebCore 51 { 52 extern bool isPathSkiaSafe(const SkMatrix& transform, const SkPath& path); 53 } 54 55 // State ----------------------------------------------------------------------- 56 57 // Encapsulates the additional painting state information we store for each 58 // pushed graphics state. 59 struct PlatformContextSkia::State { 60 State(); 61 State(const State&); 62 ~State(); 63 64 // Common shader state. 65 float m_alpha; 66 SkXfermode::Mode m_xferMode; 67 bool m_useAntialiasing; 68 SkDrawLooper* m_looper; 69 70 // Fill. 71 SkColor m_fillColor; 72 SkShader* m_fillShader; 73 74 // Stroke. 75 WebCore::StrokeStyle m_strokeStyle; 76 SkColor m_strokeColor; 77 SkShader* m_strokeShader; 78 float m_strokeThickness; 79 int m_dashRatio; // Ratio of the length of a dash to its width. 80 float m_miterLimit; 81 SkPaint::Cap m_lineCap; 82 SkPaint::Join m_lineJoin; 83 SkDashPathEffect* m_dash; 84 85 // Text. (See cTextFill & friends in GraphicsContext.h.) 86 int m_textDrawingMode; 87 88 // Helper function for applying the state's alpha value to the given input 89 // color to produce a new output color. 90 SkColor applyAlpha(SkColor) const; 91 92 #if OS(LINUX) || OS(WINDOWS) 93 // If non-empty, the current State is clipped to this image. 94 SkBitmap m_imageBufferClip; 95 // If m_imageBufferClip is non-empty, this is the region the image is clipped to. 96 WebCore::FloatRect m_clip; 97 #endif 98 99 // This is a list of clipping paths which are currently active, in the 100 // order in which they were pushed. 101 WTF::Vector<SkPath> m_antiAliasClipPaths; 102 103 private: 104 // Not supported. 105 void operator=(const State&); 106 }; 107 108 // Note: Keep theses default values in sync with GraphicsContextState. 109 PlatformContextSkia::State::State() 110 : m_alpha(1) 111 , m_xferMode(SkXfermode::kSrcOver_Mode) 112 , m_useAntialiasing(true) 113 , m_looper(0) 114 , m_fillColor(0xFF000000) 115 , m_fillShader(0) 116 , m_strokeStyle(WebCore::SolidStroke) 117 , m_strokeColor(WebCore::Color::black) 118 , m_strokeShader(0) 119 , m_strokeThickness(0) 120 , m_dashRatio(3) 121 , m_miterLimit(4) 122 , m_lineCap(SkPaint::kDefault_Cap) 123 , m_lineJoin(SkPaint::kDefault_Join) 124 , m_dash(0) 125 , m_textDrawingMode(WebCore::cTextFill) 126 { 127 } 128 129 PlatformContextSkia::State::State(const State& other) 130 : m_alpha(other.m_alpha) 131 , m_xferMode(other.m_xferMode) 132 , m_useAntialiasing(other.m_useAntialiasing) 133 , m_looper(other.m_looper) 134 , m_fillColor(other.m_fillColor) 135 , m_fillShader(other.m_fillShader) 136 , m_strokeStyle(other.m_strokeStyle) 137 , m_strokeColor(other.m_strokeColor) 138 , m_strokeShader(other.m_strokeShader) 139 , m_strokeThickness(other.m_strokeThickness) 140 , m_dashRatio(other.m_dashRatio) 141 , m_miterLimit(other.m_miterLimit) 142 , m_lineCap(other.m_lineCap) 143 , m_lineJoin(other.m_lineJoin) 144 , m_dash(other.m_dash) 145 , m_textDrawingMode(other.m_textDrawingMode) 146 #if OS(LINUX) || OS(WINDOWS) 147 , m_imageBufferClip(other.m_imageBufferClip) 148 , m_clip(other.m_clip) 149 #endif 150 { 151 // Up the ref count of these. saveRef does nothing if 'this' is NULL. 152 m_looper->safeRef(); 153 m_dash->safeRef(); 154 m_fillShader->safeRef(); 155 m_strokeShader->safeRef(); 156 } 157 158 PlatformContextSkia::State::~State() 159 { 160 m_looper->safeUnref(); 161 m_dash->safeUnref(); 162 m_fillShader->safeUnref(); 163 m_strokeShader->safeUnref(); 164 } 165 166 SkColor PlatformContextSkia::State::applyAlpha(SkColor c) const 167 { 168 int s = roundf(m_alpha * 256); 169 if (s >= 256) 170 return c; 171 if (s < 0) 172 return 0; 173 174 int a = SkAlphaMul(SkColorGetA(c), s); 175 return (c & 0x00FFFFFF) | (a << 24); 176 } 177 178 // PlatformContextSkia --------------------------------------------------------- 179 180 // Danger: canvas can be NULL. 181 PlatformContextSkia::PlatformContextSkia(skia::PlatformCanvas* canvas) 182 : m_canvas(canvas) 183 #if OS(WINDOWS) 184 , m_drawingToImageBuffer(false) 185 #endif 186 { 187 m_stateStack.append(State()); 188 m_state = &m_stateStack.last(); 189 } 190 191 PlatformContextSkia::~PlatformContextSkia() 192 { 193 } 194 195 void PlatformContextSkia::setCanvas(skia::PlatformCanvas* canvas) 196 { 197 m_canvas = canvas; 198 } 199 200 #if OS(WINDOWS) 201 void PlatformContextSkia::setDrawingToImageBuffer(bool value) 202 { 203 m_drawingToImageBuffer = value; 204 } 205 206 bool PlatformContextSkia::isDrawingToImageBuffer() const 207 { 208 return m_drawingToImageBuffer; 209 } 210 #endif 211 212 void PlatformContextSkia::save() 213 { 214 m_stateStack.append(*m_state); 215 m_state = &m_stateStack.last(); 216 217 #if OS(LINUX) || OS(WINDOWS) 218 // The clip image only needs to be applied once. Reset the image so that we 219 // don't attempt to clip multiple times. 220 m_state->m_imageBufferClip.reset(); 221 #endif 222 223 // Save our native canvas. 224 canvas()->save(); 225 } 226 227 #if OS(LINUX) || OS(WINDOWS) 228 void PlatformContextSkia::beginLayerClippedToImage(const WebCore::FloatRect& rect, 229 const WebCore::ImageBuffer* imageBuffer) 230 { 231 // Skia doesn't support clipping to an image, so we create a layer. The next 232 // time restore is invoked the layer and |imageBuffer| are combined to 233 // create the resulting image. 234 m_state->m_clip = rect; 235 SkRect bounds = { SkFloatToScalar(rect.x()), SkFloatToScalar(rect.y()), 236 SkFloatToScalar(rect.right()), SkFloatToScalar(rect.bottom()) }; 237 238 canvas()->clipRect(bounds); 239 canvas()->saveLayerAlpha(&bounds, 255, 240 static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag)); 241 // Copy off the image as |imageBuffer| may be deleted before restore is invoked. 242 const SkBitmap* bitmap = imageBuffer->context()->platformContext()->bitmap(); 243 if (!bitmap->pixelRef()) { 244 // The bitmap owns it's pixels. This happens when we've allocated the 245 // pixels in some way and assigned them directly to the bitmap (as 246 // happens when we allocate a DIB). In this case the assignment operator 247 // does not copy the pixels, rather the copied bitmap ends up 248 // referencing the same pixels. As the pixels may not live as long as we 249 // need it to, we copy the image. 250 bitmap->copyTo(&m_state->m_imageBufferClip, SkBitmap::kARGB_8888_Config); 251 } else { 252 // If there is a pixel ref, we can safely use the assignment operator. 253 m_state->m_imageBufferClip = *bitmap; 254 } 255 } 256 #endif 257 258 void PlatformContextSkia::clipPathAntiAliased(const SkPath& clipPath) 259 { 260 // If we are currently tracking any anti-alias clip paths, then we already 261 // have a layer in place and don't need to add another. 262 bool haveLayerOutstanding = m_state->m_antiAliasClipPaths.size(); 263 264 // See comments in applyAntiAliasedClipPaths about how this works. 265 m_state->m_antiAliasClipPaths.append(clipPath); 266 267 if (!haveLayerOutstanding) { 268 SkRect bounds = clipPath.getBounds(); 269 canvas()->saveLayerAlpha(&bounds, 255, static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag)); 270 } 271 } 272 273 void PlatformContextSkia::restore() 274 { 275 #if OS(LINUX) || OS(WINDOWS) 276 if (!m_state->m_imageBufferClip.empty()) { 277 applyClipFromImage(m_state->m_clip, m_state->m_imageBufferClip); 278 canvas()->restore(); 279 } 280 #endif 281 282 if (!m_state->m_antiAliasClipPaths.isEmpty()) 283 applyAntiAliasedClipPaths(m_state->m_antiAliasClipPaths); 284 285 m_stateStack.removeLast(); 286 m_state = &m_stateStack.last(); 287 288 // Restore our native canvas. 289 canvas()->restore(); 290 } 291 292 void PlatformContextSkia::drawRect(SkRect rect) 293 { 294 SkPaint paint; 295 int fillcolorNotTransparent = m_state->m_fillColor & 0xFF000000; 296 if (fillcolorNotTransparent) { 297 setupPaintForFilling(&paint); 298 canvas()->drawRect(rect, paint); 299 } 300 301 if (m_state->m_strokeStyle != WebCore::NoStroke && 302 (m_state->m_strokeColor & 0xFF000000)) { 303 // We do a fill of four rects to simulate the stroke of a border. 304 SkColor oldFillColor = m_state->m_fillColor; 305 306 // setFillColor() will set the shader to NULL, so save a ref to it now. 307 SkShader* oldFillShader = m_state->m_fillShader; 308 oldFillShader->safeRef(); 309 setFillColor(m_state->m_strokeColor); 310 paint.reset(); 311 setupPaintForFilling(&paint); 312 SkRect topBorder = { rect.fLeft, rect.fTop, rect.fRight, rect.fTop + 1 }; 313 canvas()->drawRect(topBorder, paint); 314 SkRect bottomBorder = { rect.fLeft, rect.fBottom - 1, rect.fRight, rect.fBottom }; 315 canvas()->drawRect(bottomBorder, paint); 316 SkRect leftBorder = { rect.fLeft, rect.fTop + 1, rect.fLeft + 1, rect.fBottom - 1 }; 317 canvas()->drawRect(leftBorder, paint); 318 SkRect rightBorder = { rect.fRight - 1, rect.fTop + 1, rect.fRight, rect.fBottom - 1 }; 319 canvas()->drawRect(rightBorder, paint); 320 setFillColor(oldFillColor); 321 setFillShader(oldFillShader); 322 oldFillShader->safeUnref(); 323 } 324 } 325 326 void PlatformContextSkia::setupPaintCommon(SkPaint* paint) const 327 { 328 #if defined(SK_DEBUG) 329 { 330 SkPaint defaultPaint; 331 SkASSERT(*paint == defaultPaint); 332 } 333 #endif 334 335 paint->setAntiAlias(m_state->m_useAntialiasing); 336 paint->setXfermodeMode(m_state->m_xferMode); 337 paint->setLooper(m_state->m_looper); 338 } 339 340 void PlatformContextSkia::setupPaintForFilling(SkPaint* paint) const 341 { 342 setupPaintCommon(paint); 343 paint->setColor(m_state->applyAlpha(m_state->m_fillColor)); 344 paint->setShader(m_state->m_fillShader); 345 } 346 347 static SkScalar scalarBound(SkScalar v, SkScalar min, SkScalar max) 348 { 349 if (v < min) 350 return min; 351 if (v > max) 352 return max; 353 return v; 354 } 355 356 float PlatformContextSkia::setupPaintForStroking(SkPaint* paint, SkRect* rect, int length) const 357 { 358 setupPaintCommon(paint); 359 float width = m_state->m_strokeThickness; 360 361 paint->setColor(m_state->applyAlpha(m_state->m_strokeColor)); 362 paint->setShader(m_state->m_strokeShader); 363 paint->setStyle(SkPaint::kStroke_Style); 364 // The limits here (512 and 256) were made up but are hopefully large 365 // enough to be reasonable. They are, empirically, small enough not to 366 // cause overflows in Skia. 367 paint->setStrokeWidth(scalarBound(SkFloatToScalar(width), 0, 512)); 368 paint->setStrokeCap(m_state->m_lineCap); 369 paint->setStrokeJoin(m_state->m_lineJoin); 370 paint->setStrokeMiter(scalarBound(SkFloatToScalar(m_state->m_miterLimit), 0, 256)); 371 372 if (m_state->m_dash) 373 paint->setPathEffect(m_state->m_dash); 374 else { 375 switch (m_state->m_strokeStyle) { 376 case WebCore::NoStroke: 377 case WebCore::SolidStroke: 378 break; 379 case WebCore::DashedStroke: 380 width = m_state->m_dashRatio * width; 381 // Fall through. 382 case WebCore::DottedStroke: 383 // Truncate the width, since we don't want fuzzy dots or dashes. 384 int dashLength = static_cast<int>(width); 385 // Subtract off the endcaps, since they're rendered separately. 386 int distance = length - 2 * static_cast<int>(m_state->m_strokeThickness); 387 int phase = 1; 388 if (dashLength > 1) { 389 // Determine how many dashes or dots we should have. 390 int numDashes = distance / dashLength; 391 int remainder = distance % dashLength; 392 // Adjust the phase to center the dashes within the line. 393 if (numDashes % 2 == 0) { 394 // Even: shift right half a dash, minus half the remainder 395 phase = (dashLength - remainder) / 2; 396 } else { 397 // Odd: shift right a full dash, minus half the remainder 398 phase = dashLength - remainder / 2; 399 } 400 } 401 SkScalar dashLengthSk = SkIntToScalar(dashLength); 402 SkScalar intervals[2] = { dashLengthSk, dashLengthSk }; 403 paint->setPathEffect(new SkDashPathEffect(intervals, 2, SkIntToScalar(phase)))->unref(); 404 } 405 } 406 407 return width; 408 } 409 410 void PlatformContextSkia::setDrawLooper(SkDrawLooper* dl) 411 { 412 SkRefCnt_SafeAssign(m_state->m_looper, dl); 413 } 414 415 void PlatformContextSkia::setMiterLimit(float ml) 416 { 417 m_state->m_miterLimit = ml; 418 } 419 420 void PlatformContextSkia::setAlpha(float alpha) 421 { 422 m_state->m_alpha = alpha; 423 } 424 425 void PlatformContextSkia::setLineCap(SkPaint::Cap lc) 426 { 427 m_state->m_lineCap = lc; 428 } 429 430 void PlatformContextSkia::setLineJoin(SkPaint::Join lj) 431 { 432 m_state->m_lineJoin = lj; 433 } 434 435 void PlatformContextSkia::setXfermodeMode(SkXfermode::Mode pdm) 436 { 437 m_state->m_xferMode = pdm; 438 } 439 440 void PlatformContextSkia::setFillColor(SkColor color) 441 { 442 m_state->m_fillColor = color; 443 setFillShader(NULL); 444 } 445 446 SkDrawLooper* PlatformContextSkia::getDrawLooper() const 447 { 448 return m_state->m_looper; 449 } 450 451 WebCore::StrokeStyle PlatformContextSkia::getStrokeStyle() const 452 { 453 return m_state->m_strokeStyle; 454 } 455 456 void PlatformContextSkia::setStrokeStyle(WebCore::StrokeStyle strokeStyle) 457 { 458 m_state->m_strokeStyle = strokeStyle; 459 } 460 461 void PlatformContextSkia::setStrokeColor(SkColor strokeColor) 462 { 463 m_state->m_strokeColor = strokeColor; 464 setStrokeShader(NULL); 465 } 466 467 float PlatformContextSkia::getStrokeThickness() const 468 { 469 return m_state->m_strokeThickness; 470 } 471 472 void PlatformContextSkia::setStrokeThickness(float thickness) 473 { 474 m_state->m_strokeThickness = thickness; 475 } 476 477 void PlatformContextSkia::setStrokeShader(SkShader* strokeShader) 478 { 479 if (strokeShader != m_state->m_strokeShader) { 480 m_state->m_strokeShader->safeUnref(); 481 m_state->m_strokeShader = strokeShader; 482 m_state->m_strokeShader->safeRef(); 483 } 484 } 485 486 int PlatformContextSkia::getTextDrawingMode() const 487 { 488 return m_state->m_textDrawingMode; 489 } 490 491 float PlatformContextSkia::getAlpha() const 492 { 493 return m_state->m_alpha; 494 } 495 496 void PlatformContextSkia::setTextDrawingMode(int mode) 497 { 498 // cTextClip is never used, so we assert that it isn't set: 499 // https://bugs.webkit.org/show_bug.cgi?id=21898 500 ASSERT((mode & WebCore::cTextClip) == 0); 501 m_state->m_textDrawingMode = mode; 502 } 503 504 void PlatformContextSkia::setUseAntialiasing(bool enable) 505 { 506 m_state->m_useAntialiasing = enable; 507 } 508 509 SkColor PlatformContextSkia::effectiveFillColor() const 510 { 511 return m_state->applyAlpha(m_state->m_fillColor); 512 } 513 514 SkColor PlatformContextSkia::effectiveStrokeColor() const 515 { 516 return m_state->applyAlpha(m_state->m_strokeColor); 517 } 518 519 void PlatformContextSkia::beginPath() 520 { 521 m_path.reset(); 522 } 523 524 void PlatformContextSkia::addPath(const SkPath& path) 525 { 526 m_path.addPath(path, m_canvas->getTotalMatrix()); 527 } 528 529 SkPath PlatformContextSkia::currentPathInLocalCoordinates() const 530 { 531 SkPath localPath = m_path; 532 const SkMatrix& matrix = m_canvas->getTotalMatrix(); 533 SkMatrix inverseMatrix; 534 if (!matrix.invert(&inverseMatrix)) 535 return SkPath(); 536 localPath.transform(inverseMatrix); 537 return localPath; 538 } 539 540 void PlatformContextSkia::setFillRule(SkPath::FillType fr) 541 { 542 m_path.setFillType(fr); 543 } 544 545 void PlatformContextSkia::setFillShader(SkShader* fillShader) 546 { 547 if (fillShader != m_state->m_fillShader) { 548 m_state->m_fillShader->safeUnref(); 549 m_state->m_fillShader = fillShader; 550 m_state->m_fillShader->safeRef(); 551 } 552 } 553 554 void PlatformContextSkia::setDashPathEffect(SkDashPathEffect* dash) 555 { 556 if (dash != m_state->m_dash) { 557 m_state->m_dash->safeUnref(); 558 m_state->m_dash = dash; 559 } 560 } 561 562 void PlatformContextSkia::paintSkPaint(const SkRect& rect, 563 const SkPaint& paint) 564 { 565 m_canvas->drawRect(rect, paint); 566 } 567 568 const SkBitmap* PlatformContextSkia::bitmap() const 569 { 570 return &m_canvas->getDevice()->accessBitmap(false); 571 } 572 573 bool PlatformContextSkia::isPrinting() 574 { 575 return m_canvas->getTopPlatformDevice().IsVectorial(); 576 } 577 578 #if OS(LINUX) || OS(WINDOWS) 579 void PlatformContextSkia::applyClipFromImage(const WebCore::FloatRect& rect, const SkBitmap& imageBuffer) 580 { 581 // NOTE: this assumes the image mask contains opaque black for the portions that are to be shown, as such we 582 // only look at the alpha when compositing. I'm not 100% sure this is what WebKit expects for image clipping. 583 SkPaint paint; 584 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 585 m_canvas->drawBitmap(imageBuffer, SkFloatToScalar(rect.x()), SkFloatToScalar(rect.y()), &paint); 586 } 587 #endif 588 589 void PlatformContextSkia::applyAntiAliasedClipPaths(WTF::Vector<SkPath>& paths) 590 { 591 // Anti-aliased clipping: 592 // 593 // Skia's clipping is 1-bit only. Consider what would happen if it were 8-bit: 594 // We have a square canvas, filled with white and we declare a circular 595 // clipping path. Then we fill twice with a black rectangle. The fractional 596 // pixels would first get the correct color (white * alpha + black * (1 - 597 // alpha)), but the second fill would apply the alpha to the already 598 // modified color and the result would be too dark. 599 // 600 // This, anti-aliased clipping needs to be performed after the drawing has 601 // been done. In order to do this, we create a new layer of the canvas in 602 // clipPathAntiAliased and store the clipping path. All drawing is done to 603 // the layer's bitmap while it's in effect. When WebKit calls restore() to 604 // undo the clipping, this function is called. 605 // 606 // Here, we walk the list of clipping paths backwards and, for each, we 607 // clear outside of the clipping path. We only need a single extra layer 608 // for any number of clipping paths. 609 // 610 // When we call restore on the SkCanvas, the layer's bitmap is composed 611 // into the layer below and we end up with correct, anti-aliased clipping. 612 613 SkPaint paint; 614 paint.setXfermodeMode(SkXfermode::kClear_Mode); 615 paint.setAntiAlias(true); 616 paint.setStyle(SkPaint::kFill_Style); 617 618 for (size_t i = paths.size() - 1; i < paths.size(); --i) { 619 paths[i].setFillType(SkPath::kInverseWinding_FillType); 620 m_canvas->drawPath(paths[i], paint); 621 } 622 623 m_canvas->restore(); 624 } 625