Add support for circular gradients to the GL renderer.

This change also adds full support for local transformation matrices on
sweep and radial gradients.

Change-Id: Id8773bc0766575190e3f3d51984fc5e57b266c3f
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 344669c..ee44747 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -226,10 +226,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static SkShader* RadialGradient_create1(JNIEnv* env, jobject,
-                                        float x, float y, float radius,
-                                        jintArray colorArray, jfloatArray posArray, int tileMode)
-{
+static SkShader* RadialGradient_create1(JNIEnv* env, jobject, float x, float y, float radius,
+        jintArray colorArray, jfloatArray posArray, int tileMode) {
     SkPoint center;
     center.set(SkFloatToScalar(x), SkFloatToScalar(y));
 
@@ -259,10 +257,8 @@
     return shader;
 }
 
-static SkShader* RadialGradient_create2(JNIEnv* env, jobject,
-                                        float x, float y, float radius,
-                                        int color0, int color1, int tileMode)
-{
+static SkShader* RadialGradient_create2(JNIEnv* env, jobject, float x, float y, float radius,
+        int color0, int color1, int tileMode) {
     SkPoint center;
     center.set(SkFloatToScalar(x), SkFloatToScalar(y));
 
@@ -276,11 +272,65 @@
     return s;
 }
 
+static SkiaShader* RadialGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, float radius, jintArray colorArray, jfloatArray posArray, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
+    size_t count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    jfloat* storedPositions = new jfloat[count];
+    uint32_t* storedColors = new uint32_t[count];
+    for (size_t i = 0; i < count; i++) {
+        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
+    }
+
+    if (posArray) {
+        AutoJavaFloatArray autoPos(env, posArray, count);
+        const float* posValues = autoPos.ptr();
+        for (size_t i = 0; i < count; i++) {
+            storedPositions[i] = posValues[i];
+        }
+    } else {
+        storedPositions[0] = 0.0f;
+        storedPositions[1] = 1.0f;
+    }
+
+    SkiaShader* skiaShader = new SkiaCircularGradientShader(x, y, radius, storedColors,
+            storedPositions, count, shader, (SkShader::TileMode) tileMode, NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
+static SkiaShader* RadialGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, float radius, int color0, int color1, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
+    float* storedPositions = new float[2];
+    storedPositions[0] = 0.0f;
+    storedPositions[1] = 1.0f;
+
+    uint32_t* storedColors = new uint32_t[2];
+    storedColors[0] = static_cast<uint32_t>(color0);
+    storedColors[1] = static_cast<uint32_t>(color1);
+
+    SkiaShader* skiaShader = new SkiaCircularGradientShader(x, y, radius, storedColors,
+            storedPositions, 2, shader, (SkShader::TileMode) tileMode, NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static SkShader* SweepGradient_create1(JNIEnv* env, jobject, float x, float y,
-                                    jintArray jcolors, jfloatArray jpositions)
-{
+        jintArray jcolors, jfloatArray jpositions) {
     size_t      count = env->GetArrayLength(jcolors);
     const jint* colors = env->GetIntArrayElements(jcolors, NULL);
     
@@ -307,8 +357,7 @@
 }
 
 static SkShader* SweepGradient_create2(JNIEnv* env, jobject, float x, float y,
-                                        int color0, int color1)
-{
+        int color0, int color1) {
     SkColor colors[2];
     colors[0] = color0;
     colors[1] = color1;
@@ -437,22 +486,24 @@
 };
 
 static JNINativeMethod gRadialGradientMethods[] = {
-    {"nativeCreate1",   "(FFF[I[FI)I",  (void*)RadialGradient_create1   },
-    {"nativeCreate2",   "(FFFIII)I",    (void*)RadialGradient_create2   }
+    { "nativeCreate1",     "(FFF[I[FI)I",  (void*)RadialGradient_create1     },
+    { "nativeCreate2",     "(FFFIII)I",    (void*)RadialGradient_create2     },
+    { "nativePostCreate1", "(IFFF[I[FI)I", (void*)RadialGradient_postCreate1 },
+    { "nativePostCreate2", "(IFFFIII)I",   (void*)RadialGradient_postCreate2 }
 };
 
 static JNINativeMethod gSweepGradientMethods[] = {
-    {"nativeCreate1",      "(FF[I[F)I",  (void*)SweepGradient_create1     },
-    {"nativeCreate2",      "(FFII)I",    (void*)SweepGradient_create2     },
+    { "nativeCreate1",     "(FF[I[F)I",  (void*)SweepGradient_create1     },
+    { "nativeCreate2",     "(FFII)I",    (void*)SweepGradient_create2     },
     { "nativePostCreate1", "(IFF[I[F)I", (void*)SweepGradient_postCreate1 },
     { "nativePostCreate2", "(IFFII)I",   (void*)SweepGradient_postCreate2 }
 };
 
 static JNINativeMethod gComposeShaderMethods[] = {
-    {"nativeCreate1",      "(III)I",   (void*)ComposeShader_create1     },
-    {"nativeCreate2",      "(III)I",   (void*)ComposeShader_create2     },
-    {"nativePostCreate1",  "(IIII)I",  (void*)ComposeShader_postCreate1 },
-    {"nativePostCreate2",  "(IIII)I",  (void*)ComposeShader_postCreate2 }
+    { "nativeCreate1",      "(III)I",   (void*)ComposeShader_create1     },
+    { "nativeCreate2",      "(III)I",   (void*)ComposeShader_create2     },
+    { "nativePostCreate1",  "(IIII)I",  (void*)ComposeShader_postCreate1 },
+    { "nativePostCreate2",  "(IIII)I",  (void*)ComposeShader_postCreate2 }
 };
 
 #include <android_runtime/AndroidRuntime.h>
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index b4e902d..897762c 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -40,6 +40,8 @@
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
         native_instance = nativeCreate1(x, y, radius, colors, positions, tile.nativeInt);
+        native_shader = nativePostCreate1(native_instance, x, y, radius, colors, positions,
+                tile.nativeInt);
     }
 
 	/**	Create a shader that draws a radial gradient given the center and radius.
@@ -56,11 +58,18 @@
             throw new IllegalArgumentException("radius must be > 0");
         }
         native_instance = nativeCreate2(x, y, radius, color0, color1, tile.nativeInt);
+        native_shader = nativePostCreate2(native_instance, x, y, radius, color0, color1,
+                tile.nativeInt);
     }
 
 	private static native int nativeCreate1(float x, float y, float radius,
-                                            int colors[], float positions[], int tileMode);
+            int colors[], float positions[], int tileMode);
 	private static native int nativeCreate2(float x, float y, float radius,
-                                            int color0, int color1, int tileMode);
+            int color0, int color1, int tileMode);
+
+    private static native int nativePostCreate1(int native_shader, float x, float y, float radius,
+            int colors[], float positions[], int tileMode);
+    private static native int nativePostCreate2(int native_shader, float x, float y, float radius,
+            int color0, int color1, int tileMode);
 }
 
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 264ad3d..c698b5a 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -144,6 +144,12 @@
     return data[kTranslateY];
 }
 
+void Matrix4::multiply(float v) {
+    for (int i = 0; i < 16; i++) {
+        data[i] *= v;
+    }
+}
+
 void Matrix4::loadTranslate(float x, float y, float z) {
     loadIdentity();
     data[kTranslateX] = x;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index c247a67..0608efe 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -83,6 +83,8 @@
         load(u);
     }
 
+    void multiply(float v);
+
     void translate(float x, float y, float z) {
         Matrix4 u;
         u.loadTranslate(x, y, z);
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index bcc1edf..3e9412c 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -40,9 +40,12 @@
         "uniform vec2 gradientStart;\n"
         "uniform mat4 screenSpace;\n",
         // Circular
-        "",
+        "uniform vec2 gradientStart;\n"
+        "uniform mat4 gradientMatrix;\n"
+        "uniform mat4 screenSpace;\n",
         // Sweep
         "uniform vec2 gradientStart;\n"
+        "uniform mat4 gradientMatrix;\n"
         "uniform mat4 screenSpace;\n"
 };
 const char* gVS_Header_Uniforms_HasBitmap =
@@ -56,7 +59,7 @@
         // Linear
         "varying float index;\n",
         // Circular
-        "",
+        "varying vec2 circular;\n",
         // Sweep
         "varying vec2 sweep;\n"
 };
@@ -69,10 +72,11 @@
         "    vec4 location = screenSpace * position;\n"
         "    index = dot(location.xy - gradientStart, gradient) * gradientLength;\n",
         // Circular
-        "",
+        "    vec4 location = screenSpace * position;\n"
+        "    circular = (gradientMatrix * vec4(location.xy - gradientStart, 0.0, 0.0)).xy;\n",
         // Sweep
         "    vec4 location = screenSpace * position;\n"
-        "    sweep = location.xy - gradientStart;\n"
+        "    sweep = (gradientMatrix * vec4(location.xy - gradientStart, 0.0, 0.0)).xy;\n"
 };
 const char* gVS_Main_OutBitmapTexCoords =
         "    vec4 bitmapCoords = textureTransform * position;\n"
@@ -98,6 +102,7 @@
         // Linear
         "uniform sampler2D gradientSampler;\n",
         // Circular
+        "uniform float gradientRadius;\n"
         "uniform sampler2D gradientSampler;\n",
         // Sweep
         "uniform sampler2D gradientSampler;\n"
@@ -129,7 +134,8 @@
         // Linear
         "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n",
         // Circular
-        "",
+        "    float index = length(circular) * gradientRadius;\n"
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n",
         // Sweep
         "    float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
         "    vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n"
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index c5d9767..9e1f6c2 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -209,6 +209,30 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Circular gradient shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaCircularGradientShader::SkiaCircularGradientShader(float x, float y, float radius,
+        uint32_t* colors, float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
+        SkMatrix* matrix, bool blend):
+        SkiaSweepGradientShader(kCircularGradient, x, y, colors, positions, count, key,
+                tileMode, matrix, blend),
+        mRadius(radius) {
+}
+
+void SkiaCircularGradientShader::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.hasGradient = true;
+    description.gradientType = ProgramDescription::kGradientCircular;
+}
+
+void SkiaCircularGradientShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    SkiaSweepGradientShader::setupProgram(program, modelView, snapshot, textureUnit);
+    glUniform1f(program->getUniform("gradientRadius"), 1.0f / mRadius);
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Sweep gradient shader
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -219,6 +243,13 @@
         mX(x), mY(y), mColors(colors), mPositions(positions), mCount(count) {
 }
 
+SkiaSweepGradientShader::SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors,
+        float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
+        SkMatrix* matrix, bool blend):
+        SkiaShader(type, key, tileMode, tileMode, matrix, blend),
+        mX(x), mY(y), mColors(colors), mPositions(positions), mCount(count) {
+}
+
 SkiaSweepGradientShader::~SkiaSweepGradientShader() {
     delete[] mColors;
     delete[] mPositions;
@@ -243,10 +274,15 @@
     float left = mX;
     float top = mY;
 
+    mat4 shaderMatrix;
     if (mMatrix) {
-        mat4 shaderMatrix(*mMatrix);
+        shaderMatrix.load(*mMatrix);
         shaderMatrix.mapPoint(left, top);
     }
+
+    mat4 copy(shaderMatrix);
+    shaderMatrix.loadInverse(copy);
+
     snapshot.transform->mapPoint(left, top);
 
     mat4 screenSpace(*snapshot.transform);
@@ -255,6 +291,7 @@
     // Uniforms
     bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
     glUniform1i(program->getUniform("gradientSampler"), textureSlot);
+    glUniformMatrix4fv(program->getUniform("gradientMatrix"), 1, GL_FALSE, &shaderMatrix.data[0]);
     glUniform2f(program->getUniform("gradientStart"), left, top);
     glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
 }
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index b67bfee..0023c46 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -152,12 +152,15 @@
             SkShader* key, SkMatrix* matrix, bool blend);
     ~SkiaSweepGradientShader();
 
-    void describe(ProgramDescription& description, const Extensions& extensions);
-    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+    virtual void describe(ProgramDescription& description, const Extensions& extensions);
+    virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
             GLuint* textureUnit);
     void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
 
-private:
+protected:
+    SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions,
+            int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
+
     float mX, mY;
     uint32_t* mColors;
     float* mPositions;
@@ -165,6 +168,21 @@
 }; // struct SkiaSweepGradientShader
 
 /**
+ * A shader that draws a circular gradient.
+ */
+struct SkiaCircularGradientShader: public SkiaSweepGradientShader {
+    SkiaCircularGradientShader(float x, float y, float radius, uint32_t* colors, float* positions,
+            int count, SkShader* key,SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+
+private:
+    float mRadius;
+}; // struct SkiaCircularGradientShader
+
+/**
  * A shader that draws two shaders, composited with an xfermode.
  */
 struct SkiaComposeShader: public SkiaShader {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
index e232021..27974e7 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
@@ -42,19 +42,25 @@
         private final RadialGradient mRadialGradient;
         private final Matrix mMatrix;
         private final Matrix mMatrix2;
+        private final Matrix mMatrix3;
 
         GradientsView(Context c) {
             super(c);
 
-            mSweepGradient = new SweepGradient(100.0f, 100.0f, 0xff000000, 0xffffffff);
-            mRadialGradient = new RadialGradient(100.0f, 100.0f, 100.0f, 0xff000000, 0xffffffff,
+            mSweepGradient = new SweepGradient(0.0f, 0.0f, 0xff000000, 0xffffffff);
+            mRadialGradient = new RadialGradient(0.0f, 0.0f, 100.0f, 0xff000000, 0xffffffff,
                     Shader.TileMode.MIRROR);
             
             mMatrix = new Matrix();
-            mMatrix.setTranslate(50.0f, 50.0f);
+            mMatrix.setRotate(-45, 0.0f, 0.0f);
+            mMatrix.postTranslate(100.0f, 100.0f);
 
             mMatrix2 = new Matrix();
-            mMatrix2.setScale(2.0f, 2.0f);
+            mMatrix2.setScale(1.0f, 2.0f);
+            mMatrix2.postRotate(-45, 0.0f, 0.0f);
+
+            mMatrix3 = new Matrix();
+            mMatrix3.setTranslate(100.0f, 100.0f);            
             
             mPaint = new Paint();
         }
@@ -64,30 +70,42 @@
             super.onDraw(canvas);
             canvas.drawRGB(255, 255, 255);
 
-            // Bitmap shaders
             canvas.save();
             canvas.translate(130.0f, 100.0f);
 
-            mSweepGradient.setLocalMatrix(null);
+            mSweepGradient.setLocalMatrix(mMatrix3);
             mPaint.setShader(mSweepGradient);
             canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
 
             canvas.translate(400.0f, 000.0f);
             
-            mPaint.setShader(mRadialGradient);
-            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
-
-            canvas.translate(400.0f, 000.0f);
-            
             mSweepGradient.setLocalMatrix(mMatrix);
             mPaint.setShader(mSweepGradient);
-            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);            
 
-            canvas.translate(-800.0f, 300.0f);
+            canvas.translate(400.0f, 000.0f);
             
             mSweepGradient.setLocalMatrix(mMatrix2);
             mPaint.setShader(mSweepGradient);
             canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(-800.0f, 300.0f);
+
+            mRadialGradient.setLocalMatrix(null);
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+            
+            canvas.translate(400.0f, 000.0f);
+            
+            mRadialGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(400.0f, 000.0f);
+            
+            mRadialGradient.setLocalMatrix(mMatrix2);
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
             
             
             canvas.restore();