Improve clip support (add intersect, union and replace.)

This change also modifies the way the clip is stored. The clip is now
always stored in screen-space coordinates.

Change-Id: I96375784d82dfe975bc6477a159e6866e7052487
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index a2ab8bd..676aae3 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -149,43 +149,44 @@
 
     @Override
     public boolean clipRect(float left, float top, float right, float bottom) {
-        return nClipRect(mRenderer, left, top, right, bottom);
+        return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
     }
     
-    private native boolean nClipRect(int renderer, float left, float top, float right, float bottom);
+    private native boolean nClipRect(int renderer, float left, float top,
+            float right, float bottom, int op);
 
     @Override
     public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) {
-        throw new UnsupportedOperationException();
+        return nClipRect(mRenderer, left, top, right, bottom, op.nativeInt);
     }
 
     @Override
     public boolean clipRect(int left, int top, int right, int bottom) {
-        return nClipRect(mRenderer, left, top, right, bottom);        
+        return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);        
     }
     
-    private native boolean nClipRect(int renderer, int left, int top, int right, int bottom);
+    private native boolean nClipRect(int renderer, int left, int top, int right, int bottom, int op);
 
     @Override
     public boolean clipRect(Rect rect) {
-        return clipRect(rect.left, rect.top, rect.right, rect.bottom);        
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);        
     }
 
     @Override
     public boolean clipRect(Rect rect, Region.Op op) {
-        // TODO: Implement
-        throw new UnsupportedOperationException();
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
     }
 
     @Override
     public boolean clipRect(RectF rect) {
-        return clipRect(rect.left, rect.top, rect.right, rect.bottom);
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);
     }
 
     @Override
     public boolean clipRect(RectF rect, Region.Op op) {
-        // TODO: Implement
-        throw new UnsupportedOperationException();
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
     }
 
     @Override
@@ -347,7 +348,6 @@
 
     @Override
     public void setDrawFilter(DrawFilter filter) {
-        // Don't crash, but ignore the draw filter
         // TODO: Implement PaintDrawFilter
         mFilter = filter;
     }
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index dbf482e..e5aa5dd 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -23,6 +23,7 @@
 #include <SkCanvas.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
+#include <SkRegion.h>
 #include <SkXfermode.h>
 
 #include <OpenGLRenderer.h>
@@ -120,13 +121,15 @@
 }
 
 static bool android_view_GLES20Canvas_clipRectF(JNIEnv* env, jobject canvas,
-        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom) {
-    return renderer->clipRect(left, top, right, bottom);
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        SkRegion::Op op) {
+    return renderer->clipRect(left, top, right, bottom, op);
 }
 
 static bool android_view_GLES20Canvas_clipRect(JNIEnv* env, jobject canvas,
-        OpenGLRenderer* renderer, jint left, jint top, jint right, jint bottom) {
-    return renderer->clipRect(float(left), float(top), float(right), float(bottom));
+        OpenGLRenderer* renderer, jint left, jint top, jint right, jint bottom,
+        SkRegion::Op op) {
+    return renderer->clipRect(float(left), float(top), float(right), float(bottom), op);
 }
 
 static bool android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject canvas,
