LayoutLib: Misc rendering fixes.

- always set up the stroke. Paint may not have the proper
  style when drawing lines. stroke should still be setup.

- Fixed vertical linear gradient. Old code generated
  a gradient ratio of NaN

- Fixed alpha rendering when using shaders. In that
  case the alpha channel from the paint color should be
  used in conjunction with the shader.

- Fixed miter limit. Java expects the value to be multiplied
  by the stroke width

- Fixed support for drawing ALPHA_8 bitmaps. Java2D doesn't
  have bitmaps with only alpha channels, so we keep using
  ARGB bitmaps but when drawing them into a bitmap we erase
  the color information.

Change-Id: I4f04341fc843e3f7dadd1fdbf709b11a4f1e24b9
diff --git a/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/bridge/src/android/graphics/AvoidXfermode_Delegate.java
index 9c49bb8..190eb37 100644
--- a/bridge/src/android/graphics/AvoidXfermode_Delegate.java
+++ b/bridge/src/android/graphics/AvoidXfermode_Delegate.java
@@ -42,7 +42,7 @@
     // ---- Public Helper methods ----
 
     @Override
-    public Composite getComposite() {
+    public Composite getComposite(int alpha) {
         // FIXME
         return null;
     }
diff --git a/bridge/src/android/graphics/Bitmap_Delegate.java b/bridge/src/android/graphics/Bitmap_Delegate.java
index 9d60a43..b5f5005 100644
--- a/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -54,9 +54,11 @@
     // ---- delegate helper data ----
 
     // ---- delegate data ----
+    private final Config mConfig;
     private BufferedImage mImage;
     private boolean mHasAlpha = true;
 
+
     // ---- Public Helper methods ----
 
     /**
@@ -86,7 +88,7 @@
     public static Bitmap createBitmap(File input, boolean isMutable, ResourceDensity density)
             throws IOException {
         // create a delegate with the content of the file.
-        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input));
+        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
 
         return createBitmap(delegate, isMutable, density.getDpi());
     }
@@ -104,7 +106,7 @@
     public static Bitmap createBitmap(InputStream input, boolean isMutable, ResourceDensity density)
             throws IOException {
         // create a delegate with the content of the stream.
-        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input));
+        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
 
         return createBitmap(delegate, isMutable, density.getDpi());
     }
@@ -122,7 +124,7 @@
     public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
             ResourceDensity density) throws IOException {
         // create a delegate with the given image.
-        Bitmap_Delegate delegate = new Bitmap_Delegate(image);
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
 
         return createBitmap(delegate, isMutable, density.getDpi());
     }
@@ -163,6 +165,15 @@
         return mImage;
     }
 
+    /**
+     * Returns the Android bitmap config. Note that this not the config of the underlying
+     * Java2D bitmap.
+     */
+    public Config getConfig() {
+        return mConfig;
+    }
+
+
     // ---- native methods ----
 
     /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
@@ -175,7 +186,7 @@
         // FIXME fill the bitmap!
 
         // create a delegate with the content of the stream.
-        Bitmap_Delegate delegate = new Bitmap_Delegate(image);
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
 
         return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
     }
