/* * Copyright (c) 2012, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "flutter/sky/engine/platform/graphics/RegionTracker.h" #include "flutter/sky/engine/platform/graphics/GraphicsContext.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/core/SkShader.h" namespace blink { RegionTracker::RegionTracker() : m_opaqueRect(SkRect::MakeEmpty()) , m_trackedRegionType(Opaque) { } void RegionTracker::reset() { ASSERT(m_canvasLayerStack.isEmpty()); m_opaqueRect = SkRect::MakeEmpty(); } IntRect RegionTracker::asRect() const { // Returns the largest enclosed rect. // TODO: actually, this logic looks like its returning the smallest. // to return largest, shouldn't we take floor of left/top // and the ceil of right/bottom? int left = SkScalarCeilToInt(m_opaqueRect.fLeft); int top = SkScalarCeilToInt(m_opaqueRect.fTop); int right = SkScalarFloorToInt(m_opaqueRect.fRight); int bottom = SkScalarFloorToInt(m_opaqueRect.fBottom); return IntRect(left, top, right-left, bottom-top); } // Returns true if the xfermode will force the dst to be opaque, regardless of the current dst. static inline bool xfermodeIsOpaque(const SkPaint& paint, bool srcIsOpaque) { if (!srcIsOpaque) return false; SkXfermode* xfermode = paint.getXfermode(); if (!xfermode) return true; // default to kSrcOver_Mode SkXfermode::Mode mode; if (!xfermode->asMode(&mode)) return false; switch (mode) { case SkXfermode::kSrc_Mode: // source case SkXfermode::kSrcOver_Mode: // source + dest - source*dest case SkXfermode::kDstOver_Mode: // source + dest - source*dest case SkXfermode::kDstATop_Mode: // source case SkXfermode::kPlus_Mode: // source+dest default: // the rest are all source + dest - source*dest return true; case SkXfermode::kClear_Mode: // 0 case SkXfermode::kDst_Mode: // dest case SkXfermode::kSrcIn_Mode: // source * dest case SkXfermode::kDstIn_Mode: // dest * source case SkXfermode::kSrcOut_Mode: // source * (1-dest) case SkXfermode::kDstOut_Mode: // dest * (1-source) case SkXfermode::kSrcATop_Mode: // dest case SkXfermode::kXor_Mode: // source + dest - 2*(source*dest) return false; } } static inline bool xfermodeIsOverwrite(const SkPaint& paint) { SkXfermode* xfermode = paint.getXfermode(); if (!xfermode) return false; // default to kSrcOver_Mode SkXfermode::Mode mode; if (!xfermode->asMode(&mode)) return false; switch (mode) { case SkXfermode::kSrc_Mode: case SkXfermode::kClear_Mode: return true; default: return false; } } // Returns true if the xfermode will keep the dst opaque, assuming the dst is already opaque. static inline bool xfermodePreservesOpaque(const SkPaint& paint, bool srcIsOpaque) { SkXfermode* xfermode = paint.getXfermode(); if (!xfermode) return true; // default to kSrcOver_Mode SkXfermode::Mode mode; if (!xfermode->asMode(&mode)) return false; switch (mode) { case SkXfermode::kDst_Mode: // dest case SkXfermode::kSrcOver_Mode: // source + dest - source*dest case SkXfermode::kDstOver_Mode: // source + dest - source*dest case SkXfermode::kSrcATop_Mode: // dest case SkXfermode::kPlus_Mode: // source+dest default: // the rest are all source + dest - source*dest return true; case SkXfermode::kClear_Mode: // 0 case SkXfermode::kSrcOut_Mode: // source * (1-dest) case SkXfermode::kDstOut_Mode: // dest * (1-source) case SkXfermode::kXor_Mode: // source + dest - 2*(source*dest) return false; case SkXfermode::kSrc_Mode: // source case SkXfermode::kSrcIn_Mode: // source * dest case SkXfermode::kDstIn_Mode: // dest * source case SkXfermode::kDstATop_Mode: // source return srcIsOpaque; } } // Returns true if all pixels painted will be opaque. static inline bool paintIsOpaque(const SkPaint& paint, RegionTracker::DrawType drawType, const SkBitmap* bitmap) { if (paint.getAlpha() < 0xFF) return false; bool checkFillOnly = drawType != RegionTracker::FillOrStroke; if (!checkFillOnly && paint.getStyle() != SkPaint::kFill_Style && paint.isAntiAlias()) return false; SkShader* shader = paint.getShader(); if (shader && !shader->isOpaque()) return false; if (bitmap && !bitmap->isOpaque()) return false; if (paint.getLooper()) return false; if (paint.getImageFilter()) return false; if (paint.getMaskFilter()) return false; SkColorFilter* colorFilter = paint.getColorFilter(); if (colorFilter && !(colorFilter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag)) return false; return true; } // Returns true if there is a rectangular clip, with the result in |deviceClipRect|. static inline bool getDeviceClipAsRect(const GraphicsContext* context, SkRect& deviceClipRect) { // Get the current clip in device coordinate space. if (!context->canvas()->isClipRect()) { deviceClipRect.setEmpty(); return false; } SkIRect deviceClipIRect; if (context->canvas()->getClipDeviceBounds(&deviceClipIRect)) deviceClipRect.set(deviceClipIRect); else deviceClipRect.setEmpty(); return true; } void RegionTracker::pushCanvasLayer(const SkPaint* paint) { CanvasLayerState state; if (paint) state.paint = *paint; m_canvasLayerStack.append(state); } void RegionTracker::popCanvasLayer(const GraphicsContext* context) { ASSERT(!m_canvasLayerStack.isEmpty()); if (m_canvasLayerStack.isEmpty()) return; const CanvasLayerState& canvasLayer = m_canvasLayerStack.last(); SkRect layerOpaqueRect = canvasLayer.opaqueRect; SkPaint layerPaint = canvasLayer.paint; // Apply the image mask. if (canvasLayer.hasImageMask && !layerOpaqueRect.intersect(canvasLayer.imageOpaqueRect)) layerOpaqueRect.setEmpty(); m_canvasLayerStack.removeLast(); applyOpaqueRegionFromLayer(context, layerOpaqueRect, layerPaint); } void RegionTracker::setImageMask(const SkRect& imageOpaqueRect) { ASSERT(!m_canvasLayerStack.isEmpty()); m_canvasLayerStack.last().hasImageMask = true; m_canvasLayerStack.last().imageOpaqueRect = imageOpaqueRect; } void RegionTracker::didDrawRect(const GraphicsContext* context, const SkRect& fillRect, const SkPaint& paint, const SkBitmap* sourceBitmap) { // Any stroking may put alpha in pixels even if the filling part does not. if (paint.getStyle() != SkPaint::kFill_Style) { bool fillsBounds = false; if (!paint.canComputeFastBounds()) { didDrawUnbounded(context, paint, FillOrStroke); } else { SkRect strokeRect; strokeRect = paint.computeFastBounds(fillRect, &strokeRect); didDraw(context, strokeRect, paint, sourceBitmap, fillsBounds, FillOrStroke); } } bool fillsBounds = paint.getStyle() != SkPaint::kStroke_Style; didDraw(context, fillRect, paint, sourceBitmap, fillsBounds, FillOnly); } void RegionTracker::didDrawPath(const GraphicsContext* context, const SkPath& path, const SkPaint& paint) { SkRect rect; if (path.isRect(&rect)) { didDrawRect(context, rect, paint, 0); return; } bool fillsBounds = false; if (!paint.canComputeFastBounds()) { didDrawUnbounded(context, paint, FillOrStroke); } else { rect = paint.computeFastBounds(path.getBounds(), &rect); didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); } } void RegionTracker::didDrawPoints(const GraphicsContext* context, SkCanvas::PointMode mode, int numPoints, const SkPoint points[], const SkPaint& paint) { if (!numPoints) return; SkRect rect; rect.fLeft = points[0].fX; rect.fRight = points[0].fX + 1; rect.fTop = points[0].fY; rect.fBottom = points[0].fY + 1; for (int i = 1; i < numPoints; ++i) { rect.fLeft = std::min(rect.fLeft, points[i].fX); rect.fRight = std::max(rect.fRight, points[i].fX + 1); rect.fTop = std::min(rect.fTop, points[i].fY); rect.fBottom = std::max(rect.fBottom, points[i].fY + 1); } bool fillsBounds = false; if (!paint.canComputeFastBounds()) { didDrawUnbounded(context, paint, FillOrStroke); } else { rect = paint.computeFastBounds(rect, &rect); didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); } } void RegionTracker::didDrawBounded(const GraphicsContext* context, const SkRect& bounds, const SkPaint& paint) { bool fillsBounds = false; if (!paint.canComputeFastBounds()) { didDrawUnbounded(context, paint, FillOrStroke); } else { SkRect rect; rect = paint.computeFastBounds(bounds, &rect); didDraw(context, rect, paint, 0, fillsBounds, FillOrStroke); } } void RegionTracker::didDraw(const GraphicsContext* context, const SkRect& rect, const SkPaint& paint, const SkBitmap* sourceBitmap, bool fillsBounds, DrawType drawType) { SkRect targetRect = rect; // Apply the transform to device coordinate space. SkMatrix canvasTransform = context->canvas()->getTotalMatrix(); if (!canvasTransform.mapRect(&targetRect)) fillsBounds = false; // Apply the current clip. SkRect deviceClipRect; if (!getDeviceClipAsRect(context, deviceClipRect)) fillsBounds = false; else if (!targetRect.intersect(deviceClipRect)) return; if (m_trackedRegionType == Overwrite && fillsBounds && xfermodeIsOverwrite(paint)) { markRectAsOpaque(targetRect); return; } bool drawsOpaque = paintIsOpaque(paint, drawType, sourceBitmap); bool xfersOpaque = xfermodeIsOpaque(paint, drawsOpaque); if (fillsBounds && xfersOpaque) { markRectAsOpaque(targetRect); } else if (m_trackedRegionType == Opaque && !xfermodePreservesOpaque(paint, drawsOpaque)) { markRectAsNonOpaque(targetRect); } } void RegionTracker::didDrawUnbounded(const GraphicsContext* context, const SkPaint& paint, DrawType drawType) { bool drawsOpaque = paintIsOpaque(paint, drawType, 0); bool preservesOpaque = xfermodePreservesOpaque(paint, drawsOpaque); if (preservesOpaque) return; SkRect deviceClipRect; getDeviceClipAsRect(context, deviceClipRect); markRectAsNonOpaque(deviceClipRect); } void RegionTracker::applyOpaqueRegionFromLayer(const GraphicsContext* context, const SkRect& layerOpaqueRect, const SkPaint& paint) { SkRect deviceClipRect; bool deviceClipIsARect = getDeviceClipAsRect(context, deviceClipRect); if (deviceClipIsARect && deviceClipRect.isEmpty()) return; SkRect sourceOpaqueRect = layerOpaqueRect; // Save the opaque area in the destination, so we can preserve the parts of it under the source opaque area if possible. SkRect destinationOpaqueRect = currentTrackingOpaqueRect(); bool outsideSourceOpaqueRectPreservesOpaque = xfermodePreservesOpaque(paint, false); if (!outsideSourceOpaqueRectPreservesOpaque) { if (!deviceClipIsARect) { markAllAsNonOpaque(); return; } markRectAsNonOpaque(deviceClipRect); } if (!deviceClipIsARect) return; if (!sourceOpaqueRect.intersect(deviceClipRect)) return; bool sourceOpaqueRectDrawsOpaque = paintIsOpaque(paint, FillOnly, 0); bool sourceOpaqueRectXfersOpaque = xfermodeIsOpaque(paint, sourceOpaqueRectDrawsOpaque); bool sourceOpaqueRectPreservesOpaque = xfermodePreservesOpaque(paint, sourceOpaqueRectDrawsOpaque); // If the layer's opaque area is being drawn opaque in the layer below, then mark it opaque. Otherwise, // if it preserves opaque then keep the intersection of the two. if (sourceOpaqueRectXfersOpaque) markRectAsOpaque(sourceOpaqueRect); else if (sourceOpaqueRectPreservesOpaque && sourceOpaqueRect.intersect(destinationOpaqueRect)) markRectAsOpaque(sourceOpaqueRect); } void RegionTracker::markRectAsOpaque(const SkRect& rect) { // We want to keep track of an opaque region but bound its complexity at a constant size. // We keep track of the largest rectangle seen by area. If we can add the new rect to this // rectangle then we do that, as that is the cheapest way to increase the area returned // without increasing the complexity. SkRect& opaqueRect = currentTrackingOpaqueRect(); if (rect.isEmpty()) return; if (opaqueRect.contains(rect)) return; if (rect.contains(opaqueRect)) { opaqueRect = rect; return; } if (rect.fTop <= opaqueRect.fTop && rect.fBottom >= opaqueRect.fBottom) { if (rect.fLeft < opaqueRect.fLeft && rect.fRight >= opaqueRect.fLeft) opaqueRect.fLeft = rect.fLeft; if (rect.fRight > opaqueRect.fRight && rect.fLeft <= opaqueRect.fRight) opaqueRect.fRight = rect.fRight; } else if (rect.fLeft <= opaqueRect.fLeft && rect.fRight >= opaqueRect.fRight) { if (rect.fTop < opaqueRect.fTop && rect.fBottom >= opaqueRect.fTop) opaqueRect.fTop = rect.fTop; if (rect.fBottom > opaqueRect.fBottom && rect.fTop <= opaqueRect.fBottom) opaqueRect.fBottom = rect.fBottom; } long opaqueArea = (long)opaqueRect.width() * (long)opaqueRect.height(); long area = (long)rect.width() * (long)rect.height(); if (area > opaqueArea) opaqueRect = rect; } void RegionTracker::markRectAsNonOpaque(const SkRect& rect) { // We want to keep as much of the current opaque rectangle as we can, so find the one largest // rectangle inside m_opaqueRect that does not intersect with |rect|. SkRect& opaqueRect = currentTrackingOpaqueRect(); if (!SkRect::Intersects(rect, opaqueRect)) return; if (rect.contains(opaqueRect)) { markAllAsNonOpaque(); return; } int deltaLeft = rect.fLeft - opaqueRect.fLeft; int deltaRight = opaqueRect.fRight - rect.fRight; int deltaTop = rect.fTop - opaqueRect.fTop; int deltaBottom = opaqueRect.fBottom - rect.fBottom; // horizontal is the larger of the two rectangles to the left or to the right of |rect| and inside opaqueRect. // vertical is the larger of the two rectangles above or below |rect| and inside opaqueRect. SkRect horizontal = opaqueRect; if (deltaTop > deltaBottom) horizontal.fBottom = rect.fTop; else horizontal.fTop = rect.fBottom; SkRect vertical = opaqueRect; if (deltaLeft > deltaRight) vertical.fRight = rect.fLeft; else vertical.fLeft = rect.fRight; if ((long)horizontal.width() * (long)horizontal.height() > (long)vertical.width() * (long)vertical.height()) opaqueRect = horizontal; else opaqueRect = vertical; } void RegionTracker::markAllAsNonOpaque() { SkRect& opaqueRect = currentTrackingOpaqueRect(); opaqueRect.setEmpty(); } SkRect& RegionTracker::currentTrackingOpaqueRect() { // If we are drawing into a canvas layer, then track the opaque rect in that layer. return m_canvasLayerStack.isEmpty() ? m_opaqueRect : m_canvasLayerStack.last().opaqueRect; } } // namespace blink