root/Source/platform/graphics/skia/OpaqueRegionSkia.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. asRect
  2. xfermodeIsOpaque
  3. xfermodePreservesOpaque
  4. paintIsOpaque
  5. getDeviceClipAsRect
  6. pushCanvasLayer
  7. popCanvasLayer
  8. setImageMask
  9. didDrawRect
  10. didDrawPath
  11. didDrawPoints
  12. didDrawBounded
  13. didDraw
  14. didDrawUnbounded
  15. applyOpaqueRegionFromLayer
  16. markRectAsOpaque
  17. markRectAsNonOpaque
  18. markAllAsNonOpaque
  19. currentTrackingOpaqueRect

/*
 * 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 "config.h"

#include "platform/graphics/skia/OpaqueRegionSkia.h"

#include "platform/graphics/GraphicsContext.h"

#include "SkColorFilter.h"
#include "SkShader.h"

namespace WebCore {

OpaqueRegionSkia::OpaqueRegionSkia()
    : m_opaqueRect(SkRect::MakeEmpty())
{
}

IntRect OpaqueRegionSkia::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;
    }
}

// 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, OpaqueRegionSkia::DrawType drawType, const SkBitmap* bitmap)
{
    if (paint.getAlpha() < 0xFF)
        return false;
    bool checkFillOnly = drawType != OpaqueRegionSkia::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())
        return false;

    SkIRect deviceClipIRect;
    if (context->canvas()->getClipDeviceBounds(&deviceClipIRect))
        deviceClipRect.set(deviceClipIRect);
    else
        deviceClipRect.setEmpty();

    return true;
}

void OpaqueRegionSkia::pushCanvasLayer(const SkPaint* paint)
{
    CanvasLayerState state;
    if (paint)
        state.paint = *paint;
    m_canvasLayerStack.append(state);
}

void OpaqueRegionSkia::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 OpaqueRegionSkia::setImageMask(const SkRect& imageOpaqueRect)
{
    ASSERT(!m_canvasLayerStack.isEmpty());
    m_canvasLayerStack.last().hasImageMask = true;
    m_canvasLayerStack.last().imageOpaqueRect = imageOpaqueRect;
}

void OpaqueRegionSkia::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 OpaqueRegionSkia::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 OpaqueRegionSkia::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 OpaqueRegionSkia::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 OpaqueRegionSkia::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;

    bool drawsOpaque = paintIsOpaque(paint, drawType, sourceBitmap);
    bool xfersOpaque = xfermodeIsOpaque(paint, drawsOpaque);
    bool preservesOpaque = xfermodePreservesOpaque(paint, drawsOpaque);

    if (fillsBounds && xfersOpaque)
        markRectAsOpaque(targetRect);
    else if (!preservesOpaque)
        markRectAsNonOpaque(targetRect);
}

void OpaqueRegionSkia::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 OpaqueRegionSkia::applyOpaqueRegionFromLayer(const GraphicsContext* context, const SkRect& layerOpaqueRect, const SkPaint& paint)
{
    SkRect deviceClipRect;
    bool deviceClipIsARect = getDeviceClipAsRect(context, deviceClipRect);

    if (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)
        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 OpaqueRegionSkia::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 OpaqueRegionSkia::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 OpaqueRegionSkia::markAllAsNonOpaque()
{
    SkRect& opaqueRect = currentTrackingOpaqueRect();
    opaqueRect.setEmpty();
}

SkRect& OpaqueRegionSkia::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 WebCore

/* [<][>][^][v][top][bottom][index][help] */