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
RegionTracker()41 RegionTracker::RegionTracker()
42 : m_opaqueRect(SkRect::MakeEmpty())
43 , m_trackedRegionType(Opaque)
44 {
45 }
46
reset()47 void RegionTracker::reset()
48 {
49 ASSERT(m_canvasLayerStack.isEmpty());
50 m_opaqueRect = SkRect::MakeEmpty();
51 }
52
asRect() const53 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.
xfermodeIsOpaque(const SkPaint & paint,bool srcIsOpaque)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
xfermodeIsOverwrite(const SkPaint & paint)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.
xfermodePreservesOpaque(const SkPaint & paint,bool srcIsOpaque)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.
paintIsOpaque(const SkPaint & paint,RegionTracker::DrawType drawType,const SkBitmap * bitmap)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|.
getDeviceClipAsRect(const GraphicsContext * context,SkRect & 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
pushCanvasLayer(const SkPaint * paint)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
popCanvasLayer(const GraphicsContext * context)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
setImageMask(const SkRect & imageOpaqueRect)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
didDrawRect(const GraphicsContext * context,const SkRect & fillRect,const SkPaint & paint,const SkBitmap * sourceBitmap)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
didDrawPath(const GraphicsContext * context,const SkPath & path,const SkPaint & paint)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
didDrawPoints(const GraphicsContext * context,SkCanvas::PointMode mode,int numPoints,const SkPoint points[],const SkPaint & paint)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
didDrawBounded(const GraphicsContext * context,const SkRect & bounds,const SkPaint & paint)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
didDraw(const GraphicsContext * context,const SkRect & rect,const SkPaint & paint,const SkBitmap * sourceBitmap,bool fillsBounds,DrawType drawType)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
didDrawUnbounded(const GraphicsContext * context,const SkPaint & paint,DrawType drawType)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
applyOpaqueRegionFromLayer(const GraphicsContext * context,const SkRect & layerOpaqueRect,const SkPaint & paint)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
markRectAsOpaque(const SkRect & rect)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
markRectAsNonOpaque(const SkRect & rect)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
markAllAsNonOpaque()458 void RegionTracker::markAllAsNonOpaque()
459 {
460 SkRect& opaqueRect = currentTrackingOpaqueRect();
461 opaqueRect.setEmpty();
462 }
463
currentTrackingOpaqueRect()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