Ejemplo n.º 1
0
    RegionBench(int count, Proc proc, const char name[])  {
        fProc = proc;
        fName.printf("region_%s_%d", name, count);

        SkRandom rand;
        for (int i = 0; i < count; i++) {
            fA.op(randrect(rand), SkRegion::kXOR_Op);
            fB.op(randrect(rand), SkRegion::kXOR_Op);
        }
    }
Ejemplo n.º 2
0
    RegionBench(void* param, int count, Proc proc, const char name[], int mul = 1) : INHERITED(param) {
        fProc = proc;
        fName.printf("region_%s_%d", name, count);
        fLoopMul = mul;

        SkRandom rand;
        for (int i = 0; i < count; i++) {
            fA.op(randrect(rand), SkRegion::kXOR_Op);
            fB.op(randrect(rand), SkRegion::kXOR_Op);
        }
    }
Ejemplo n.º 3
0
void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int /* width */, int /* offset */, const Color& color)
{
    if (paintingDisabled())
        return;

    unsigned rectCount = rects.size();
    if (!rectCount)
        return;

    SkRegion focusRingRegion;
    const SkScalar focusRingOutset = WebCoreFloatToSkScalar(0.5);
    for (unsigned i = 0; i < rectCount; i++) {
        SkIRect r = rects[i];
        r.inset(-focusRingOutset, -focusRingOutset);
        focusRingRegion.op(r, SkRegion::kUnion_Op);
    }

    SkPath path;
    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setStyle(SkPaint::kStroke_Style);

    paint.setColor(color.rgb());
    paint.setStrokeWidth(focusRingOutset * 2);
    paint.setPathEffect(new SkCornerPathEffect(focusRingOutset * 2))->unref();
    focusRingRegion.getBoundaryPath(&path);
    platformContext()->canvas()->drawPath(path, paint);
}
Ejemplo n.º 4
0
void DefaultTapHighlight::paintContents(const GraphicsLayer*, GraphicsContext& c, GraphicsLayerPaintingPhase, const IntRect& /*inClip*/)
{
    std::vector<Platform::IntRect> rects = m_region.rects();
    Platform::IntRect rect = m_region.extents();
    SkRegion overlayRegion;

    unsigned rectCount = m_region.numRects();
    if (!rectCount)
        return;

    for (unsigned i = 0; i < rectCount; ++i) {
        Platform::IntRect rectToPaint = rects[i];
        SkIRect r = SkIRect::MakeXYWH(rectToPaint.x(), rectToPaint.y(), rectToPaint.width(), rectToPaint.height());
        overlayRegion.op(r, SkRegion::kUnion_Op);
    }

    SkPath pathToPaint;
    overlayRegion.getBoundaryPath(&pathToPaint);

    Path path(pathToPaint);
    c.save();
    c.translate(-rect.x(), -rect.y());

    // Draw tap highlight
    c.setFillColor(m_color, ColorSpaceDeviceRGB);
    c.fillPath(path);
    Color darker = Color(m_color.red(), m_color.green(), m_color.blue()); // Get rid of alpha.
    c.setStrokeColor(darker, ColorSpaceDeviceRGB);
    c.setStrokeThickness(1);
    c.strokePath(path);
    c.restore();
}
static jboolean Region_op2(JNIEnv* env, jobject, jlong dstHandle, jlong region1Handle, jlong region2Handle, jint op) {
    SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle);
    const SkRegion* region1 = reinterpret_cast<SkRegion*>(region1Handle);
    const SkRegion* region2 = reinterpret_cast<SkRegion*>(region2Handle);
    bool result = dst->op(*region1, *region2, (SkRegion::Op)op);
    return boolTojboolean(result);
}
Ejemplo n.º 6
0
void SkPageFlipper::inval(const SkRegion& rgn) {
    SkRegion r;
    r.setRect(0, 0, fWidth, fHeight);
    if (r.op(rgn, SkRegion::kIntersect_Op)) {
        fDirty1->op(r, SkRegion::kUnion_Op);
    }
}
Ejemplo n.º 7
0
void RegionLayerDrawExtra::addHighlightRegion(const LayerAndroid* layer, const Vector<IntRect>& rects,
                                              const IntPoint& additionalOffset,
                                              const IntRect& clipRect)
{
    if (rects.isEmpty())
        return;
    int layerId = layer ? layer->uniqueId() : 0;
    SkRegion* region = m_highlightRegions.get(layerId);
    if (!region) {
        region = new SkRegion();
        m_highlightRegions.set(layerId, region);
    }
    IntPoint offset = additionalOffset;
    WebViewCore::layerToAbsoluteOffset(layer, offset);
    for (size_t i = 0; i < rects.size(); i++) {
        IntRect r = rects.at(i);
        if (!clipRect.isEmpty()) {
            r.intersect(clipRect);
            if (r.isEmpty())
                continue; // don't add it to the region
        }
        r.move(-offset.x(), -offset.y());
        region->op(r.x(), r.y(), r.maxX(), r.maxY(), SkRegion::kUnion_Op);
    }
}
Ejemplo n.º 8
0
void PicturePile::drawWithClipRecursive(SkCanvas* canvas, SkRegion& clipRegion,
                                        int index)
{
    // TODO: Add some debug visualizations of this
    if (index < 0 || clipRegion.isEmpty())
        return;
    PictureContainer& pc = m_pile[index];
    IntRect intersection = clipRegion.getBounds();
    intersection.intersect(pc.area);
    if (pc.picture && !intersection.isEmpty()) {
// SAMSUNG CHANGE ++ : Animation GIF frame remain in Base Picture
// As the previous logic : Base Picture draws picture already drawn by other pile due to only using RECT.
// It uses PATH and RECT, both now.
		SkPath pathClip;
		clipRegion.getBoundaryPath(&pathClip);
// SAMSUNG CHANGE --
        clipRegion.op(intersection, SkRegion::kDifference_Op);
        drawWithClipRecursive(canvas, clipRegion, index - 1);
        int saved = canvas->save();
        canvas->clipRect(intersection);
		canvas->clipPath(pathClip);	// SAMSUNG CHANGE : Animation GIF frame remain in Base Picture
        canvas->translate(pc.area.x(), pc.area.y());
        canvas->drawPicture(*pc.picture);
        canvas->restoreToCount(saved);
    } else
        drawWithClipRecursive(canvas, clipRegion, index - 1);
}
void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int offset, const Color& color)
{
    if (paintingDisabled())
        return;

    unsigned rectCount = rects.size();
    if (!rectCount)
        return;

    SkRegion focusRingRegion;
    const int focusRingOutset = getFocusRingOutset(offset);
    for (unsigned i = 0; i < rectCount; i++) {
        SkIRect r = rects[i];
        r.inset(-focusRingOutset, -focusRingOutset);
        focusRingRegion.op(r, SkRegion::kUnion_Op);
    }

    SkPath path;
    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setStyle(SkPaint::kStroke_Style);

    paint.setColor(color.rgb());
    focusRingRegion.getBoundaryPath(&path);
    drawOuterPath(platformContext(), path, paint, width);
    drawInnerPath(platformContext(), path, paint, width);
}
static jboolean Region_op1(JNIEnv* env, jobject, jlong dstHandle, jobject rectObject, jlong regionHandle, jint op) {
    SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle);
    const SkRegion* region = reinterpret_cast<SkRegion*>(regionHandle);
    SkIRect    ir;
    GraphicsJNI::jrect_to_irect(env, rectObject, &ir);
    bool result = dst->op(ir, *region, (SkRegion::Op)op);
    return boolTojboolean(result);
}
static jboolean Region_op0(JNIEnv* env, jobject, jlong dstHandle, jint left, jint top, jint right, jint bottom, jint op) {
    SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle);
    SkIRect ir;

    ir.set(left, top, right, bottom);
    bool result = dst->op(ir, (SkRegion::Op)op);
    return boolTojboolean(result);
}
// Scale the region by given scale and set the reuslt to the dst.
// dest and src can be the same region instance.
static void scale_rgn(SkRegion* dst, const SkRegion& src, float scale) {
   SkRegion tmp;
   SkRegion::Iterator iter(src);

   for (; !iter.done(); iter.next()) {
       SkIRect r;
       scale_rect(&r, iter.rect(), scale);
       tmp.op(r, SkRegion::kUnion_Op);
   }
   dst->swap(tmp);
}
Ejemplo n.º 13
0
void SkCanvasStack::onClipRegion(const SkRegion& deviceRgn, SkClipOp op) {
    SkASSERT(fList.count() == fCanvasData.count());
    for (int i = 0; i < fList.count(); ++i) {
        SkRegion tempRegion;
        deviceRgn.translate(-fCanvasData[i].origin.x(),
                            -fCanvasData[i].origin.y(), &tempRegion);
        tempRegion.op(fCanvasData[i].requiredClip, SkRegion::kIntersect_Op);
        fList[i]->clipRegion(tempRegion, op);
    }
    this->SkCanvas::onClipRegion(deviceRgn, op);
}
Ejemplo n.º 14
0
    RegionContainBench(Proc proc, const char name[])  {
        fProc = proc;
        fName.printf("region_contains_%s", name);

        SkRandom rand;
        for (int i = 0; i < COUNT; i++) {
            fA.op(randrect(rand, i), SkRegion::kXOR_Op);
        }

        fB.setRect(0, 0, H, W);
    }
