ADT/Layoutlib: implement radial gradient.

Also refactored some parts of LinearGradient to reuse them
in the radial gradient

Change-Id: I2ec69bd60190bd014217d989177dcc7269188dea
diff --git a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java b/tools/layoutlib/bridge/src/android/graphics/GradientShader.java
new file mode 100644
index 0000000..40e5df2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/GradientShader.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+
+/**
+ * Base class for Gradient shader. This is not a standard android class and is just used
+ * as a base class for the re-implemented gradient classes.
+ *
+ * It also provides a base class to handle common code between the different shaders'
+ * implementations of {@link java.awt.Paint}.
+ *
+ * @see LinearGradient
+ * @see RadialGradient
+ * @see SweepGradient
+ */
+public abstract class GradientShader extends Shader {
+
+    protected final int[] mColors;
+    protected final float[] mPositions;
+
+    /**
+     * Creates the base shader and do some basic test on the parameters.
+     *
+     * @param colors The colors to be distributed along the gradient line
+     * @param positions May be null. The relative positions [0..1] of each
+     *            corresponding color in the colors array. If this is null, the
+     *            the colors are distributed evenly along the gradient line.
+     */
+    protected GradientShader(int colors[], float positions[]) {
+        if (colors.length < 2) {
+            throw new IllegalArgumentException("needs >= 2 number of colors");
+        }
+        if (positions != null && colors.length != positions.length) {
+            throw new IllegalArgumentException("color and position arrays must be of equal length");
+        }
+
+        if (positions == null) {
+            float spacing = 1.f / (colors.length - 1);
+            positions = new float[colors.length];
+            positions[0] = 0.f;
+            positions[colors.length-1] = 1.f;
+            for (int i = 1; i < colors.length - 1 ; i++) {
+                positions[i] = spacing * i;
+            }
+        }
+
+        mColors = colors;
+        mPositions = positions;
+    }
+
+    /**
+     * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
+     * on the color and position lists, as well as the {@link TileMode}
+     *
+     */
+    protected abstract static class GradientPaint implements java.awt.Paint {
+        private final static int GRADIENT_SIZE = 100;
+
+        private final int[] mColors;
+        private final float[] mPositions;
+        private final TileMode mTileMode;
+        private int[] mGradient;
+
+        protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
+            mColors = colors;
+            mPositions = positions;
+            mTileMode = tileMode;
+        }
+
+        public int getTransparency() {
+            return java.awt.Paint.TRANSLUCENT;
+        }
+
+        /**
+         * Pre-computes the colors for the gradient. This must be called once before any call
+         * to {@link #getGradientColor(float)}
+         */
+        protected synchronized void precomputeGradientColors() {
+            if (mGradient == null) {
+                // actually create an array with an extra size, so that we can really go
+                // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+                mGradient = new int[GRADIENT_SIZE+1];
+
+                int prevPos = 0;
+                int nextPos = 1;
+                for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
+                    // compute current position
+                    float currentPos = (float)i/GRADIENT_SIZE;
+                    while (currentPos > mPositions[nextPos]) {
+                        prevPos = nextPos++;
+                    }
+
+                    float percent = (currentPos - mPositions[prevPos]) /
+                            (mPositions[nextPos] - mPositions[prevPos]);
+
+                    mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+                }
+            }
+        }
+
+        /**
+         * Returns the color based on the position in the gradient.
+         * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
+         * will use {@link TileMode} value to convert it into a [0,1] value.
+         */
+        protected int getGradientColor(float pos) {
+            if (pos < 0.f) {
+                switch (mTileMode) {
+                    case CLAMP:
+                        pos = 0.f;
+                        break;
+                    case REPEAT:
+                        // remove the integer part to stay in the [0,1] range
+                        // careful: this is a negative value, so use ceil instead of floor
+                        pos = pos - (float)Math.ceil(pos);
+                        break;
+                    case MIRROR:
+                        // get the integer and the decimal part
+                        // careful: this is a negative value, so use ceil instead of floor
+                        int intPart = (int)Math.ceil(pos);
+                        pos = pos - intPart;
+                        // 0  -> -1 : mirrored order
+                        // -1 -> -2: normal order
+                        // etc..
+                        // this means if the intpart is even we invert
+                        if ((intPart % 2) == 0) {
+                            pos = 1.f - pos;
+                        }
+                        break;
+                }
+            } else if (pos > 1f) {
+                switch (mTileMode) {
+                    case CLAMP:
+                        pos = 1.f;
+                        break;
+                    case REPEAT:
+                        // remove the integer part to stay in the [0,1] range
+                        pos = pos - (float)Math.floor(pos);
+                        break;
+                    case MIRROR:
+                        // get the integer and the decimal part
+                        int intPart = (int)Math.floor(pos);
+                        pos = pos - intPart;
+                        // 0 -> 1 : normal order
+                        // 1 -> 2: mirrored
+                        // etc..
+                        // this means if the intpart is odd we invert
+                        if ((intPart % 2) == 1) {
+                            pos = 1.f - pos;
+                        }
+                        break;
+                }
+            }
+
+            int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+            return mGradient[index];
+        }
+
+        /**
+         * Returns the color between c1, and c2, based on the percent of the distance
+         * between c1 and c2.
+         */
+        private int computeColor(int c1, int c2, float percent) {
+            int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+            int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+            int g = computeChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
+            int b = computeChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
+            return a << 24 | r << 16 | g << 8 | b;
+        }
+
+        /**
+         * Returns the channel value between 2 values based on the percent of the distance between
+         * the 2 values..
+         */
+        private int computeChannel(int c1, int c2, float percent) {
+            return c1 + (int)((percent * (c2-c1)) + .5);
+        }
+
+
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
index 38ffed3..10c4a5e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
@@ -16,19 +16,9 @@
 
 package android.graphics;
 