@@ -257,8 +260,8 @@
     {   "nSaveLayerAlpha",    "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayerAlpha },
 
     {   "nQuickReject",       "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_quickReject },
-    {   "nClipRect",          "(IFFFF)Z",        (void*) android_view_GLES20Canvas_clipRectF },
-    {   "nClipRect",          "(IIIII)Z",        (void*) android_view_GLES20Canvas_clipRect },
+    {   "nClipRect",          "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_clipRectF },
+    {   "nClipRect",          "(IIIIII)Z",       (void*) android_view_GLES20Canvas_clipRect },
 
     {   "nTranslate",         "(IFF)V",          (void*) android_view_GLES20Canvas_translate },
     {   "nRotate",            "(IF)V",           (void*) android_view_GLES20Canvas_rotate },
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index 2b080aa..489ef83 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -33,7 +33,11 @@
         Op(int nativeInt) {
             this.nativeInt = nativeInt;
         }
-        final int nativeInt;
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
     }
 
     /** Create an empty region
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 877d3bb..b459202 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -93,6 +93,25 @@
     v.set(SkMatrix::kMPersp2, data[15]);
 }
 
+void Matrix4::loadInverse(const Matrix4& v) {
+    double scale = 1.0 /
+            (v.data[0]  * ((double) v.data[5]  * v.data[15] - (double) v.data[13] * v.data[7]) +
+             v.data[4]  * ((double) v.data[13] * v.data[3]  - (double) v.data[1]  * v.data[15]) +
+             v.data[12] * ((double) v.data[1]  * v.data[7]  - (double) v.data[5]  * v.data[3]));
+
+    data[0]  = (v.data[5] * v.data[15] - v.data[13] * v.data[7])  * scale;
+    data[4]  = (v.data[12] * v.data[7] - v.data[4]  * v.data[15]) * scale;
+    data[12] = (v.data[4] * v.data[13] - v.data[12] * v.data[5])  * scale;
+
+    data[1]  = (v.data[13] * v.data[3] - v.data[1]  * v.data[15]) * scale;
+    data[5]  = (v.data[0] * v.data[15] - v.data[12] * v.data[3])  * scale;
+    data[13] = (v.data[12] * v.data[1] - v.data[0]  * v.data[13]) * scale;
+
+    data[3]  = (v.data[1] * v.data[7] - v.data[5] * v.data[3]) * scale;
+    data[7]  = (v.data[4] * v.data[3] - v.data[0] * v.data[7]) * scale;
+    data[15] = (v.data[0] * v.data[5] - v.data[4] * v.data[1]) * scale;
+}
+
 void Matrix4::copyTo(float* v) const {
     memcpy(v, data, sizeof(data));
 }
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index ba5be03..b8a4da7 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -54,6 +54,8 @@
     void load(const Matrix4& v);
     void load(const SkMatrix& v);
 
+    void loadInverse(const Matrix4& v);
+
     void loadTranslate(float x, float y, float z);
     void loadScale(float sx, float sy, float sz);
     void loadRotate(float angle, float x, float y, float z);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index decfecf..d950ffa 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -244,7 +244,7 @@
     glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
 
     // Restore the clip from the previous snapshot
-    const Rect& clip = previous->getMappedClip();
+    const Rect& clip = previous->clipRect;
     glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
 
     Layer* layer = current->layer;
@@ -339,12 +339,11 @@
     saveSnapshot();
     // TODO: This doesn't preserve other transformations (check Skia first)
     mSnapshot->transform.loadTranslate(-left, -top, 0.0f);
-    mSnapshot->setClip(left, top, right, bottom);
+    mSnapshot->setClip(0.0f, 0.0f, right - left, bottom - top);
     mSnapshot->height = bottom - top;
     setScissorFromClip();
 
-    mSnapshot->flags = Snapshot::kFlagDirtyTransform | Snapshot::kFlagDirtyOrtho |
-            Snapshot::kFlagClipSet;
+    mSnapshot->flags = Snapshot::kFlagDirtyOrtho | Snapshot::kFlagClipSet;
     mSnapshot->orthoMatrix.load(mOrthoMatrix);
 
     // Change the ortho projection
@@ -359,22 +358,18 @@
 
 void OpenGLRenderer::translate(float dx, float dy) {
     mSnapshot->transform.translate(dx, dy, 0.0f);
-    mSnapshot->flags |= Snapshot::kFlagDirtyTransform;
 }
 
 void OpenGLRenderer::rotate(float degrees) {
     mSnapshot->transform.rotate(degrees, 0.0f, 0.0f, 1.0f);
-    mSnapshot->flags |= Snapshot::kFlagDirtyTransform;
 }
 
 void OpenGLRenderer::scale(float sx, float sy) {
     mSnapshot->transform.scale(sx, sy, 1.0f);
-    mSnapshot->flags |= Snapshot::kFlagDirtyTransform;
 }
 
 void OpenGLRenderer::setMatrix(SkMatrix* matrix) {
     mSnapshot->transform.load(*matrix);
-    mSnapshot->flags |= Snapshot::kFlagDirtyTransform;
 }
 
 void OpenGLRenderer::getMatrix(SkMatrix* matrix) {
@@ -384,7 +379,6 @@
 void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
     mat4 m(*matrix);
     mSnapshot->transform.multiply(m);
-    mSnapshot->flags |= Snapshot::kFlagDirtyTransform;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -392,38 +386,26 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::setScissorFromClip() {
-    const Rect& clip = mSnapshot->getMappedClip();
+    const Rect& clip = mSnapshot->clipRect;
     glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
 }
 
 const Rect& OpenGLRenderer::getClipBounds() {
-    return mSnapshot->clipRect;
+    return mSnapshot->getLocalClip();
 }
 
 bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) {
-    /*
-     * The documentation of quickReject() indicates that the specified rect
-     * is transformed before being compared to the clip rect. However, the
-     * clip rect is not stored transformed in the snapshot and can thus be
-     * compared directly
-     *
-     * The following code can be used instead to performed a mapped comparison:
-     *
-     *     mSnapshot->transform.mapRect(r);
-     *     const Rect& clip = mSnapshot->getMappedClip();
-     *     return !clip.intersects(r);
-     */
     Rect r(left, top, right, bottom);
+    mSnapshot->transform.mapRect(r);
     return !mSnapshot->clipRect.intersects(r);
 }
 
-bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom) {
-    bool clipped = mSnapshot->clip(left, top, right, bottom);
+bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+    bool clipped = mSnapshot->clip(left, top, right, bottom, op);
     if (clipped) {
-        mSnapshot->flags |= Snapshot::kFlagClipSet;
         setScissorFromClip();
     }
-    return clipped;
+    return !mSnapshot->clipRect.isEmpty();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -504,7 +486,7 @@
 }
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
-    const Rect& clip = mSnapshot->getMappedClip();
+    const Rect& clip = mSnapshot->clipRect;
     drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
 }
 