Ejemplo n.º 15
0
void GLExtras::drawCursorRings()
{
    SkRegion region;
    for (size_t i = 0; i < m_ring->rings().size(); i++) {
        IntRect rect = m_ring->rings().at(i);
        if (i == 0)
            region.setRect(rect);
        else
            region.op(rect, SkRegion::kUnion_Op);
    }
    drawRegion(region, m_ring->m_isPressed, !m_ring->m_isButton, false);
}
    RegionContainBench(void* param, Proc proc, const char name[]) : INHERITED(param) {
        fProc = proc;
        fName.printf("region_contains_%s", name);

        SkRandom rand;
        for (int i = 0; i < COUNT; i++) {
            fA.op(randrect(rand, i), SkRegion::kXOR_Op);
        }

        fB.setRect(0, 0, H, W);

        fIsRendering = false;
    }
Ejemplo n.º 17
0
void SkConservativeClip::opIRect(const SkIRect& devRect, SkRegion::Op op) {
    if (SkRegion::kIntersect_Op == op) {
        if (!fBounds.intersect(devRect)) {
            fBounds.setEmpty();
        }
        return;
    }

    // This may still create a complex region (which we would then take the bounds
    // Perhaps we should inline the op-logic directly to never create the rgn...
    SkRegion result;
    result.op(SkRegion(fBounds), SkRegion(devRect), op);
    fBounds = result.getBounds();
    this->applyClipRestriction(op, &fBounds);
}
Ejemplo n.º 18
0
extern "C" bool complex_clips_draw_from_canvas_state(SkCanvasState* state,
        int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t clipOp,
        int32_t regionRects, int32_t* rectCoords) {
    std::unique_ptr<SkCanvas> canvas = SkCanvasStateUtils::MakeFromCanvasState(state);
    if (!canvas) {
        return false;
    }

    SkRegion localRegion;
    for (int32_t i = 0; i < regionRects; ++i) {
        localRegion.op(rectCoords[0], rectCoords[1], rectCoords[2], rectCoords[3],
                       SkRegion::kUnion_Op);
        rectCoords += 4;
    }

    complex_clips_draw(canvas.get(), left, top, right, bottom, clipOp, localRegion);
    return true;
}
Ejemplo n.º 19
0
void CursorRing::draw(SkCanvas* canvas, LayerAndroid* layer)
{
#if USE(ACCELERATED_COMPOSITING)
    int layerId = m_node->isInLayer() ? m_frame->layer(m_node)->uniqueId() : -1;
    if (layer->uniqueId() != layerId)
        return;
#endif
    if (canvas->quickReject(m_bounds, SkCanvas::kAA_EdgeType)) {
        DBG_NAV_LOGD("canvas->quickReject cursorNode=%d (nodePointer=%p)"
            " bounds=(%d,%d,w=%d,h=%d)", m_node->index(), m_node->nodePointer(),
            m_bounds.x(), m_bounds.y(), m_bounds.width(), m_bounds.height());
        m_followedLink = false;
        return;
    }
    unsigned rectCount = m_rings.size();
    SkRegion rgn;
    SkPath path;
    for (unsigned i = 0; i < rectCount; i++)
    {
        SkRect  r(m_rings[i]);
        SkIRect ir;

        r.round(&ir);
        ir.inset(-CURSOR_RING_OUTER_OUTSET, -CURSOR_RING_OUTER_OUTSET);
        rgn.op(ir, SkRegion::kUnion_Op);
    }
    rgn.getBoundaryPath(&path);

    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setPathEffect(new SkCornerPathEffect(CURSOR_RING_ROUNDEDNESS))->unref();
    if (m_flavor >= NORMAL_ANIMATING) { // pressed
        paint.setColor(cursorPressedColors[m_flavor - NORMAL_ANIMATING]);
        canvas->drawPath(path, paint);
    }
    paint.setStyle(SkPaint::kStroke_Style);
    paint.setStrokeWidth(CURSOR_RING_OUTER_DIAMETER);
    paint.setColor(cursorOuterColors[m_flavor]);
    canvas->drawPath(path, paint);
    paint.setStrokeWidth(CURSOR_RING_INNER_DIAMETER);
    paint.setColor(cursorInnerColors[m_flavor]);
    canvas->drawPath(path, paint);
}
Ejemplo n.º 20
0
static void setup_canvas_from_MC_state(const SkMCState& state, SkCanvas* canvas) {
    // reconstruct the matrix
    SkMatrix matrix;
    for (int i = 0; i < 9; i++) {
        matrix.set(i, state.matrix[i]);
    }

    // reconstruct the clip
    SkRegion clip;
    for (int i = 0; i < state.clipRectCount; ++i) {
        clip.op(SkIRect::MakeLTRB(state.clipRects[i].left,
                                  state.clipRects[i].top,
                                  state.clipRects[i].right,
                                  state.clipRects[i].bottom),
                SkRegion::kUnion_Op);
    }

    canvas->setMatrix(matrix);
    canvas->setClipRegion(clip);
}
Ejemplo n.º 21
0
    void updateMC(const SkMatrix& totalMatrix, const SkRegion& totalClip,
                  const SkClipStack& clipStack, SkRegion* updateClip) {
        int x = fDevice->getOrigin().x();
        int y = fDevice->getOrigin().y();
        int width = fDevice->width();
        int height = fDevice->height();

        if ((x | y) == 0) {
            fMatrix = &totalMatrix;
            fClip = totalClip;
        } else {
            fMatrixStorage = totalMatrix;
            fMatrixStorage.postTranslate(SkIntToScalar(-x),
                                         SkIntToScalar(-y));
            fMatrix = &fMatrixStorage;

            totalClip.translate(-x, -y, &fClip);
        }

        fClip.op(0, 0, width, height, SkRegion::kIntersect_Op);

        // intersect clip, but don't translate it (yet)

        if (updateClip) {
            updateClip->op(x, y, x + width, y + height,
                           SkRegion::kDifference_Op);
        }

        fDevice->setMatrixClip(*fMatrix, fClip, clipStack);

#ifdef SK_DEBUG
        if (!fClip.isEmpty()) {
            SkIRect deviceR;
            deviceR.set(0, 0, width, height);
            SkASSERT(deviceR.contains(fClip.getBounds()));
        }
#endif
        // default is to assume no external matrix
        fMVMatrix = NULL;
        fExtMatrix = NULL;
    }
    void createContent(int width, int height, Canvas& canvas) override {
        canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
        canvas.insertReorderBarrier(true);

        card = TestUtils::createNode(50, 50, 250, 250, [](RenderProperties& props, Canvas& canvas) {
            canvas.drawColor(0xFFFF00FF, SkBlendMode::kSrcOver);

            SkRegion region;
            for (int xOffset = 0; xOffset < 200; xOffset += 2) {
                for (int yOffset = 0; yOffset < 200; yOffset += 2) {
                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
                }
            }

            SkPaint paint;
            paint.setColor(0xff00ffff);
            canvas.drawRegion(region, paint);
        });
        canvas.drawRenderNode(card.get());

        canvas.insertReorderBarrier(false);
    }