-import java.awt.Paint;
-import java.awt.PaintContext;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorModel;
-import java.awt.image.Raster;
+public class LinearGradient extends GradientShader {
 
-public class LinearGradient extends Shader {
-
-    private Paint mJavaPaint;
+    private java.awt.Paint mJavaPaint;
 
     /**
      * Create a shader that draws a linear gradient along a line.
@@ -45,24 +35,8 @@
      */
     public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
             TileMode tile) {
-        if (colors.length < 2) {
-            throw new IllegalArgumentException("needs >= 2 number of colors");
-        }
-        if (positions != null && colors.length != positions.length) {
-            throw new IllegalArgumentException("color and position arrays must be of equal length");
-        }
-
-        if (positions == null) {
-            float spacing = 1.f / (colors.length - 1);
-            positions = new float[colors.length];
-            positions[0] = 0.f;
-            positions[colors.length-1] = 1.f;
-            for (int i = 1; i < colors.length - 1 ; i++) {
-                positions[i] = spacing * i;
-            }
-        }
-
-        mJavaPaint = new MultiPointLinearGradientPaint(x0, y0, x1, y1, colors, positions, tile);
+        super(colors, positions);
+        mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile);
     }
 
     /**
@@ -84,117 +58,62 @@
     // ---------- Custom Methods
 
     @Override
-    public Paint getJavaPaint() {
+    java.awt.Paint getJavaPaint() {
         return mJavaPaint;
     }
 
-    private static class MultiPointLinearGradientPaint implements Paint {
-        private final static int GRADIENT_SIZE = 100;
+    /**
+     * Linear Gradient (Java) Paint able to handle more than 2 points, as
+     * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile
+     * modes.
+     */
+    private static class LinearGradientPaint extends GradientPaint {
 
         private final float mX0;
         private final float mY0;
         private final float mDx;
         private final float mDy;
         private final float mDSize2;
-        private final int[] mColors;
-        private final float[] mPositions;
-        private final TileMode mTile;
-        private int[] mGradient;
 
-        public MultiPointLinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+        public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
                 float positions[], TileMode tile) {
+            super(colors, positions, tile);
                 mX0 = x0;
                 mY0 = y0;
                 mDx = x1 - x0;
                 mDy = y1 - y0;
                 mDSize2 = mDx * mDx + mDy * mDy;
-
-                mColors = colors;
-                mPositions = positions;
-                mTile = tile;
         }
 
-        public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
-                Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
-            prepareColors();
-            return new MultiPointLinearGradientPaintContext(cm, deviceBounds,
-                    userBounds, xform, hints);
+        public java.awt.PaintContext createContext(
+                java.awt.image.ColorModel      colorModel,
+                java.awt.Rectangle             deviceBounds,
+                java.awt.geom.Rectangle2D      userBounds,
+                java.awt.geom.AffineTransform  xform,
+                java.awt.RenderingHints        hints) {
+            precomputeGradientColors();
+            return new LinearGradientPaintContext(colorModel);
         }
 