@@ -584,22 +566,20 @@
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
 
-    // TODO: Pick the program matching the current shader
-    sp<DrawColorProgram> program = mDrawColorProgram;
-    if (!useProgram(program)) {
+    if (!useProgram(mDrawColorProgram)) {
         const GLvoid* p = &gDrawColorVertices[0].position[0];
-        glVertexAttribPointer(program->position, 2, GL_FLOAT, GL_FALSE,
+        glVertexAttribPointer(mDrawColorProgram->position, 2, GL_FLOAT, GL_FALSE,
                 gDrawColorVertexStride, p);
     }
 
     if (!ignoreTransform) {
-        program->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+        mDrawColorProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
     } else {
         mat4 identity;
-        program->set(mOrthoMatrix, mModelView, identity);
+        mDrawColorProgram->set(mOrthoMatrix, mModelView, identity);
     }
 
-    glUniform4f(program->color, r, g, b, a);
+    glUniform4f(mDrawColorProgram->color, r, g, b, a);
 
     glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawColorVertexCount);
 }
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 2a96432..5667229 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -23,6 +23,7 @@
 #include <SkBitmap.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
+#include <SkRegion.h>
 #include <SkShader.h>
 #include <SkXfermode.h>
 
@@ -88,7 +89,7 @@
 
     const Rect& getClipBounds();
     bool quickReject(float left, float top, float right, float bottom);
-    bool clipRect(float left, float top, float right, float bottom);
+    bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
     void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
     void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index ad57550..7be0c340 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -129,6 +129,25 @@
         return intersect(r.left, r.top, r.right, r.bottom);
     }
 
+    bool unionWith(const Rect& r) {
+        if (r.left < r.right && r.top < r.bottom) {
+            if (left < right && top < bottom) {
+                if (left > r.left) left = r.left;
+                if (top > r.top) top = r.top;
+                if (right < r.right) right = r.right;
+                if (bottom < r.bottom) bottom = r.bottom;
+                return true;
+            } else {
+                left = r.left;
+                top = r.top;
+                right = r.right;
+                bottom = r.bottom;
+                return true;
+            }
+        }
+        return false;
+    }
+
     void dump() const {
         LOGD("Rect[l=%f t=%f r=%f b=%f]", left, top, right, bottom);
     }
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 32fee32..96dfab9 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -22,6 +22,8 @@
 
 #include <utils/RefBase.h>
 