static jlong Region_createFromParcel(JNIEnv* env, jobject clazz, jobject parcel)
{
    if (parcel == nullptr) {
        return 0;
    }

    android::Parcel* p = android::parcelForJavaObject(env, parcel);

    std::vector<int32_t> rects;
    p->readInt32Vector(&rects);

    if ((rects.size() % 4) != 0) {
        return 0;
    }

    SkRegion* region = new SkRegion;
    for (size_t x = 0; x + 4 <= rects.size(); x += 4) {
        region->op(rects[x], rects[x+1], rects[x+2], rects[x+3], SkRegion::kUnion_Op);
    }

    return reinterpret_cast<jlong>(region);
}
Ejemplo n.º 24
0
static bool union_proc(SkRegion& a, SkRegion& b) {
    SkRegion result;
    return result.op(a, b, SkRegion::kUnion_Op);
}
Ejemplo n.º 25
0
void TileGrid::drawGL(const IntRect& visibleContentArea, float opacity,
                      const TransformationMatrix* transform,
                      const Color* background)
{
    m_area = computeTilesArea(visibleContentArea, m_scale);
    if (m_area.width() == 0 || m_area.height() == 0)
        return;

    float invScale = 1.0 / m_scale;
    const float tileWidth = TilesManager::tileWidth() * invScale;
    const float tileHeight = TilesManager::tileHeight() * invScale;

    int drawn = 0;

    SkRegion missingRegion;
    bool semiOpaqueBaseSurface =
        background ? (background->hasAlpha() && background->alpha() > 0) : false;
    if (semiOpaqueBaseSurface) {
        SkIRect totalArea = SkIRect::MakeXYWH(m_area.x(), m_area.y(),
                                              m_area.width(), m_area.height());
        missingRegion = SkRegion(totalArea);
    }

    bool usePointSampling =
        TilesManager::instance()->shader()->usePointSampling(m_scale, transform);

    float minTileX =  visibleContentArea.x() / tileWidth;
    float minTileY =  visibleContentArea.y() / tileWidth;
    float maxTileWidth = visibleContentArea.maxX() / tileWidth;
    float maxTileHeight = visibleContentArea.maxY() / tileWidth;
    ALOGV("minTileX, minTileY, maxTileWidth, maxTileHeight %f, %f, %f %f",
          minTileX, minTileY, maxTileWidth, maxTileHeight);
    for (unsigned int i = 0; i < m_tiles.size(); i++) {
        Tile* tile = m_tiles[i];

        bool tileInView = tile->isTileVisible(m_area);
        if (tileInView) {
            SkRect rect;
            rect.fLeft = tile->x() * tileWidth;
            rect.fTop = tile->y() * tileHeight;
            rect.fRight = rect.fLeft + tileWidth;
            rect.fBottom = rect.fTop + tileHeight;
            ALOGV("tile %p (layer tile: %d) %d,%d at scale %.2f vs %.2f [ready: %d] dirty: %d",
                  tile, tile->isLayerTile(), tile->x(), tile->y(),
                  tile->scale(), m_scale, tile->isTileReady(), tile->isDirty());

            bool forceBaseBlending = background ? background->hasAlpha() : false;

            float left = std::max(minTileX - tile->x(), 0.0f);
            float top = std::max(minTileY - tile->y(), 0.0f);
            float right = std::min(maxTileWidth - tile->x(), 1.0f);
            float bottom = std::min(maxTileHeight - tile->y(), 1.0f);
            if (left > 1.0f || top > 1.0f || right < 0.0f || bottom < 0.0f) {
                ALOGE("Unexpected portion:left, top, right, bottom %f %f %f %f",
                      left, top, right, bottom);
                left = 0.0f;
                top = 0.0f;
                right = 1.0f;
                bottom = 1.0f;
            }
            FloatRect fillPortion(left, top, right - left, bottom - top);

            bool success = tile->drawGL(opacity, rect, m_scale, transform,
                                        forceBaseBlending, usePointSampling, fillPortion);
            if (semiOpaqueBaseSurface && success) {
                // Cut the successful drawn tile area from the missing region.
                missingRegion.op(SkIRect::MakeXYWH(tile->x(), tile->y(), 1, 1),
                                 SkRegion::kDifference_Op);
            }
            if (tile->frontTexture())
                drawn++;
        }

        // log tile information for base, high res tiles
        if (m_isBaseSurface && background)
            TilesManager::instance()->getProfiler()->nextTile(tile, invScale, tileInView);
    }

    // Draw missing Regions with blend turned on
    if (semiOpaqueBaseSurface)
        drawMissingRegion(missingRegion, opacity, background);

    ALOGV("TG %p drew %d tiles, scale %f",
          this, drawn, m_scale);
}
Ejemplo n.º 26
0
bool Surface::tryUpdateSurface(Surface* oldSurface)
{
    if (!needsTexture() || !oldSurface->needsTexture())
        return false;

    // merge surfaces based on first layer ID
    if (getFirstLayer()->uniqueId() != oldSurface->getFirstLayer()->uniqueId())
        return false;

    m_surfaceBacking = oldSurface->m_surfaceBacking;
    SkSafeRef(m_surfaceBacking);

    ALOGV("%p taking old SurfBack %p from surface %p, nt %d",
          this, m_surfaceBacking, oldSurface, oldSurface->needsTexture());

    if (!m_surfaceBacking) {
        // no SurfBack to inval, so don't worry about it.
        return true;
    }

    SkRegion invalRegion;
    bool fullInval = false;
    if (singleLayer() && oldSurface->singleLayer()) {
        // both are single matching layers, simply apply inval
        SkRegion* layerInval = getFirstLayer()->getInvalRegion();
        invalRegion = *layerInval;

        if (isBase()) {
            // the base layer paints outside it's content area to ensure the
            // viewport is convered, so fully invalidate all tiles if its size
            // changes to ensure no stale content remains
            LayerContent* newContent = getFirstLayer()->content();
            LayerContent* oldContent = oldSurface->getFirstLayer()->content();
            fullInval = newContent->width() != oldContent->width()
                || newContent->height() != oldContent->height();
        }
    } else {
        fullInval = m_layers.size() != oldSurface->m_layers.size();
        if (!fullInval) {
            for (unsigned int i = 0; i < m_layers.size(); i++) {
                if ((m_layers[i]->uniqueId() != oldSurface->m_layers[i]->uniqueId())
                    || (m_layers[i]->fullContentAreaMapped() != oldSurface->m_layers[i]->fullContentAreaMapped())) {
                    // layer list has changed, fully invalidate
                    // TODO: partially invalidate based on layer size/position
                    fullInval = true;
                    break;
                } else if (!m_layers[i]->getInvalRegion()->isEmpty()) {
                    // merge layer inval - translate the layer's inval region into surface coordinates
                    // TODO: handle scale/3d transform mapping
                    FloatRect layerPos = m_layers[i]->fullContentAreaMapped();
                    m_layers[i]->getInvalRegion()->translate(layerPos.x(), layerPos.y());
                    invalRegion.op(*(m_layers[i]->getInvalRegion()), SkRegion::kUnion_Op);
                }
            }
        }
    }

    if (fullInval)
        invalRegion.setRect(-1e8, -1e8, 2e8, 2e8);

    m_surfaceBacking->markAsDirty(invalRegion);
    return true;
}
Ejemplo n.º 27
0
static bool diffrectbig_proc(SkRegion& a, SkRegion& b) {
    SkRegion result;
    return result.op(a, a.getBounds(), SkRegion::kDifference_Op);
}
Ejemplo n.º 28
0
static bool diff_proc(SkRegion& a, SkRegion& b) {
    SkRegion result;
    return result.op(a, b, SkRegion::kDifference_Op);
}
Ejemplo n.º 29
0
static bool sect_proc(SkRegion& a, SkRegion& b) {
    SkRegion result;
    return result.op(a, b, SkRegion::kIntersect_Op);
}
Ejemplo n.º 30
0
DEF_TEST(CanvasState_test_complex_clips, reporter) {
    const int WIDTH = 400;
    const int HEIGHT = 400;
    const int SPACER = 10;

    SkIRect layerRect = SkIRect::MakeWH(WIDTH, HEIGHT / 4);
    layerRect.inset(2*SPACER, 2*SPACER);

    SkIRect clipRect = layerRect;
    clipRect.fRight = clipRect.fLeft + (clipRect.width() / 2) - (2*SPACER);
    clipRect.outset(SPACER, SPACER);

    SkIRect regionBounds = clipRect;
    regionBounds.offset(clipRect.width() + (2*SPACER), 0);

    SkIRect regionInterior = regionBounds;
    regionInterior.inset(SPACER*3, SPACER*3);

    SkRegion clipRegion;
    clipRegion.setRect(regionBounds);
    clipRegion.op(regionInterior, SkRegion::kDifference_Op);


    const SkRegion::Op clipOps[] = { SkRegion::kIntersect_Op,
                                     SkRegion::kIntersect_Op,
                                     SkRegion::kReplace_Op,
    };
    const SkCanvas::SaveLayerFlags flags[] = {
        static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag),
        0,
        static_cast<SkCanvas::SaveLayerFlags>(SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag),
    };
    REPORTER_ASSERT(reporter, sizeof(clipOps) == sizeof(flags));

    bool (*drawFn)(SkCanvasState* state, int32_t l, int32_t t,
                   int32_t r, int32_t b, int32_t clipOp,
                   int32_t regionRects, int32_t* rectCoords);

    OpenLibResult openLibResult(reporter);
    if (openLibResult.handle() != nullptr) {
        *(void**) (&drawFn) = dlsym(openLibResult.handle(),
                                    "complex_clips_draw_from_canvas_state");
    } else {
        drawFn = complex_clips_draw_from_canvas_state;
    }

    REPORTER_ASSERT(reporter, drawFn);
    if (!drawFn) {
        return;
    }

    SkBitmap bitmaps[2];
    for (int i = 0; i < 2; ++i) {
        bitmaps[i].allocN32Pixels(WIDTH, HEIGHT);

        SkCanvas canvas(bitmaps[i]);

        canvas.drawColor(SK_ColorRED);

        SkRegion localRegion = clipRegion;

        SkPaint paint;
        paint.setAlpha(128);
        for (size_t j = 0; j < SK_ARRAY_COUNT(flags); ++j) {
            SkRect layerBounds = SkRect::Make(layerRect);
            canvas.saveLayer(SkCanvas::SaveLayerRec(&layerBounds, &paint, flags[j]));

            if (i) {
                SkCanvasState* state = SkCanvasStateUtils::CaptureCanvasState(&canvas);
                REPORTER_ASSERT(reporter, state);

                SkRegion::Iterator iter(localRegion);
                SkTDArray<int32_t> rectCoords;
                for (; !iter.done(); iter.next()) {
                    const SkIRect& rect = iter.rect();
                    *rectCoords.append() = rect.fLeft;
                    *rectCoords.append() = rect.fTop;
                    *rectCoords.append() = rect.fRight;
                    *rectCoords.append() = rect.fBottom;
                }
                bool success = drawFn(state, clipRect.fLeft, clipRect.fTop,
                                      clipRect.fRight, clipRect.fBottom, clipOps[j],
                                      rectCoords.count() / 4, rectCoords.begin());
                REPORTER_ASSERT(reporter, success);

                SkCanvasStateUtils::ReleaseCanvasState(state);
            } else {
                complex_clips_draw(&canvas, clipRect.fLeft, clipRect.fTop,
                                   clipRect.fRight, clipRect.fBottom, clipOps[j],
                                   localRegion);
            }

            canvas.restore();

            // translate the canvas and region for the next iteration
            canvas.translate(0, SkIntToScalar(2*(layerRect.height() + (SPACER))));
            localRegion.translate(0, 2*(layerRect.height() + SPACER));
        }
    }

    // now we memcmp the two bitmaps
    REPORTER_ASSERT(reporter, bitmaps[0].getSize() == bitmaps[1].getSize());
    REPORTER_ASSERT(reporter, !memcmp(bitmaps[0].getPixels(),
                                      bitmaps[1].getPixels(),
                                      bitmaps[0].getSize()));
}