-        public int getTransparency() {
-            return TRANSLUCENT;
-        }
+        private class LinearGradientPaintContext implements java.awt.PaintContext {
 
-        private synchronized void prepareColors() {
-            if (mGradient == null) {
-                // actually create an array with an extra size, so that we can really go
-                // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
-                mGradient = new int[GRADIENT_SIZE+1];
+            private final java.awt.image.ColorModel mColorModel;
 
-                int prevPos = 0;
-                int nextPos = 1;
-                for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
-                    // compute current position
-                    float currentPos = (float)i/GRADIENT_SIZE;
-                    while (currentPos > mPositions[nextPos]) {
-                        prevPos = nextPos++;
-                    }
-
-                    float percent = (currentPos - mPositions[prevPos]) /
-                            (mPositions[nextPos] - mPositions[prevPos]);
-
-                    mGradient[i] = getColor(mColors[prevPos], mColors[nextPos], percent);
-                }
-            }
-        }
-
-        /**
-         * Returns the color between c1, and c2, based on the percent of the distance
-         * between c1 and c2.
-         */
-        private int getColor(int c1, int c2, float percent) {
-            int a = getChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
-            int r = getChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
-            int g = getChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
-            int b = getChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
-            return a << 24 | r << 16 | g << 8 | b;
-        }
-
-        /**
-         * Returns the channel value between 2 values based on the percent of the distance between
-         * the 2 values..
-         */
-        private int getChannel(int c1, int c2, float percent) {
-            return c1 + (int)((percent * (c2-c1)) + .5);
-        }
-
-        private class MultiPointLinearGradientPaintContext implements PaintContext {
-
-            private ColorModel mColorModel;
-            private final Rectangle mDeviceBounds;
-            private final Rectangle2D mUserBounds;
-            private final AffineTransform mXform;
-            private final RenderingHints mHints;
-
-            public MultiPointLinearGradientPaintContext(ColorModel cm, Rectangle deviceBounds,
-                    Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
-                mColorModel = cm;
+            public LinearGradientPaintContext(java.awt.image.ColorModel colorModel) {
+                mColorModel = colorModel;
                 // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix?
-                mDeviceBounds = deviceBounds;
-                mUserBounds = userBounds;
-                mXform = xform;
-                mHints = hints;
             }
 
             public void dispose() {
             }
 
-            public ColorModel getColorModel() {
+            public java.awt.image.ColorModel getColorModel() {
                 return mColorModel;
             }
 
-            public Raster getRaster(int x, int y, int w, int h) {
-                BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+            public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+                java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+                        java.awt.image.BufferedImage.TYPE_INT_ARGB);
 
                 int[] data = new int[w*h];
 
@@ -236,7 +155,7 @@
         private int getColor(float absPos, float refPos, float refSize) {
             float pos = (absPos - refPos) / refSize;
 
-            return getIndexFromPos(pos);
+            return getGradientColor(pos);
         }
 
         /**
@@ -248,66 +167,7 @@
             // from it get the position relative to the vector
             float pos = (float) ((_x - mX0) / mDx);
 
-            return getIndexFromPos(pos);
-        }
-
-        /**
-         * Returns the color based on the position in the gradient.
-         * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
-         * will use {@link TileMode} value to convert it into a [0,1] value.
-         */
-        private int getIndexFromPos(float pos) {
-            if (pos < 0.f) {
-                switch (mTile) {
-                    case CLAMP:
-                        pos = 0.f;
-                        break;
-                    case REPEAT:
-                        // remove the integer part to stay in the [0,1] range
-                        // careful: this is a negative value, so use ceil instead of floor
-                        pos = pos - (float)Math.ceil(pos);
-                        break;
-                    case MIRROR:
-                        // get the integer and the decimal part
-                        // careful: this is a negative value, so use ceil instead of floor
-                        int intPart = (int)Math.ceil(pos);
-                        pos = pos - intPart;
-                        // 0  -> -1 : mirrored order
-                        // -1 -> -2: normal order
-                        // etc..
-                        // this means if the intpart is even we invert
-                        if ((intPart % 2) == 0) {
-                            pos = 1.f - pos;
-                        }
-                        break;
-                }
-            } else if (pos > 1f) {
-                switch (mTile) {
-                    case CLAMP:
-                        pos = 1.f;
-                        break;
-                    case REPEAT:
-                        // remove the integer part to stay in the [0,1] range
-                        pos = pos - (float)Math.floor(pos);
-                        break;
-                    case MIRROR:
-                        // get the integer and the decimal part
-                        int intPart = (int)Math.floor(pos);
-                        pos = pos - intPart;
-                        // 0 -> 1 : normal order
-                        // 1 -> 2: mirrored
-                        // etc..
-                        // this means if the intpart is odd we invert
-                        if ((intPart % 2) == 1) {
-                            pos = 1.f - pos;
-                        }
-                        break;
-                }
-            }
-
-            int index = (int)((pos * GRADIENT_SIZE) + .5);
-
-            return mGradient[index];
+            return getGradientColor(pos);
         }
     }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
index 13848c5..db8dff2 100644
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
@@ -16,55 +16,117 @@
 
 package android.graphics;
 
-import java.awt.Paint;
+public class RadialGradient extends GradientShader {
 
-public class RadialGradient extends Shader {
+    private RadialGradientPaint mPaint;
 
-   /** Create a shader that draws a radial gradient given the center and radius.
-       @param x        The x-coordinate of the center of the radius
-       @param y        The y-coordinate of the center of the radius
-       @param radius   Must be positive. The radius of the circle for this gradient
-       @param colors   The colors to be distributed between the center and edge of the circle
-       @param positions May be NULL. The relative position of
-                       each corresponding color in the colors array. If this is NULL,
-                       the the colors are distributed evenly between the center and edge of the circle.
-       @param  tile    The Shader tiling mode
-   */
-   public RadialGradient(float x, float y, float radius,
-                         int colors[], float positions[], TileMode tile) {
-       if (radius <= 0) {
-           throw new IllegalArgumentException("radius must be > 0");
-       }
-       if (colors.length < 2) {
-           throw new IllegalArgumentException("needs >= 2 number of colors");
-       }
-       if (positions != null && colors.length != positions.length) {
-           throw new IllegalArgumentException("color and position arrays must be of equal length");
-       }
+    /**
+     * Create a shader that draws a radial gradient given the center and radius.
+     *
+     * @param x The x-coordinate of the center of the radius
+     * @param y The y-coordinate of the center of the radius
+     * @param radius Must be positive. The radius of the circle for this
+     *            gradient
+     * @param colors The colors to be distributed between the center and edge of
+     *            the circle
+     * @param positions May be NULL. The relative position of each corresponding
+     *            color in the colors array. If this is NULL, the the colors are
+     *            distributed evenly between the center and edge of the circle.
+     * @param tile The Shader tiling mode
+     */
+    public RadialGradient(float x, float y, float radius, int colors[], float positions[],
+            TileMode tile) {
+        super(colors, positions);
+        if (radius <= 0) {
+            throw new IllegalArgumentException("radius must be > 0");
+        }
 
-       // FIXME Implement shader
-   }
+        mPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile);
+    }
 
-   /** Create a shader that draws a radial gradient given the center and radius.
-       @param x        The x-coordinate of the center of the radius
-       @param y        The y-coordinate of the center of the radius
-       @param radius   Must be positive. The radius of the circle for this gradient
-       @param color0   The color at the center of the circle.
-       @param color1   The color at the edge of the circle.
-       @param tile     The Shader tiling mode
-   */
-   public RadialGradient(float x, float y, float radius,
-                         int color0, int color1, TileMode tile) {
-       if (radius <= 0) {
-           throw new IllegalArgumentException("radius must be > 0");
-       }
-       // FIXME Implement shader
-   }
+    /**
+     * Create a shader that draws a radial gradient given the center and radius.
+     *
+     * @param x The x-coordinate of the center of the radius
+     * @param y The y-coordinate of the center of the radius
+     * @param radius Must be positive. The radius of the circle for this
+     *            gradient
+     * @param color0 The color at the center of the circle.
+     * @param color1 The color at the edge of the circle.
+     * @param tile The Shader tiling mode
+     */
+    public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile) {
+        this(x, y, radius, new int[] { color0, color1 }, null /* positions */, tile);
+    }
 
     @Override
-    Paint getJavaPaint() {
-        // TODO Auto-generated method stub
-        return null;
+    java.awt.Paint getJavaPaint() {
+        return mPaint;
     }
-}
 
+    private static class RadialGradientPaint extends GradientPaint {
+
+        private final float mX;
+        private final float mY;
+        private final float mRadius;
+
+        public RadialGradientPaint(float x, float y, float radius, int[] colors, float[] positions, TileMode mode) {
+            super(colors, positions, mode);
+            mX = x;
+            mY = y;
+            mRadius = radius;
+        }
+
+        public java.awt.PaintContext createContext(
+                java.awt.image.ColorModel colorModel,
+                java.awt.Rectangle deviceBounds,
+                java.awt.geom.Rectangle2D userBounds,
+                java.awt.geom.AffineTransform xform,
+                java.awt.RenderingHints hints) {
+            precomputeGradientColors();
+            return new RadialGradientPaintContext(colorModel);
+        }
+
+        private class RadialGradientPaintContext implements java.awt.PaintContext {
+
+            private final java.awt.image.ColorModel mColorModel;
+
+            public RadialGradientPaintContext(java.awt.image.ColorModel colorModel) {
+                mColorModel = colorModel;
+            }
+
+            public void dispose() {
+            }
+
+            public java.awt.image.ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+                java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+                        java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+                int[] data = new int[w*h];
+
+                // compute distance from each point to the center, and figure out the distance from
+                // it.
+                int index = 0;
+                for (int iy = 0 ; iy < h ; iy++) {
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        float _x = x + ix - mX;
+                        float _y = y + iy - mY;
+                        float distance = (float) Math.sqrt(_x * _x + _y * _y);
+
+                        data[index++] = getGradientColor(distance / mRadius);
+                    }
+                }
+
+                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+                return image.getRaster();
+            }
+
+        }
+    }
+
+}