+#include <SkRegion.h>
+
 #include "Layer.h"
 #include "Matrix.h"
 #include "Rect.h"
@@ -40,7 +42,7 @@
  */
 class Snapshot: public LightRefBase<Snapshot> {
 public:
-    Snapshot(): layer(NULL), fbo(0) { }
+    Snapshot(): flags(0x0), previous(NULL), layer(NULL), fbo(0) { }
 
     /**
      * Copies the specified snapshot. Only the transform and clip rectangle
@@ -52,12 +54,10 @@
             height(s->height),
             transform(s->transform),
             clipRect(s->clipRect),
-            flags(kFlagDirtyTransform),
+            flags(0x0),
             previous(s),
             layer(NULL),
             fbo(s->fbo) {
-        mappedClip.set(s->clipRect);
-        transform.mapRect(mappedClip);
     }
 
     /**
@@ -70,38 +70,48 @@
          */
         kFlagClipSet = 0x1,
         /**
-         * Indicates that the snapshot holds new transform
-         * information.
-         */
-        kFlagDirtyTransform = 0x2,
-        /**
          * Indicates that this snapshot was created when saving
          * a new layer.
          */
-        kFlagIsLayer = 0x4,
+        kFlagIsLayer = 0x2,
         /**
          * Indicates that this snapshot has changed the ortho matrix.
          */
-        kFlagDirtyOrtho = 0x8,
+        kFlagDirtyOrtho = 0x4,
     };
 
     /**
-     * Returns the current clip region mapped by the current transform.
-     */
-    const Rect& getMappedClip() {
-        return mappedClip;
-    }
-
-    /**
      * Intersects the current clip with the new clip rectangle.
      */
-    bool clip(float left, float top, float right, float bottom) {
-        bool clipped = clipRect.intersect(left, top, right, bottom);
-        if (flags & kFlagDirtyTransform) {
-            flags &= ~kFlagDirtyTransform;
-            mappedClip.set(clipRect);
-            transform.mapRect(mappedClip);
+    bool clip(float left, float top, float right, float bottom, SkRegion::Op op) {
+        bool clipped = false;
+
+        Rect r(left, top, right, bottom);
+        transform.mapRect(r);
+
+        switch (op) {
+        case SkRegion::kDifference_Op:
+            break;
+        case SkRegion::kIntersect_Op:
+            clipped = clipRect.intersect(r);
+            break;
+        case SkRegion::kUnion_Op:
+            clipped = clipRect.unionWith(r);
+            break;
+        case SkRegion::kXOR_Op:
+            break;
+        case SkRegion::kReverseDifference_Op:
+            break;
+        case SkRegion::kReplace_Op:
+            clipRect.set(r);
+            clipped = true;
+            break;
         }
+
+        if (clipped) {
+            flags |= Snapshot::kFlagClipSet;
+        }
+
         return clipped;
     }
 
@@ -110,11 +120,15 @@
      */
     void setClip(float left, float top, float right, float bottom) {
         clipRect.set(left, top, right, bottom);
-        if (flags & kFlagDirtyTransform) {
-            flags &= ~kFlagDirtyTransform;
-            mappedClip.set(clipRect);
-            transform.mapRect(mappedClip);
-        }
+        flags |= Snapshot::kFlagClipSet;
+    }
+
+    const Rect& getLocalClip() {
+        mat4 inverse;
+        inverse.loadInverse(transform);
+        localClip.set(clipRect);
+        inverse.mapRect(localClip);
+        return localClip;
     }
 
     /**
@@ -129,7 +143,8 @@
     mat4 transform;
 
     /**
-     * Current clip region.
+     * Current clip region. The clip is stored in canvas-space coordinates,
+     * (screen-space coordinates in the regular case.)
      */
     Rect clipRect;
 
@@ -155,8 +170,8 @@
     mat4 orthoMatrix;
 
 private:
-    // Clipping rectangle mapped with the transform
-    Rect mappedClip;
+    Rect localClip;
+
 }; // class Snapshot
 
 }; // namespace uirenderer