@@ -211,10 +222,7 @@
 
         Graphics2D g = image.createGraphics();
         try {
-            if (delegate.mHasAlpha == false) {
-                color |= color & 0xFF000000;
-            }
-            g.setColor(new java.awt.Color(color));
+            g.setColor(new java.awt.Color(color, delegate.mHasAlpha));
 
             g.fillRect(0, 0, image.getWidth(), image.getHeight());
         } finally {
@@ -256,7 +264,14 @@
     }
 
     /*package*/ static int nativeConfig(int nativeBitmap) {
-        return Config.ARGB_8888.nativeInt;
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return delegate.mConfig.nativeInt;
     }
 
     /*package*/ static boolean nativeHasAlpha(int nativeBitmap) {
@@ -355,8 +370,9 @@
 
     // ---- Private delegate/helper methods ----
 
-    private Bitmap_Delegate(BufferedImage image) {
+    private Bitmap_Delegate(BufferedImage image, Config config) {
         mImage = image;
+        mConfig = config;
     }
 
     private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
diff --git a/bridge/src/android/graphics/Canvas_Delegate.java b/bridge/src/android/graphics/Canvas_Delegate.java
index ba4bed6..2720b61 100644
--- a/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/bridge/src/android/graphics/Canvas_Delegate.java
@@ -528,7 +528,8 @@
             // set the color
             graphics.setColor(new Color(color, true /*alpha*/));
 
-            Composite composite = PorterDuffXfermode_Delegate.getComposite(mode);
+            Composite composite = PorterDuffXfermode_Delegate.getComposite(
+                    PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
             if (composite != null) {
                 graphics.setComposite(composite);
             }
@@ -759,7 +760,7 @@
         float right = left + image.getWidth();
         float bottom = top + image.getHeight();
 
-        drawBitmap(nativeCanvas, image, nativePaintOrZero,
+        drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
                 0, 0, image.getWidth(), image.getHeight(),
                 (int)left, (int)top, (int)right, (int)bottom);
     }
@@ -779,11 +780,11 @@
         BufferedImage image = bitmapDelegate.getImage();
 
         if (src == null) {
-            drawBitmap(nativeCanvas, image, nativePaintOrZero,
+            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
                     0, 0, image.getWidth(), image.getHeight(),
                     (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
         } else {
-            drawBitmap(nativeCanvas, image, nativePaintOrZero,
+            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
                     src.left, src.top, src.width(), src.height(),
                     (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
         }
@@ -804,11 +805,11 @@
         BufferedImage image = bitmapDelegate.getImage();
 
         if (src == null) {
-            drawBitmap(nativeCanvas, image, nativePaintOrZero,
+            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
                     0, 0, image.getWidth(), image.getHeight(),
                     dst.left, dst.top, dst.right, dst.bottom);
         } else {
-            drawBitmap(nativeCanvas, image, nativePaintOrZero,
+            drawBitmap(nativeCanvas, image, bitmapDelegate.getConfig(), nativePaintOrZero,
                     src.left, src.top, src.width(), src.height(),
                     dst.left, dst.top, dst.right, dst.bottom);
         }
@@ -1042,7 +1043,7 @@
     /**
      * Executes a {@link Drawable} with a given canvas and paint.
      */
-    private static void draw(int nCanvas, int nPaint, Drawable runnable) {
+    private static void draw(int nCanvas, int nPaint, Drawable drawable) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
         if (canvasDelegate == null) {
@@ -1060,7 +1061,7 @@
         Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
 
         try {
-            runnable.draw(g, paintDelegate);
+            drawable.draw(g, paintDelegate);
         } finally {
             // dispose Graphics2D object
             g.dispose();
@@ -1135,9 +1136,8 @@
                     RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
         }
 
-        boolean useColorPaint = true;
-
         // get the shader first, as it'll replace the color if it can be used it.
+        boolean customShader = false;
         int nativeShader = paint.getShader();
         if (nativeShader > 0) {
             Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
@@ -1148,7 +1148,7 @@
                     assert shaderPaint != null;
                     if (shaderPaint != null) {
                         g.setPaint(shaderPaint);
-                        useColorPaint = false;
+                        customShader = true;
                     }
                 } else {
                     Bridge.getLog().fidelityWarning(null,
@@ -1158,56 +1158,57 @@
             }
         }
 
-        if (useColorPaint) {
+        // if no shader, use the paint color
+        if (customShader == false) {
             g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
         }
 
-        int style = paint.getStyle();
-        if (style == Paint.Style.STROKE.nativeInt ||
-                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-
-            boolean customStroke = false;
-
-            int pathEffect = paint.getPathEffect();
-            if (pathEffect > 0) {
-                PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
-                assert effectDelegate != null;
-                if (effectDelegate != null) {
-                    if (effectDelegate.isSupported()) {
-                        Stroke stroke = effectDelegate.getStroke(paint);
-                        assert stroke != null;
-                        if (stroke != null) {
-                            g.setStroke(stroke);
-                            customStroke = true;
-                        }
-                    } else {
-                        Bridge.getLog().fidelityWarning(null,
-                                effectDelegate.getSupportMessage(),
-                                null);
+        boolean customStroke = false;
+        int pathEffect = paint.getPathEffect();
+        if (pathEffect > 0) {
+            PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
+            assert effectDelegate != null;
+            if (effectDelegate != null) {
+                if (effectDelegate.isSupported()) {
+                    Stroke stroke = effectDelegate.getStroke(paint);
+                    assert stroke != null;
+                    if (stroke != null) {
+                        g.setStroke(stroke);
+                        customStroke = true;
                     }
+                } else {
+                    Bridge.getLog().fidelityWarning(null,
+                            effectDelegate.getSupportMessage(),
+                            null);
                 }
             }
-
-            // if no custom stroke as been set, set the default one.
-            if (customStroke == false) {
-                g.setStroke(new BasicStroke(
-                        paint.getStrokeWidth(),
-                        paint.getJavaCap(),
-                        paint.getJavaJoin(),
-                        paint.getStrokeMiter()));
-            }
         }
 
+        // if no custom stroke as been set, set the default one.
+        if (customStroke == false) {
+            g.setStroke(new BasicStroke(
+                    paint.getStrokeWidth(),
+                    paint.getJavaCap(),
+                    paint.getJavaJoin(),
+                    paint.getJavaStrokeMiter()));
+        }
+
+        // the alpha for the composite. Always opaque if the normal paint color is used since
+        // it contains the alpha
+        int alpha = customShader ? paint.getAlpha() : 0xFF;
+
+        boolean customXfermode = false;
         int xfermode = paint.getXfermode();
         if (xfermode > 0) {
             Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
             assert xfermodeDelegate != null;
             if (xfermodeDelegate != null) {
                 if (xfermodeDelegate.isSupported()) {
-                    Composite composite = xfermodeDelegate.getComposite();
+                    Composite composite = xfermodeDelegate.getComposite(alpha);
                     assert composite != null;
                     if (composite != null) {
                         g.setComposite(composite);
+                        customXfermode = true;
                     }
                 } else {
                     Bridge.getLog().fidelityWarning(null,
@@ -1217,12 +1218,21 @@
             }
         }
 
+        // if there was no custom xfermode, but we have alpha (due to a shader and a non
+        // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
+        // that will handle the alpha.
+        if (customXfermode == false && alpha != 0xFF) {
+            g.setComposite(PorterDuffXfermode_Delegate.getComposite(
+                    PorterDuff.Mode.SRC_OVER, alpha));
+        }
+
         return g;
     }
 
     private static void drawBitmap(
             int nativeCanvas,
             BufferedImage image,
+            Bitmap.Config mBitmapConfig,
             int nativePaintOrZero,
             int sleft, int stop, int sright, int sbottom,
             int dleft, int dtop, int dright, int dbottom) {
@@ -1243,7 +1253,7 @@
             }
         }
 
-        drawBitmap(canvasDelegate, image, paintDelegate,
+        drawBitmap(canvasDelegate, image, mBitmapConfig, paintDelegate,
                 sleft, stop, sright, sbottom,
                 dleft, dtop, dright, dbottom);
     }
@@ -1251,6 +1261,7 @@
     private static void drawBitmap(
             Canvas_Delegate canvasDelegate,
             BufferedImage image,
+            Bitmap.Config mBitmapConfig,
             Paint_Delegate paintDelegate,
             int sleft, int stop, int sright, int sbottom,
             int dleft, int dtop, int dright, int dbottom) {
@@ -1263,6 +1274,21 @@
                         RenderingHints.VALUE_INTERPOLATION_BILINEAR);
             }
 
+            // if the bitmap config is alpha_8, then we erase all color value from it
+            // before drawing it.
+            if (mBitmapConfig == Bitmap.Config.ALPHA_8) {
+                int w = image.getWidth();
+                int h = image.getHeight();
+                int[] argb = new int[w*h];
+                image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+                final int length = argb.length;
+                for (int i = 0 ; i < length; i++) {
+                    argb[i] &= 0xFF000000;
+                }
+                image.setRGB(0, 0, w, h, argb, 0, w);
+            }
+
             g.drawImage(image, dleft, dtop, dright, dbottom,
                     sleft, stop, sright, sbottom, null);
         } finally {
diff --git a/bridge/src/android/graphics/DashPathEffect_Delegate.java b/bridge/src/android/graphics/DashPathEffect_Delegate.java
index 1b539ce..5a704a7 100644
--- a/bridge/src/android/graphics/DashPathEffect_Delegate.java
+++ b/bridge/src/android/graphics/DashPathEffect_Delegate.java
@@ -53,7 +53,7 @@
                 paint.getStrokeWidth(),
                 paint.getJavaCap(),
                 paint.getJavaJoin(),
-                paint.getStrokeMiter(),
+                paint.getJavaStrokeMiter(),
                 mIntervals,
                 mPhase);
     }
diff --git a/bridge/src/android/graphics/LinearGradient_Delegate.java b/bridge/src/android/graphics/LinearGradient_Delegate.java
index baa0a6a..9fb48c8 100644
--- a/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ b/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -207,10 +207,17 @@
          * Returns a color for an arbitrary point.
          */
         private int getColor(float x, float y) {
-            // find the x position on the gradient vector.
-            float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
-            // from it get the position relative to the vector
-            float pos = (float) ((_x - mX0) / mDx);
+            float pos;
+            if (mDx == 0) {
+                pos = (y - mY0) / mDy;
+            } else if (mDy == 0) {
+                pos = (x - mX0) / mDx;
+            } else {
+                // find the x position on the gradient vector.
+                float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+                // from it get the position relative to the vector
+                pos = (_x - mX0) / mDx;
+            }
 
             return getGradientColor(pos);
         }
diff --git a/bridge/src/android/graphics/Paint_Delegate.java b/bridge/src/android/graphics/Paint_Delegate.java
index fa26bcf..0a597ca 100644
--- a/bridge/src/android/graphics/Paint_Delegate.java
+++ b/bridge/src/android/graphics/Paint_Delegate.java
@@ -114,6 +114,10 @@
         return mColor;
     }
 
+    public int getAlpha() {
+        return mColor >>> 24;
+    }
+
     public int getTextAlign() {
         return mTextAlign;
     }
@@ -122,8 +126,11 @@
         return mStrokeWidth;
     }
 
-    public float getStrokeMiter() {
-        return mStrokeMiter;
+    /**
+     * returns the value of stroke miter needed by the java api.
+     */
+    public float getJavaStrokeMiter() {
+        return mStrokeMiter * mStrokeWidth;
     }
 
     public int getJavaCap() {
@@ -256,7 +263,7 @@
             return 0;
         }
 
-        return delegate.mColor >>> 24;
+        return delegate.getAlpha();
     }
 
     /*package*/ static void setAlpha(Paint thisPaint, int a) {
@@ -860,9 +867,9 @@
     private void reset() {
         mFlags = Paint.DEFAULT_PAINT_FLAGS;
         mColor = 0;
-        mStyle = 0;
-        mCap = 0;
-        mJoin = 0;
+        mStyle = Paint.Style.FILL.nativeInt;
+        mCap = Paint.Cap.BUTT.nativeInt;
+        mJoin = Paint.Join.MITER.nativeInt;
         mTextAlign = 0;
         mTypeface = Typeface.sDefaults[0].native_instance;
         mStrokeWidth = 1.f;
diff --git a/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
index e8f29ad..516a2b9 100644
--- a/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
+++ b/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
@@ -42,7 +42,7 @@
     // ---- Public Helper methods ----
 
     @Override
-    public Composite getComposite() {
+    public Composite getComposite(int alpha) {
         // FIXME
         return null;
     }
diff --git a/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
index 3e9a346..097dfce 100644
--- a/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ b/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -46,8 +46,8 @@
     // ---- Public Helper methods ----
 
     @Override
-    public Composite getComposite() {
-        return getComposite(mMode);
+    public Composite getComposite(int alpha) {
+        return getComposite(getPorterDuffMode(mMode), alpha);
     }
 
     @Override
@@ -61,23 +61,35 @@
         return null;
     }
 
-    public static Composite getComposite(int mode) {
-        PorterDuff.Mode m = getMode(mode);
-        switch (m) {
+    public static PorterDuff.Mode getPorterDuffMode(int mode) {
+        for (PorterDuff.Mode m : PorterDuff.Mode.values()) {
+            if (m.nativeInt == mode) {
+                return m;
+            }
+        }
+
+        Bridge.getLog().error(null, String.format("Unknown PorterDuff.Mode: %d", mode));
+        assert false;
+        return PorterDuff.Mode.SRC_OVER;
+    }
+
+    public static Composite getComposite(PorterDuff.Mode mode, int alpha) {
+        float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f;
+        switch (mode) {
             case CLEAR:
-                return AlphaComposite.getInstance(AlphaComposite.CLEAR, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha);
             case DARKEN:
                 break;
             case DST:
-                return AlphaComposite.getInstance(AlphaComposite.DST, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.DST, falpha);
             case DST_ATOP:
-                return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha);
             case DST_IN:
-                return AlphaComposite.getInstance(AlphaComposite.DST_IN, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha);
             case DST_OUT:
-                return AlphaComposite.getInstance(AlphaComposite.DST_OUT, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha);
             case DST_OVER:
-                return AlphaComposite.getInstance(AlphaComposite.DST_OVER, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha);
             case LIGHTEN:
                 break;
             case MULTIPLY:
@@ -85,24 +97,24 @@
             case SCREEN:
                 break;
             case SRC:
-                return AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.SRC, falpha);
             case SRC_ATOP:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha);
             case SRC_IN:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_IN, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha);
             case SRC_OUT:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha);
             case SRC_OVER:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha);
             case XOR:
-                return AlphaComposite.getInstance(AlphaComposite.XOR, 1.0f /*alpha*/);
+                return AlphaComposite.getInstance(AlphaComposite.XOR, falpha);
         }
 
         Bridge.getLog().fidelityWarning(null,
-                String.format("Unsupported PorterDuff Mode: %s", m.name()),
+                String.format("Unsupported PorterDuff Mode: %s", mode.name()),
                 null);
 
-        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
+        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha);
     }
 
     // ---- native methods ----
@@ -117,16 +129,4 @@
     private PorterDuffXfermode_Delegate(int mode) {
         mMode = mode;
     }
-
-    private static PorterDuff.Mode getMode(int mode) {
-        for (PorterDuff.Mode m : PorterDuff.Mode.values()) {
-            if (m.nativeInt == mode) {
-                return m;
-            }
-        }
-
-        Bridge.getLog().error(null, String.format("Unknown PorterDuff.Mode: %d", mode));
-        assert false;
-        return PorterDuff.Mode.SRC_OVER;
-    }
 }
diff --git a/bridge/src/android/graphics/Xfermode_Delegate.java b/bridge/src/android/graphics/Xfermode_Delegate.java
index e783fd1..312318a 100644
--- a/bridge/src/android/graphics/Xfermode_Delegate.java
+++ b/bridge/src/android/graphics/Xfermode_Delegate.java
@@ -51,7 +51,7 @@
         return sManager.getDelegate(native_instance);
     }
 
-    public abstract Composite getComposite();
+    public abstract Composite getComposite(int alpha);
     public abstract boolean isSupported();
     public abstract String getSupportMessage();