1 /* 2 * Copyright (c) 2012, 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 "platform/graphics/RegionTracker.h" 34 35 #include "platform/graphics/GraphicsContext.h" 36 #include "third_party/skia/include/core/SkColorFilter.h" 37 #include "third_party/skia/include/core/SkShader.h" 38 39 namespace blink { 40 41 RegionTracker::RegionTracker() 42 : m_opaqueRect(SkRect::MakeEmpty()) 43 , m_trackedRegionType(Opaque) 44 { 45 } 46 47 void RegionTracker::reset() 48 { 49 ASSERT(m_canvasLayerStack.isEmpty()); 50 m_opaqueRect = SkRect::MakeEmpty(); 51 } 52 53 IntRect RegionTracker::asRect() const 54 { 55 // Returns the largest enclosed rect. 56 // TODO: actually, this logic looks like its returning the smallest. 57 // to return largest, shouldn't we take floor of left/top 58 // and the ceil of right/bottom? 59 int left = SkScalarCeilToInt(m_opaqueRect.fLeft); 60 int top = SkScalarCeilToInt(m_opaqueRect.fTop); 61 int right = SkScalarFloorToInt(m_opaqueRect.fRight); 62 int bottom = SkScalarFloorToInt(m_opaqueRect.fBottom); 63 return IntRect(left, top, right-left, bottom-top); 64 } 65 66 // Returns true if the xfermode will force the dst to be opaque, regardless of the current dst. 67 static inline bool xfermodeIsOpaque(const SkPaint& paint, bool srcIsOpaque) 68 { 69 if (!srcIsOpaque) 70 return false; 71 72 SkXfermode* xfermode = paint.getXfermode(); 73 if (!xfermode) 74 return true; // default to kSrcOver_Mode 75 SkXfermode::Mode mode; 76 if (!xfermode->asMode(&mode)) 77 return false; 78 79 switch (mode) { 80 case SkXfermode::kSrc_Mode: // source 81 case SkXfermode::kSrcOver_Mode: // source + dest - source*dest 82 case SkXfermode::kDstOver_Mode: // source + dest - source*dest 83 case SkXfermode::kDstATop_Mode: // source 84 case SkXfermode::kPlus_Mode: // source+dest 85 default: // the rest are all source + dest - source*dest 86 return true; 87 case SkXfermode::kClear_Mode: // 0 88 case SkXfermode::kDst_Mode: // dest 89 case SkXfermode::kSrcIn_Mode: // source * dest 90 case SkXfermode::kDstIn_Mode: // dest * source 91 case SkXfermode::kSrcOut_Mode: // source * (1-dest) 92 case SkXfermode::kDstOut_Mode: // dest * (1-source) 93 case SkXfermode::kSrcATop_Mode: // dest 94 case SkXfermode::kXor_Mode: // source + dest - 2*(source*dest) 95 return false; 96 } 97 } 98 99 static inline bool xfermodeIsOverwrite(const SkPaint& paint) 100 { 101 SkXfermode* xfermode = paint.getXfermode(); 102 if (!xfermode) 103 return false; // default to kSrcOver_Mode 104 SkXfermode::Mode mode; 105 if (!xfermode->asMode(&mode)) 106 return false; 107 switch (mode) { 108 case SkXfermode::kSrc_Mode: 109 case SkXfermode::kClear_Mode: 110 return true; 111 default: 112 return false; 113 } 114 } 115 116 // Returns true if the xfermode will keep the dst opaque, assuming the dst is already opaque. 117 static inline bool xfermodePreservesOpaque(const SkPaint& paint, bool srcIsOpaque) 118 { 119 SkXfermode* xfermode = paint.getXfermode(); 120 if (!xfermode) 121 return true; // default to kSrcOver_Mode 122 SkXfermode::Mode mode; 123 if (!xfermode->asMode(&mode)) 124 return false; 125 126 switch (mode) { 127 case SkXfermode::kDst_Mode: // dest 128 case SkXfermode::kSrcOver_Mode: // source + dest - source*dest 129 case SkXfermode::kDstOver_Mode: // source + dest - source*dest 130 case SkXfermode::kSrcATop_Mode: // dest 131 case SkXfermode::kPlus_Mode: // source+dest 132 default: // the rest are all source + dest - source*dest 133 return true; 134 case SkXfermode::kClear_Mode: // 0 135 case SkXfermode::kSrcOut_Mode: // source * (1-dest) 136 case SkXfermode::kDstOut_Mode: // dest * (1-source) 137 case SkXfermode::kXor_Mode: // source + dest - 2*(source*dest) 138 return false; 139 case SkXfermode::kSrc_Mode: // source 140 case SkXfermode::kSrcIn_Mode: // source * dest 141 case SkXfermode::kDstIn_Mode: // dest * source 142 case SkXfermode::kDstATop_Mode: // source 143 return srcIsOpaque; 144 } 145 } 146 147 // Returns true if all pixels painted will be opaque. 148 static inline bool paintIsOpaque(const SkPaint& paint, RegionTracker::DrawType drawType, const SkBitmap* bitmap) 149 { 150 if (paint.getAlpha() < 0xFF) 151 return false; 152 bool checkFillOnly = drawType != RegionTracker::FillOrStroke; 153 if (!checkFillOnly && paint.getStyle() != SkPaint::kFill_Style && paint.isAntiAlias()) 154 return false; 155 SkShader* shader = paint.getShader(); 156 if (shader && !shader->isOpaque()) 157 return false; 158 if (bitmap && !bitmap->isOpaque()) 159 return false; 160 if (paint.getLooper()) 161 return false; 162 if (paint.getImageFilter()) 163 return false; 164 if (paint.getMaskFilter()) 165 return false; 166 SkColorFilter* colorFilter = paint.getColorFilter(); 167 if (colorFilter && !(colorFilter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag)) 168 return false; 169 return true; 170 } 171 172 // Returns true if there is a rectangular clip, with the result in |deviceClipRect|. 173 static inline bool getDeviceClipAsRect(const GraphicsContext* context, SkRect& deviceClipRect) 174 { 175 // Get the current clip in device coordinate space. 176 if (!context->canvas()->isClipRect()) { 177 deviceClipRect.setEmpty(); 178 return false; 179 } 180 181 SkIRect deviceClipIRect; 182 if (context->canvas()->getClipDeviceBounds(&deviceClipIRect)) 183 deviceClipRect.set(deviceClipIRect); 184 else 185 deviceClipRect.setEmpty(); 186 187 return true; 188 } 189 190 void RegionTracker::pushCanvasLayer(const SkPaint* paint) 191 { 192 CanvasLayerState state; 193 if (paint) 194 state.paint = *paint; 195 m_canvasLayerStack.append(state); 196 } 197 198 void RegionTracker::popCanvasLayer(const GraphicsContext* context) 199 { 200 ASSERT(!m_canvasLayerStack.isEmpty()); 201 if (m_canvasLayerStack.isEmpty()) 202 return; 203 204 const CanvasLayerState& canvasLayer = m_canvasLayerStack.last(); 205 SkRect layerOpaqueRect = canvasLayer.opaqueRect; 206 SkPaint layerPaint = canvasLayer.paint; 207 208 // Apply the image mask. 209 if (canvasLayer.hasImageMask && !layerOpaqueRect.intersect(canvasLayer.imageOpaqueRect)) 210 layerOpaqueRect.setEmpty(); 211 212 m_canvasLayerStack.removeLast(); 213 214 applyOpaqueRegionFromLayer(context, layerOpaqueRect, layerPaint); 215 } 216 217 void RegionTracker::setImageMask(const SkRect& imageOpaqueRect) 218 { 219 ASSERT(!m_canvasLayerStack.isEmpty()); 220 m_canvasLayerStack.last().hasImageMask = true; 221 m_canvasLayerStack.last().imageOpaqueRect = imageOpaqueRect; 222 } 223 224 void RegionTracker::didDrawRect(const GraphicsContext* context, const SkRect& fillRect, const SkPaint& paint, const SkBitmap* sourceBitmap) 225 { 226 // Any stroking may put alpha in pixels even if the filling part does not. 227 if (paint.getStyle() != SkPaint::kFill_Style) { 228 bool fillsBounds = false; 229 230 if (!paint.canComputeFastBounds()) { 231 didDrawUnbounded(context, paint, FillOrStroke); 232 } else { 233 SkRect strokeRect; 234 strokeRect = paint.computeFastBounds(fillRect, &strokeRect); 235 didDraw(context, strokeRect, paint, sourceBitmap, fillsBounds, FillOrStroke); 236 } 237 } 238 239 bool fillsBounds = paint.getStyle() != SkPaint::kStroke_Style; 240 didDraw(context, fillRect, paint, sourceBitmap, fillsBounds, FillOnly); 241 } 242 243 void RegionTracker::didDrawPath(const GraphicsContext* context, const SkPath& path, const SkPaint& paint) 244 { 245 SkRect rect; 246 if (path.isRect(&rect)) { 247 didDrawRect(context, rect, paint, 0); 248 return; 249 } 250 251 bool fillsBounds = false; 252 253 if (!paint.canComputeFastBounds()) { 254 didDrawUnbounded(context, paint, FillOrStroke); 255 } else { 256 rect = paint.computeFastBounds(path.getBounds(), &rect); 257 didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); 258 } 259 } 260 261 void RegionTracker::didDrawPoints(const GraphicsContext* context, SkCanvas::PointMode mode, int numPoints, const SkPoint points[], const SkPaint& paint) 262 { 263 if (!numPoints) 264 return; 265 266 SkRect rect; 267 rect.fLeft = points[0].fX; 268 rect.fRight = points[0].fX + 1; 269 rect.fTop = points[0].fY; 270 rect.fBottom = points[0].fY + 1; 271 272 for (int i = 1; i < numPoints; ++i) { 273 rect.fLeft = std::min(rect.fLeft, points[i].fX); 274 rect.fRight = std::max(rect.fRight, points[i].fX + 1); 275 rect.fTop = std::min(rect.fTop, points[i].fY); 276 rect.fBottom = std::max(rect.fBottom, points[i].fY + 1); 277 } 278 279 bool fillsBounds = false; 280 281 if (!paint.canComputeFastBounds()) { 282 didDrawUnbounded(context, paint, FillOrStroke); 283 } else { 284 rect = paint.computeFastBounds(rect, &rect); 285 didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); 286 } 287 } 288 289 void RegionTracker::didDrawBounded(const GraphicsContext* context, const SkRect& bounds, const SkPaint& paint) 290 { 291 bool fillsBounds = false; 292 293 if (!paint.canComputeFastBounds()) { 294 didDrawUnbounded(context, paint, FillOrStroke); 295 } else { 296 SkRect rect; 297 rect = paint.computeFastBounds(bounds, &rect); 298 didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); 299 } 300 } 301 302 void RegionTracker::didDraw(const GraphicsContext* context, const SkRect& rect, const SkPaint& paint, const SkBitmap* sourceBitmap, bool fillsBounds, DrawType drawType) 303 { 304 SkRect targetRect = rect; 305 306 // Apply the transform to device coordinate space. 307 SkMatrix canvasTransform = context->canvas()->getTotalMatrix(); 308 if (!canvasTransform.mapRect(&targetRect)) 309 fillsBounds = false; 310 311 // Apply the current clip. 312 SkRect deviceClipRect; 313 if (!getDeviceClipAsRect(context, deviceClipRect)) 314 fillsBounds = false; 315 else if (!targetRect.intersect(deviceClipRect)) 316 return; 317 318 if (m_trackedRegionType == Overwrite && fillsBounds && xfermodeIsOverwrite(paint)) { 319 markRectAsOpaque(targetRect); 320 return; 321 } 322 323 bool drawsOpaque = paintIsOpaque(paint, drawType, sourceBitmap); 324 bool xfersOpaque = xfermodeIsOpaque(paint, drawsOpaque); 325 326 if (fillsBounds && xfersOpaque) { 327 markRectAsOpaque(targetRect); 328 } else if (m_trackedRegionType == Opaque && !xfermodePreservesOpaque(paint, drawsOpaque)) { 329 markRectAsNonOpaque(targetRect); 330 } 331 } 332 333 void RegionTracker::didDrawUnbounded(const GraphicsContext* context, const SkPaint& paint, DrawType drawType) 334 { 335 bool drawsOpaque = paintIsOpaque(paint, drawType, 0); 336 bool preservesOpaque = xfermodePreservesOpaque(paint, drawsOpaque); 337 338 if (preservesOpaque) 339 return; 340 341 SkRect deviceClipRect; 342 getDeviceClipAsRect(context, deviceClipRect); 343 markRectAsNonOpaque(deviceClipRect); 344 } 345 346 void RegionTracker::applyOpaqueRegionFromLayer(const GraphicsContext* context, const SkRect& layerOpaqueRect, const SkPaint& paint) 347 { 348 SkRect deviceClipRect; 349 bool deviceClipIsARect = getDeviceClipAsRect(context, deviceClipRect); 350 351 if (deviceClipIsARect && deviceClipRect.isEmpty()) 352 return; 353 354 SkRect sourceOpaqueRect = layerOpaqueRect; 355 // Save the opaque area in the destination, so we can preserve the parts of it under the source opaque area if possible. 356 SkRect destinationOpaqueRect = currentTrackingOpaqueRect(); 357 358 bool outsideSourceOpaqueRectPreservesOpaque = xfermodePreservesOpaque(paint, false); 359 if (!outsideSourceOpaqueRectPreservesOpaque) { 360 if (!deviceClipIsARect) { 361 markAllAsNonOpaque(); 362 return; 363 } 364 markRectAsNonOpaque(deviceClipRect); 365 } 366 367 if (!deviceClipIsARect) 368 return; 369 if (!sourceOpaqueRect.intersect(deviceClipRect)) 370 return; 371 372 bool sourceOpaqueRectDrawsOpaque = paintIsOpaque(paint, FillOnly, 0); 373 bool sourceOpaqueRectXfersOpaque = xfermodeIsOpaque(paint, sourceOpaqueRectDrawsOpaque); 374 bool sourceOpaqueRectPreservesOpaque = xfermodePreservesOpaque(paint, sourceOpaqueRectDrawsOpaque); 375 376 // If the layer's opaque area is being drawn opaque in the layer below, then mark it opaque. Otherwise, 377 // if it preserves opaque then keep the intersection of the two. 378 if (sourceOpaqueRectXfersOpaque) 379 markRectAsOpaque(sourceOpaqueRect); 380 else if (sourceOpaqueRectPreservesOpaque && sourceOpaqueRect.intersect(destinationOpaqueRect)) 381 markRectAsOpaque(sourceOpaqueRect); 382 } 383 384 void RegionTracker::markRectAsOpaque(const SkRect& rect) 385 { 386 // We want to keep track of an opaque region but bound its complexity at a constant size. 387 // We keep track of the largest rectangle seen by area. If we can add the new rect to this 388 // rectangle then we do that, as that is the cheapest way to increase the area returned 389 // without increasing the complexity. 390 391 SkRect& opaqueRect = currentTrackingOpaqueRect(); 392 393 if (rect.isEmpty()) 394 return; 395 if (opaqueRect.contains(rect)) 396 return; 397 if (rect.contains(opaqueRect)) { 398 opaqueRect = rect; 399 return; 400 } 401 402 if (rect.fTop <= opaqueRect.fTop && rect.fBottom >= opaqueRect.fBottom) { 403 if (rect.fLeft < opaqueRect.fLeft && rect.fRight >= opaqueRect.fLeft) 404 opaqueRect.fLeft = rect.fLeft; 405 if (rect.fRight > opaqueRect.fRight && rect.fLeft <= opaqueRect.fRight) 406 opaqueRect.fRight = rect.fRight; 407 } else if (rect.fLeft <= opaqueRect.fLeft && rect.fRight >= opaqueRect.fRight) { 408 if (rect.fTop < opaqueRect.fTop && rect.fBottom >= opaqueRect.fTop) 409 opaqueRect.fTop = rect.fTop; 410 if (rect.fBottom > opaqueRect.fBottom && rect.fTop <= opaqueRect.fBottom) 411 opaqueRect.fBottom = rect.fBottom; 412 } 413 414 long opaqueArea = (long)opaqueRect.width() * (long)opaqueRect.height(); 415 long area = (long)rect.width() * (long)rect.height(); 416 if (area > opaqueArea) 417 opaqueRect = rect; 418 } 419 420 void RegionTracker::markRectAsNonOpaque(const SkRect& rect) 421 { 422 // We want to keep as much of the current opaque rectangle as we can, so find the one largest 423 // rectangle inside m_opaqueRect that does not intersect with |rect|. 424 425 SkRect& opaqueRect = currentTrackingOpaqueRect(); 426 427 if (!SkRect::Intersects(rect, opaqueRect)) 428 return; 429 if (rect.contains(opaqueRect)) { 430 markAllAsNonOpaque(); 431 return; 432 } 433 434 int deltaLeft = rect.fLeft - opaqueRect.fLeft; 435 int deltaRight = opaqueRect.fRight - rect.fRight; 436 int deltaTop = rect.fTop - opaqueRect.fTop; 437 int deltaBottom = opaqueRect.fBottom - rect.fBottom; 438 439 // horizontal is the larger of the two rectangles to the left or to the right of |rect| and inside opaqueRect. 440 // vertical is the larger of the two rectangles above or below |rect| and inside opaqueRect. 441 SkRect horizontal = opaqueRect; 442 if (deltaTop > deltaBottom) 443 horizontal.fBottom = rect.fTop; 444 else 445 horizontal.fTop = rect.fBottom; 446 SkRect vertical = opaqueRect; 447 if (deltaLeft > deltaRight) 448 vertical.fRight = rect.fLeft; 449 else 450 vertical.fLeft = rect.fRight; 451 452 if ((long)horizontal.width() * (long)horizontal.height() > (long)vertical.width() * (long)vertical.height()) 453 opaqueRect = horizontal; 454 else 455 opaqueRect = vertical; 456 } 457 458 void RegionTracker::markAllAsNonOpaque() 459 { 460 SkRect& opaqueRect = currentTrackingOpaqueRect(); 461 opaqueRect.setEmpty(); 462 } 463 464 SkRect& RegionTracker::currentTrackingOpaqueRect() 465 { 466 // If we are drawing into a canvas layer, then track the opaque rect in that layer. 467 return m_canvasLayerStack.isEmpty() ? m_opaqueRect : m_canvasLayerStack.last().opaqueRect; 468 } 469 470 } // namespace blink 471