diff options
Diffstat (limited to 'tools/layoutlib/bridge/src/android')
62 files changed, 9368 insertions, 6134 deletions
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java new file mode 100644 index 0000000..7b444aa --- /dev/null +++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java @@ -0,0 +1,59 @@ +/* + * 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.animation; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.animation.PropertyValuesHolder + * + * Through the layoutlib_create tool, the original native methods of PropertyValuesHolder have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + * The main goal of this class' methods are to provide a native way to access setters and getters + * on some object. In this case we want to default to using Java reflection instead so the native + * methods do nothing. + * + */ +/*package*/ class PropertyValuesHolder_Delegate { + + @LayoutlibDelegate + /*package*/ static int nGetIntMethod(Class<?> targetClass, String methodName) { + // return 0 to force PropertyValuesHolder to use Java reflection. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nGetFloatMethod(Class<?> targetClass, String methodName) { + // return 0 to force PropertyValuesHolder to use Java reflection. + return 0; + } + + @LayoutlibDelegate + /*package*/ static void nCallIntMethod(Object target, int methodID, int arg) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallFloatMethod(Object target, int methodID, float arg) { + // do nothing + } +} diff --git a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java new file mode 100644 index 0000000..aabd3f1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java @@ -0,0 +1,104 @@ +/* + * 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.app; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.Context; +import android.os.Bundle; + +/** + * Delegate used to provide new implementation of a select few methods of {@link Fragment} + * + * Through the layoutlib_create tool, the original methods of Fragment have been replaced + * by calls to methods of the same name in this delegate class. + * + * The methods being re-implemented are the ones responsible for instantiating Fragment objects. + * Because the classes of these objects are found in the project, these methods need access to + * {@link IProjectCallback} object. They are however static methods, so the callback is set + * before the inflation through {@link #setProjectCallback(IProjectCallback)}. + */ +public class Fragment_Delegate { + + private static IProjectCallback sProjectCallback; + + /** + * Sets the current {@link IProjectCallback} to be used to instantiate classes coming + * from the project being rendered. + */ + public static void setProjectCallback(IProjectCallback projectCallback) { + sProjectCallback = projectCallback; + } + + /** + * Like {@link #instantiate(Context, String, Bundle)} but with a null + * argument Bundle. + */ + @LayoutlibDelegate + /*package*/ static Fragment instantiate(Context context, String fname) { + return instantiate(context, fname, null); + } + + /** + * Create a new instance of a Fragment with the given class name. This is + * the same as calling its empty constructor. + * + * @param context The calling context being used to instantiate the fragment. + * This is currently just used to get its ClassLoader. + * @param fname The class name of the fragment to instantiate. + * @param args Bundle of arguments to supply to the fragment, which it + * can retrieve with {@link #getArguments()}. May be null. + * @return Returns a new fragment instance. + * @throws InstantiationException If there is a failure in instantiating + * the given fragment class. This is a runtime exception; it is not + * normally expected to happen. + */ + @LayoutlibDelegate + /*package*/ static Fragment instantiate(Context context, String fname, Bundle args) { + try { + if (sProjectCallback != null) { + Fragment f = (Fragment) sProjectCallback.loadView(fname, + new Class[0], new Object[0]); + + if (args != null) { + args.setClassLoader(f.getClass().getClassLoader()); + f.mArguments = args; + } + return f; + } + + return null; + } catch (ClassNotFoundException e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (java.lang.InstantiationException e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (IllegalAccessException e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (Exception e) { + throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java new file mode 100644 index 0000000..413894b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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.content.res; + +import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.Resources.NotFoundException; +import android.content.res.Resources.Theme; +import android.util.AttributeSet; +import android.util.TypedValue; + +/** + * Delegate used to provide new implementation of a select few methods of {@link Theme} + * + * Through the layoutlib_create tool, the original methods of Theme have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class Resources_Theme_Delegate { + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + int[] attrs) { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + } + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + int resid, int[] attrs) + throws NotFoundException { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + } + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes( + set, attrs, defStyleAttr, defStyleRes); + } + + @LayoutlibDelegate + /*package*/ static boolean resolveAttribute( + Resources thisResources, Theme thisTheme, + int resid, TypedValue outValue, + boolean resolveRefs) { + return RenderSessionImpl.getCurrentContext().resolveThemeAttribute( + resid, outValue, resolveRefs); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java new file mode 100644 index 0000000..a50a2bd --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.AvoidXfermode + * + * Through the layoutlib_create tool, the original native methods of AvoidXfermode have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original AvoidXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + */ +public class AvoidXfermode_Delegate extends Xfermode_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Composite getComposite(int alpha) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Avoid Xfermodes are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int opColor, int tolerance, int nativeMode) { + AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java deleted file mode 100644 index 35f022e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2008 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; - - -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import javax.imageio.ImageIO; - -public final class Bitmap extends _Original_Bitmap { - - private BufferedImage mImage; - - public Bitmap(File input) throws IOException { - super(1, true, null, -1); - - mImage = ImageIO.read(input); - } - - public Bitmap(InputStream is) throws IOException { - super(1, true, null, -1); - - mImage = ImageIO.read(is); - } - - Bitmap(BufferedImage image) { - super(1, true, null, -1); - mImage = image; - } - - public BufferedImage getImage() { - return mImage; - } - - // ----- overriden methods - - public enum Config { - // these native values must match up with the enum in SkBitmap.h - ALPHA_8 (2), - RGB_565 (4), - ARGB_4444 (5), - ARGB_8888 (6); - - Config(int ni) { - this.nativeInt = ni; - } - final int nativeInt; - - /* package */ static Config nativeToConfig(int ni) { - return sConfigs[ni]; - } - - private static Config sConfigs[] = { - null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 - }; - } - - @Override - public int getWidth() { - return mImage.getWidth(); - } - - @Override - public int getHeight() { - return mImage.getHeight(); - } - - /** - * Returns an immutable bitmap from the source bitmap. The new bitmap may - * be the same object as source, or a copy may have been made. - */ - public static Bitmap createBitmap(Bitmap src) { - return createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), null, false); - } - - /** - * Returns an immutable bitmap from the specified subset of the source - * bitmap. The new bitmap may be the same object as source, or a copy may - * have been made. - * - * @param source The bitmap we are subsetting - * @param x The x coordinate of the first pixel in source - * @param y The y coordinate of the first pixel in source - * @param width The number of pixels in each row - * @param height The number of rows - */ - public static Bitmap createBitmap(Bitmap source, int x, int y, - int width, int height) { - return new Bitmap(source.mImage.getSubimage(x, y, width, height)); - } - - /** - * Returns an immutable bitmap from subset of the source bitmap, - * transformed by the optional matrix. - * - * @param source The bitmap we are subsetting - * @param x The x coordinate of the first pixel in source - * @param y The y coordinate of the first pixel in source - * @param width The number of pixels in each row - * @param height The number of rows - * @param m Option matrix to be applied to the pixels - * @param filter true if the source should be filtered. - * Only applies if the matrix contains more than just - * translation. - * @return A bitmap that represents the specified subset of source - * @throws IllegalArgumentException if the x, y, width, height values are - * outside of the dimensions of the source bitmap. - */ - public static Bitmap createBitmap(Bitmap source, int x, int y, int width, - int height, Matrix m, boolean filter) { - checkXYSign(x, y); - checkWidthHeight(width, height); - if (x + width > source.getWidth()) { - throw new IllegalArgumentException( - "x + width must be <= bitmap.width()"); - } - if (y + height > source.getHeight()) { - throw new IllegalArgumentException( - "y + height must be <= bitmap.height()"); - } - - // check if we can just return our argument unchanged - if (!source.isMutable() && x == 0 && y == 0 - && width == source.getWidth() && height == source.getHeight() - && (m == null || m.isIdentity())) { - return source; - } - - if (m == null || m.isIdentity()) { - return new Bitmap(source.mImage.getSubimage(x, y, width, height)); - } - - int neww = width; - int newh = height; - Paint paint; - - Rect srcR = new Rect(x, y, x + width, y + height); - RectF dstR = new RectF(0, 0, width, height); - - /* the dst should have alpha if the src does, or if our matrix - doesn't preserve rectness - */ - boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect(); - RectF deviceR = new RectF(); - m.mapRect(deviceR, dstR); - neww = Math.round(deviceR.width()); - newh = Math.round(deviceR.height()); - - Canvas canvas = new Canvas(neww, newh); - - canvas.translate(-deviceR.left, -deviceR.top); - canvas.concat(m); - paint = new Paint(); - paint.setFilterBitmap(filter); - if (!m.rectStaysRect()) { - paint.setAntiAlias(true); - } - - canvas.drawBitmap(source, srcR, dstR, paint); - - return new Bitmap(canvas.getImage()); - } - - /** - * Returns a mutable bitmap with the specified width and height. - * - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. - * @throws IllegalArgumentException if the width or height are <= 0 - */ - public static Bitmap createBitmap(int width, int height, Config config) { - return new Bitmap(new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)); - } - - /** - * Returns a immutable bitmap with the specified width and height, with each - * pixel value set to the corresponding value in the colors array. - * - * @param colors Array of {@link Color} used to initialize the pixels. - * @param offset Number of values to skip before the first color in the - * array of colors. - * @param stride Number of colors in the array between rows (must be >= - * width or <= -width). - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. If the config does not - * support per-pixel alpha (e.g. RGB_565), then the alpha - * bytes in the colors[] will be ignored (assumed to be FF) - * @throws IllegalArgumentException if the width or height are <= 0, or if - * the color array's length is less than the number of pixels. - */ - public static Bitmap createBitmap(int colors[], int offset, int stride, - int width, int height, Config config) { - checkWidthHeight(width, height); - if (Math.abs(stride) < width) { - throw new IllegalArgumentException("abs(stride) must be >= width"); - } - int lastScanline = offset + (height - 1) * stride; - int length = colors.length; - if (offset < 0 || (offset + width > length) - || lastScanline < 0 - || (lastScanline + width > length)) { - throw new ArrayIndexOutOfBoundsException(); - } - - // TODO: create an immutable bitmap... - throw new UnsupportedOperationException(); - } - - /** - * Returns a immutable bitmap with the specified width and height, with each - * pixel value set to the corresponding value in the colors array. - * - * @param colors Array of {@link Color} used to initialize the pixels. - * This array must be at least as large as width * height. - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. If the config does not - * support per-pixel alpha (e.g. RGB_565), then the alpha - * bytes in the colors[] will be ignored (assumed to be FF) - * @throws IllegalArgumentException if the width or height are <= 0, or if - * the color array's length is less than the number of pixels. - */ - public static Bitmap createBitmap(int colors[], int width, int height, - Config config) { - return createBitmap(colors, 0, width, width, height, config); - } - - public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, - int dstHeight, boolean filter) { - Matrix m; - synchronized (Bitmap.class) { - // small pool of just 1 matrix - m = sScaleMatrix; - sScaleMatrix = null; - } - - if (m == null) { - m = new Matrix(); - } - - final int width = src.getWidth(); - final int height = src.getHeight(); - final float sx = dstWidth / (float)width; - final float sy = dstHeight / (float)height; - m.setScale(sx, sy); - Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter); - - synchronized (Bitmap.class) { - // do we need to check for null? why not just assign everytime? - if (sScaleMatrix == null) { - sScaleMatrix = m; - } - } - - return b; - } - - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java deleted file mode 100644 index e978fe8..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java +++ /dev/null @@ -1,566 +0,0 @@ -/* - * 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; - -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import java.io.BufferedInputStream; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Creates Bitmap objects from various sources, including files, streams, - * and byte-arrays. - */ -public class BitmapFactory { - public static class Options { - /** - * Create a default Options object, which if left unchanged will give - * the same result from the decoder as if null were passed. - */ - public Options() { - inDither = true; - inScaled = true; - } - - /** - * If set to true, the decoder will return null (no bitmap), but - * the out... fields will still be set, allowing the caller to query - * the bitmap without having to allocate the memory for its pixels. - */ - public boolean inJustDecodeBounds; - - /** - * If set to a value > 1, requests the decoder to subsample the original - * image, returning a smaller image to save memory. The sample size is - * the number of pixels in either dimension that correspond to a single - * pixel in the decoded bitmap. For example, inSampleSize == 4 returns - * an image that is 1/4 the width/height of the original, and 1/16 the - * number of pixels. Any value <= 1 is treated the same as 1. Note: the - * decoder will try to fulfill this request, but the resulting bitmap - * may have different dimensions that precisely what has been requested. - * Also, powers of 2 are often faster/easier for the decoder to honor. - */ - public int inSampleSize; - - /** - * If this is non-null, the decoder will try to decode into this - * internal configuration. If it is null, or the request cannot be met, - * the decoder will try to pick the best matching config based on the - * system's screen depth, and characteristics of the original image such - * as if it has per-pixel alpha (requiring a config that also does). - */ - public Bitmap.Config inPreferredConfig; - - /** - * If dither is true, the decoder will attempt to dither the decoded - * image. - */ - public boolean inDither; - - /** - * The pixel density to use for the bitmap. This will always result - * in the returned bitmap having a density set for it (see - * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition, - * if {@link #inScaled} is set (which it is by default} and this - * density does not match {@link #inTargetDensity}, then the bitmap - * will be scaled to the target density before being returned. - * - * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, - * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, - * and {@link BitmapFactory#decodeResourceStream} - * will fill in the density associated with the resource. The other - * functions will leave it as-is and no density will be applied. - * - * @see #inTargetDensity - * @see #inScreenDensity - * @see #inScaled - * @see Bitmap#setDensity(int) - * @see android.util.DisplayMetrics#densityDpi - */ - public int inDensity; - - /** - * The pixel density of the destination this bitmap will be drawn to. - * This is used in conjunction with {@link #inDensity} and - * {@link #inScaled} to determine if and how to scale the bitmap before - * returning it. - * - * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, - * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, - * and {@link BitmapFactory#decodeResourceStream} - * will fill in the density associated the Resources object's - * DisplayMetrics. The other - * functions will leave it as-is and no scaling for density will be - * performed. - * - * @see #inDensity - * @see #inScreenDensity - * @see #inScaled - * @see android.util.DisplayMetrics#densityDpi - */ - public int inTargetDensity; - - /** - * The pixel density of the actual screen that is being used. This is - * purely for applications running in density compatibility code, where - * {@link #inTargetDensity} is actually the density the application - * sees rather than the real screen density. - * - * <p>By setting this, you - * allow the loading code to avoid scaling a bitmap that is currently - * in the screen density up/down to the compatibility density. Instead, - * if {@link #inDensity} is the same as {@link #inScreenDensity}, the - * bitmap will be left as-is. Anything using the resulting bitmap - * must also used {@link Bitmap#getScaledWidth(int) - * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight - * Bitmap.getScaledHeight} to account for any different between the - * bitmap's density and the target's density. - * - * <p>This is never set automatically for the caller by - * {@link BitmapFactory} itself. It must be explicitly set, since the - * caller must deal with the resulting bitmap in a density-aware way. - * - * @see #inDensity - * @see #inTargetDensity - * @see #inScaled - * @see android.util.DisplayMetrics#densityDpi - */ - public int inScreenDensity; - - /** - * When this flag is set, if {@link #inDensity} and - * {@link #inTargetDensity} are not 0, the - * bitmap will be scaled to match {@link #inTargetDensity} when loaded, - * rather than relying on the graphics system scaling it each time it - * is drawn to a Canvas. - * - * <p>This flag is turned on by default and should be turned off if you need - * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this - * flag and are always scaled. - */ - public boolean inScaled; - - /** - * If this is set to true, then the resulting bitmap will allocate its - * pixels such that they can be purged if the system needs to reclaim - * memory. In that instance, when the pixels need to be accessed again - * (e.g. the bitmap is drawn, getPixels() is called), they will be - * automatically re-decoded. - * - * For the re-decode to happen, the bitmap must have access to the - * encoded data, either by sharing a reference to the input - * or by making a copy of it. This distinction is controlled by - * inInputShareable. If this is true, then the bitmap may keep a shallow - * reference to the input. If this is false, then the bitmap will - * explicitly make a copy of the input data, and keep that. Even if - * sharing is allowed, the implementation may still decide to make a - * deep copy of the input data. - */ - public boolean inPurgeable; - - /** - * This field works in conjuction with inPurgeable. If inPurgeable is - * false, then this field is ignored. If inPurgeable is true, then this - * field determines whether the bitmap can share a reference to the - * input data (inputstream, array, etc.) or if it must make a deep copy. - */ - public boolean inInputShareable; - - /** - * Normally bitmap allocations count against the dalvik heap, which - * means they help trigger GCs when a lot have been allocated. However, - * in rare cases, the caller may want to allocate the bitmap outside of - * that heap. To request that, set inNativeAlloc to true. In these - * rare instances, it is solely up to the caller to ensure that OOM is - * managed explicitly by calling bitmap.recycle() as soon as such a - * bitmap is no longer needed. - * - * @hide pending API council approval - */ - public boolean inNativeAlloc; - - /** - * The resulting width of the bitmap, set independent of the state of - * inJustDecodeBounds. However, if there is an error trying to decode, - * outWidth will be set to -1. - */ - public int outWidth; - - /** - * The resulting height of the bitmap, set independent of the state of - * inJustDecodeBounds. However, if there is an error trying to decode, - * outHeight will be set to -1. - */ - public int outHeight; - - /** - * If known, this string is set to the mimetype of the decoded image. - * If not know, or there is an error, it is set to null. - */ - public String outMimeType; - - /** - * Temp storage to use for decoding. Suggest 16K or so. - */ - public byte[] inTempStorage; - - private native void requestCancel(); - - /** - * Flag to indicate that cancel has been called on this object. This - * is useful if there's an intermediary that wants to first decode the - * bounds and then decode the image. In that case the intermediary - * can check, inbetween the bounds decode and the image decode, to see - * if the operation is canceled. - */ - public boolean mCancel; - - /** - * This can be called from another thread while this options object is - * inside a decode... call. Calling this will notify the decoder that - * it should cancel its operation. This is not guaranteed to cancel - * the decode, but if it does, the decoder... operation will return - * null, or if inJustDecodeBounds is true, will set outWidth/outHeight - * to -1 - */ - public void requestCancelDecode() { - mCancel = true; - requestCancel(); - } - } - - /** - * Decode a file path into a bitmap. If the specified file name is null, - * or cannot be decoded into a bitmap, the function returns null. - * - * @param pathName complete path name for the file to be decoded. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeFile(String pathName, Options opts) { - Bitmap bm = null; - InputStream stream = null; - try { - stream = new FileInputStream(pathName); - bm = decodeStream(stream, null, opts); - } catch (Exception e) { - /* do nothing. - If the exception happened on open, bm will be null. - */ - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - // do nothing here - } - } - } - return bm; - } - - /** - * Decode a file path into a bitmap. If the specified file name is null, - * or cannot be decoded into a bitmap, the function returns null. - * - * @param pathName complete path name for the file to be decoded. - * @return the resulting decoded bitmap, or null if it could not be decoded. - */ - public static Bitmap decodeFile(String pathName) { - return decodeFile(pathName, null); - } - - /** - * Decode a new Bitmap from an InputStream. This InputStream was obtained from - * resources, which we pass to be able to scale the bitmap accordingly. - */ - public static Bitmap decodeResourceStream(Resources res, TypedValue value, - InputStream is, Rect pad, Options opts) { - - if (opts == null) { - opts = new Options(); - } - - if (opts.inDensity == 0 && value != null) { - final int density = value.density; - if (density == TypedValue.DENSITY_DEFAULT) { - opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; - } else if (density != TypedValue.DENSITY_NONE) { - opts.inDensity = density; - } - } - - if (opts.inTargetDensity == 0 && res != null) { - opts.inTargetDensity = res.getDisplayMetrics().densityDpi; - } - - return decodeStream(is, pad, opts); - } - - /** - * Synonym for opening the given resource and calling - * {@link #decodeResourceStream}. - * - * @param res The resources object containing the image data - * @param id The resource id of the image data - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeResource(Resources res, int id, Options opts) { - Bitmap bm = null; - InputStream is = null; - - try { - final TypedValue value = new TypedValue(); - is = res.openRawResource(id, value); - - bm = decodeResourceStream(res, value, is, null, opts); - } catch (Exception e) { - /* do nothing. - If the exception happened on open, bm will be null. - If it happened on close, bm is still valid. - */ - } finally { - try { - if (is != null) is.close(); - } catch (IOException e) { - // Ignore - } - } - - return bm; - } - - /** - * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)} - * will null Options. - * - * @param res The resources object containing the image data - * @param id The resource id of the image data - * @return The decoded bitmap, or null if the image could not be decode. - */ - public static Bitmap decodeResource(Resources res, int id) { - return decodeResource(res, id, null); - } - - /** - * Decode an immutable bitmap from the specified byte array. - * - * @param data byte array of compressed image data - * @param offset offset into imageData for where the decoder should begin - * parsing. - * @param length the number of bytes, beginning at offset, to parse - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) { - if ((offset | length) < 0 || data.length < offset + length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge. - return null; - //return nativeDecodeByteArray(data, offset, length, opts); - } - - /** - * Decode an immutable bitmap from the specified byte array. - * - * @param data byte array of compressed image data - * @param offset offset into imageData for where the decoder should begin - * parsing. - * @param length the number of bytes, beginning at offset, to parse - * @return The decoded bitmap, or null if the image could not be decode. - */ - public static Bitmap decodeByteArray(byte[] data, int offset, int length) { - return decodeByteArray(data, offset, length, null); - } - - /** - * Decode an input stream into a bitmap. If the input stream is null, or - * cannot be used to decode a bitmap, the function returns null. - * The stream's position will be where ever it was after the encoded data - * was read. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { - // we don't throw in this case, thus allowing the caller to only check - // the cache, and not force the image to be decoded. - if (is == null) { - return null; - } - - // we need mark/reset to work properly - - if (!is.markSupported()) { - is = new BufferedInputStream(is, 16 * 1024); - } - - // so we can call reset() if a given codec gives up after reading up to - // this many bytes. FIXME: need to find out from the codecs what this - // value should be. - is.mark(1024); - - Bitmap bm; - - if (is instanceof AssetManager.AssetInputStream) { - // FIXME: log this. - return null; - } else { - // pass some temp storage down to the native code. 1024 is made up, - // but should be large enough to avoid too many small calls back - // into is.read(...) This number is not related to the value passed - // to mark(...) above. - try { - bm = new Bitmap(is); - } catch (IOException e) { - return null; - } - } - - return finishDecode(bm, outPadding, opts); - } - - private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { - if (bm == null || opts == null) { - return bm; - } - - final int density = opts.inDensity; - if (density == 0) { - return bm; - } - - bm.setDensity(density); - final int targetDensity = opts.inTargetDensity; - if (targetDensity == 0 || density == targetDensity - || density == opts.inScreenDensity) { - return bm; - } - - byte[] np = bm.getNinePatchChunk(); - final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np); - if (opts.inScaled || isNinePatch) { - float scale = targetDensity / (float)density; - // TODO: This is very inefficient and should be done in native by Skia - final Bitmap oldBitmap = bm; - bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), - (int) (bm.getHeight() * scale + 0.5f), true); - oldBitmap.recycle(); - - if (isNinePatch) { - //np = nativeScaleNinePatch(np, scale, outPadding); - bm.setNinePatchChunk(np); - } - bm.setDensity(targetDensity); - } - - return bm; - } - - /** - * Decode an input stream into a bitmap. If the input stream is null, or - * cannot be used to decode a bitmap, the function returns null. - * The stream's position will be where ever it was after the encoded data - * was read. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is) { - return decodeStream(is, null, null); - } - - /** - * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded - * return null. The position within the descriptor will not be changed when - * this returns, so the descriptor can be used again as-is. - * - * @param fd The file descriptor containing the bitmap data to decode - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return the decoded bitmap, or null - */ - public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { - return null; - - /* FIXME: implement as needed - try { - if (MemoryFile.isMemoryFile(fd)) { - int mappedlength = MemoryFile.getMappedSize(fd); - MemoryFile file = new MemoryFile(fd, mappedlength, "r"); - InputStream is = file.getInputStream(); - Bitmap bm = decodeStream(is, outPadding, opts); - return finishDecode(bm, outPadding, opts); - } - } catch (IOException ex) { - // invalid filedescriptor, no need to call nativeDecodeFileDescriptor() - return null; - } - //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts); - //return finishDecode(bm, outPadding, opts); - */ - } - - /** - * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded - * return null. The position within the descriptor will not be changed when - * this returns, so the descriptor can be used again as is. - * - * @param fd The file descriptor containing the bitmap data to decode - * @return the decoded bitmap, or null - */ - public static Bitmap decodeFileDescriptor(FileDescriptor fd) { - return decodeFileDescriptor(fd, null, null); - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java new file mode 100644 index 0000000..080b85f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2011 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; + +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeResources.NinePatchInputStream; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.Density; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.BitmapFactory.Options; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; + +/** + * Delegate implementing the native methods of android.graphics.BitmapFactory + * + * Through the layoutlib_create tool, the original native methods of BitmapFactory have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +/*package*/ class BitmapFactory_Delegate { + + // ------ Java delegates ------ + + @LayoutlibDelegate + /*package*/ static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { + if (bm == null || opts == null) { + return bm; + } + + final int density = opts.inDensity; + if (density == 0) { + return bm; + } + + bm.setDensity(density); + final int targetDensity = opts.inTargetDensity; + if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { + return bm; + } + + byte[] np = bm.getNinePatchChunk(); + final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); + // DELEGATE CHANGE: never scale 9-patch + if (opts.inScaled && isNinePatch == false) { + float scale = targetDensity / (float)density; + // TODO: This is very inefficient and should be done in native by Skia + final Bitmap oldBitmap = bm; + bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), + (int) (bm.getHeight() * scale + 0.5f), true); + oldBitmap.recycle(); + + if (isNinePatch) { + np = nativeScaleNinePatch(np, scale, outPadding); + bm.setNinePatchChunk(np); + } + bm.setDensity(targetDensity); + } + + return bm; + } + + + // ------ Native Delegates ------ + + @LayoutlibDelegate + /*package*/ static void nativeSetDefaultConfig(int nativeConfig) { + // pass + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, + Rect padding, Options opts) { + Bitmap bm = null; + + Density density = Density.MEDIUM; + if (opts != null) { + density = Density.getEnum(opts.inDensity); + } + + try { + if (is instanceof NinePatchInputStream) { + NinePatchInputStream npis = (NinePatchInputStream) is; + npis.disableFakeMarkSupport(); + + // load the bitmap as a nine patch + com.android.ninepatch.NinePatch ninePatch = com.android.ninepatch.NinePatch.load( + npis, true /*is9Patch*/, false /*convert*/); + + // get the bitmap and chunk objects. + bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), true /*isMutable*/, + density); + NinePatchChunk chunk = ninePatch.getChunk(); + + // put the chunk in the bitmap + bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk)); + + // read the padding + int[] paddingarray = chunk.getPadding(); + padding.left = paddingarray[0]; + padding.top = paddingarray[1]; + padding.right = paddingarray[2]; + padding.bottom = paddingarray[3]; + } else { + // load the bitmap directly. + bm = Bitmap_Delegate.createBitmap(is, true, density); + } + } catch (IOException e) { + Bridge.getLog().error(null,"Failed to load image" , e, null); + } + + return bm; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, + Rect padding, Options opts) { + opts.inBitmap = null; + return null; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts) { + opts.inBitmap = null; + return null; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset, + int length, Options opts) { + opts.inBitmap = null; + return null; + } + + @LayoutlibDelegate + /*package*/ static byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad) { + // don't scale for now. This should not be called anyway since we re-implement + // BitmapFactory.finishDecode(); + return chunk; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) { + return true; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java deleted file mode 100644 index ad3974c..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import java.awt.Paint; - -public class BitmapShader extends Shader { - - // we hold on just for the GC, since our native counterpart is using it - private final Bitmap mBitmap; - - /** - * Call this to create a new shader that will draw with a bitmap. - * - * @param bitmap The bitmap to use inside the shader - * @param tileX The tiling mode for x to draw the bitmap in. - * @param tileY The tiling mode for y to draw the bitmap in. - */ - public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) { - mBitmap = bitmap; - } - - //---------- Custom methods - - public Bitmap getBitmap() { - return mBitmap; - } - - @Override - Paint getJavaPaint() { - return null; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java new file mode 100644 index 0000000..9a8cf04 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -0,0 +1,247 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.BitmapShader + * + * Through the layoutlib_create tool, the original native methods of BitmapShader have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original BitmapShader class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class BitmapShader_Delegate extends Shader_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int native_bitmap, int shaderTileModeX, + int shaderTileModeY) { + Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(native_bitmap); + if (bitmap == null) { + return 0; + } + + BitmapShader_Delegate newDelegate = new BitmapShader_Delegate( + bitmap.getImage(), + Shader_Delegate.getTileMode(shaderTileModeX), + Shader_Delegate.getTileMode(shaderTileModeY)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate(int native_shader, int native_bitmap, + int shaderTileModeX, int shaderTileModeY) { + // pass, not needed. + return 0; + } + + // ---- Private delegate/helper methods ---- + + private BitmapShader_Delegate(java.awt.image.BufferedImage image, + TileMode tileModeX, TileMode tileModeY) { + mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY); + } + + private class BitmapShaderPaint implements java.awt.Paint { + private final java.awt.image.BufferedImage mImage; + private final TileMode mTileModeX; + private final TileMode mTileModeY; + + BitmapShaderPaint(java.awt.image.BufferedImage image, + TileMode tileModeX, TileMode tileModeY) { + mImage = image; + mTileModeX = tileModeX; + mTileModeY = tileModeY; + } + + 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) { + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in BitmapShader", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in BitmapShader", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel); + } + + private class BitmapShaderContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public BitmapShaderContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + 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]; + + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix. + pt1[0] = pt2[0]; + pt1[1] = pt2[1]; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + data[index++] = getColor(pt2[0], pt2[1]); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + } + + /** + * Returns a color for an arbitrary point. + */ + private int getColor(float fx, float fy) { + int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX); + int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY); + + return mImage.getRGB(x, y); + } + + private int getCoordinate(int i, int size, TileMode mode) { + if (i < 0) { + switch (mode) { + case CLAMP: + i = 0; + break; + case REPEAT: + i = size - 1 - (-i % size); + break; + case MIRROR: + // this is the same as the positive side, just make the value positive + // first. + i = -i; + int count = i / size; + i = i % size; + + if ((count % 2) == 1) { + i = size - 1 - i; + } + break; + } + } else if (i >= size) { + switch (mode) { + case CLAMP: + i = size - 1; + break; + case REPEAT: + i = i % size; + break; + case MIRROR: + int count = i / size; + i = i % size; + + if ((count % 2) == 1) { + i = size - 1 - i; + } + break; + } + } + + return i; + } + + + public int getTransparency() { + return java.awt.Paint.TRANSLUCENT; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java new file mode 100644 index 0000000..66e59d8 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -0,0 +1,563 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.resources.Density; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.os.Parcel; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.Buffer; +import java.util.Arrays; + +import javax.imageio.ImageIO; + +/** + * Delegate implementing the native methods of android.graphics.Bitmap + * + * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Bitmap class. + * + * @see DelegateManager + * + */ +public final class Bitmap_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Bitmap_Delegate> sManager = + new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private final Config mConfig; + private BufferedImage mImage; + private boolean mHasAlpha = true; + private int mGenerationId = 0; + + + // ---- Public Helper methods ---- + + /** + * Returns the native delegate associated to a given {@link Bitmap_Delegate} object. + */ + public static Bitmap_Delegate getDelegate(Bitmap bitmap) { + return sManager.getDelegate(bitmap.mNativeBitmap); + } + + /** + * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object. + */ + public static Bitmap_Delegate getDelegate(int native_bitmap) { + return sManager.getDelegate(native_bitmap); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given file content. + * + * @param input the file from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(File input, boolean isMutable, Density density) + throws IOException { + // create a delegate with the content of the file. + Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given stream content. + * + * @param input the stream from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density) + throws IOException { + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} + * + * @param image the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(BufferedImage image, boolean isMutable, + Density density) throws IOException { + // create a delegate with the given image. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. + */ + public static BufferedImage getImage(Bitmap bitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap); + if (delegate == null) { + return null; + } + + return delegate.mImage; + } + + public static int getBufferedImageType(int nativeBitmapConfig) { + switch (Config.sConfigs[nativeBitmapConfig]) { + case ALPHA_8: + return BufferedImage.TYPE_INT_ARGB; + case RGB_565: + return BufferedImage.TYPE_INT_ARGB; + case ARGB_4444: + return BufferedImage.TYPE_INT_ARGB; + case ARGB_8888: + return BufferedImage.TYPE_INT_ARGB; + } + + return BufferedImage.TYPE_INT_ARGB; + } + + /** + * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. + */ + public BufferedImage getImage() { + return mImage; + } + + /** + * Returns the Android bitmap config. Note that this not the config of the underlying + * Java2D bitmap. + */ + public Config getConfig() { + return mConfig; + } + + /** + * Returns the hasAlpha rendering hint + * @return true if the bitmap alpha should be used at render time + */ + public boolean hasAlpha() { + return mHasAlpha && mConfig != Config.RGB_565; + } + + /** + * Update the generationId. + * + * @see Bitmap#getGenerationId() + */ + public void change() { + mGenerationId++; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width, + int height, int nativeConfig, boolean mutable) { + int imageType = getBufferedImageType(nativeConfig); + + // create the image + BufferedImage image = new BufferedImage(width, height, imageType); + + if (colors != null) { + image.setRGB(0, 0, width, height, colors, offset, stride); + } + + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); + + return createBitmap(delegate, mutable, Bitmap.getDefaultDensity()); + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) { + Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap); + if (srcBmpDelegate == null) { + return null; + } + + BufferedImage srcImage = srcBmpDelegate.getImage(); + + int width = srcImage.getWidth(); + int height = srcImage.getHeight(); + + int imageType = getBufferedImageType(nativeConfig); + + // create the image + BufferedImage image = new BufferedImage(width, height, imageType); + + // copy the source image into the image. + int[] argb = new int[width * height]; + srcImage.getRGB(0, 0, width, height, argb, 0, width); + image.setRGB(0, 0, width, height, argb, 0, width); + + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); + + return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity()); + } + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int nativeBitmap) { + sManager.removeJavaReferenceFor(nativeBitmap); + } + + @LayoutlibDelegate + /*package*/ static void nativeRecycle(int nativeBitmap) { + sManager.removeJavaReferenceFor(nativeBitmap); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, + OutputStream stream, byte[] tempStorage) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.compress() is not supported", null /*data*/); + return true; + } + + @LayoutlibDelegate + /*package*/ static void nativeErase(int nativeBitmap, int color) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + BufferedImage image = delegate.mImage; + + Graphics2D g = image.createGraphics(); + try { + g.setColor(new java.awt.Color(color, true)); + + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } finally { + g.dispose(); + } + } + + @LayoutlibDelegate + /*package*/ static int nativeWidth(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int nativeHeight(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getHeight(); + } + + @LayoutlibDelegate + /*package*/ static int nativeRowBytes(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int nativeConfig(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mConfig.nativeInt; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeHasAlpha(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return true; + } + + return delegate.mHasAlpha; + } + + @LayoutlibDelegate + /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getRGB(x, y); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, + int stride, int x, int y, int width, int height) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); + } + + + @LayoutlibDelegate + /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().setRGB(x, y, color); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, + int stride, int x, int y, int width, int height) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); + } + + @LayoutlibDelegate + /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) { + // FIXME implement native delegate + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) { + // FIXME implement native delegate + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static int nativeGenerationId(int nativeBitmap) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mGenerationId; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { + // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only + // used during aidl call so really this should not be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", + null /*data*/); + return null; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable, + int density, Parcel p) { + // This is only called when sending a bitmap through aidl, so really this should not + // be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", + null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint, + int[] offsetXY) { + Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); + if (bitmap == null) { + return null; + } + + // get the paint which can be null if nativePaint is 0. + Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); + + if (paint != null && paint.getMaskFilter() != null) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, + "MaskFilter not supported in Bitmap.extractAlpha", + null, null /*data*/); + } + + int alpha = paint != null ? paint.getAlpha() : 0xFF; + BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); + + // create the delegate. The actual Bitmap config is only an alpha channel + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); + + // the density doesn't matter, it's set by the Java method. + return createBitmap(delegate, false /*isMutable*/, + Density.DEFAULT_DENSITY /*density*/); + } + + @LayoutlibDelegate + /*package*/ static void nativePrepareToDraw(int nativeBitmap) { + // nothing to be done here. + } + + @LayoutlibDelegate + /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.mHasAlpha = hasAlpha; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSameAs(int nb0, int nb1) { + Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); + if (delegate1 == null) { + return false; + } + + Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); + if (delegate2 == null) { + return false; + } + + BufferedImage image1 = delegate1.getImage(); + BufferedImage image2 = delegate2.getImage(); + if (delegate1.mConfig != delegate2.mConfig || + image1.getWidth() != image2.getWidth() || + image1.getHeight() != image2.getHeight()) { + return false; + } + + // get the internal data + int w = image1.getWidth(); + int h = image2.getHeight(); + int[] argb1 = new int[w*h]; + int[] argb2 = new int[w*h]; + + image1.getRGB(0, 0, w, h, argb1, 0, w); + image2.getRGB(0, 0, w, h, argb2, 0, w); + + // compares + if (delegate1.mConfig == Config.ALPHA_8) { + // in this case we have to manually compare the alpha channel as the rest is garbage. + final int length = w*h; + for (int i = 0 ; i < length ; i++) { + if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { + return false; + } + } + return true; + } + + return Arrays.equals(argb1, argb2); + } + + // ---- Private delegate/helper methods ---- + + private Bitmap_Delegate(BufferedImage image, Config config) { + mImage = image; + mConfig = config; + } + + private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) { + // get its native_int + int nativeInt = sManager.addNewDelegate(delegate); + + // and create/return a new Bitmap with it + return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density); + } + + /** + * Creates and returns a copy of a given BufferedImage. + * <p/> + * if alpha is different than 255, then it is applied to the alpha channel of each pixel. + * + * @param image the image to copy + * @param imageType the type of the new image + * @param alpha an optional alpha modifier + * @return a new BufferedImage + */ + /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { + int w = image.getWidth(); + int h = image.getHeight(); + + BufferedImage result = new BufferedImage(w, h, imageType); + + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + if (alpha != 255) { + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + int a = (argb[i] >>> 24 * alpha) / 255; + argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); + } + } + + result.setRGB(0, 0, w, h, argb, 0, w); + + return result; + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java new file mode 100644 index 0000000..4becba1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.BlurMaskFilter + * + * Through the layoutlib_create tool, the original native methods of BlurMaskFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original BlurMaskFilter class. + * + * Because this extends {@link MaskFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link MaskFilter_Delegate}. + * + * @see MaskFilter_Delegate + * + */ +public class BlurMaskFilter_Delegate extends MaskFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Blur Mask Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(float radius, int style) { + BlurMaskFilter_Delegate newDelegate = new BlurMaskFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java deleted file mode 100644 index d5d315e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java +++ /dev/null @@ -1,1279 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import com.android.layoutlib.api.ILayoutLog; - -import android.graphics.DrawFilter; -import android.graphics.Picture; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Xfermode; -import android.graphics.Paint.Align; -import android.graphics.Paint.FontInfo; -import android.graphics.Paint.Style; -import android.graphics.Region.Op; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.util.List; -import java.util.Stack; - -import javax.microedition.khronos.opengles.GL; - -/** - * Re-implementation of the Canvas, 100% in java on top of a BufferedImage. - */ -public class Canvas extends _Original_Canvas { - - private BufferedImage mBufferedImage; - private final Stack<Graphics2D> mGraphicsStack = new Stack<Graphics2D>(); - private final ILayoutLog mLogger; - - public Canvas() { - mLogger = null; - // the mBufferedImage will be taken from a bitmap in #setBitmap() - } - - public Canvas(Bitmap bitmap) { - mLogger = null; - mBufferedImage = bitmap.getImage(); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - public Canvas(int nativeCanvas) { - mLogger = null; - throw new UnsupportedOperationException("Can't create Canvas(int)"); - } - - public Canvas(javax.microedition.khronos.opengles.GL gl) { - mLogger = null; - throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)"); - } - - // custom constructors for our use. - public Canvas(int width, int height, ILayoutLog logger) { - mLogger = logger; - mBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - public Canvas(int width, int height) { - this(width, height, null /* logger*/); - } - - // custom mehtods - public BufferedImage getImage() { - return mBufferedImage; - } - - public Graphics2D getGraphics2d() { - return mGraphicsStack.peek(); - } - - public void dispose() { - while (mGraphicsStack.size() > 0) { - mGraphicsStack.pop().dispose(); - } - } - - /** - * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. - * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. - */ - private Graphics2D getCustomGraphics(Paint paint) { - // make new one - Graphics2D g = getGraphics2d(); - g = (Graphics2D)g.create(); - - // configure it - g.setColor(new Color(paint.getColor())); - int alpha = paint.getAlpha(); - float falpha = alpha / 255.f; - - Style style = paint.getStyle(); - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - PathEffect e = paint.getPathEffect(); - if (e instanceof DashPathEffect) { - DashPathEffect dpe = (DashPathEffect)e; - g.setStroke(new BasicStroke( - paint.getStrokeWidth(), - paint.getStrokeCap().getJavaCap(), - paint.getStrokeJoin().getJavaJoin(), - paint.getStrokeMiter(), - dpe.getIntervals(), - dpe.getPhase())); - } else { - g.setStroke(new BasicStroke( - paint.getStrokeWidth(), - paint.getStrokeCap().getJavaCap(), - paint.getStrokeJoin().getJavaJoin(), - paint.getStrokeMiter())); - } - } - - Xfermode xfermode = paint.getXfermode(); - if (xfermode instanceof PorterDuffXfermode) { - PorterDuff.Mode mode = ((PorterDuffXfermode)xfermode).getMode(); - - setModeInGraphics(mode, g, falpha); - } else { - if (mLogger != null && xfermode != null) { - mLogger.warning(String.format( - "Xfermode '%1$s' is not supported in the Layout Editor.", - xfermode.getClass().getCanonicalName())); - } - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - } - - Shader shader = paint.getShader(); - if (shader != null) { - java.awt.Paint shaderPaint = shader.getJavaPaint(); - if (shaderPaint != null) { - g.setPaint(shaderPaint); - } else { - if (mLogger != null) { - mLogger.warning(String.format( - "Shader '%1$s' is not supported in the Layout Editor.", - shader.getClass().getCanonicalName())); - } - } - } - - return g; - } - - private void setModeInGraphics(PorterDuff.Mode mode, Graphics2D g, float falpha) { - switch (mode) { - case CLEAR: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha)); - break; - case DARKEN: - break; - case DST: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST, falpha)); - break; - case DST_ATOP: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha)); - break; - case DST_IN: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha)); - break; - case DST_OUT: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha)); - break; - case DST_OVER: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha)); - break; - case LIGHTEN: - break; - case MULTIPLY: - break; - case SCREEN: - break; - case SRC: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, falpha)); - break; - case SRC_ATOP: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha)); - break; - case SRC_IN: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha)); - break; - case SRC_OUT: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha)); - break; - case SRC_OVER: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - break; - case XOR: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.XOR, falpha)); - break; - } - } - - - // -------------------- - // OVERRIDEN ENUMS - // This is needed since we rename Canvas into _Original_Canvas - // -------------------- - - public enum EdgeType { - BW(0), //!< treat edges by just rounding to nearest pixel boundary - AA(1); //!< treat edges by rounding-out, since they may be antialiased - - EdgeType(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - - // -------------------- - // OVERRIDEN METHODS - // -------------------- - - /* (non-Javadoc) - * @see android.graphics.Canvas#setBitmap(android.graphics.Bitmap) - */ - @Override - public void setBitmap(Bitmap bitmap) { - mBufferedImage = bitmap.getImage(); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#translate(float, float) - */ - @Override - public void translate(float dx, float dy) { - getGraphics2d().translate(dx, dy); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#save() - */ - @Override - public int save() { - // get the current save count - int count = mGraphicsStack.size(); - - // create a new graphics and add it to the stack - Graphics2D g = (Graphics2D)getGraphics2d().create(); - mGraphicsStack.push(g); - - // return the old save count - return count; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#save(int) - */ - @Override - public int save(int saveFlags) { - // For now we ignore saveFlags - return save(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#restore() - */ - @Override - public void restore() { - mGraphicsStack.pop(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#restoreToCount(int) - */ - @Override - public void restoreToCount(int saveCount) { - while (mGraphicsStack.size() > saveCount) { - mGraphicsStack.pop(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getSaveCount() - */ - @Override - public int getSaveCount() { - return mGraphicsStack.size(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(float left, float top, float right, float bottom, Op op) { - return clipRect(left, top, right, bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(float, float, float, float) - */ - @Override - public boolean clipRect(float left, float top, float right, float bottom) { - getGraphics2d().clipRect((int)left, (int)top, (int)(right-left), (int)(bottom-top)); - return true; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(int, int, int, int) - */ - @Override - public boolean clipRect(int left, int top, int right, int bottom) { - getGraphics2d().clipRect(left, top, right-left, bottom-top); - return true; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.Rect, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(Rect rect, Op op) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.Rect) - */ - @Override - public boolean clipRect(Rect rect) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.RectF, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(RectF rect, Op op) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.RectF) - */ - @Override - public boolean clipRect(RectF rect) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - public boolean quickReject(RectF rect, EdgeType type) { - return false; - } - - @Override - public boolean quickReject(RectF rect, _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public boolean quickReject(Path path, EdgeType type) { - return false; - } - - @Override - public boolean quickReject(Path path, _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public boolean quickReject(float left, float top, float right, float bottom, - EdgeType type) { - return false; - } - - @Override - public boolean quickReject(float left, float top, float right, float bottom, - _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Retrieve the clip bounds, returning true if they are non-empty. - * - * @param bounds Return the clip bounds here. If it is null, ignore it but - * still return true if the current clip is non-empty. - * @return true if the current clip is non-empty. - */ - @Override - public boolean getClipBounds(Rect bounds) { - Rectangle rect = getGraphics2d().getClipBounds(); - if (rect != null) { - bounds.left = rect.x; - bounds.top = rect.y; - bounds.right = rect.x + rect.width; - bounds.bottom = rect.y + rect.height; - return true; - } - return false; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawColor(int, android.graphics.PorterDuff.Mode) - */ - @Override - public void drawColor(int color, PorterDuff.Mode mode) { - Graphics2D g = getGraphics2d(); - - // save old color - Color c = g.getColor(); - - Composite composite = g.getComposite(); - - // get the alpha from the color - int alpha = color >>> 24; - float falpha = alpha / 255.f; - - setModeInGraphics(mode, g, falpha); - - g.setColor(new Color(color)); - - g.fillRect(0, 0, getWidth(), getHeight()); - - g.setComposite(composite); - - // restore color - g.setColor(c); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawColor(int) - */ - @Override - public void drawColor(int color) { - drawColor(color, PorterDuff.Mode.SRC_OVER); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawARGB(int, int, int, int) - */ - @Override - public void drawARGB(int a, int r, int g, int b) { - drawColor(a << 24 | r << 16 | g << 8 | b, PorterDuff.Mode.SRC_OVER); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRGB(int, int, int) - */ - @Override - public void drawRGB(int r, int g, int b) { - drawColor(0xFF << 24 | r << 16 | g << 8 | b, PorterDuff.Mode.SRC_OVER); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#getWidth() - */ - @Override - public int getWidth() { - return mBufferedImage.getWidth(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getHeight() - */ - @Override - public int getHeight() { - return mBufferedImage.getHeight(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPaint(android.graphics.Paint) - */ - @Override - public void drawPaint(Paint paint) { - drawColor(paint.getColor()); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, float, float, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - (int)left, (int)top, - (int)left+bitmap.getWidth(), (int)top+bitmap.getHeight(), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Matrix, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - boolean needsRestore = false; - if (matrix.isIdentity() == false) { - // create a new graphics and apply the matrix to it - save(); // this creates a new Graphics2D, and stores it for children call to use - needsRestore = true; - Graphics2D g = getGraphics2d(); // get the newly create Graphics2D - - // get the Graphics2D current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform from the matrix - AffineTransform matrixTx = matrix.getTransform(); - - // combine them so that the matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - // draw the bitmap - drawBitmap(bitmap, 0, 0, paint); - - if (needsRestore) { - // remove the new graphics - restore(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.Rect, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - if (src == null) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - dst.left, dst.top, dst.right, dst.bottom, paint); - } else { - drawBitmap(bitmap, src.left, src.top, src.width(), src.height(), - dst.left, dst.top, dst.right, dst.bottom, paint); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - if (src == null) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom, paint); - } else { - drawBitmap(bitmap, src.left, src.top, src.width(), src.height(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom, paint); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(int[], int, int, int, int, int, int, boolean, android.graphics.Paint) - */ - @Override - public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, - int height, boolean hasAlpha, Paint paint) { - throw new UnsupportedOperationException(); - } - - private void drawBitmap(Bitmap bitmap, int sleft, int stop, int sright, int sbottom, int dleft, - int dtop, int dright, int dbottom, Paint paint) { - BufferedImage image = bitmap.getImage(); - - Graphics2D g = getGraphics2d(); - - Composite c = null; - - if (paint != null) { - if (paint.isFilterBitmap()) { - g = (Graphics2D)g.create(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - if (paint.getAlpha() != 0xFF) { - c = g.getComposite(); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - paint.getAlpha()/255.f)); - } - } - - g.drawImage(image, dleft, dtop, dright, dbottom, - sleft, stop, sright, sbottom, null); - - if (paint != null) { - if (paint.isFilterBitmap()) { - g.dispose(); - } - if (c != null) { - g.setComposite(c); - } - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#rotate(float, float, float) - */ - @Override - public void rotate(float degrees, float px, float py) { - if (degrees != 0) { - Graphics2D g = getGraphics2d(); - g.translate(px, py); - g.rotate(Math.toRadians(degrees)); - g.translate(-px, -py); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#rotate(float) - */ - @Override - public void rotate(float degrees) { - getGraphics2d().rotate(Math.toRadians(degrees)); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#scale(float, float, float, float) - */ - @Override - public void scale(float sx, float sy, float px, float py) { - Graphics2D g = getGraphics2d(); - g.translate(px, py); - g.scale(sx, sy); - g.translate(-px, -py); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#scale(float, float) - */ - @Override - public void scale(float sx, float sy) { - getGraphics2d().scale(sx, sy); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(char[], int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { - // WARNING: the logic in this method is similar to Paint.measureText. - // Any change to this method should be reflected in Paint.measureText - Graphics2D g = getGraphics2d(); - - g = (Graphics2D)g.create(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // set the color. because this only handles RGB, the alpha channel is handled - // as a composite. - g.setColor(new Color(paint.getColor())); - int alpha = paint.getAlpha(); - float falpha = alpha / 255.f; - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - - - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - if (paint.getTextAlign() != Align.LEFT) { - float m = paint.measureText(text, index, count); - if (paint.getTextAlign() == Align.CENTER) { - x -= m / 2; - } else if (paint.getTextAlign() == Align.RIGHT) { - x -= m; - } - } - - List<FontInfo> fonts = paint.getFonts(); - try { - if (fonts.size() > 0) { - FontInfo mainFont = fonts.get(0); - int i = index; - int lastIndex = index + count; - while (i < lastIndex) { - // always start with the main font. - int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); - if (upTo == -1) { - // draw all the rest and exit. - g.setFont(mainFont.mFont); - g.drawChars(text, i, lastIndex - i, (int)x, (int)y); - return; - } else if (upTo > 0) { - // draw what's possible - g.setFont(mainFont.mFont); - g.drawChars(text, i, upTo - i, (int)x, (int)y); - - // compute the width that was drawn to increase x - x += mainFont.mMetrics.charsWidth(text, i, upTo - i); - - // move index to the first non displayed char. - i = upTo; - - // don't call continue at this point. Since it is certain the main font - // cannot display the font a index upTo (now ==i), we move on to the - // fallback fonts directly. - } - - // no char supported, attempt to read the next char(s) with the - // fallback font. In this case we only test the first character - // and then go back to test with the main font. - // Special test for 2-char characters. - boolean foundFont = false; - for (int f = 1 ; f < fonts.size() ; f++) { - FontInfo fontInfo = fonts.get(f); - - // need to check that the font can display the character. We test - // differently if the char is a high surrogate. - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); - if (upTo == -1) { - // draw that char - g.setFont(fontInfo.mFont); - g.drawChars(text, i, charCount, (int)x, (int)y); - - // update x - x += fontInfo.mMetrics.charsWidth(text, i, charCount); - - // update the index in the text, and move on - i += charCount; - foundFont = true; - break; - - } - } - - // in case no font can display the char, display it with the main font. - // (it'll put a square probably) - if (foundFont == false) { - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - - g.setFont(mainFont.mFont); - g.drawChars(text, i, charCount, (int)x, (int)y); - - // measure it to advance x - x += mainFont.mMetrics.charsWidth(text, i, charCount); - - // and move to the next chars. - i += charCount; - } - } - } - } finally { - g.dispose(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { - drawText(text.toString().toCharArray(), start, end - start, x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.String, float, float, android.graphics.Paint) - */ - @Override - public void drawText(String text, float x, float y, Paint paint) { - drawText(text.toCharArray(), 0, text.length(), x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.String, int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(String text, int start, int end, float x, float y, Paint paint) { - drawText(text.toCharArray(), start, end - start, x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawRect(RectF rect, Paint paint) { - doDrawRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(float, float, float, float, android.graphics.Paint) - */ - @Override - public void drawRect(float left, float top, float right, float bottom, Paint paint) { - doDrawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(android.graphics.Rect, android.graphics.Paint) - */ - @Override - public void drawRect(Rect r, Paint paint) { - doDrawRect(r.left, r.top, r.width(), r.height(), paint); - } - - private final void doDrawRect(int left, int top, int width, int height, Paint paint) { - if (width > 0 && height > 0) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillRect(left, top, width, height); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawRect(left, top, width, height); - } - - // dispose Graphics2D object - g.dispose(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRoundRect(android.graphics.RectF, float, float, android.graphics.Paint) - */ - @Override - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { - if (rect.width() > 0 && rect.height() > 0) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - - int arcWidth = (int)(rx * 2); - int arcHeight = (int)(ry * 2); - - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), - arcWidth, arcHeight); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), - arcWidth, arcHeight); - } - - // dispose Graphics2D object - g.dispose(); - } - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLine(float, float, float, float, android.graphics.Paint) - */ - @Override - public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLines(float[], int, int, android.graphics.Paint) - */ - @Override - public void drawLines(float[] pts, int offset, int count, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - for (int i = 0 ; i < count ; i += 4) { - g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], - (int)pts[i + offset + 2], (int)pts[i + offset + 3]); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLines(float[], android.graphics.Paint) - */ - @Override - public void drawLines(float[] pts, Paint paint) { - drawLines(pts, 0, pts.length, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawCircle(float, float, float, android.graphics.Paint) - */ - @Override - public void drawCircle(float cx, float cy, float radius, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - int size = (int)(radius * 2); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillOval((int)(cx - radius), (int)(cy - radius), size, size); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawOval((int)(cx - radius), (int)(cy - radius), size, size); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawOval(android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawOval(RectF oval, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height()); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height()); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPath(android.graphics.Path, android.graphics.Paint) - */ - @Override - public void drawPath(Path path, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fill(path.getAwtShape()); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.draw(path.getAwtShape()); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setMatrix(android.graphics.Matrix) - */ - @Override - public void setMatrix(Matrix matrix) { - // get the new current graphics - Graphics2D g = getGraphics2d(); - - // and apply the matrix - g.setTransform(matrix.getTransform()); - - if (mLogger != null && matrix.hasPerspective()) { - mLogger.warning("android.graphics.Canvas#setMatrix(android.graphics.Matrix) only supports affine transformations in the Layout Editor."); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#concat(android.graphics.Matrix) - */ - @Override - public void concat(Matrix matrix) { - // get the current top graphics2D object. - Graphics2D g = getGraphics2d(); - - // get its current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform of the given matrix - AffineTransform matrixTx = matrix.getTransform(); - - // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics2D as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - - // -------------------- - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipPath(android.graphics.Path, android.graphics.Region.Op) - */ - @Override - public boolean clipPath(Path path, Op op) { - // TODO Auto-generated method stub - return super.clipPath(path, op); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipPath(android.graphics.Path) - */ - @Override - public boolean clipPath(Path path) { - // TODO Auto-generated method stub - return super.clipPath(path); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRegion(android.graphics.Region, android.graphics.Region.Op) - */ - @Override - public boolean clipRegion(Region region, Op op) { - // TODO Auto-generated method stub - return super.clipRegion(region, op); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRegion(android.graphics.Region) - */ - @Override - public boolean clipRegion(Region region) { - // TODO Auto-generated method stub - return super.clipRegion(region); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint) - */ - @Override - public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, - Paint paint) { - // TODO Auto-generated method stub - super.drawArc(oval, startAngle, sweepAngle, useCenter, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmapMesh(android.graphics.Bitmap, int, int, float[], int, int[], int, android.graphics.Paint) - */ - @Override - public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, - int vertOffset, int[] colors, int colorOffset, Paint paint) { - // TODO Auto-generated method stub - super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture, android.graphics.Rect) - */ - @Override - public void drawPicture(Picture picture, Rect dst) { - // TODO Auto-generated method stub - super.drawPicture(picture, dst); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture, android.graphics.RectF) - */ - @Override - public void drawPicture(Picture picture, RectF dst) { - // TODO Auto-generated method stub - super.drawPicture(picture, dst); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture) - */ - @Override - public void drawPicture(Picture picture) { - // TODO Auto-generated method stub - super.drawPicture(picture); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoint(float, float, android.graphics.Paint) - */ - @Override - public void drawPoint(float x, float y, Paint paint) { - // TODO Auto-generated method stub - super.drawPoint(x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoints(float[], int, int, android.graphics.Paint) - */ - @Override - public void drawPoints(float[] pts, int offset, int count, Paint paint) { - // TODO Auto-generated method stub - super.drawPoints(pts, offset, count, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoints(float[], android.graphics.Paint) - */ - @Override - public void drawPoints(float[] pts, Paint paint) { - // TODO Auto-generated method stub - super.drawPoints(pts, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPosText(char[], int, int, float[], android.graphics.Paint) - */ - @Override - public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { - // TODO Auto-generated method stub - super.drawPosText(text, index, count, pos, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPosText(java.lang.String, float[], android.graphics.Paint) - */ - @Override - public void drawPosText(String text, float[] pos, Paint paint) { - // TODO Auto-generated method stub - super.drawPosText(text, pos, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawTextOnPath(char[], int, int, android.graphics.Path, float, float, android.graphics.Paint) - */ - @Override - public void drawTextOnPath(char[] text, int index, int count, Path path, float offset, - float offset2, Paint paint) { - // TODO Auto-generated method stub - super.drawTextOnPath(text, index, count, path, offset, offset2, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawTextOnPath(java.lang.String, android.graphics.Path, float, float, android.graphics.Paint) - */ - @Override - public void drawTextOnPath(String text, Path path, float offset, float offset2, Paint paint) { - // TODO Auto-generated method stub - super.drawTextOnPath(text, path, offset, offset2, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawVertices(android.graphics.Canvas.VertexMode, int, float[], int, float[], int, int[], int, short[], int, int, android.graphics.Paint) - */ - @Override - public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, - float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, - int indexOffset, int indexCount, Paint paint) { - // TODO Auto-generated method stub - super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, - indices, indexOffset, indexCount, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getDrawFilter() - */ - @Override - public DrawFilter getDrawFilter() { - // TODO Auto-generated method stub - return super.getDrawFilter(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getGL() - */ - @Override - public GL getGL() { - // TODO Auto-generated method stub - return super.getGL(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getMatrix() - */ - @Override - public Matrix getMatrix() { - // TODO Auto-generated method stub - return super.getMatrix(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getMatrix(android.graphics.Matrix) - */ - @Override - public void getMatrix(Matrix ctm) { - // TODO Auto-generated method stub - super.getMatrix(ctm); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#isOpaque() - */ - @Override - public boolean isOpaque() { - // TODO Auto-generated method stub - return super.isOpaque(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayer(float, float, float, float, android.graphics.Paint, int) - */ - @Override - public int saveLayer(float left, float top, float right, float bottom, Paint paint, - int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayer(left, top, right, bottom, paint, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayer(android.graphics.RectF, android.graphics.Paint, int) - */ - @Override - public int saveLayer(RectF bounds, Paint paint, int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayer(bounds, paint, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayerAlpha(float, float, float, float, int, int) - */ - @Override - public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, - int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayerAlpha(left, top, right, bottom, alpha, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayerAlpha(android.graphics.RectF, int, int) - */ - @Override - public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayerAlpha(bounds, alpha, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setDrawFilter(android.graphics.DrawFilter) - */ - @Override - public void setDrawFilter(DrawFilter filter) { - // TODO Auto-generated method stub - super.setDrawFilter(filter); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setViewport(int, int) - */ - @Override - public void setViewport(int width, int height) { - // TODO Auto-generated method stub - super.setViewport(width, height); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#skew(float, float) - */ - @Override - public void skew(float sx, float sy) { - // TODO Auto-generated method stub - super.skew(sx, sy); - } - - - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java new file mode 100644 index 0000000..4decd1a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -0,0 +1,1334 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Bitmap.Config; +import android.graphics.Paint_Delegate.FontInfo; +import android.text.TextUtils; + +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.List; + + +/** + * Delegate implementing the native methods of android.graphics.Canvas + * + * Through the layoutlib_create tool, the original native methods of Canvas have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Canvas class. + * + * @see DelegateManager + * + */ +public final class Canvas_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Canvas_Delegate> sManager = + new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); + + // ---- delegate helper data ---- + + private final static boolean[] sBoolOut = new boolean[1]; + + // ---- delegate data ---- + private Bitmap_Delegate mBitmap; + private GcSnapshot mSnapshot; + + private DrawFilter_Delegate mDrawFilter = null; + + // ---- Public Helper methods ---- + + /** + * Returns the native delegate associated to a given {@link Canvas} object. + */ + public static Canvas_Delegate getDelegate(Canvas canvas) { + return sManager.getDelegate(canvas.mNativeCanvas); + } + + /** + * Returns the native delegate associated to a given an int referencing a {@link Canvas} object. + */ + public static Canvas_Delegate getDelegate(int native_canvas) { + return sManager.getDelegate(native_canvas); + } + + /** + * Returns the current {@link Graphics2D} used to draw. + */ + public GcSnapshot getSnapshot() { + return mSnapshot; + } + + /** + * Returns the {@link DrawFilter} delegate or null if none have been set. + * + * @return the delegate or null. + */ + public DrawFilter_Delegate getDrawFilter() { + return mDrawFilter; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isOpaque(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; + } + + @LayoutlibDelegate + /*package*/ static int getWidth(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.mBitmap.getImage().getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int getHeight(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.mBitmap.getImage().getHeight(); + } + + @LayoutlibDelegate + /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().translate(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void rotate(Canvas thisCanvas, float degrees) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); + } + + @LayoutlibDelegate + /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().scale(sx, sy); + } + + @LayoutlibDelegate + /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot g = canvasDelegate.getSnapshot(); + + // get its current matrix + AffineTransform currentTx = g.getTransform(); + // get the AffineTransform for the given skew. + float[] mtx = Matrix_Delegate.getSkew(kx, ky); + AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + g.setTransform(currentTx); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) { + return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) { + return clipRect(thisCanvas, (float) rect.left, (float) rect.top, + (float) rect.right, (float) rect.bottom); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right, + float bottom) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right, + int bottom) { + + return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom); + } + + @LayoutlibDelegate + /*package*/ static int save(Canvas thisCanvas) { + return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG); + } + + @LayoutlibDelegate + /*package*/ static int save(Canvas thisCanvas, int saveFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.save(saveFlags); + } + + @LayoutlibDelegate + /*package*/ static void restore(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.restore(); + } + + @LayoutlibDelegate + /*package*/ static int getSaveCount(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.getSnapshot().size(); + } + + @LayoutlibDelegate + /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.restoreTo(saveCount); + } + + @LayoutlibDelegate + /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count, + Paint paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void drawLines(Canvas thisCanvas, + final float[] pts, final int offset, final int count, + Paint paint) { + draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + for (int i = 0 ; i < count ; i += 4) { + graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], + (int)pts[i + offset + 2], (int)pts[i + offset + 3]); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void freeCaches() { + // nothing to be done here. + } + + @LayoutlibDelegate + /*package*/ static int initRaster(int nativeBitmapOrZero) { + if (nativeBitmapOrZero > 0) { + // get the Bitmap from the int + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); + + // create a new Canvas_Delegate with the given bitmap and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); + + return sManager.addNewDelegate(newDelegate); + } else { + // create a new Canvas_Delegate and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(); + + return sManager.addNewDelegate(newDelegate); + } + } + + @LayoutlibDelegate + /*package*/ static void native_setBitmap(int nativeCanvas, int bitmap) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + canvasDelegate.setBitmap(bitmapDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds, + int paint, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayer(int nativeCanvas, float l, + float t, float r, float b, + int paint, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(new RectF(l, t, r, b), + paintDelegate, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayerAlpha(int nativeCanvas, + RectF bounds, int alpha, + int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l, + float t, float r, float b, + int alpha, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); + } + + + @LayoutlibDelegate + /*package*/ static void native_concat(int nCanvas, int nMatrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot snapshot = canvasDelegate.getSnapshot(); + + // get its current matrix + AffineTransform currentTx = snapshot.getTransform(); + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrixDelegate.getAffineTransform(); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + snapshot.setTransform(currentTx); + } + + @LayoutlibDelegate + /*package*/ static void native_setMatrix(int nCanvas, int nMatrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot snapshot = canvasDelegate.getSnapshot(); + + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrixDelegate.getAffineTransform(); + + // give it to the graphics2D as a new matrix replacing all previous transform + snapshot.setTransform(matrixTx); + + if (matrixDelegate.hasPerspective()) { + assert false; + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, + "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " + + "supports affine transformations.", null, null /*data*/); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipRect(int nCanvas, + float left, float top, + float right, float bottom, + int regionOp) { + + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.clipRect(left, top, right, bottom, regionOp); + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipPath(int nativeCanvas, + int nativePath, + int regionOp) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return true; + } + + Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath); + if (pathDelegate == null) { + return true; + } + + return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp); + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipRegion(int nativeCanvas, + int nativeRegion, + int regionOp) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return true; + } + + Region_Delegate region = Region_Delegate.getDelegate(nativeRegion); + if (region == null) { + return true; + } + + return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetDrawFilter(int nativeCanvas, int nativeFilter) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter); + + if (canvasDelegate.mDrawFilter != null && + canvasDelegate.mDrawFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER, + canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_getClipBounds(int nativeCanvas, + Rect bounds) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return false; + } + + Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds(); + if (rect != null && rect.isEmpty() == false) { + bounds.left = rect.x; + bounds.top = rect.y; + bounds.right = rect.x + rect.width; + bounds.bottom = rect.y + rect.height; + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_getCTM(int canvas, int matrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + AffineTransform transform = canvasDelegate.getSnapshot().getTransform(); + matrixDelegate.set(Matrix_Delegate.makeValues(transform)); + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + RectF rect, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + int path, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + float left, float top, + float right, float bottom, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_drawRGB(int nativeCanvas, int r, int g, int b) { + native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF), + PorterDuff.Mode.SRC_OVER.nativeInt); + + } + + @LayoutlibDelegate + /*package*/ static void native_drawARGB(int nativeCanvas, int a, int r, int g, int b) { + native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF), + PorterDuff.Mode.SRC_OVER.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static void native_drawColor(int nativeCanvas, int color) { + native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + final int w = canvasDelegate.mBitmap.getImage().getWidth(); + final int h = canvasDelegate.mBitmap.getImage().getHeight(); + draw(nativeCanvas, new GcSnapshot.Drawable() { + + public void draw(Graphics2D graphics, Paint_Delegate paint) { + // reset its transform just in case + graphics.setTransform(new AffineTransform()); + + // set the color + graphics.setColor(new Color(color, true /*alpha*/)); + + Composite composite = PorterDuffXfermode_Delegate.getComposite( + PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF); + if (composite != null) { + graphics.setComposite(composite); + } + + graphics.fillRect(0, 0, w, h); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPaint(int nativeCanvas, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPaint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawLine(int nativeCanvas, + final float startX, final float startY, final float stopX, final float stopY, + int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRect(int nativeCanvas, RectF rect, + int paint) { + native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRect(int nativeCanvas, + final float left, final float top, final float right, final float bottom, int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) { + if (oval.right > oval.left && oval.bottom > oval.top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillOval((int)oval.left, (int)oval.top, + (int)oval.width(), (int)oval.height()); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawOval((int)oval.left, (int)oval.top, + (int)oval.width(), (int)oval.height()); + } + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawCircle(int nativeCanvas, + float cx, float cy, float radius, int paint) { + native_drawOval(nativeCanvas, + new RectF(cx - radius, cy - radius, radius*2, radius*2), + paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawArc(int nativeCanvas, + RectF oval, float startAngle, float sweep, boolean useCenter, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawArc is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRoundRect(int nativeCanvas, + final RectF rect, final float rx, final float ry, int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRoundRect( + (int)rect.left, (int)rect.top, + (int)rect.width(), (int)rect.height(), + (int)rx, (int)ry); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRoundRect( + (int)rect.left, (int)rect.top, + (int)rect.width(), (int)rect.height(), + (int)rx, (int)ry); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPath(int nativeCanvas, int path, int paint) { + final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); + if (pathDelegate == null) { + return; + } + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + Shape shape = pathDelegate.getJavaShape(); + int style = paint.getStyle(); + + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(shape); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(shape); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap, + float left, float top, + int nativePaintOrZero, + int canvasDensity, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + float right = left + image.getWidth(); + float bottom = top + image.getHeight(); + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)left, (int)top, (int)right, (int)bottom); + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap, + Rect src, RectF dst, + int nativePaintOrZero, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + + if (src == null) { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); + } else { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + src.left, src.top, src.width(), src.height(), + (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(int nativeCanvas, int bitmap, + Rect src, Rect dst, + int nativePaintOrZero, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + + if (src == null) { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + dst.left, dst.top, dst.right, dst.bottom); + } else { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + src.left, src.top, src.width(), src.height(), + dst.left, dst.top, dst.right, dst.bottom); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(int nativeCanvas, int[] colors, + int offset, int stride, final float x, + final float y, int width, int height, + boolean hasAlpha, + int nativePaintOrZero) { + + // create a temp BufferedImage containing the content. + final BufferedImage image = new BufferedImage(width, height, + hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, width, height, colors, offset, stride); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + graphics.drawImage(image, (int) x, (int) y, null); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawBitmapMatrix(int nCanvas, int nBitmap, + int nMatrix, int nPaint) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int, which can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nBitmap); + if (bitmapDelegate == null) { + return; + } + + final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + final AffineTransform mtx = matrixDelegate.getAffineTransform(); + + canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, mtx, null); + } + }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawBitmapMesh(int nCanvas, int nBitmap, + int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, + int colorOffset, int nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawVertices(int nCanvas, int mode, int n, + float[] verts, int vertOffset, + float[] texs, int texOffset, + int[] colors, int colorOffset, + short[] indices, int indexOffset, + int indexCount, int nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawVertices is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawText(int nativeCanvas, + final char[] text, final int index, final int count, + final float startX, final float startY, int flags, int paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + // WARNING: the logic in this method is similar to Paint_Delegate.measureText. + // Any change to this method should be reflected in Paint.measureText + // Paint.TextAlign indicates how the text is positioned relative to X. + // LEFT is the default and there's nothing to do. + float x = startX; + float y = startY; + if (paint.getTextAlign() != Paint.Align.LEFT.nativeInt) { + float m = paint.measureText(text, index, count); + if (paint.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paint.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + List<FontInfo> fonts = paint.getFonts(); + + if (fonts.size() > 0) { + FontInfo mainFont = fonts.get(0); + int i = index; + int lastIndex = index + count; + while (i < lastIndex) { + // always start with the main font. + int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); + if (upTo == -1) { + // draw all the rest and exit. + graphics.setFont(mainFont.mFont); + graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y); + return; + } else if (upTo > 0) { + // draw what's possible + graphics.setFont(mainFont.mFont); + graphics.drawChars(text, i, upTo - i, (int)x, (int)y); + + // compute the width that was drawn to increase x + x += mainFont.mMetrics.charsWidth(text, i, upTo - i); + + // move index to the first non displayed char. + i = upTo; + + // don't call continue at this point. Since it is certain the main font + // cannot display the font a index upTo (now ==i), we move on to the + // fallback fonts directly. + } + + // no char supported, attempt to read the next char(s) with the + // fallback font. In this case we only test the first character + // and then go back to test with the main font. + // Special test for 2-char characters. + boolean foundFont = false; + for (int f = 1 ; f < fonts.size() ; f++) { + FontInfo fontInfo = fonts.get(f); + + // need to check that the font can display the character. We test + // differently if the char is a high surrogate. + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); + if (upTo == -1) { + // draw that char + graphics.setFont(fontInfo.mFont); + graphics.drawChars(text, i, charCount, (int)x, (int)y); + + // update x + x += fontInfo.mMetrics.charsWidth(text, i, charCount); + + // update the index in the text, and move on + i += charCount; + foundFont = true; + break; + + } + } + + // in case no font can display the char, display it with the main font. + // (it'll put a square probably) + if (foundFont == false) { + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + + graphics.setFont(mainFont.mFont); + graphics.drawChars(text, i, charCount, (int)x, (int)y); + + // measure it to advance x + x += mainFont.mMetrics.charsWidth(text, i, charCount); + + // and move to the next chars. + i += charCount; + } + } + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawText(int nativeCanvas, String text, + int start, int end, float x, + float y, int flags, int paint) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextRun(int nativeCanvas, String text, + int start, int end, int contextStart, int contextEnd, + float x, float y, int flags, int paint) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + native_drawText(nativeCanvas, buffer, start, end, x, y, flags, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextRun(int nativeCanvas, char[] text, + int start, int count, int contextStart, int contextCount, + float x, float y, int flags, int paint) { + native_drawText(nativeCanvas, text, start, count, x, y, flags, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPosText(int nativeCanvas, + char[] text, int index, + int count, float[] pos, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPosText is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPosText(int nativeCanvas, + String text, float[] pos, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPosText is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextOnPath(int nativeCanvas, + char[] text, int index, + int count, int path, + float hOffset, + float vOffset, int bidiFlags, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextOnPath(int nativeCanvas, + String text, int path, + float hOffset, + float vOffset, + int flags, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPicture(int nativeCanvas, + int nativePicture) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPicture is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nativeCanvas) { + // get the delegate from the native int so that it can be disposed. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.dispose(); + + // remove it from the manager. + sManager.removeJavaReferenceFor(nativeCanvas); + } + + // ---- Private delegate/helper methods ---- + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, int, int)}. + */ + private static void draw(int nCanvas, int nPaint, boolean compositeOnly, boolean forceSrcMode, + GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint which can be null if nPaint is 0; + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); + } + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided + * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, int, int)}. + */ + private static void draw(int nCanvas, GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mSnapshot.draw(drawable); + } + + private Canvas_Delegate(Bitmap_Delegate bitmap) { + mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + } + + private Canvas_Delegate() { + mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); + } + + /** + * Disposes of the {@link Graphics2D} stack. + */ + private void dispose() { + mSnapshot.dispose(); + } + + private int save(int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.save(saveFlags); + + // return the old save count + return count; + } + + private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { + Paint_Delegate paint = new Paint_Delegate(); + paint.setAlpha(alpha); + return saveLayer(rect, paint, saveFlags); + } + + private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); + + // return the old save count + return count; + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + private void restoreTo(int saveCount) { + mSnapshot = mSnapshot.restoreTo(saveCount); + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + private void restore() { + mSnapshot = mSnapshot.restore(); + } + + private boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return mSnapshot.clipRect(left, top, right, bottom, regionOp); + } + + private void setBitmap(Bitmap_Delegate bitmap) { + mBitmap = bitmap; + assert mSnapshot.size() == 1; + mSnapshot.setBitmap(mBitmap); + } + + private static void drawBitmap( + int nativeCanvas, + Bitmap_Delegate bitmap, + int nativePaintOrZero, + final int sleft, final int stop, final int sright, final int sbottom, + final int dleft, final int dtop, final int dright, final int dbottom) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint, which could be null if the int is 0 + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, dleft, dtop, dright, dbottom, + sleft, stop, sright, sbottom, null); + } + }); + } + + + /** + * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. + * The image returns, through a 1-size boolean array, whether the drawing code should + * use a SRC composite no matter what the paint says. + * + * @param bitmap the bitmap + * @param paint the paint that will be used to draw + * @param forceSrcMode whether the composite will have to be SRC + * @return the image to draw + */ + private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, + boolean[] forceSrcMode) { + BufferedImage image = bitmap.getImage(); + forceSrcMode[0] = false; + + // if the bitmap config is alpha_8, then we erase all color value from it + // before drawing it. + if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { + fixAlpha8Bitmap(image); + } else if (bitmap.hasAlpha() == false) { + // hasAlpha is merely a rendering hint. There can in fact be alpha values + // in the bitmap but it should be ignored at drawing time. + // There is two ways to do this: + // - override the composite to be SRC. This can only be used if the composite + // was going to be SRC or SRC_OVER in the first place + // - Create a different bitmap to draw in which all the alpha channel values is set + // to 0xFF. + if (paint != null) { + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) { + PorterDuff.Mode mode = + ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode(); + + forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || + mode == PorterDuff.Mode.SRC; + } + } + + // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB + if (forceSrcMode[0] == false) { + image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); + } + } + + return image; + } + + private static void fixAlpha8Bitmap(final BufferedImage image) { + 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); + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java new file mode 100644 index 0000000..e5a7ab6 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.ColorFilter + * + * Through the layoutlib_create tool, the original native methods of ColorFilter have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ColorFilter class. + * + * This also serve as a base class for all ColorFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class ColorFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<ColorFilter_Delegate> sManager = + new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static ColorFilter_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance, int nativeColorFilter) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java new file mode 100644 index 0000000..2de344b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.ColorMatrixColorFilter + * + * Through the layoutlib_create tool, the original native methods of ColorMatrixColorFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ColorMatrixColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "ColorMatrix Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeColorMatrixFilter(float[] array) { + ColorMatrixColorFilter_Delegate newDelegate = new ColorMatrixColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nColorMatrixFilter(int nativeFilter, float[] array) { + // pass + return 0; + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java new file mode 100644 index 0000000..7c04a87 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.ComposePathEffect + * + * Through the layoutlib_create tool, the original native methods of ComposePathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ComposePathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class ComposePathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Compose Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int outerpe, int innerpe) { + ComposePathEffect_Delegate newDelegate = new ComposePathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java deleted file mode 100644 index 863d64a..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import java.awt.Paint; - -/** A subclass of shader that returns the composition of two other shaders, combined by - an {@link android.graphics.Xfermode} subclass. -*/ -public class ComposeShader extends Shader { - /** Create a new compose shader, given shaders A, B, and a combining mode. - When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". - @param shaderA The colors from this shader are seen as the "dst" by the mode - @param shaderB The colors from this shader are seen as the "src" by the mode - @param mode The mode that combines the colors from the two shaders. If mode - is null, then SRC_OVER is assumed. - */ - public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) { - // FIXME Implement shader - } - - /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. - When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". - @param shaderA The colors from this shader are seen as the "dst" by the mode - @param shaderB The colors from this shader are seen as the "src" by the mode - @param mode The PorterDuff mode that combines the colors from the two shaders. - */ - public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) { - // FIXME Implement shader - } - - @Override - Paint getJavaPaint() { - return null; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java new file mode 100644 index 0000000..f6e1d00 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -0,0 +1,97 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Paint; + +/** + * Delegate implementing the native methods of android.graphics.ComposeShader + * + * Through the layoutlib_create tool, the original native methods of ComposeShader have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ComposeShader class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class ComposeShader_Delegate extends Shader_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Paint getJavaPaint() { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Compose Shaders are not supported in Layout Preview mode."; + } + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(int native_shaderA, int native_shaderB, + int native_mode) { + // FIXME not supported yet. + ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(int native_shaderA, int native_shaderB, + int porterDuffMode) { + // FIXME not supported yet. + ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(int native_shader, int native_skiaShaderA, + int native_skiaShaderB, int native_mode) { + // pass, not needed. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(int native_shader, int native_skiaShaderA, + int native_skiaShaderB, int porterDuffMode) { + // pass, not needed. + return 0; + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java new file mode 100644 index 0000000..b0f8168 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.CornerPathEffect + * + * Through the layoutlib_create tool, the original native methods of CornerPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original CornerPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class CornerPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Corner Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float radius) { + CornerPathEffect_Delegate newDelegate = new CornerPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java deleted file mode 100644 index 46d4c70..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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; - -public class DashPathEffect extends PathEffect { - - private final float[] mIntervals; - private final float mPhase; - - /** - * The intervals array must contain an even number of entries (>=2), with - * the even indices specifying the "on" intervals, and the odd indices - * specifying the "off" intervals. phase is an offset into the intervals - * array (mod the sum of all of the intervals). The intervals array - * controls the length of the dashes. The paint's strokeWidth controls the - * thickness of the dashes. - * Note: this patheffect only affects drawing with the paint's style is set - * to STROKE or STROKE_AND_FILL. It is ignored if the drawing is done with - * style == FILL. - * @param intervals array of ON and OFF distances - * @param phase offset into the intervals array - */ - public DashPathEffect(float intervals[], float phase) { - if (intervals.length < 2) { - throw new ArrayIndexOutOfBoundsException(); - } - - mIntervals = intervals; - mPhase = phase; - } - - public float[] getIntervals() { - return mIntervals; - } - - public float getPhase() { - return mPhase; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java new file mode 100644 index 0000000..d97c2ec --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java @@ -0,0 +1,89 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.BasicStroke; +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.DashPathEffect + * + * Through the layoutlib_create tool, the original native methods of DashPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original DashPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public final class DashPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + private final float[] mIntervals; + private final float mPhase; + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + return new BasicStroke( + paint.getStrokeWidth(), + paint.getJavaCap(), + paint.getJavaJoin(), + paint.getJavaStrokeMiter(), + mIntervals, + mPhase); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float intervals[], float phase) { + DashPathEffect_Delegate newDelegate = new DashPathEffect_Delegate(intervals, phase); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private DashPathEffect_Delegate(float intervals[], float phase) { + mIntervals = new float[intervals.length]; + System.arraycopy(intervals, 0, mIntervals, 0, intervals.length); + mPhase = phase; + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java new file mode 100644 index 0000000..ec4a810 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.DiscretePathEffect + * + * Through the layoutlib_create tool, the original native methods of DiscretePathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original DiscretePathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class DiscretePathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Discrete Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float length, float deviation) { + DiscretePathEffect_Delegate newDelegate = new DiscretePathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java new file mode 100644 index 0000000..870c46b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.DrawFilter + * + * Through the layoutlib_create tool, the original native methods of DrawFilter have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original DrawFilter class. + * + * This also serve as a base class for all DrawFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class DrawFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<DrawFilter_Delegate> sManager = + new DelegateManager<DrawFilter_Delegate>(DrawFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static DrawFilter_Delegate getDelegate(int nativeDrawFilter) { + return sManager.getDelegate(nativeDrawFilter); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int nativeDrawFilter) { + sManager.removeJavaReferenceFor(nativeDrawFilter); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java new file mode 100644 index 0000000..ebc1c1d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java @@ -0,0 +1,65 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.EmbossMaskFilter + * + * Through the layoutlib_create tool, the original native methods of EmbossMaskFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original EmbossMaskFilter class. + * + * Because this extends {@link MaskFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link MaskFilter_Delegate}. + * + * @see MaskFilter_Delegate + * + */ +public class EmbossMaskFilter_Delegate extends MaskFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Emboss Mask Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(float[] direction, float ambient, + float specular, float blurRadius) { + EmbossMaskFilter_Delegate newDelegate = new EmbossMaskFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java index 8c5a2a4..38c092d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java +++ b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java @@ -16,23 +16,28 @@ package android.graphics; +import android.graphics.Shader.TileMode; /** - * 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 + * Base class for true Gradient shader delegate. */ -public abstract class GradientShader extends Shader { +public abstract class Gradient_Delegate extends Shader_Delegate { protected final int[] mColors; protected final float[] mPositions; + @Override + public boolean isSupported() { + // all gradient shaders are supported. + return true; + } + + @Override + public String getSupportMessage() { + // all gradient shaders are supported, no need for a gradient support + return null; + } + /** * Creates the base shader and do some basic test on the parameters. * @@ -41,7 +46,7 @@ public abstract class GradientShader extends Shader { * 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[]) { + protected Gradient_Delegate(int colors[], float positions[]) { if (colors.length < 2) { throw new IllegalArgumentException("needs >= 2 number of colors"); } @@ -90,7 +95,7 @@ public abstract class GradientShader extends Shader { * Pre-computes the colors for the gradient. This must be called once before any call * to {@link #getGradientColor(float)} */ - protected synchronized void precomputeGradientColors() { + protected 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 @@ -126,20 +131,23 @@ public abstract class GradientShader extends Shader { 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); + // remove the integer part to stay in the [0,1] range. + // we also need to invert the value from [-1,0] to [0, 1] + pos = pos - (float)Math.floor(pos); break; case MIRROR: + // this is the same as the positive side, just make the value positive + // first. + pos = Math.abs(pos); + // 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); + int intPart = (int)Math.floor(pos); pos = pos - intPart; - // 0 -> -1 : mirrored order - // -1 -> -2: normal order + // 0 -> 1 : normal order + // 1 -> 2: mirrored // etc.. - // this means if the intpart is even we invert - if ((intPart % 2) == 0) { + // this means if the intpart is odd we invert + if ((intPart % 2) == 1) { pos = 1.f - pos; } break; @@ -199,7 +207,5 @@ public abstract class GradientShader extends Shader { 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/LayerRasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java new file mode 100644 index 0000000..51e0576 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java @@ -0,0 +1,69 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.LayerRasterizer + * + * Through the layoutlib_create tool, the original native methods of LayerRasterizer have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original LayerRasterizer class. + * + * Because this extends {@link Rasterizer_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link Rasterizer_Delegate}. + * + * @see Rasterizer_Delegate + * + */ +public class LayerRasterizer_Delegate extends Rasterizer_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Layer Rasterizers are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor() { + LayerRasterizer_Delegate newDelegate = new LayerRasterizer_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeAddLayer(int native_layer, int native_paint, float dx, float dy) { + + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java new file mode 100644 index 0000000..0ee883d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.LightingColorFilter + * + * Through the layoutlib_create tool, the original native methods of LightingColorFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original LightingColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class LightingColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Lighting Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_CreateLightingFilter(int mul, int add) { + LightingColorFilter_Delegate newDelegate = new LightingColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nCreateLightingFilter(int nativeFilter, int mul, int add) { + // pass + return 0; + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java deleted file mode 100644 index 10c4a5e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2008 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; - -public class LinearGradient extends GradientShader { - - private java.awt.Paint mJavaPaint; - - /** - * Create a shader that draws a linear gradient along a line. - * - * @param x0 The x-coordinate for the start of the gradient line - * @param y0 The y-coordinate for the start of the gradient line - * @param x1 The x-coordinate for the end of the gradient line - * @param y1 The y-coordinate for the end of the gradient line - * @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. - * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], - TileMode tile) { - super(colors, positions); - mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile); - } - - /** - * Create a shader that draws a linear gradient along a line. - * - * @param x0 The x-coordinate for the start of the gradient line - * @param y0 The y-coordinate for the start of the gradient line - * @param x1 The x-coordinate for the end of the gradient line - * @param y1 The y-coordinate for the end of the gradient line - * @param color0 The color at the start of the gradient line. - * @param color1 The color at the end of the gradient line. - * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, - TileMode tile) { - this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile); - } - - // ---------- Custom Methods - - @Override - java.awt.Paint getJavaPaint() { - return mJavaPaint; - } - - /** - * 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; - - 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; - } - - 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); - } - - private class LinearGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - 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? - } - - 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]; - - if (mDx == 0) { // vertical gradient - // compute first column and copy to all other columns - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - int color = getColor(iy + y, mY0, mDy); - for (int ix = 0 ; ix < w ; ix++) { - data[index++] = color; - } - } - } else if (mDy == 0) { // horizontal - // compute first line in a tmp array and copy to all lines - int[] line = new int[w]; - for (int ix = 0 ; ix < w ; ix++) { - line[ix] = getColor(ix + x, mX0, mDx); - } - - for (int iy = 0 ; iy < h ; iy++) { - System.arraycopy(line, 0, data, iy*w, line.length); - } - } else { - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - for (int ix = 0 ; ix < w ; ix++) { - data[index++] = getColor(ix + x, iy + y); - } - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - } - - /** Returns a color for the easy vertical/horizontal mode */ - private int getColor(float absPos, float refPos, float refSize) { - float pos = (absPos - refPos) / refSize; - - return getGradientColor(pos); - } - - /** - * 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); - - return getGradientColor(pos); - } - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java new file mode 100644 index 0000000..a2ba758 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -0,0 +1,236 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.LinearGradient + * + * Through the layoutlib_create tool, the original native methods of LinearGradient have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original LinearGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public final class LinearGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(LinearGradient thisGradient, + float x0, float y0, float x1, float y1, + int colors[], float positions[], int tileMode) { + LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(x0, y0, x1, y1, + colors, positions, Shader_Delegate.getTileMode(tileMode)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(LinearGradient thisGradient, + float x0, float y0, float x1, float y1, + int color0, int color1, int tileMode) { + return nativeCreate1(thisGradient, + x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, + tileMode); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(LinearGradient thisGradient, + int native_shader, float x0, float y0, float x1, float y1, + int colors[], float positions[], int tileMode) { + // nothing to be done here. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(LinearGradient thisGradient, + int native_shader, float x0, float y0, float x1, float y1, + int color0, int color1, int tileMode) { + // nothing to be done here. + return 0; + } + + // ---- Private delegate/helper methods ---- + + /** + * Create a shader that draws a linear gradient along a line. + * + * @param x0 The x-coordinate for the start of the gradient line + * @param y0 The y-coordinate for the start of the gradient line + * @param x1 The x-coordinate for the end of the gradient line + * @param y1 The y-coordinate for the end of the gradient line + * @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. + * @param tile The Shader tiling mode + */ + private LinearGradient_Delegate(float x0, float y0, float x1, float y1, + int colors[], float positions[], TileMode tile) { + super(colors, positions); + mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile); + } + + // ---- Custom Java Paint ---- + /** + * 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 class LinearGradientPaint extends GradientPaint { + + private final float mX0; + private final float mY0; + private final float mDx; + private final float mDy; + private final float mDSize2; + + 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; + } + + 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(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in LinearGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in LinearGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new LinearGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class LinearGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + private LinearGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + 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]; + + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix. + pt1[0] = pt2[0]; + pt1[1] = pt2[1]; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + data[index++] = getColor(pt2[0], pt2[1]); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + } + + /** + * Returns a color for an arbitrary point. + */ + private int getColor(float x, float y) { + 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/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java new file mode 100644 index 0000000..c2f27e4 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.MaskFilter + * + * Through the layoutlib_create tool, the original native methods of MaskFilter have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original MaskFilter class. + * + * This also serve as a base class for all MaskFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class MaskFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<MaskFilter_Delegate> sManager = + new DelegateManager<MaskFilter_Delegate>(MaskFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static MaskFilter_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_filter) { + sManager.removeJavaReferenceFor(native_filter); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java deleted file mode 100644 index 9e30671..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java +++ /dev/null @@ -1,1032 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import java.awt.geom.AffineTransform; -import java.awt.geom.NoninvertibleTransformException; - - -/** - * A matrix implementation overridden by the LayoutLib bridge. - */ -public class Matrix extends _Original_Matrix { - - float mValues[] = new float[9]; - - /** - * Create an identity matrix - */ - public Matrix() { - reset(); - } - - /** - * Create a matrix that is a (deep) copy of src - * @param src The matrix to copy into this matrix - */ - public Matrix(Matrix src) { - set(src); - } - - /** - * Creates a Matrix object from the float array. The array becomes the internal storage - * of the object. - * @param data - */ - private Matrix(float[] data) { - assert data.length != 9; - mValues = data; - } - - //---------- Custom Methods - - /** - * Adds the given transformation to the current Matrix - * <p/>This in effect does this = this*matrix - * @param matrix - */ - private void addTransform(float[] matrix) { - float[] tmp = new float[9]; - - // first row - tmp[0] = matrix[0] * mValues[0] + matrix[1] * mValues[3] + matrix[2] * mValues[6]; - tmp[1] = matrix[0] * mValues[1] + matrix[1] * mValues[4] + matrix[2] * mValues[7]; - tmp[2] = matrix[0] * mValues[2] + matrix[1] * mValues[5] + matrix[2] * mValues[8]; - - // 2nd row - tmp[3] = matrix[3] * mValues[0] + matrix[4] * mValues[3] + matrix[5] * mValues[6]; - tmp[4] = matrix[3] * mValues[1] + matrix[4] * mValues[4] + matrix[5] * mValues[7]; - tmp[5] = matrix[3] * mValues[2] + matrix[4] * mValues[5] + matrix[5] * mValues[8]; - - // 3rd row - tmp[6] = matrix[6] * mValues[0] + matrix[7] * mValues[3] + matrix[8] * mValues[6]; - tmp[7] = matrix[6] * mValues[1] + matrix[7] * mValues[4] + matrix[8] * mValues[7]; - tmp[8] = matrix[6] * mValues[2] + matrix[7] * mValues[5] + matrix[8] * mValues[8]; - - // copy the result over to mValues - mValues = tmp; - } - - public AffineTransform getTransform() { - // the AffineTransform constructor takes the value in a different order - // for a matrix [ 0 1 2 ] - // [ 3 4 5 ] - // the order is 0, 3, 1, 4, 2, 5... - return new AffineTransform(mValues[0], mValues[3], mValues[1], - mValues[4], mValues[2], mValues[5]); - } - - public boolean hasPerspective() { - return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1); - } - - //---------- - - /** - * Returns true if the matrix is identity. - * This maybe faster than testing if (getType() == 0) - */ - @Override - public boolean isIdentity() { - for (int i = 0, k = 0; i < 3; i++) { - for (int j = 0; j < 3; j++, k++) { - if (mValues[k] != ((i==j) ? 1 : 0)) { - return false; - } - } - } - - return true; - } - - /** - * Returns true if will map a rectangle to another rectangle. This can be - * true if the matrix is identity, scale-only, or rotates a multiple of 90 - * degrees. - */ - @Override - public boolean rectStaysRect() { - return (computeTypeMask() & kRectStaysRect_Mask) != 0; - } - - /** - * (deep) copy the src matrix into this matrix. If src is null, reset this - * matrix to the identity matrix. - */ - public void set(Matrix src) { - if (src == null) { - reset(); - } else { - System.arraycopy(src.mValues, 0, mValues, 0, mValues.length); - } - } - - @Override - public void set(_Original_Matrix src) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** Returns true if obj is a Matrix and its values equal our values. - */ - @Override - public boolean equals(Object obj) { - if (obj != null && obj instanceof Matrix) { - Matrix matrix = (Matrix)obj; - for (int i = 0 ; i < 9 ; i++) { - if (mValues[i] != matrix.mValues[i]) { - return false; - } - } - - return true; - } - - return false; - } - - /** Set the matrix to identity */ - @Override - public void reset() { - for (int i = 0, k = 0; i < 3; i++) { - for (int j = 0; j < 3; j++, k++) { - mValues[k] = ((i==j) ? 1 : 0); - } - } - } - - /** Set the matrix to translate by (dx, dy). */ - @Override - public void setTranslate(float dx, float dy) { - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = dx; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = dy; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to scale by sx and sy, with a pivot point at (px, py). - * The pivot point is the coordinate that should remain unchanged by the - * specified transformation. - */ - @Override - public void setScale(float sx, float sy, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** Set the matrix to scale by sx and sy. */ - @Override - public void setScale(float sx, float sy) { - mValues[0] = sx; - mValues[1] = 0; - mValues[2] = 0; - mValues[3] = 0; - mValues[4] = sy; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to rotate by the specified number of degrees, with a pivot - * point at (px, py). The pivot point is the coordinate that should remain - * unchanged by the specified transformation. - */ - @Override - public void setRotate(float degrees, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** - * Set the matrix to rotate about (0,0) by the specified number of degrees. - */ - @Override - public void setRotate(float degrees) { - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - - mValues[0] = cos; - mValues[1] = -sin; - mValues[2] = 0; - mValues[3] = sin; - mValues[4] = cos; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to rotate by the specified sine and cosine values, with a - * pivot point at (px, py). The pivot point is the coordinate that should - * remain unchanged by the specified transformation. - */ - @Override - public void setSinCos(float sinValue, float cosValue, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - addTransform(new float[] { cosValue, -sinValue, 0, sinValue, cosValue, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** Set the matrix to rotate by the specified sine and cosine values. */ - @Override - public void setSinCos(float sinValue, float cosValue) { - mValues[0] = cosValue; - mValues[1] = -sinValue; - mValues[2] = 0; - mValues[3] = sinValue; - mValues[4] = cosValue; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to skew by sx and sy, with a pivot point at (px, py). - * The pivot point is the coordinate that should remain unchanged by the - * specified transformation. - */ - @Override - public void setSkew(float kx, float ky, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** Set the matrix to skew by sx and sy. */ - @Override - public void setSkew(float kx, float ky) { - mValues[0] = 1; - mValues[1] = kx; - mValues[2] = -0; - mValues[3] = ky; - mValues[4] = 1; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to the concatenation of the two specified matrices, - * returning true if the the result can be represented. Either of the two - * matrices may also be the target matrix. this = a * b - */ - public boolean setConcat(Matrix a, Matrix b) { - if (a == this) { - preConcat(b); - } else if (b == this) { - postConcat(b); - } else { - Matrix tmp = new Matrix(b); - tmp.addTransform(a.mValues); - set(tmp); - } - - return true; - } - - @Override - public boolean setConcat(_Original_Matrix a, _Original_Matrix b) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Preconcats the matrix with the specified translation. - * M' = M * T(dx, dy) - */ - @Override - public boolean preTranslate(float dx, float dy) { - // create a matrix that will be multiply by this - Matrix m = new Matrix(new float[] { 1, 0, dx, 0, 1, dy, 0, 0, 1 }); - m.addTransform(this.mValues); - - System.arraycopy(m.mValues, 0, mValues, 0, 9); - return true; - } - - /** - * Preconcats the matrix with the specified scale. - * M' = M * S(sx, sy, px, py) - */ - @Override - public boolean preScale(float sx, float sy, float px, float py) { - Matrix m = new Matrix(); - m.setScale(sx, sy, px, py); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified scale. - * M' = M * S(sx, sy) - */ - @Override - public boolean preScale(float sx, float sy) { - Matrix m = new Matrix(); - m.setScale(sx, sy); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified rotation. - * M' = M * R(degrees, px, py) - */ - @Override - public boolean preRotate(float degrees, float px, float py) { - Matrix m = new Matrix(); - m.setRotate(degrees, px, py); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified rotation. - * M' = M * R(degrees) - */ - @Override - public boolean preRotate(float degrees) { - Matrix m = new Matrix(); - m.setRotate(degrees); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified skew. - * M' = M * K(kx, ky, px, py) - */ - @Override - public boolean preSkew(float kx, float ky, float px, float py) { - Matrix m = new Matrix(); - m.setSkew(kx, ky, px, py); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified skew. - * M' = M * K(kx, ky) - */ - @Override - public boolean preSkew(float kx, float ky) { - Matrix m = new Matrix(); - m.setSkew(kx, ky); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified matrix. - * M' = M * other - */ - public boolean preConcat(Matrix other) { - Matrix m = new Matrix(other); - other.addTransform(mValues); - set(m); - - return true; - } - - @Override - public boolean preConcat(_Original_Matrix other) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Postconcats the matrix with the specified translation. - * M' = T(dx, dy) * M - */ - @Override - public boolean postTranslate(float dx, float dy) { - addTransform(new float[] { 1, 0, dx, 0, 1, dy, 0, 0, 1 }); - return true; - } - - /** - * Postconcats the matrix with the specified scale. - * M' = S(sx, sy, px, py) * M - */ - @Override - public boolean postScale(float sx, float sy, float px, float py) { - // TODO: do it in one pass - // translate so that the pivot is in 0,0 - addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 }); - // scale - addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified scale. - * M' = S(sx, sy) * M - */ - @Override - public boolean postScale(float sx, float sy) { - addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }); - return true; - } - - /** - * Postconcats the matrix with the specified rotation. - * M' = R(degrees, px, py) * M - */ - @Override - public boolean postRotate(float degrees, float px, float py) { - // TODO: do it in one pass - // translate so that the pivot is in 0,0 - addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 }); - // scale - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified rotation. - * M' = R(degrees) * M - */ - @Override - public boolean postRotate(float degrees) { - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified skew. - * M' = K(kx, ky, px, py) * M - */ - @Override - public boolean postSkew(float kx, float ky, float px, float py) { - // TODO: do it in one pass - // translate so that the pivot is in 0,0 - addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 }); - // scale - addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified skew. - * M' = K(kx, ky) * M - */ - @Override - public boolean postSkew(float kx, float ky) { - addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified matrix. - * M' = other * M - */ - public boolean postConcat(Matrix other) { - addTransform(other.mValues); - - return true; - } - - @Override - public boolean postConcat(_Original_Matrix other) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** Controlls how the src rect should align into the dst rect for - setRectToRect(). - */ - public enum ScaleToFit { - /** - * Scale in X and Y independently, so that src matches dst exactly. - * This may change the aspect ratio of the src. - */ - FILL (0), - /** - * Compute a scale that will maintain the original src aspect ratio, - * but will also ensure that src fits entirely inside dst. At least one - * axis (X or Y) will fit exactly. START aligns the result to the - * left and top edges of dst. - */ - START (1), - /** - * Compute a scale that will maintain the original src aspect ratio, - * but will also ensure that src fits entirely inside dst. At least one - * axis (X or Y) will fit exactly. The result is centered inside dst. - */ - CENTER (2), - /** - * Compute a scale that will maintain the original src aspect ratio, - * but will also ensure that src fits entirely inside dst. At least one - * axis (X or Y) will fit exactly. END aligns the result to the - * right and bottom edges of dst. - */ - END (3); - - // the native values must match those in SkMatrix.h - ScaleToFit(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * Set the matrix to the scale and translate values that map the source - * rectangle to the destination rectangle, returning true if the result - * can be represented. - * - * @param src the source rectangle to map from. - * @param dst the destination rectangle to map to. - * @param stf the ScaleToFit option - * @return true if the matrix can be represented by the rectangle mapping. - */ - public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) { - if (dst == null || src == null) { - throw new NullPointerException(); - } - - if (src.isEmpty()) { - reset(); - return false; - } - - if (dst.isEmpty()) { - mValues[0] = mValues[1] = mValues[2] = mValues[3] = mValues[4] = mValues[5] - = mValues[6] = mValues[7] = 0; - mValues[8] = 1; - } else { - float tx, sx = dst.width() / src.width(); - float ty, sy = dst.height() / src.height(); - boolean xLarger = false; - - if (stf != ScaleToFit.FILL) { - if (sx > sy) { - xLarger = true; - sx = sy; - } else { - sy = sx; - } - } - - tx = dst.left - src.left * sx; - ty = dst.top - src.top * sy; - if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) { - float diff; - - if (xLarger) { - diff = dst.width() - src.width() * sy; - } else { - diff = dst.height() - src.height() * sy; - } - - if (stf == ScaleToFit.CENTER) { - diff = diff / 2; - } - - if (xLarger) { - tx += diff; - } else { - ty += diff; - } - } - - mValues[0] = sx; - mValues[4] = sy; - mValues[2] = tx; - mValues[5] = ty; - mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0; - - } - // shared cleanup - mValues[8] = 1; - return true; - } - - @Override - public boolean setRectToRect(RectF src, RectF dst, _Original_Matrix.ScaleToFit stf) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Set the matrix such that the specified src points would map to the - * specified dst points. The "points" are represented as an array of floats, - * order [x0, y0, x1, y1, ...], where each "point" is 2 float values. - * - * @param src The array of src [x,y] pairs (points) - * @param srcIndex Index of the first pair of src values - * @param dst The array of dst [x,y] pairs (points) - * @param dstIndex Index of the first pair of dst values - * @param pointCount The number of pairs/points to be used. Must be [0..4] - * @return true if the matrix was set to the specified transformation - */ - @Override - public boolean setPolyToPoly(float[] src, int srcIndex, - float[] dst, int dstIndex, - int pointCount) { - if (pointCount > 4) { - throw new IllegalArgumentException(); - } - checkPointArrays(src, srcIndex, dst, dstIndex, pointCount); - throw new UnsupportedOperationException("STUB NEEDED"); - } - - /** - * If this matrix can be inverted, return true and if inverse is not null, - * set inverse to be the inverse of this matrix. If this matrix cannot be - * inverted, ignore inverse and return false. - */ - public boolean invert(Matrix inverse) { - if (inverse == null) { - return false; - } - - try { - AffineTransform affineTransform = getTransform(); - AffineTransform inverseTransform = affineTransform.createInverse(); - inverse.mValues[0] = (float)inverseTransform.getScaleX(); - inverse.mValues[1] = (float)inverseTransform.getShearX(); - inverse.mValues[2] = (float)inverseTransform.getTranslateX(); - inverse.mValues[3] = (float)inverseTransform.getScaleX(); - inverse.mValues[4] = (float)inverseTransform.getShearY(); - inverse.mValues[5] = (float)inverseTransform.getTranslateY(); - - return true; - } catch (NoninvertibleTransformException e) { - return false; - } - } - - @Override - public boolean invert(_Original_Matrix inverse) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Apply this matrix to the array of 2D points specified by src, and write - * the transformed points into the array of points specified by dst. The - * two arrays represent their "points" as pairs of floats [x, y]. - * - * @param dst The array of dst points (x,y pairs) - * @param dstIndex The index of the first [x,y] pair of dst floats - * @param src The array of src points (x,y pairs) - * @param srcIndex The index of the first [x,y] pair of src floats - * @param pointCount The number of points (x,y pairs) to transform - */ - @Override - public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, - int pointCount) { - checkPointArrays(src, srcIndex, dst, dstIndex, pointCount); - - for (int i = 0 ; i < pointCount ; i++) { - // just in case we are doing in place, we better put this in temp vars - float x = mValues[0] * src[i + srcIndex] + - mValues[1] * src[i + srcIndex + 1] + - mValues[2]; - float y = mValues[3] * src[i + srcIndex] + - mValues[4] * src[i + srcIndex + 1] + - mValues[5]; - - dst[i + dstIndex] = x; - dst[i + dstIndex + 1] = y; - } - } - - /** - * Apply this matrix to the array of 2D vectors specified by src, and write - * the transformed vectors into the array of vectors specified by dst. The - * two arrays represent their "vectors" as pairs of floats [x, y]. - * - * @param dst The array of dst vectors (x,y pairs) - * @param dstIndex The index of the first [x,y] pair of dst floats - * @param src The array of src vectors (x,y pairs) - * @param srcIndex The index of the first [x,y] pair of src floats - * @param vectorCount The number of vectors (x,y pairs) to transform - */ - @Override - public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, - int vectorCount) { - checkPointArrays(src, srcIndex, dst, dstIndex, vectorCount); - throw new UnsupportedOperationException("STUB NEEDED"); - } - - /** - * Apply this matrix to the array of 2D points specified by src, and write - * the transformed points into the array of points specified by dst. The - * two arrays represent their "points" as pairs of floats [x, y]. - * - * @param dst The array of dst points (x,y pairs) - * @param src The array of src points (x,y pairs) - */ - @Override - public void mapPoints(float[] dst, float[] src) { - if (dst.length != src.length) { - throw new ArrayIndexOutOfBoundsException(); - } - mapPoints(dst, 0, src, 0, dst.length >> 1); - } - - /** - * Apply this matrix to the array of 2D vectors specified by src, and write - * the transformed vectors into the array of vectors specified by dst. The - * two arrays represent their "vectors" as pairs of floats [x, y]. - * - * @param dst The array of dst vectors (x,y pairs) - * @param src The array of src vectors (x,y pairs) - */ - @Override - public void mapVectors(float[] dst, float[] src) { - if (dst.length != src.length) { - throw new ArrayIndexOutOfBoundsException(); - } - mapVectors(dst, 0, src, 0, dst.length >> 1); - } - - /** - * Apply this matrix to the array of 2D points, and write the transformed - * points back into the array - * - * @param pts The array [x0, y0, x1, y1, ...] of points to transform. - */ - @Override - public void mapPoints(float[] pts) { - mapPoints(pts, 0, pts, 0, pts.length >> 1); - } - - /** - * Apply this matrix to the array of 2D vectors, and write the transformed - * vectors back into the array. - * @param vecs The array [x0, y0, x1, y1, ...] of vectors to transform. - */ - @Override - public void mapVectors(float[] vecs) { - mapVectors(vecs, 0, vecs, 0, vecs.length >> 1); - } - - /** - * Apply this matrix to the src rectangle, and write the transformed - * rectangle into dst. This is accomplished by transforming the 4 corners of - * src, and then setting dst to the bounds of those points. - * - * @param dst Where the transformed rectangle is written. - * @param src The original rectangle to be transformed. - * @return the result of calling rectStaysRect() - */ - @Override - public boolean mapRect(RectF dst, RectF src) { - if (dst == null || src == null) { - throw new NullPointerException(); - } - - // array with 4 corners - float[] corners = new float[] { - src.left, src.top, - src.right, src.top, - src.right, src.bottom, - src.left, src.bottom, - }; - - // apply the transform to them. - mapPoints(corners); - - // now put the result in the rect. We take the min/max of Xs and min/max of Ys - dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); - dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); - - dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); - dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); - - return rectStaysRect(); - } - - /** - * Apply this matrix to the rectangle, and write the transformed rectangle - * back into it. This is accomplished by transforming the 4 corners of rect, - * and then setting it to the bounds of those points - * - * @param rect The rectangle to transform. - * @return the result of calling rectStaysRect() - */ - @Override - public boolean mapRect(RectF rect) { - return mapRect(rect, rect); - } - - /** - * Return the mean radius of a circle after it has been mapped by - * this matrix. NOTE: in perspective this value assumes the circle - * has its center at the origin. - */ - @Override - public float mapRadius(float radius) { - throw new UnsupportedOperationException("STUB NEEDED"); - } - - /** Copy 9 values from the matrix into the array. - */ - @Override - public void getValues(float[] values) { - if (values.length < 9) { - throw new ArrayIndexOutOfBoundsException(); - } - System.arraycopy(mValues, 0, values, 0, mValues.length); - } - - /** Copy 9 values from the array into the matrix. - Depending on the implementation of Matrix, these may be - transformed into 16.16 integers in the Matrix, such that - a subsequent call to getValues() will not yield exactly - the same values. - */ - @Override - public void setValues(float[] values) { - if (values.length < 9) { - throw new ArrayIndexOutOfBoundsException(); - } - System.arraycopy(values, 0, mValues, 0, mValues.length); - } - - @SuppressWarnings("unused") - private final static int kIdentity_Mask = 0; - private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation - private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale - private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates - private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective - private final static int kRectStaysRect_Mask = 0x10; - @SuppressWarnings("unused") - private final static int kUnknown_Mask = 0x80; - - @SuppressWarnings("unused") - private final static int kAllMasks = kTranslate_Mask | - kScale_Mask | - kAffine_Mask | - kPerspective_Mask | - kRectStaysRect_Mask; - - // these guys align with the masks, so we can compute a mask from a variable 0/1 - @SuppressWarnings("unused") - private final static int kTranslate_Shift = 0; - @SuppressWarnings("unused") - private final static int kScale_Shift = 1; - @SuppressWarnings("unused") - private final static int kAffine_Shift = 2; - @SuppressWarnings("unused") - private final static int kPerspective_Shift = 3; - private final static int kRectStaysRect_Shift = 4; - - private int computeTypeMask() { - int mask = 0; - - if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) { - mask |= kPerspective_Mask; - } - - if (mValues[2] != 0. || mValues[5] != 0.) { - mask |= kTranslate_Mask; - } - - float m00 = mValues[0]; - float m01 = mValues[1]; - float m10 = mValues[3]; - float m11 = mValues[4]; - - if (m01 != 0. || m10 != 0.) { - mask |= kAffine_Mask; - } - - if (m00 != 1. || m11 != 1.) { - mask |= kScale_Mask; - } - - if ((mask & kPerspective_Mask) == 0) { - // map non-zero to 1 - int im00 = m00 != 0 ? 1 : 0; - int im01 = m01 != 0 ? 1 : 0; - int im10 = m10 != 0 ? 1 : 0; - int im11 = m11 != 0 ? 1 : 0; - - // record if the (p)rimary and (s)econdary diagonals are all 0 or - // all non-zero (answer is 0 or 1) - int dp0 = (im00 | im11) ^ 1; // true if both are 0 - int dp1 = im00 & im11; // true if both are 1 - int ds0 = (im01 | im10) ^ 1; // true if both are 0 - int ds1 = im01 & im10; // true if both are 1 - - // return 1 if primary is 1 and secondary is 0 or - // primary is 0 and secondary is 1 - mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift; - } - - return mask; - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java new file mode 100644 index 0000000..451edd2 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -0,0 +1,1129 @@ +/* + * 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; + + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Matrix.ScaleToFit; + +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; + +/** + * Delegate implementing the native methods of android.graphics.Matrix + * + * Through the layoutlib_create tool, the original native methods of Matrix have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Matrix class. + * + * @see DelegateManager + * + */ +public final class Matrix_Delegate { + + private final static int MATRIX_SIZE = 9; + + // ---- delegate manager ---- + private static final DelegateManager<Matrix_Delegate> sManager = + new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class); + + // ---- delegate data ---- + private float mValues[] = new float[MATRIX_SIZE]; + + // ---- Public Helper methods ---- + + public static Matrix_Delegate getDelegate(int native_instance) { + return sManager.getDelegate(native_instance); + } + + /** + * Returns an {@link AffineTransform} matching the given Matrix. + */ + public static AffineTransform getAffineTransform(Matrix m) { + Matrix_Delegate delegate = sManager.getDelegate(m.native_instance); + if (delegate == null) { + return null; + } + + return delegate.getAffineTransform(); + } + + public static boolean hasPerspective(Matrix m) { + Matrix_Delegate delegate = sManager.getDelegate(m.native_instance); + if (delegate == null) { + return false; + } + + return delegate.hasPerspective(); + } + + /** + * Sets the content of the matrix with the content of another matrix. + */ + public void set(Matrix_Delegate matrix) { + System.arraycopy(matrix.mValues, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Sets the content of the matrix with the content of another matrix represented as an array + * of values. + */ + public void set(float[] values) { + System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Resets the matrix to be the identity matrix. + */ + public void reset() { + reset(mValues); + } + + /** + * Returns whether or not the matrix is identity. + */ + public boolean isIdentity() { + for (int i = 0, k = 0; i < 3; i++) { + for (int j = 0; j < 3; j++, k++) { + if (mValues[k] != ((i==j) ? 1 : 0)) { + return false; + } + } + } + + return true; + } + + public static float[] makeValues(AffineTransform matrix) { + float[] values = new float[MATRIX_SIZE]; + values[0] = (float) matrix.getScaleX(); + values[1] = (float) matrix.getShearX(); + values[2] = (float) matrix.getTranslateX(); + values[3] = (float) matrix.getShearY(); + values[4] = (float) matrix.getScaleY(); + values[5] = (float) matrix.getTranslateY(); + values[6] = 0.f; + values[7] = 0.f; + values[8] = 1.f; + + return values; + } + + public static Matrix_Delegate make(AffineTransform matrix) { + return new Matrix_Delegate(makeValues(matrix)); + } + + public boolean mapRect(RectF dst, RectF src) { + // array with 4 corners + float[] corners = new float[] { + src.left, src.top, + src.right, src.top, + src.right, src.bottom, + src.left, src.bottom, + }; + + // apply the transform to them. + mapPoints(corners); + + // now put the result in the rect. We take the min/max of Xs and min/max of Ys + dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); + dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); + + dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); + dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); + + + return (computeTypeMask() & kRectStaysRect_Mask) != 0; + } + + + /** + * Returns an {@link AffineTransform} matching the matrix. + */ + public AffineTransform getAffineTransform() { + return getAffineTransform(mValues); + } + + public boolean hasPerspective() { + return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1); + } + + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_create(int native_src_or_zero) { + // create the delegate + Matrix_Delegate newDelegate = new Matrix_Delegate(); + + // copy from values if needed. + if (native_src_or_zero > 0) { + Matrix_Delegate oldDelegate = sManager.getDelegate(native_src_or_zero); + if (oldDelegate != null) { + System.arraycopy( + oldDelegate.mValues, 0, + newDelegate.mValues, 0, + MATRIX_SIZE); + } + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static boolean native_isIdentity(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + return d.isIdentity(); + } + + @LayoutlibDelegate + /*package*/ static boolean native_rectStaysRect(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return true; + } + + return (d.computeTypeMask() & kRectStaysRect_Mask) != 0; + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + reset(d.mValues); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_object, int other) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + Matrix_Delegate src = sManager.getDelegate(other); + if (src == null) { + return; + } + + System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static void native_setTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setTranslate(d.mValues, dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_setScale(int native_object, float sx, float sy, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getScale(sx, sy, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues[0] = sx; + d.mValues[1] = 0; + d.mValues[2] = 0; + d.mValues[3] = 0; + d.mValues[4] = sy; + d.mValues[5] = 0; + d.mValues[6] = 0; + d.mValues[7] = 0; + d.mValues[8] = 1; + } + + @LayoutlibDelegate + /*package*/ static void native_setRotate(int native_object, float degrees, float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getRotate(degrees, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setRotate(d.mValues, degrees); + } + + @LayoutlibDelegate + /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + // TODO: do it in one pass + + // translate so that the pivot is in 0,0 + setTranslate(d.mValues, -px, -py); + + // scale + d.postTransform(getRotate(sinValue, cosValue)); + // translate back the pivot + d.postTransform(getTranslate(px, py)); + } + + @LayoutlibDelegate + /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setRotate(d.mValues, sinValue, cosValue); + } + + @LayoutlibDelegate + /*package*/ static void native_setSkew(int native_object, float kx, float ky, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getSkew(kx, ky, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues[0] = 1; + d.mValues[1] = kx; + d.mValues[2] = -0; + d.mValues[3] = ky; + d.mValues[4] = 1; + d.mValues[5] = 0; + d.mValues[6] = 0; + d.mValues[7] = 0; + d.mValues[8] = 1; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setConcat(int native_object, int a, int b) { + if (a == native_object) { + return native_preConcat(native_object, b); + } else if (b == native_object) { + return native_postConcat(native_object, a); + } + + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate a_mtx = sManager.getDelegate(a); + if (a_mtx == null) { + return false; + } + + Matrix_Delegate b_mtx = sManager.getDelegate(b); + if (b_mtx == null) { + return false; + } + + multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); + + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getTranslate(dx, dy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preScale(int native_object, float sx, float sy, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getScale(sx, sy, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getScale(sx, sy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preRotate(int native_object, float degrees, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getRotate(degrees, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + double rad = Math.toRadians(degrees); + float sin = (float)Math.sin(rad); + float cos = (float)Math.cos(rad); + + d.preTransform(getRotate(sin, cos)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preSkew(int native_object, float kx, float ky, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getSkew(kx, ky, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getSkew(kx, ky)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preConcat(int native_object, int other_matrix) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate other = sManager.getDelegate(other_matrix); + if (d == null) { + return false; + } + + d.preTransform(other.mValues); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getTranslate(dx, dy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postScale(int native_object, float sx, float sy, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getScale(sx, sy, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getScale(sx, sy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postRotate(int native_object, float degrees, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getRotate(degrees, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getRotate(degrees)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postSkew(int native_object, float kx, float ky, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getSkew(kx, ky, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getSkew(kx, ky)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postConcat(int native_object, int other_matrix) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate other = sManager.getDelegate(other_matrix); + if (d == null) { + return false; + } + + d.postTransform(other.mValues); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setRectToRect(int native_object, RectF src, + RectF dst, int stf) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + if (src.isEmpty()) { + reset(d.mValues); + return false; + } + + if (dst.isEmpty()) { + d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5] + = d.mValues[6] = d.mValues[7] = 0; + d.mValues[8] = 1; + } else { + float tx, sx = dst.width() / src.width(); + float ty, sy = dst.height() / src.height(); + boolean xLarger = false; + + if (stf != ScaleToFit.FILL.nativeInt) { + if (sx > sy) { + xLarger = true; + sx = sy; + } else { + sy = sx; + } + } + + tx = dst.left - src.left * sx; + ty = dst.top - src.top * sy; + if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) { + float diff; + + if (xLarger) { + diff = dst.width() - src.width() * sy; + } else { + diff = dst.height() - src.height() * sy; + } + + if (stf == ScaleToFit.CENTER.nativeInt) { + diff = diff / 2; + } + + if (xLarger) { + tx += diff; + } else { + ty += diff; + } + } + + d.mValues[0] = sx; + d.mValues[4] = sy; + d.mValues[2] = tx; + d.mValues[5] = ty; + d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0; + + } + // shared cleanup + d.mValues[8] = 1; + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setPolyToPoly(int native_object, float[] src, int srcIndex, + float[] dst, int dstIndex, int pointCount) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Matrix.setPolyToPoly is not supported.", + null, null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_invert(int native_object, int inverse) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate inv_mtx = sManager.getDelegate(inverse); + if (inv_mtx == null) { + return false; + } + + try { + AffineTransform affineTransform = d.getAffineTransform(); + AffineTransform inverseTransform = affineTransform.createInverse(); + inv_mtx.mValues[0] = (float)inverseTransform.getScaleX(); + inv_mtx.mValues[1] = (float)inverseTransform.getShearX(); + inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX(); + inv_mtx.mValues[3] = (float)inverseTransform.getScaleX(); + inv_mtx.mValues[4] = (float)inverseTransform.getShearY(); + inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY(); + + return true; + } catch (NoninvertibleTransformException e) { + return false; + } + } + + @LayoutlibDelegate + /*package*/ static void native_mapPoints(int native_object, float[] dst, int dstIndex, + float[] src, int srcIndex, int ptCount, boolean isPts) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + if (isPts) { + d.mapPoints(dst, dstIndex, src, srcIndex, ptCount); + } else { + d.mapVectors(dst, dstIndex, src, srcIndex, ptCount); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_mapRect(int native_object, RectF dst, RectF src) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + return d.mapRect(dst, src); + } + + @LayoutlibDelegate + /*package*/ static float native_mapRadius(int native_object, float radius) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return 0.f; + } + + float[] src = new float[] { radius, 0.f, 0.f, radius }; + d.mapVectors(src, 0, src, 0, 2); + + float l1 = getPointLength(src, 0); + float l2 = getPointLength(src, 2); + + return (float) Math.sqrt(l1 * l2); + } + + @LayoutlibDelegate + /*package*/ static void native_getValues(int native_object, float[] values) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + System.arraycopy(d.mValues, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static void native_setValues(int native_object, float[] values) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static boolean native_equals(int native_a, int native_b) { + Matrix_Delegate a = sManager.getDelegate(native_a); + if (a == null) { + return false; + } + + Matrix_Delegate b = sManager.getDelegate(native_b); + if (b == null) { + return false; + } + + for (int i = 0 ; i < MATRIX_SIZE ; i++) { + if (a.mValues[i] != b.mValues[i]) { + return false; + } + } + + return true; + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private helper methods ---- + + /*package*/ static AffineTransform getAffineTransform(float[] matrix) { + // the AffineTransform constructor takes the value in a different order + // for a matrix [ 0 1 2 ] + // [ 3 4 5 ] + // the order is 0, 3, 1, 4, 2, 5... + return new AffineTransform( + matrix[0], matrix[3], matrix[1], + matrix[4], matrix[2], matrix[5]); + } + + /** + * Reset a matrix to the identity + */ + private static void reset(float[] mtx) { + for (int i = 0, k = 0; i < 3; i++) { + for (int j = 0; j < 3; j++, k++) { + mtx[k] = ((i==j) ? 1 : 0); + } + } + } + + @SuppressWarnings("unused") + private final static int kIdentity_Mask = 0; + private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation + private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale + private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates + private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective + private final static int kRectStaysRect_Mask = 0x10; + @SuppressWarnings("unused") + private final static int kUnknown_Mask = 0x80; + + @SuppressWarnings("unused") + private final static int kAllMasks = kTranslate_Mask | + kScale_Mask | + kAffine_Mask | + kPerspective_Mask | + kRectStaysRect_Mask; + + // these guys align with the masks, so we can compute a mask from a variable 0/1 + @SuppressWarnings("unused") + private final static int kTranslate_Shift = 0; + @SuppressWarnings("unused") + private final static int kScale_Shift = 1; + @SuppressWarnings("unused") + private final static int kAffine_Shift = 2; + @SuppressWarnings("unused") + private final static int kPerspective_Shift = 3; + private final static int kRectStaysRect_Shift = 4; + + private int computeTypeMask() { + int mask = 0; + + if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) { + mask |= kPerspective_Mask; + } + + if (mValues[2] != 0. || mValues[5] != 0.) { + mask |= kTranslate_Mask; + } + + float m00 = mValues[0]; + float m01 = mValues[1]; + float m10 = mValues[3]; + float m11 = mValues[4]; + + if (m01 != 0. || m10 != 0.) { + mask |= kAffine_Mask; + } + + if (m00 != 1. || m11 != 1.) { + mask |= kScale_Mask; + } + + if ((mask & kPerspective_Mask) == 0) { + // map non-zero to 1 + int im00 = m00 != 0 ? 1 : 0; + int im01 = m01 != 0 ? 1 : 0; + int im10 = m10 != 0 ? 1 : 0; + int im11 = m11 != 0 ? 1 : 0; + + // record if the (p)rimary and (s)econdary diagonals are all 0 or + // all non-zero (answer is 0 or 1) + int dp0 = (im00 | im11) ^ 1; // true if both are 0 + int dp1 = im00 & im11; // true if both are 1 + int ds0 = (im01 | im10) ^ 1; // true if both are 0 + int ds1 = im01 & im10; // true if both are 1 + + // return 1 if primary is 1 and secondary is 0 or + // primary is 0 and secondary is 1 + mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift; + } + + return mask; + } + + private Matrix_Delegate() { + reset(); + } + + private Matrix_Delegate(float[] values) { + System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Adds the given transformation to the current Matrix + * <p/>This in effect does this = this*matrix + * @param matrix + */ + private void postTransform(float[] matrix) { + float[] tmp = new float[9]; + multiply(tmp, mValues, matrix); + mValues = tmp; + } + + /** + * Adds the given transformation to the current Matrix + * <p/>This in effect does this = matrix*this + * @param matrix + */ + private void preTransform(float[] matrix) { + float[] tmp = new float[9]; + multiply(tmp, matrix, mValues); + mValues = tmp; + } + + /** + * Apply this matrix to the array of 2D points specified by src, and write + * the transformed points into the array of points specified by dst. The + * two arrays represent their "points" as pairs of floats [x, y]. + * + * @param dst The array of dst points (x,y pairs) + * @param dstIndex The index of the first [x,y] pair of dst floats + * @param src The array of src points (x,y pairs) + * @param srcIndex The index of the first [x,y] pair of src floats + * @param pointCount The number of points (x,y pairs) to transform + */ + + private void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, + int pointCount) { + final int count = pointCount * 2; + + float[] tmpDest = dst; + boolean inPlace = dst == src; + if (inPlace) { + tmpDest = new float[dstIndex + count]; + } + + for (int i = 0 ; i < count ; i += 2) { + // just in case we are doing in place, we better put this in temp vars + float x = mValues[0] * src[i + srcIndex] + + mValues[1] * src[i + srcIndex + 1] + + mValues[2]; + float y = mValues[3] * src[i + srcIndex] + + mValues[4] * src[i + srcIndex + 1] + + mValues[5]; + + tmpDest[i + dstIndex] = x; + tmpDest[i + dstIndex + 1] = y; + } + + if (inPlace) { + System.arraycopy(tmpDest, dstIndex, dst, dstIndex, count); + } + } + + /** + * Apply this matrix to the array of 2D points, and write the transformed + * points back into the array + * + * @param pts The array [x0, y0, x1, y1, ...] of points to transform. + */ + + private void mapPoints(float[] pts) { + mapPoints(pts, 0, pts, 0, pts.length >> 1); + } + + private void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount) { + if (hasPerspective()) { + // transform the (0,0) point + float[] origin = new float[] { 0.f, 0.f}; + mapPoints(origin); + + // translate the vector data as points + mapPoints(dst, dstIndex, src, srcIndex, ptCount); + + // then substract the transformed origin. + final int count = ptCount * 2; + for (int i = 0 ; i < count ; i += 2) { + dst[dstIndex + i] = dst[dstIndex + i] - origin[0]; + dst[dstIndex + i + 1] = dst[dstIndex + i + 1] - origin[1]; + } + } else { + // make a copy of the matrix + Matrix_Delegate copy = new Matrix_Delegate(mValues); + + // remove the translation + setTranslate(copy.mValues, 0, 0); + + // map the content as points. + copy.mapPoints(dst, dstIndex, src, srcIndex, ptCount); + } + } + + private static float getPointLength(float[] src, int index) { + return (float) Math.sqrt(src[index] * src[index] + src[index + 1] * src[index + 1]); + } + + /** + * multiply two matrices and store them in a 3rd. + * <p/>This in effect does dest = a*b + * dest cannot be the same as a or b. + */ + /*package*/ static void multiply(float dest[], float[] a, float[] b) { + // first row + dest[0] = b[0] * a[0] + b[1] * a[3] + b[2] * a[6]; + dest[1] = b[0] * a[1] + b[1] * a[4] + b[2] * a[7]; + dest[2] = b[0] * a[2] + b[1] * a[5] + b[2] * a[8]; + + // 2nd row + dest[3] = b[3] * a[0] + b[4] * a[3] + b[5] * a[6]; + dest[4] = b[3] * a[1] + b[4] * a[4] + b[5] * a[7]; + dest[5] = b[3] * a[2] + b[4] * a[5] + b[5] * a[8]; + + // 3rd row + dest[6] = b[6] * a[0] + b[7] * a[3] + b[8] * a[6]; + dest[7] = b[6] * a[1] + b[7] * a[4] + b[8] * a[7]; + dest[8] = b[6] * a[2] + b[7] * a[5] + b[8] * a[8]; + } + + /** + * Returns a matrix that represents a given translate + * @param dx + * @param dy + * @return + */ + /*package*/ static float[] getTranslate(float dx, float dy) { + return setTranslate(new float[9], dx, dy); + } + + /*package*/ static float[] setTranslate(float[] dest, float dx, float dy) { + dest[0] = 1; + dest[1] = 0; + dest[2] = dx; + dest[3] = 0; + dest[4] = 1; + dest[5] = dy; + dest[6] = 0; + dest[7] = 0; + dest[8] = 1; + return dest; + } + + /*package*/ static float[] getScale(float sx, float sy) { + return new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; + } + + /** + * Returns a matrix that represents the given scale info. + * @param sx + * @param sy + * @param px + * @param py + */ + /*package*/ static float[] getScale(float sx, float sy, float px, float py) { + float[] tmp = new float[9]; + float[] tmp2 = new float[9]; + + // TODO: do it in one pass + + // translate tmp so that the pivot is in 0,0 + setTranslate(tmp, -px, -py); + + // scale into tmp2 + multiply(tmp2, tmp, getScale(sx, sy)); + + // translate back the pivot back into tmp + multiply(tmp, tmp2, getTranslate(px, py)); + + return tmp; + } + + + /*package*/ static float[] getRotate(float degrees) { + double rad = Math.toRadians(degrees); + float sin = (float)Math.sin(rad); + float cos = (float)Math.cos(rad); + + return getRotate(sin, cos); + } + + /*package*/ static float[] getRotate(float sin, float cos) { + return setRotate(new float[9], sin, cos); + } + + /*package*/ static float[] setRotate(float[] dest, float degrees) { + double rad = Math.toRadians(degrees); + float sin = (float)Math.sin(rad); + float cos = (float)Math.cos(rad); + + return setRotate(dest, sin, cos); + } + + /*package*/ static float[] setRotate(float[] dest, float sin, float cos) { + dest[0] = cos; + dest[1] = -sin; + dest[2] = 0; + dest[3] = sin; + dest[4] = cos; + dest[5] = 0; + dest[6] = 0; + dest[7] = 0; + dest[8] = 1; + return dest; + } + + /*package*/ static float[] getRotate(float degrees, float px, float py) { + float[] tmp = new float[9]; + float[] tmp2 = new float[9]; + + // TODO: do it in one pass + + // translate so that the pivot is in 0,0 + setTranslate(tmp, -px, -py); + + // rotate into tmp2 + double rad = Math.toRadians(degrees); + float cos = (float)Math.cos(rad); + float sin = (float)Math.sin(rad); + multiply(tmp2, tmp, getRotate(sin, cos)); + + // translate back the pivot back into tmp + multiply(tmp, tmp2, getTranslate(px, py)); + + return tmp; + } + + /*package*/ static float[] getSkew(float kx, float ky) { + return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }; + } + + /*package*/ static float[] getSkew(float kx, float ky, float px, float py) { + float[] tmp = new float[9]; + float[] tmp2 = new float[9]; + + // TODO: do it in one pass + + // translate so that the pivot is in 0,0 + setTranslate(tmp, -px, -py); + + // skew into tmp2 + multiply(tmp2, tmp, new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); + // translate back the pivot back into tmp + multiply(tmp, tmp2, getTranslate(px, py)); + + return tmp; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java new file mode 100644 index 0000000..5e882ce --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -0,0 +1,225 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.ninepatch.NinePatchChunk; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.drawable.NinePatchDrawable; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; + +/** + * Delegate implementing the native methods of android.graphics.NinePatch + * + * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public final class NinePatch_Delegate { + + /** + * Cache map for {@link NinePatchChunk}. + * When the chunks are created they are serialized into a byte[], and both are put + * in the cache, using a {@link SoftReference} for the chunk. The default Java classes + * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and + * provide this for drawing. + * Using the cache map allows us to not have to deserialize the byte[] back into a + * {@link NinePatchChunk} every time a rendering is done. + */ + private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache = + new HashMap<byte[], SoftReference<NinePatchChunk>>(); + + // ---- Public Helper methods ---- + + /** + * Serializes the given chunk. + * + * @return the serialized data for the chunk. + */ + public static byte[] serialize(NinePatchChunk chunk) { + // serialize the chunk to get a byte[] + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(baos); + oos.writeObject(chunk); + } catch (IOException e) { + Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/); + return null; + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + } + } + } + + // get the array and add it to the cache + byte[] array = baos.toByteArray(); + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + return array; + } + + /** + * Returns a {@link NinePatchChunk} object for the given serialized representation. + * + * If the chunk is present in the cache then the object from the cache is returned, otherwise + * the array is deserialized into a {@link NinePatchChunk} object. + * + * @param array the serialized representation of the chunk. + * @return the NinePatchChunk or null if deserialization failed. + */ + public static NinePatchChunk getChunk(byte[] array) { + SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array); + NinePatchChunk chunk = chunkRef.get(); + if (chunk == null) { + ByteArrayInputStream bais = new ByteArrayInputStream(array); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(bais); + chunk = (NinePatchChunk) ois.readObject(); + + // put back the chunk in the cache + if (chunk != null) { + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + } + } catch (IOException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to deserialize NinePatchChunk content.", e, null /*data*/); + return null; + } catch (ClassNotFoundException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to deserialize NinePatchChunk class.", e, null /*data*/); + return null; + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException e) { + } + } + } + } + + return chunk; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isNinePatchChunk(byte[] chunk) { + NinePatchChunk chunkObject = getChunk(chunk); + if (chunkObject != null) { + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) { + // the default JNI implementation only checks that the byte[] has the same + // size as the C struct it represent. Since we cannot do the same check (serialization + // will return different size depending on content), we do nothing. + } + + @LayoutlibDelegate + /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + @LayoutlibDelegate + /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + loc.left, loc.top, loc.width(), loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + @LayoutlibDelegate + /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) { + return 0; + } + + // ---- Private Helper methods ---- + + private static void draw(int canvas_instance, + final int left, final int top, final int right, final int bottom, + int bitmap_instance, byte[] c, int paint_instance_or_null, + final int destDensity, final int srcDensity) { + // get the delegate from the native int. + final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance); + if (bitmap_delegate == null) { + return; + } + + if (c == null) { + // not a 9-patch? + BufferedImage image = bitmap_delegate.getImage(); + Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance, + new Rect(0, 0, image.getWidth(), image.getHeight()), + new Rect(left, top, right, bottom), + paint_instance_or_null, destDensity, srcDensity); + return; + } + + final NinePatchChunk chunkObject = getChunk(c); + assert chunkObject != null; + if (chunkObject == null) { + return; + } + + Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance); + if (canvas_delegate == null) { + return; + } + + // this one can be null + Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null); + + canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + chunkObject.draw(bitmap_delegate.getImage(), graphics, + left, top, right - left, bottom - top, destDensity, srcDensity); + } + }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/); + + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java deleted file mode 100644 index d13b5fe..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Paint.java +++ /dev/null @@ -1,1211 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.SpannedString; -import android.text.TextUtils; - -import java.awt.BasicStroke; -import java.awt.Font; -import java.awt.Toolkit; -import java.awt.font.FontRenderContext; -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A paint implementation overridden by the LayoutLib bridge. - */ -public class Paint extends _Original_Paint { - - private int mColor = 0xFFFFFFFF; - private float mStrokeWidth = 1.f; - private float mTextSize = 20; - private float mScaleX = 1; - private float mSkewX = 0; - private Align mAlign = Align.LEFT; - private Style mStyle = Style.FILL; - private float mStrokeMiter = 4.0f; - private Cap mCap = Cap.BUTT; - private Join mJoin = Join.MITER; - private int mFlags = 0; - - /** - * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. - */ - public static final class FontInfo { - Font mFont; - java.awt.FontMetrics mMetrics; - } - - private List<FontInfo> mFonts; - private final FontRenderContext mFontContext = new FontRenderContext( - new AffineTransform(), true, true); - - public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG; - public static final int FILTER_BITMAP_FLAG = _Original_Paint.FILTER_BITMAP_FLAG; - public static final int DITHER_FLAG = _Original_Paint.DITHER_FLAG; - public static final int UNDERLINE_TEXT_FLAG = _Original_Paint.UNDERLINE_TEXT_FLAG; - public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG; - public static final int FAKE_BOLD_TEXT_FLAG = _Original_Paint.FAKE_BOLD_TEXT_FLAG; - public static final int LINEAR_TEXT_FLAG = _Original_Paint.LINEAR_TEXT_FLAG; - public static final int SUBPIXEL_TEXT_FLAG = _Original_Paint.SUBPIXEL_TEXT_FLAG; - public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG; - - public static class FontMetrics extends _Original_Paint.FontMetrics { - } - - public static class FontMetricsInt extends _Original_Paint.FontMetricsInt { - } - - /** - * The Style specifies if the primitive being drawn is filled, - * stroked, or both (in the same color). The default is FILL. - */ - public enum Style { - /** - * Geometry and text drawn with this style will be filled, ignoring all - * stroke-related settings in the paint. - */ - FILL (0), - /** - * Geometry and text drawn with this style will be stroked, respecting - * the stroke-related fields on the paint. - */ - STROKE (1), - /** - * Geometry and text drawn with this style will be both filled and - * stroked at the same time, respecting the stroke-related fields on - * the paint. - */ - FILL_AND_STROKE (2); - - Style(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * The Cap specifies the treatment for the beginning and ending of - * stroked lines and paths. The default is BUTT. - */ - public enum Cap { - /** - * The stroke ends with the path, and does not project beyond it. - */ - BUTT (0), - /** - * The stroke projects out as a square, with the center at the end - * of the path. - */ - ROUND (1), - /** - * The stroke projects out as a semicircle, with the center at the - * end of the path. - */ - SQUARE (2); - - private Cap(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - - /** custom for layoutlib */ - public int getJavaCap() { - switch (this) { - case BUTT: - return BasicStroke.CAP_BUTT; - case ROUND: - return BasicStroke.CAP_ROUND; - default: - case SQUARE: - return BasicStroke.CAP_SQUARE; - } - } - } - - /** - * The Join specifies the treatment where lines and curve segments - * join on a stroked path. The default is MITER. - */ - public enum Join { - /** - * The outer edges of a join meet at a sharp angle - */ - MITER (0), - /** - * The outer edges of a join meet in a circular arc. - */ - ROUND (1), - /** - * The outer edges of a join meet with a straight line - */ - BEVEL (2); - - private Join(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - - /** custom for layoutlib */ - public int getJavaJoin() { - switch (this) { - default: - case MITER: - return BasicStroke.JOIN_MITER; - case ROUND: - return BasicStroke.JOIN_ROUND; - case BEVEL: - return BasicStroke.JOIN_BEVEL; - } - } - } - - /** - * Align specifies how drawText aligns its text relative to the - * [x,y] coordinates. The default is LEFT. - */ - public enum Align { - /** - * The text is drawn to the right of the x,y origin - */ - LEFT (0), - /** - * The text is drawn centered horizontally on the x,y origin - */ - CENTER (1), - /** - * The text is drawn to the left of the x,y origin - */ - RIGHT (2); - - private Align(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - public Paint() { - this(0); - } - - /* - * Do not remove or com.android.layoutlib.bridge.TestClassReplacement fails. - */ - @Override - public void finalize() { } - - public Paint(int flags) { - setFlags(flags | DEFAULT_PAINT_FLAGS); - initFont(); - } - - public Paint(Paint paint) { - set(paint); - initFont(); - } - - @Override - public void reset() { - super.reset(); - } - - /** - * Returns the list of {@link Font} objects. The first item is the main font, the rest - * are fall backs for characters not present in the main font. - */ - public List<FontInfo> getFonts() { - return mFonts; - } - - private void initFont() { - mTypeface = Typeface.DEFAULT; - updateFontObject(); - } - - /** - * Update the {@link Font} object from the typeface, text size and scaling - */ - @SuppressWarnings("deprecation") - private void updateFontObject() { - if (mTypeface != null) { - // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(); - - // create new font objects as well as FontMetrics, based on the current text size - // and skew info. - ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); - for (Font font : fonts) { - FontInfo info = new FontInfo(); - info.mFont = font.deriveFont(mTextSize); - if (mScaleX != 1.0 || mSkewX != 0) { - // TODO: support skew - info.mFont = info.mFont.deriveFont(new AffineTransform( - mScaleX, mSkewX, 0, 0, 1, 0)); - } - info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); - - infoList.add(info); - } - - mFonts = Collections.unmodifiableList(infoList); - } - } - - //---------------------------------------- - - public void set(Paint src) { - if (this != src) { - mColor = src.mColor; - mTextSize = src.mTextSize; - mScaleX = src.mScaleX; - mSkewX = src.mSkewX; - mAlign = src.mAlign; - mStyle = src.mStyle; - mFlags = src.mFlags; - - updateFontObject(); - - super.set(src); - } - } - - @Override - public void setCompatibilityScaling(float factor) { - super.setCompatibilityScaling(factor); - } - - @Override - public int getFlags() { - return mFlags; - } - - @Override - public void setFlags(int flags) { - mFlags = flags; - } - - @Override - public boolean isAntiAlias() { - return super.isAntiAlias(); - } - - @Override - public boolean isDither() { - return super.isDither(); - } - - @Override - public boolean isLinearText() { - return super.isLinearText(); - } - - @Override - public boolean isStrikeThruText() { - return super.isStrikeThruText(); - } - - @Override - public boolean isUnderlineText() { - return super.isUnderlineText(); - } - - @Override - public boolean isFakeBoldText() { - return super.isFakeBoldText(); - } - - @Override - public boolean isSubpixelText() { - return super.isSubpixelText(); - } - - @Override - public boolean isFilterBitmap() { - return super.isFilterBitmap(); - } - - /** - * Return the font's recommended interline spacing, given the Paint's - * settings for typeface, textSize, etc. If metrics is not null, return the - * fontmetric values in it. - * - * @param metrics If this object is not null, its fields are filled with - * the appropriate values given the paint's text attributes. - * @return the font's recommended interline spacing. - */ - public float getFontMetrics(FontMetrics metrics) { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - if (metrics != null) { - // Android expects negative ascent so we invert the value from Java. - metrics.top = - javaMetrics.getMaxAscent(); - metrics.ascent = - javaMetrics.getAscent(); - metrics.descent = javaMetrics.getDescent(); - metrics.bottom = javaMetrics.getMaxDescent(); - metrics.leading = javaMetrics.getLeading(); - } - - return javaMetrics.getHeight(); - } - - return 0; - } - - public int getFontMetricsInt(FontMetricsInt metrics) { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - if (metrics != null) { - // Android expects negative ascent so we invert the value from Java. - metrics.top = - javaMetrics.getMaxAscent(); - metrics.ascent = - javaMetrics.getAscent(); - metrics.descent = javaMetrics.getDescent(); - metrics.bottom = javaMetrics.getMaxDescent(); - metrics.leading = javaMetrics.getLeading(); - } - - return javaMetrics.getHeight(); - } - - return 0; - } - - /** - * Reimplemented to return Paint.FontMetrics instead of _Original_Paint.FontMetrics - */ - public FontMetrics getFontMetrics() { - FontMetrics fm = new FontMetrics(); - getFontMetrics(fm); - return fm; - } - - /** - * Reimplemented to return Paint.FontMetricsInt instead of _Original_Paint.FontMetricsInt - */ - public FontMetricsInt getFontMetricsInt() { - FontMetricsInt fm = new FontMetricsInt(); - getFontMetricsInt(fm); - return fm; - } - - - - @Override - public float getFontMetrics(_Original_Paint.FontMetrics metrics) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - @Override - public int getFontMetricsInt(_Original_Paint.FontMetricsInt metrics) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - @Override - public Typeface setTypeface(Typeface typeface) { - if (typeface != null) { - mTypeface = typeface; - } else { - mTypeface = Typeface.DEFAULT; - } - - updateFontObject(); - - return typeface; - } - - @Override - public Typeface getTypeface() { - return super.getTypeface(); - } - - @Override - public int getColor() { - return mColor; - } - - @Override - public void setColor(int color) { - mColor = color; - } - - @Override - public void setARGB(int a, int r, int g, int b) { - super.setARGB(a, r, g, b); - } - - @Override - public void setAlpha(int alpha) { - mColor = (alpha << 24) | (mColor & 0x00FFFFFF); - } - - @Override - public int getAlpha() { - return mColor >>> 24; - } - - /** - * Set or clear the shader object. - * <p /> - * Pass null to clear any previous shader. - * As a convenience, the parameter passed is also returned. - * - * @param shader May be null. the new shader to be installed in the paint - * @return shader - */ - @Override - public Shader setShader(Shader shader) { - return mShader = shader; - } - - @Override - public Shader getShader() { - return super.getShader(); - } - - /** - * Set or clear the paint's colorfilter, returning the parameter. - * - * @param filter May be null. The new filter to be installed in the paint - * @return filter - */ - @Override - public ColorFilter setColorFilter(ColorFilter filter) { - mColorFilter = filter; - return filter; - } - - @Override - public ColorFilter getColorFilter() { - return super.getColorFilter(); - } - - /** - * Set or clear the xfermode object. - * <p /> - * Pass null to clear any previous xfermode. - * As a convenience, the parameter passed is also returned. - * - * @param xfermode May be null. The xfermode to be installed in the paint - * @return xfermode - */ - @Override - public Xfermode setXfermode(Xfermode xfermode) { - return mXfermode = xfermode; - } - - @Override - public Xfermode getXfermode() { - return super.getXfermode(); - } - - @Override - public Rasterizer setRasterizer(Rasterizer rasterizer) { - mRasterizer = rasterizer; - return rasterizer; - } - - @Override - public Rasterizer getRasterizer() { - return super.getRasterizer(); - } - - @Override - public void setShadowLayer(float radius, float dx, float dy, int color) { - // TODO Auto-generated method stub - } - - @Override - public void clearShadowLayer() { - super.clearShadowLayer(); - } - - public void setTextAlign(Align align) { - mAlign = align; - } - - @Override - public void setTextAlign(android.graphics._Original_Paint.Align align) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public Align getTextAlign() { - return mAlign; - } - - public void setStyle(Style style) { - mStyle = style; - } - - @Override - public void setStyle(android.graphics._Original_Paint.Style style) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public Style getStyle() { - return mStyle; - } - - @Override - public void setDither(boolean dither) { - mFlags |= dither ? DITHER_FLAG : ~DITHER_FLAG; - } - - @Override - public void setAntiAlias(boolean aa) { - mFlags |= aa ? ANTI_ALIAS_FLAG : ~ANTI_ALIAS_FLAG; - } - - @Override - public void setFakeBoldText(boolean flag) { - mFlags |= flag ? FAKE_BOLD_TEXT_FLAG : ~FAKE_BOLD_TEXT_FLAG; - } - - @Override - public void setLinearText(boolean flag) { - mFlags |= flag ? LINEAR_TEXT_FLAG : ~LINEAR_TEXT_FLAG; - } - - @Override - public void setSubpixelText(boolean flag) { - mFlags |= flag ? SUBPIXEL_TEXT_FLAG : ~SUBPIXEL_TEXT_FLAG; - } - - @Override - public void setUnderlineText(boolean flag) { - mFlags |= flag ? UNDERLINE_TEXT_FLAG : ~UNDERLINE_TEXT_FLAG; - } - - @Override - public void setStrikeThruText(boolean flag) { - mFlags |= flag ? STRIKE_THRU_TEXT_FLAG : ~STRIKE_THRU_TEXT_FLAG; - } - - @Override - public void setFilterBitmap(boolean flag) { - mFlags |= flag ? FILTER_BITMAP_FLAG : ~FILTER_BITMAP_FLAG; - } - - @Override - public float getStrokeWidth() { - return mStrokeWidth; - } - - @Override - public void setStrokeWidth(float width) { - mStrokeWidth = width; - } - - @Override - public float getStrokeMiter() { - return mStrokeMiter; - } - - @Override - public void setStrokeMiter(float miter) { - mStrokeMiter = miter; - } - - @Override - public void setStrokeCap(android.graphics._Original_Paint.Cap cap) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public void setStrokeCap(Cap cap) { - mCap = cap; - } - - public Cap getStrokeCap() { - return mCap; - } - - @Override - public void setStrokeJoin(android.graphics._Original_Paint.Join join) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public void setStrokeJoin(Join join) { - mJoin = join; - } - - public Join getStrokeJoin() { - return mJoin; - } - - @Override - public boolean getFillPath(Path src, Path dst) { - return super.getFillPath(src, dst); - } - - @Override - public PathEffect setPathEffect(PathEffect effect) { - mPathEffect = effect; - return effect; - } - - @Override - public PathEffect getPathEffect() { - return super.getPathEffect(); - } - - @Override - public MaskFilter setMaskFilter(MaskFilter maskfilter) { - mMaskFilter = maskfilter; - return maskfilter; - } - - @Override - public MaskFilter getMaskFilter() { - return super.getMaskFilter(); - } - - /** - * Return the paint's text size. - * - * @return the paint's text size. - */ - @Override - public float getTextSize() { - return mTextSize; - } - - /** - * Set the paint's text size. This value must be > 0 - * - * @param textSize set the paint's text size. - */ - @Override - public void setTextSize(float textSize) { - mTextSize = textSize; - - updateFontObject(); - } - - /** - * Return the paint's horizontal scale factor for text. The default value - * is 1.0. - * - * @return the paint's scale factor in X for drawing/measuring text - */ - @Override - public float getTextScaleX() { - return mScaleX; - } - - /** - * Set the paint's horizontal scale factor for text. The default value - * is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will - * stretch the text narrower. - * - * @param scaleX set the paint's scale in X for drawing/measuring text. - */ - @Override - public void setTextScaleX(float scaleX) { - mScaleX = scaleX; - - updateFontObject(); - } - - /** - * Return the paint's horizontal skew factor for text. The default value - * is 0. - * - * @return the paint's skew factor in X for drawing text. - */ - @Override - public float getTextSkewX() { - return mSkewX; - } - - /** - * Set the paint's horizontal skew factor for text. The default value - * is 0. For approximating oblique text, use values around -0.25. - * - * @param skewX set the paint's skew factor in X for drawing text. - */ - @Override - public void setTextSkewX(float skewX) { - mSkewX = skewX; - - updateFontObject(); - } - - @Override - public float getFontSpacing() { - return super.getFontSpacing(); - } - - /** - * Return the distance above (negative) the baseline (ascent) based on the - * current typeface and text size. - * - * @return the distance above (negative) the baseline (ascent) based on the - * current typeface and text size. - */ - @Override - public float ascent() { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - // Android expects negative ascent so we invert the value from Java. - return - javaMetrics.getAscent(); - } - - return 0; - } - - /** - * Return the distance below (positive) the baseline (descent) based on the - * current typeface and text size. - * - * @return the distance below (positive) the baseline (descent) based on - * the current typeface and text size. - */ - @Override - public float descent() { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - return javaMetrics.getDescent(); - } - - return 0; - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @param index The index of the first character to start measuring - * @param count THe number of characters to measure, beginning with start - * @return The width of the text - */ - @Override - public float measureText(char[] text, int index, int count) { - // WARNING: the logic in this method is similar to Canvas.drawText. - // Any change to this method should be reflected in Canvas.drawText - if (mFonts.size() > 0) { - FontInfo mainFont = mFonts.get(0); - int i = index; - int lastIndex = index + count; - float total = 0f; - while (i < lastIndex) { - // always start with the main font. - int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); - if (upTo == -1) { - // shortcut to exit - return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); - } else if (upTo > 0) { - total += mainFont.mMetrics.charsWidth(text, i, upTo - i); - i = upTo; - // don't call continue at this point. Since it is certain the main font - // cannot display the font a index upTo (now ==i), we move on to the - // fallback fonts directly. - } - - // no char supported, attempt to read the next char(s) with the - // fallback font. In this case we only test the first character - // and then go back to test with the main font. - // Special test for 2-char characters. - boolean foundFont = false; - for (int f = 1 ; f < mFonts.size() ; f++) { - FontInfo fontInfo = mFonts.get(f); - - // need to check that the font can display the character. We test - // differently if the char is a high surrogate. - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); - if (upTo == -1) { - total += fontInfo.mMetrics.charsWidth(text, i, charCount); - i += charCount; - foundFont = true; - break; - - } - } - - // in case no font can display the char, measure it with the main font. - if (foundFont == false) { - int size = Character.isHighSurrogate(text[i]) ? 2 : 1; - total += mainFont.mMetrics.charsWidth(text, i, size); - i += size; - } - } - } - - return 0; - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @param start The index of the first character to start measuring - * @param end 1 beyond the index of the last character to measure - * @return The width of the text - */ - @Override - public float measureText(String text, int start, int end) { - return measureText(text.toCharArray(), start, end - start); - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @return The width of the text - */ - @Override - public float measureText(String text) { - return measureText(text.toCharArray(), 0, text.length()); - } - - /* - * re-implement to call SpannableStringBuilder.measureText with a Paint object - * instead of an _Original_Paint - */ - @Override - public float measureText(CharSequence text, int start, int end) { - if (text instanceof String) { - return measureText((String)text, start, end); - } - if (text instanceof SpannedString || - text instanceof SpannableString) { - return measureText(text.toString(), start, end); - } - if (text instanceof SpannableStringBuilder) { - return ((SpannableStringBuilder)text).measureText(start, end, this); - } - - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - float result = measureText(buf, 0, end - start); - TemporaryBuffer.recycle(buf); - return result; - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param index The offset into text to begin measuring at - * @param count The number of maximum number of entries to measure. If count - * is negative, then the characters before index are measured - * in reverse order. This allows for measuring the end of - * string. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(count). - */ - @Override - public int breakText(char[] text, int index, int count, - float maxWidth, float[] measuredWidth) { - int inc = count > 0 ? 1 : -1; - - int measureIndex = 0; - float measureAcc = 0; - for (int i = index ; i != index + count ; i += inc, measureIndex++) { - int start, end; - if (i < index) { - start = i; - end = index; - } else { - start = index; - end = i; - } - - // measure from start to end - float res = measureText(text, start, end - start + 1); - - if (measuredWidth != null) { - measuredWidth[measureIndex] = res; - } - - measureAcc += res; - if (res > maxWidth) { - // we should not return this char index, but since it's 0-based and we need - // to return a count, we simply return measureIndex; - return measureIndex; - } - - } - - return measureIndex; - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param measureForwards If true, measure forwards, starting at index. - * Otherwise, measure backwards, starting with the - * last character in the string. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(count). - */ - @Override - public int breakText(String text, boolean measureForwards, - float maxWidth, float[] measuredWidth) { - return breakText(text, - 0 /* start */, text.length() /* end */, - measureForwards, maxWidth, measuredWidth); - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param start The offset into text to begin measuring at - * @param end The end of the text slice to measure. - * @param measureForwards If true, measure forwards, starting at start. - * Otherwise, measure backwards, starting with end. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(end - start). - */ - @Override - public int breakText(CharSequence text, int start, int end, boolean measureForwards, - float maxWidth, float[] measuredWidth) { - char[] buf = new char[end - start]; - int result; - - TextUtils.getChars(text, start, end, buf, 0); - - if (measureForwards) { - result = breakText(buf, 0, end - start, maxWidth, measuredWidth); - } else { - result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth); - } - - return result; - } - - /** - * Return the advance widths for the characters in the string. - * - * @param text The text to measure - * @param index The index of the first char to to measure - * @param count The number of chars starting with index to measure - * @param widths array to receive the advance widths of the characters. - * Must be at least a large as count. - * @return the actual number of widths returned. - */ - @Override - public int getTextWidths(char[] text, int index, int count, - float[] widths) { - if (mFonts.size() > 0) { - if ((index | count) < 0 || index + count > text.length - || count > widths.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // FIXME: handle multi-char characters. - // Need to figure out if the lengths of the width array takes into account - // multi-char characters. - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : mFonts) { - if (info.mFont.canDisplay(c)) { - widths[i] = info.mMetrics.charWidth(c); - found = true; - break; - } - } - - if (found == false) { - // we stop there. - return i; - } - } - - return count; - } - - return 0; - } - - /** - * Return the advance widths for the characters in the string. - * - * @param text The text to measure - * @param start The index of the first char to to measure - * @param end The end of the text slice to measure - * @param widths array to receive the advance widths of the characters. - * Must be at least a large as the text. - * @return the number of unichars in the specified text. - */ - @Override - public int getTextWidths(String text, int start, int end, float[] widths) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (end - start > widths.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - return getTextWidths(text.toCharArray(), start, end - start, widths); - } - - /* - * re-implement to call SpannableStringBuilder.getTextWidths with a Paint object - * instead of an _Original_Paint - */ - @Override - public int getTextWidths(CharSequence text, int start, int end, float[] widths) { - if (text instanceof String) { - return getTextWidths((String)text, start, end, widths); - } - if (text instanceof SpannedString || text instanceof SpannableString) { - return getTextWidths(text.toString(), start, end, widths); - } - if (text instanceof SpannableStringBuilder) { - return ((SpannableStringBuilder)text).getTextWidths(start, end, widths, this); - } - - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - int result = getTextWidths(buf, 0, end - start, widths); - TemporaryBuffer.recycle(buf); - return result; - } - - @Override - public int getTextWidths(String text, float[] widths) { - return super.getTextWidths(text, widths); - } - - /** - * Return the path (outline) for the specified text. - * Note: just like Canvas.drawText, this will respect the Align setting in - * the paint. - * - * @param text The text to retrieve the path from - * @param index The index of the first character in text - * @param count The number of characterss starting with index - * @param x The x coordinate of the text's origin - * @param y The y coordinate of the text's origin - * @param path The path to receive the data describing the text. Must - * be allocated by the caller. - */ - @Override - public void getTextPath(char[] text, int index, int count, - float x, float y, Path path) { - - // TODO this is the ORIGINAL implementation. REPLACE AS NEEDED OR REMOVE - - if ((index | count) < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // TODO native_getTextPath(mNativePaint, text, index, count, x, y, path.ni()); - - throw new UnsupportedOperationException("IMPLEMENT AS NEEDED"); - } - - /** - * Return the path (outline) for the specified text. - * Note: just like Canvas.drawText, this will respect the Align setting - * in the paint. - * - * @param text The text to retrieve the path from - * @param start The first character in the text - * @param end 1 past the last charcter in the text - * @param x The x coordinate of the text's origin - * @param y The y coordinate of the text's origin - * @param path The path to receive the data describing the text. Must - * be allocated by the caller. - */ - @Override - public void getTextPath(String text, int start, int end, - float x, float y, Path path) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - - getTextPath(text.toCharArray(), start, end - start, x, y, path); - } - - /** - * Return in bounds (allocated by the caller) the smallest rectangle that - * encloses all of the characters, with an implied origin at (0,0). - * - * @param text String to measure and return its bounds - * @param start Index of the first char in the string to measure - * @param end 1 past the last char in the string measure - * @param bounds Returns the unioned bounds of all the text. Must be - * allocated by the caller. - */ - @Override - public void getTextBounds(String text, int start, int end, Rect bounds) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (bounds == null) { - throw new NullPointerException("need bounds Rect"); - } - - getTextBounds(text.toCharArray(), start, end - start, bounds); - } - - /** - * Return in bounds (allocated by the caller) the smallest rectangle that - * encloses all of the characters, with an implied origin at (0,0). - * - * @param text Array of chars to measure and return their unioned bounds - * @param index Index of the first char in the array to measure - * @param count The number of chars, beginning at index, to measure - * @param bounds Returns the unioned bounds of all the text. Must be - * allocated by the caller. - */ - @Override - public void getTextBounds(char[] text, int index, int count, Rect bounds) { - // FIXME - if (mFonts.size() > 0) { - if ((index | count) < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - if (bounds == null) { - throw new NullPointerException("need bounds Rect"); - } - - FontInfo mainInfo = mFonts.get(0); - - Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext); - bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight()); - } - } - - public static void finalizer(int foo) { - // pass - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java new file mode 100644 index 0000000..71d346a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.PaintFlagsDrawFilter + * + * Through the layoutlib_create tool, the original native methods of PaintFlagsDrawFilter have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PaintFlagsDrawFilter class. + * + * Because this extends {@link DrawFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the DrawFilter classes will be added to the manager owned by + * {@link DrawFilter_Delegate}. + * + * @see DrawFilter_Delegate + * + */ +public class PaintFlagsDrawFilter_Delegate extends DrawFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Paint Flags Draw Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(int clearBits, int setBits) { + PaintFlagsDrawFilter_Delegate newDelegate = new PaintFlagsDrawFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java new file mode 100644 index 0000000..373f482 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -0,0 +1,1240 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Paint.FontMetrics; +import android.graphics.Paint.FontMetricsInt; +import android.text.TextUtils; + +import java.awt.BasicStroke; +import java.awt.Font; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.Toolkit; +import java.awt.font.FontRenderContext; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Delegate implementing the native methods of android.graphics.Paint + * + * Through the layoutlib_create tool, the original native methods of Paint have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Paint class. + * + * @see DelegateManager + * + */ +public class Paint_Delegate { + + /** + * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + */ + /*package*/ static final class FontInfo { + Font mFont; + java.awt.FontMetrics mMetrics; + } + + // ---- delegate manager ---- + private static final DelegateManager<Paint_Delegate> sManager = + new DelegateManager<Paint_Delegate>(Paint_Delegate.class); + + // ---- delegate helper data ---- + private List<FontInfo> mFonts; + private final FontRenderContext mFontContext = new FontRenderContext( + new AffineTransform(), true, true); + + // ---- delegate data ---- + private int mFlags; + private int mColor; + private int mStyle; + private int mCap; + private int mJoin; + private int mTextAlign; + private Typeface_Delegate mTypeface; + private float mStrokeWidth; + private float mStrokeMiter; + private float mTextSize; + private float mTextScaleX; + private float mTextSkewX; + + private Xfermode_Delegate mXfermode; + private ColorFilter_Delegate mColorFilter; + private Shader_Delegate mShader; + private PathEffect_Delegate mPathEffect; + private MaskFilter_Delegate mMaskFilter; + private Rasterizer_Delegate mRasterizer; + + + // ---- Public Helper methods ---- + + public static Paint_Delegate getDelegate(int native_paint) { + return sManager.getDelegate(native_paint); + } + + /** + * Returns the list of {@link Font} objects. The first item is the main font, the rest + * are fall backs for characters not present in the main font. + */ + public List<FontInfo> getFonts() { + return mFonts; + } + + public boolean isAntiAliased() { + return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0; + } + + public boolean isFilterBitmap() { + return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0; + } + + public int getStyle() { + return mStyle; + } + + public int getColor() { + return mColor; + } + + public int getAlpha() { + return mColor >>> 24; + } + + public void setAlpha(int alpha) { + mColor = (alpha << 24) | (mColor & 0x00FFFFFF); + } + + public int getTextAlign() { + return mTextAlign; + } + + public float getStrokeWidth() { + return mStrokeWidth; + } + + /** + * returns the value of stroke miter needed by the java api. + */ + public float getJavaStrokeMiter() { + float miter = mStrokeMiter * mStrokeWidth; + if (miter < 1.f) { + miter = 1.f; + } + return miter; + } + + public int getJavaCap() { + switch (Paint.sCapArray[mCap]) { + case BUTT: + return BasicStroke.CAP_BUTT; + case ROUND: + return BasicStroke.CAP_ROUND; + default: + case SQUARE: + return BasicStroke.CAP_SQUARE; + } + } + + public int getJavaJoin() { + switch (Paint.sJoinArray[mJoin]) { + default: + case MITER: + return BasicStroke.JOIN_MITER; + case ROUND: + return BasicStroke.JOIN_ROUND; + case BEVEL: + return BasicStroke.JOIN_BEVEL; + } + } + + public Stroke getJavaStroke() { + if (mPathEffect != null) { + if (mPathEffect.isSupported()) { + Stroke stroke = mPathEffect.getStroke(this); + assert stroke != null; + if (stroke != null) { + return stroke; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT, + mPathEffect.getSupportMessage(), + null, null /*data*/); + } + } + + // if no custom stroke as been set, set the default one. + return new BasicStroke( + getStrokeWidth(), + getJavaCap(), + getJavaJoin(), + getJavaStrokeMiter()); + } + + /** + * Returns the {@link Xfermode} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Xfermode_Delegate getXfermode() { + return mXfermode; + } + + /** + * Returns the {@link ColorFilter} delegate or null if none have been set + * + * @return the delegate or null. + */ + public ColorFilter_Delegate getColorFilter() { + return mColorFilter; + } + + /** + * Returns the {@link Shader} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Shader_Delegate getShader() { + return mShader; + } + + /** + * Returns the {@link MaskFilter} delegate or null if none have been set + * + * @return the delegate or null. + */ + public MaskFilter_Delegate getMaskFilter() { + return mMaskFilter; + } + + /** + * Returns the {@link Rasterizer} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Rasterizer_Delegate getRasterizer() { + return mRasterizer; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int getFlags(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.mFlags; + } + + @LayoutlibDelegate + /*package*/ static void setFlags(Paint thisPaint, int flags) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mFlags = flags; + } + + @LayoutlibDelegate + /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) { + setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter); + } + + @LayoutlibDelegate + /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) { + setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa); + } + + @LayoutlibDelegate + /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) { + setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText); + } + + @LayoutlibDelegate + /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) { + setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText); + } + + @LayoutlibDelegate + /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) { + setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText); + } + + @LayoutlibDelegate + /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) { + setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText); + } + + @LayoutlibDelegate + /*package*/ static void setDither(Paint thisPaint, boolean dither) { + setFlag(thisPaint, Paint.DITHER_FLAG, dither); + } + + @LayoutlibDelegate + /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) { + setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText); + } + + @LayoutlibDelegate + /*package*/ static int getColor(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.mColor; + } + + @LayoutlibDelegate + /*package*/ static void setColor(Paint thisPaint, int color) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mColor = color; + } + + @LayoutlibDelegate + /*package*/ static int getAlpha(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.getAlpha(); + } + + @LayoutlibDelegate + /*package*/ static void setAlpha(Paint thisPaint, int a) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.setAlpha(a); + } + + @LayoutlibDelegate + /*package*/ static float getStrokeWidth(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mStrokeWidth; + } + + @LayoutlibDelegate + /*package*/ static void setStrokeWidth(Paint thisPaint, float width) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mStrokeWidth = width; + } + + @LayoutlibDelegate + /*package*/ static float getStrokeMiter(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mStrokeMiter; + } + + @LayoutlibDelegate + /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mStrokeMiter = miter; + } + + @LayoutlibDelegate + /*package*/ static void nSetShadowLayer(Paint thisPaint, float radius, float dx, float dy, + int color) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.setShadowLayer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static float getTextSize(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextSize; + } + + @LayoutlibDelegate + /*package*/ static void setTextSize(Paint thisPaint, float textSize) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextSize = textSize; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float getTextScaleX(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextScaleX; + } + + @LayoutlibDelegate + /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextScaleX = scaleX; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float getTextSkewX(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextSkewX; + } + + @LayoutlibDelegate + /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextSkewX = skewX; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float ascent(Paint thisPaint) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + // Android expects negative ascent so we invert the value from Java. + return - javaMetrics.getAscent(); + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static float descent(Paint thisPaint) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + return javaMetrics.getDescent(); + } + + return 0; + + } + + @LayoutlibDelegate + /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.getFontMetrics(metrics); + } + + @LayoutlibDelegate + /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + if (fmi != null) { + // Android expects negative ascent so we invert the value from Java. + fmi.top = - javaMetrics.getMaxAscent(); + fmi.ascent = - javaMetrics.getAscent(); + fmi.descent = javaMetrics.getDescent(); + fmi.bottom = javaMetrics.getMaxDescent(); + fmi.leading = javaMetrics.getLeading(); + } + + return javaMetrics.getHeight(); + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index, + int count) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.measureText(text, index, count); + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end) { + return native_measureText(thisPaint, text.toCharArray(), start, end - start); + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, String text) { + return native_measureText(thisPaint, text.toCharArray(), 0, text.length()); + } + + @LayoutlibDelegate + /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count, + float maxWidth, float[] measuredWidth) { + + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + int inc = count > 0 ? 1 : -1; + + int measureIndex = 0; + float measureAcc = 0; + for (int i = index; i != index + count; i += inc, measureIndex++) { + int start, end; + if (i < index) { + start = i; + end = index; + } else { + start = index; + end = i; + } + + // measure from start to end + float res = delegate.measureText(text, start, end - start + 1); + + if (measuredWidth != null) { + measuredWidth[measureIndex] = res; + } + + measureAcc += res; + if (res > maxWidth) { + // we should not return this char index, but since it's 0-based + // and we need to return a count, we simply return measureIndex; + return measureIndex; + } + + } + + return measureIndex; + } + + @LayoutlibDelegate + /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards, + float maxWidth, float[] measuredWidth) { + return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth, + measuredWidth); + } + + @LayoutlibDelegate + /*package*/ static int native_init() { + Paint_Delegate newDelegate = new Paint_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_initWithPaint(int paint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(paint); + if (delegate == null) { + return 0; + } + + Paint_Delegate newDelegate = new Paint_Delegate(delegate); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.reset(); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_dst, int native_src) { + // get the delegate from the native int. + Paint_Delegate delegate_dst = sManager.getDelegate(native_dst); + if (delegate_dst == null) { + return; + } + + // get the delegate from the native int. + Paint_Delegate delegate_src = sManager.getDelegate(native_src); + if (delegate_src == null) { + return; + } + + delegate_dst.set(delegate_src); + } + + @LayoutlibDelegate + /*package*/ static int native_getStyle(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mStyle; + } + + @LayoutlibDelegate + /*package*/ static void native_setStyle(int native_object, int style) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mStyle = style; + } + + @LayoutlibDelegate + /*package*/ static int native_getStrokeCap(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mCap; + } + + @LayoutlibDelegate + /*package*/ static void native_setStrokeCap(int native_object, int cap) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mCap = cap; + } + + @LayoutlibDelegate + /*package*/ static int native_getStrokeJoin(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mJoin; + } + + @LayoutlibDelegate + /*package*/ static void native_setStrokeJoin(int native_object, int join) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mJoin = join; + } + + @LayoutlibDelegate + /*package*/ static boolean native_getFillPath(int native_object, int src, int dst) { + Paint_Delegate paint = sManager.getDelegate(native_object); + if (paint == null) { + return false; + } + + Path_Delegate srcPath = Path_Delegate.getDelegate(src); + if (srcPath == null) { + return true; + } + + Path_Delegate dstPath = Path_Delegate.getDelegate(dst); + if (dstPath == null) { + return true; + } + + Stroke stroke = paint.getJavaStroke(); + Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape()); + + dstPath.setJavaShape(strokeShape); + + // FIXME figure out the return value? + return true; + } + + @LayoutlibDelegate + /*package*/ static int native_setShader(int native_object, int shader) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return shader; + } + + delegate.mShader = Shader_Delegate.getDelegate(shader); + + return shader; + } + + @LayoutlibDelegate + /*package*/ static int native_setColorFilter(int native_object, int filter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return filter; + } + + delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);; + + // since none of those are supported, display a fidelity warning right away + if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER, + delegate.mColorFilter.getSupportMessage(), null, null /*data*/); + } + + return filter; + } + + @LayoutlibDelegate + /*package*/ static int native_setXfermode(int native_object, int xfermode) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return xfermode; + } + + delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode); + + return xfermode; + } + + @LayoutlibDelegate + /*package*/ static int native_setPathEffect(int native_object, int effect) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return effect; + } + + delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect); + + return effect; + } + + @LayoutlibDelegate + /*package*/ static int native_setMaskFilter(int native_object, int maskfilter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return maskfilter; + } + + delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter); + + // since none of those are supported, display a fidelity warning right away + if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, + delegate.mMaskFilter.getSupportMessage(), null, null /*data*/); + } + + return maskfilter; + } + + @LayoutlibDelegate + /*package*/ static int native_setTypeface(int native_object, int typeface) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.updateFontObject(); + return typeface; + } + + @LayoutlibDelegate + /*package*/ static int native_setRasterizer(int native_object, int rasterizer) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return rasterizer; + } + + delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer); + + // since none of those are supported, display a fidelity warning right away + if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER, + delegate.mRasterizer.getSupportMessage(), null, null /*data*/); + } + + return rasterizer; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextAlign(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mTextAlign; + } + + @LayoutlibDelegate + /*package*/ static void native_setTextAlign(int native_object, int align) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mTextAlign = align; + } + + @LayoutlibDelegate + /*package*/ static float native_getFontMetrics(int native_paint, FontMetrics metrics) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_paint); + if (delegate == null) { + return 0.f; + } + + return delegate.getFontMetrics(metrics); + } + + @LayoutlibDelegate + /*package*/ static int native_getTextWidths(int native_object, char[] text, int index, + int count, float[] widths) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + // FIXME: handle multi-char characters (see measureText) + float totalAdvance = 0; + for (int i = 0; i < count; i++) { + char c = text[i + index]; + boolean found = false; + for (FontInfo info : delegate.mFonts) { + if (info.mFont.canDisplay(c)) { + float adv = info.mMetrics.charWidth(c); + totalAdvance += adv; + if (widths != null) { + widths[i] = adv; + } + + found = true; + break; + } + } + + if (found == false) { + // no advance for this char. + if (widths != null) { + widths[i] = 0.f; + } + } + } + + return (int) totalAdvance; + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextWidths(int native_object, String text, int start, + int end, float[] widths) { + return native_getTextWidths(native_object, text.toCharArray(), start, end - start, widths); + } + + @LayoutlibDelegate + /*package*/ static float native_getTextRunAdvances(int native_object, + char[] text, int index, int count, int contextIndex, int contextCount, + int flags, float[] advances, int advancesIndex) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0.f; + } + + if (delegate.mFonts.size() > 0) { + // FIXME: handle multi-char characters (see measureText) + float totalAdvance = 0; + for (int i = 0; i < count; i++) { + char c = text[i + index]; + boolean found = false; + for (FontInfo info : delegate.mFonts) { + if (info.mFont.canDisplay(c)) { + float adv = info.mMetrics.charWidth(c); + totalAdvance += adv; + if (advances != null) { + advances[i] = adv; + } + + found = true; + break; + } + } + + if (found == false) { + // no advance for this char. + if (advances != null) { + advances[i] = 0.f; + } + } + } + + return totalAdvance; + } + + return 0; + + } + + @LayoutlibDelegate + /*package*/ static float native_getTextRunAdvances(int native_object, + String text, int start, int end, int contextStart, int contextEnd, + int flags, float[] advances, int advancesIndex) { + // FIXME: support contextStart, contextEnd and direction flag + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + return native_getTextRunAdvances(native_object, buffer, 0, count, contextStart, + contextEnd - contextStart, flags, advances, advancesIndex); + } + + @LayoutlibDelegate + /*package*/ static int native_getTextRunCursor(Paint thisPaint, int native_object, char[] text, + int contextStart, int contextLength, int flags, int offset, int cursorOpt) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextRunCursor is not supported.", null, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextRunCursor(Paint thisPaint, int native_object, String text, + int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextRunCursor is not supported.", null, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static void native_getTextPath(int native_object, int bidiFlags, + char[] text, int index, int count, float x, float y, int path) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_getTextPath(int native_object, int bidiFlags, + String text, int start, int end, float x, float y, int path) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetStringBounds(int nativePaint, String text, int start, + int end, Rect bounds) { + nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bounds); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetCharArrayBounds(int nativePaint, char[] text, int index, + int count, Rect bounds) { + + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + return; + } + + // FIXME should test if the main font can display all those characters. + // See MeasureText + if (delegate.mFonts.size() > 0) { + FontInfo mainInfo = delegate.mFonts.get(0); + + Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, + delegate.mFontContext); + bounds.set(0, 0, (int) rect.getWidth(), (int) rect.getHeight()); + } + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nativePaint) { + sManager.removeJavaReferenceFor(nativePaint); + } + + // ---- Private delegate/helper methods ---- + + /*package*/ Paint_Delegate() { + reset(); + } + + private Paint_Delegate(Paint_Delegate paint) { + set(paint); + } + + private void set(Paint_Delegate paint) { + mFlags = paint.mFlags; + mColor = paint.mColor; + mStyle = paint.mStyle; + mCap = paint.mCap; + mJoin = paint.mJoin; + mTextAlign = paint.mTextAlign; + mTypeface = paint.mTypeface; + mStrokeWidth = paint.mStrokeWidth; + mStrokeMiter = paint.mStrokeMiter; + mTextSize = paint.mTextSize; + mTextScaleX = paint.mTextScaleX; + mTextSkewX = paint.mTextSkewX; + mXfermode = paint.mXfermode; + mColorFilter = paint.mColorFilter; + mShader = paint.mShader; + mPathEffect = paint.mPathEffect; + mMaskFilter = paint.mMaskFilter; + mRasterizer = paint.mRasterizer; + updateFontObject(); + } + + private void reset() { + mFlags = Paint.DEFAULT_PAINT_FLAGS; + mColor = 0xFF000000; + mStyle = Paint.Style.FILL.nativeInt; + mCap = Paint.Cap.BUTT.nativeInt; + mJoin = Paint.Join.MITER.nativeInt; + mTextAlign = 0; + mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance); + mStrokeWidth = 1.f; + mStrokeMiter = 4.f; + mTextSize = 20.f; + mTextScaleX = 1.f; + mTextSkewX = 0.f; + mXfermode = null; + mColorFilter = null; + mShader = null; + mPathEffect = null; + mMaskFilter = null; + mRasterizer = null; + updateFontObject(); + } + + /** + * Update the {@link Font} object from the typeface, text size and scaling + */ + @SuppressWarnings("deprecation") + private void updateFontObject() { + if (mTypeface != null) { + // Get the fonts from the TypeFace object. + List<Font> fonts = mTypeface.getFonts(); + + // create new font objects as well as FontMetrics, based on the current text size + // and skew info. + ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); + for (Font font : fonts) { + FontInfo info = new FontInfo(); + info.mFont = font.deriveFont(mTextSize); + if (mTextScaleX != 1.0 || mTextSkewX != 0) { + // TODO: support skew + info.mFont = info.mFont.deriveFont(new AffineTransform( + mTextScaleX, mTextSkewX, 0, 0, 1, 0)); + } + info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); + + infoList.add(info); + } + + mFonts = Collections.unmodifiableList(infoList); + } + } + + /*package*/ float measureText(char[] text, int index, int count) { + + // WARNING: the logic in this method is similar to Canvas_Delegate.native_drawText + // Any change to this method should be reflected there as well + + if (mFonts.size() > 0) { + FontInfo mainFont = mFonts.get(0); + int i = index; + int lastIndex = index + count; + float total = 0f; + while (i < lastIndex) { + // always start with the main font. + int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); + if (upTo == -1) { + // shortcut to exit + return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); + } else if (upTo > 0) { + total += mainFont.mMetrics.charsWidth(text, i, upTo - i); + i = upTo; + // don't call continue at this point. Since it is certain the main font + // cannot display the font a index upTo (now ==i), we move on to the + // fallback fonts directly. + } + + // no char supported, attempt to read the next char(s) with the + // fallback font. In this case we only test the first character + // and then go back to test with the main font. + // Special test for 2-char characters. + boolean foundFont = false; + for (int f = 1 ; f < mFonts.size() ; f++) { + FontInfo fontInfo = mFonts.get(f); + + // need to check that the font can display the character. We test + // differently if the char is a high surrogate. + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); + if (upTo == -1) { + total += fontInfo.mMetrics.charsWidth(text, i, charCount); + i += charCount; + foundFont = true; + break; + + } + } + + // in case no font can display the char, measure it with the main font. + if (foundFont == false) { + int size = Character.isHighSurrogate(text[i]) ? 2 : 1; + total += mainFont.mMetrics.charsWidth(text, i, size); + i += size; + } + } + + return total; + } + + return 0; + } + + private float getFontMetrics(FontMetrics metrics) { + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; + if (metrics != null) { + // Android expects negative ascent so we invert the value from Java. + metrics.top = - javaMetrics.getMaxAscent(); + metrics.ascent = - javaMetrics.getAscent(); + metrics.descent = javaMetrics.getDescent(); + metrics.bottom = javaMetrics.getMaxDescent(); + metrics.leading = javaMetrics.getLeading(); + } + + return javaMetrics.getHeight(); + } + + return 0; + } + + + + private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + if (flagValue) { + delegate.mFlags |= flagMask; + } else { + delegate.mFlags &= ~flagMask; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path.java b/tools/layoutlib/bridge/src/android/graphics/Path.java deleted file mode 100644 index 12d2cde..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Path.java +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (C) 2006 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; - -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; -import java.awt.geom.PathIterator; -import java.awt.geom.Rectangle2D; - -/** - * The Path class encapsulates compound (multiple contour) geometric paths - * consisting of straight line segments, quadratic curves, and cubic curves. - * It can be drawn with canvas.drawPath(path, paint), either filled or stroked - * (based on the paint's Style), or it can be used for clipping or to draw - * text on a path. - */ -public class Path { - - private FillType mFillType = FillType.WINDING; - private GeneralPath mPath = new GeneralPath(); - - private float mLastX = 0; - private float mLastY = 0; - - //---------- Custom methods ---------- - - public Shape getAwtShape() { - return mPath; - } - - //---------- - - /** - * Create an empty path - */ - public Path() { - } - - /** - * Create a new path, copying the contents from the src path. - * - * @param src The path to copy from when initializing the new path - */ - public Path(Path src) { - mPath.append(src.mPath, false /* connect */); - } - - /** - * Clear any lines and curves from the path, making it empty. - * This does NOT change the fill-type setting. - */ - public void reset() { - mPath = new GeneralPath(); - } - - /** - * Rewinds the path: clears any lines and curves from the path but - * keeps the internal data structure for faster reuse. - */ - public void rewind() { - // FIXME - throw new UnsupportedOperationException(); - } - - /** Replace the contents of this with the contents of src. - */ - public void set(Path src) { - mPath.append(src.mPath, false /* connect */); - } - - /** Enum for the ways a path may be filled - */ - public enum FillType { - // these must match the values in SkPath.h - WINDING (GeneralPath.WIND_NON_ZERO, false), - EVEN_ODD (GeneralPath.WIND_EVEN_ODD, false), - INVERSE_WINDING (GeneralPath.WIND_NON_ZERO, true), - INVERSE_EVEN_ODD(GeneralPath.WIND_EVEN_ODD, true); - - FillType(int rule, boolean inverse) { - this.rule = rule; - this.inverse = inverse; - } - - final int rule; - final boolean inverse; - } - - /** - * Return the path's fill type. This defines how "inside" is - * computed. The default value is WINDING. - * - * @return the path's fill type - */ - public FillType getFillType() { - return mFillType; - } - - /** - * Set the path's fill type. This defines how "inside" is computed. - * - * @param ft The new fill type for this path - */ - public void setFillType(FillType ft) { - mFillType = ft; - mPath.setWindingRule(ft.rule); - } - - /** - * Returns true if the filltype is one of the INVERSE variants - * - * @return true if the filltype is one of the INVERSE variants - */ - public boolean isInverseFillType() { - return mFillType.inverse; - } - - /** - * Toggles the INVERSE state of the filltype - */ - public void toggleInverseFillType() { - switch (mFillType) { - case WINDING: - mFillType = FillType.INVERSE_WINDING; - break; - case EVEN_ODD: - mFillType = FillType.INVERSE_EVEN_ODD; - break; - case INVERSE_WINDING: - mFillType = FillType.WINDING; - break; - case INVERSE_EVEN_ODD: - mFillType = FillType.EVEN_ODD; - break; - } - } - - /** - * Returns true if the path is empty (contains no lines or curves) - * - * @return true if the path is empty (contains no lines or curves) - */ - public boolean isEmpty() { - return mPath.getCurrentPoint() == null; - } - - /** - * Returns true if the path specifies a rectangle. If so, and if rect is - * not null, set rect to the bounds of the path. If the path does not - * specify a rectangle, return false and ignore rect. - * - * @param rect If not null, returns the bounds of the path if it specifies - * a rectangle - * @return true if the path specifies a rectangle - */ - public boolean isRect(RectF rect) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Compute the bounds of the path, and write the answer into bounds. If the - * path contains 0 or 1 points, the bounds is set to (0,0,0,0) - * - * @param bounds Returns the computed bounds of the path - * @param exact If true, return the exact (but slower) bounds, else return - * just the bounds of all control points - */ - public void computeBounds(RectF bounds, boolean exact) { - Rectangle2D rect = mPath.getBounds2D(); - bounds.left = (float)rect.getMinX(); - bounds.right = (float)rect.getMaxX(); - bounds.top = (float)rect.getMinY(); - bounds.bottom = (float)rect.getMaxY(); - } - - /** - * Hint to the path to prepare for adding more points. This can allow the - * path to more efficiently allocate its storage. - * - * @param extraPtCount The number of extra points that may be added to this - * path - */ - public void incReserve(int extraPtCount) { - // pass - } - - /** - * Set the beginning of the next contour to the point (x,y). - * - * @param x The x-coordinate of the start of a new contour - * @param y The y-coordinate of the start of a new contour - */ - public void moveTo(float x, float y) { - mPath.moveTo(mLastX = x, mLastY = y); - } - - /** - * Set the beginning of the next contour relative to the last point on the - * previous contour. If there is no previous contour, this is treated the - * same as moveTo(). - * - * @param dx The amount to add to the x-coordinate of the end of the - * previous contour, to specify the start of a new contour - * @param dy The amount to add to the y-coordinate of the end of the - * previous contour, to specify the start of a new contour - */ - public void rMoveTo(float dx, float dy) { - dx += mLastX; - dy += mLastY; - mPath.moveTo(mLastX = dx, mLastY = dy); - } - - /** - * Add a line from the last point to the specified point (x,y). - * If no moveTo() call has been made for this contour, the first point is - * automatically set to (0,0). - * - * @param x The x-coordinate of the end of a line - * @param y The y-coordinate of the end of a line - */ - public void lineTo(float x, float y) { - mPath.lineTo(mLastX = x, mLastY = y); - } - - /** - * Same as lineTo, but the coordinates are considered relative to the last - * point on this contour. If there is no previous point, then a moveTo(0,0) - * is inserted automatically. - * - * @param dx The amount to add to the x-coordinate of the previous point on - * this contour, to specify a line - * @param dy The amount to add to the y-coordinate of the previous point on - * this contour, to specify a line - */ - public void rLineTo(float dx, float dy) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx += mLastX; - dy += mLastY; - mPath.lineTo(mLastX = dx, mLastY = dy); - } - - /** - * Add a quadratic bezier from the last point, approaching control point - * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for - * this contour, the first point is automatically set to (0,0). - * - * @param x1 The x-coordinate of the control point on a quadratic curve - * @param y1 The y-coordinate of the control point on a quadratic curve - * @param x2 The x-coordinate of the end point on a quadratic curve - * @param y2 The y-coordinate of the end point on a quadratic curve - */ - public void quadTo(float x1, float y1, float x2, float y2) { - mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); - } - - /** - * Same as quadTo, but the coordinates are considered relative to the last - * point on this contour. If there is no previous point, then a moveTo(0,0) - * is inserted automatically. - * - * @param dx1 The amount to add to the x-coordinate of the last point on - * this contour, for the control point of a quadratic curve - * @param dy1 The amount to add to the y-coordinate of the last point on - * this contour, for the control point of a quadratic curve - * @param dx2 The amount to add to the x-coordinate of the last point on - * this contour, for the end point of a quadratic curve - * @param dy2 The amount to add to the y-coordinate of the last point on - * this contour, for the end point of a quadratic curve - */ - public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx1 += mLastX; - dy1 += mLastY; - dx2 += mLastX; - dy2 += mLastY; - mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); - } - - /** - * Add a cubic bezier from the last point, approaching control points - * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been - * made for this contour, the first point is automatically set to (0,0). - * - * @param x1 The x-coordinate of the 1st control point on a cubic curve - * @param y1 The y-coordinate of the 1st control point on a cubic curve - * @param x2 The x-coordinate of the 2nd control point on a cubic curve - * @param y2 The y-coordinate of the 2nd control point on a cubic curve - * @param x3 The x-coordinate of the end point on a cubic curve - * @param y3 The y-coordinate of the end point on a cubic curve - */ - public void cubicTo(float x1, float y1, float x2, float y2, - float x3, float y3) { - mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); - } - - /** - * Same as cubicTo, but the coordinates are considered relative to the - * current point on this contour. If there is no previous point, then a - * moveTo(0,0) is inserted automatically. - */ - public void rCubicTo(float dx1, float dy1, float dx2, float dy2, - float dx3, float dy3) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx1 += mLastX; - dy1 += mLastY; - dx2 += mLastX; - dy2 += mLastY; - dx3 += mLastX; - dy3 += mLastY; - mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); - } - - /** - * Append the specified arc to the path as a new contour. If the start of - * the path is different from the path's current last point, then an - * automatic lineTo() is added to connect the current contour to the - * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. The sweep angle is tread mod 360. - * - * @param oval The bounds of oval defining shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated - * mod 360. - * @param forceMoveTo If true, always begin a new contour with the arc - */ - public void arcTo(RectF oval, float startAngle, float sweepAngle, - boolean forceMoveTo) { - throw new UnsupportedOperationException(); - } - - /** - * Append the specified arc to the path as a new contour. If the start of - * the path is different from the path's current last point, then an - * automatic lineTo() is added to connect the current contour to the - * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. - * - * @param oval The bounds of oval defining shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise - */ - public void arcTo(RectF oval, float startAngle, float sweepAngle) { - throw new UnsupportedOperationException(); - } - - /** - * Close the current contour. If the current point is not equal to the - * first point of the contour, a line segment is automatically added. - */ - public void close() { - mPath.closePath(); - } - - /** - * Specifies how closed shapes (e.g. rects, ovals) are oriented when they - * are added to a path. - */ - public enum Direction { - /** clockwise */ - CW (0), // must match enum in SkPath.h - /** counter-clockwise */ - CCW (1); // must match enum in SkPath.h - - Direction(int ni) { - nativeInt = ni; - } - final int nativeInt; - } - - /** - * Add a closed rectangle contour to the path - * - * @param rect The rectangle to add as a closed contour to the path - * @param dir The direction to wind the rectangle's contour - */ - public void addRect(RectF rect, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - - addRect(rect.left, rect.top, rect.right, rect.bottom, dir); - } - - /** - * Add a closed rectangle contour to the path - * - * @param left The left side of a rectangle to add to the path - * @param top The top of a rectangle to add to the path - * @param right The right side of a rectangle to add to the path - * @param bottom The bottom of a rectangle to add to the path - * @param dir The direction to wind the rectangle's contour - */ - public void addRect(float left, float top, float right, float bottom, - Direction dir) { - moveTo(left, top); - - switch (dir) { - case CW: - lineTo(right, top); - lineTo(right, bottom); - lineTo(left, bottom); - break; - case CCW: - lineTo(left, bottom); - lineTo(right, bottom); - lineTo(right, top); - break; - } - - close(); - } - - /** - * Add a closed oval contour to the path - * - * @param oval The bounds of the oval to add as a closed contour to the path - * @param dir The direction to wind the oval's contour - */ - public void addOval(RectF oval, Direction dir) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } - - // FIXME Need to support direction - Ellipse2D ovalShape = new Ellipse2D.Float(oval.left, oval.top, oval.width(), oval.height()); - - mPath.append(ovalShape, false /* connect */); - } - - /** - * Add a closed circle contour to the path - * - * @param x The x-coordinate of the center of a circle to add to the path - * @param y The y-coordinate of the center of a circle to add to the path - * @param radius The radius of a circle to add to the path - * @param dir The direction to wind the circle's contour - */ - public void addCircle(float x, float y, float radius, Direction dir) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add the specified arc to the path as a new contour. - * - * @param oval The bounds of oval defining the shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise - */ - public void addArc(RectF oval, float startAngle, float sweepAngle) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a closed round-rectangle contour to the path - * - * @param rect The bounds of a round-rectangle to add to the path - * @param rx The x-radius of the rounded corners on the round-rectangle - * @param ry The y-radius of the rounded corners on the round-rectangle - * @param dir The direction to wind the round-rectangle's contour - */ - public void addRoundRect(RectF rect, float rx, float ry, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a closed round-rectangle contour to the path. Each corner receives - * two radius values [X, Y]. The corners are ordered top-left, top-right, - * bottom-right, bottom-left - * - * @param rect The bounds of a round-rectangle to add to the path - * @param radii Array of 8 values, 4 pairs of [X,Y] radii - * @param dir The direction to wind the round-rectangle's contour - */ - public void addRoundRect(RectF rect, float[] radii, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - if (radii.length < 8) { - throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a copy of src to the path, offset by (dx,dy) - * - * @param src The path to add as a new contour - * @param dx The amount to translate the path in X as it is added - */ - public void addPath(Path src, float dx, float dy) { - PathIterator iterator = src.mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); - mPath.append(iterator, false /* connect */); - } - - /** - * Add a copy of src to the path - * - * @param src The path that is appended to the current path - */ - public void addPath(Path src) { - addPath(src, 0, 0); - } - - /** - * Add a copy of src to the path, transformed by matrix - * - * @param src The path to add as a new contour - */ - public void addPath(Path src, Matrix matrix) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Offset the path by (dx,dy), returning true on success - * - * @param dx The amount in the X direction to offset the entire path - * @param dy The amount in the Y direction to offset the entire path - * @param dst The translated path is written here. If this is null, then - * the original path is modified. - */ - public void offset(float dx, float dy, Path dst) { - GeneralPath newPath = new GeneralPath(); - - PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); - - newPath.append(iterator, false /* connect */); - - if (dst != null) { - dst.mPath = newPath; - } else { - mPath = newPath; - } - } - - /** - * Offset the path by (dx,dy), returning true on success - * - * @param dx The amount in the X direction to offset the entire path - * @param dy The amount in the Y direction to offset the entire path - */ - public void offset(float dx, float dy) { - offset(dx, dy, null /* dst */); - } - - /** - * Sets the last point of the path. - * - * @param dx The new X coordinate for the last point - * @param dy The new Y coordinate for the last point - */ - public void setLastPoint(float dx, float dy) { - mLastX = dx; - mLastY = dy; - } - - /** - * Transform the points in this path by matrix, and write the answer - * into dst. If dst is null, then the the original path is modified. - * - * @param matrix The matrix to apply to the path - * @param dst The transformed path is written here. If dst is null, - * then the the original path is modified - */ - public void transform(Matrix matrix, Path dst) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Transform the points in this path by matrix. - * - * @param matrix The matrix to apply to the path - */ - public void transform(Matrix matrix) { - transform(matrix, null /* dst */); - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java new file mode 100644 index 0000000..c448f0e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java @@ -0,0 +1,72 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.PathDashPathEffect + * + * Through the layoutlib_create tool, the original native methods of PathDashPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PathDashPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class PathDashPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Path Dash Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int native_path, float advance, float phase, + int native_style) { + PathDashPathEffect_Delegate newDelegate = new PathDashPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java new file mode 100644 index 0000000..bd2b6de --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java @@ -0,0 +1,69 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.PathEffect + * + * Through the layoutlib_create tool, the original native methods of PathEffect have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PathEffect class. + * + * This also serve as a base class for all PathEffect delegate classes. + * + * @see DelegateManager + * + */ +public abstract class PathEffect_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<PathEffect_Delegate> sManager = + new DelegateManager<PathEffect_Delegate>(PathEffect_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static PathEffect_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract Stroke getStroke(Paint_Delegate paint); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_patheffect) { + sManager.removeJavaReferenceFor(native_patheffect); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java new file mode 100644 index 0000000..6c9f48f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -0,0 +1,761 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Path.Direction; +import android.graphics.Path.FillType; + +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +/** + * Delegate implementing the native methods of android.graphics.Path + * + * Through the layoutlib_create tool, the original native methods of Path have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Path class. + * + * @see DelegateManager + * + */ +public final class Path_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Path_Delegate> sManager = + new DelegateManager<Path_Delegate>(Path_Delegate.class); + + // ---- delegate data ---- + private FillType mFillType = FillType.WINDING; + private GeneralPath mPath = new GeneralPath(); + + private float mLastX = 0; + private float mLastY = 0; + + // ---- Public Helper methods ---- + + public static Path_Delegate getDelegate(int nPath) { + return sManager.getDelegate(nPath); + } + + public Shape getJavaShape() { + return mPath; + } + + public void setJavaShape(Shape shape) { + mPath.reset(); + mPath.append(shape, false /*connect*/); + } + + public void reset() { + mPath.reset(); + } + + public void setPathIterator(PathIterator iterator) { + mPath.reset(); + mPath.append(iterator, false /*connect*/); + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int init1() { + // create the delegate + Path_Delegate newDelegate = new Path_Delegate(); + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int init2(int nPath) { + // create the delegate + Path_Delegate newDelegate = new Path_Delegate(); + + // get the delegate to copy, which could be null if nPath is 0 + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate != null) { + newDelegate.set(pathDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mPath.reset(); + } + + @LayoutlibDelegate + /*package*/ static void native_rewind(int nPath) { + // call out to reset since there's nothing to optimize in + // terms of data structs. + native_reset(nPath); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_dst, int native_src) { + Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst); + if (pathDstDelegate == null) { + return; + } + + Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src); + if (pathSrcDelegate == null) { + return; + } + + pathDstDelegate.set(pathSrcDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_getFillType(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return 0; + } + + return pathDelegate.mFillType.nativeInt; + } + + @LayoutlibDelegate + /*package*/ static void native_setFillType(int nPath, int ft) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mFillType = Path.sFillTypeArray[ft]; + } + + @LayoutlibDelegate + /*package*/ static boolean native_isEmpty(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return true; + } + + return pathDelegate.isEmpty(); + } + + @LayoutlibDelegate + /*package*/ static boolean native_isRect(int nPath, RectF rect) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return false; + } + + // create an Area that can test if the path is a rect + Area area = new Area(pathDelegate.mPath); + if (area.isRectangular()) { + if (rect != null) { + pathDelegate.fillBounds(rect); + } + + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_computeBounds(int nPath, RectF bounds) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.fillBounds(bounds); + } + + @LayoutlibDelegate + /*package*/ static void native_incReserve(int nPath, int extraPtCount) { + // since we use a java2D path, there's no way to pre-allocate new points, + // so we do nothing. + } + + @LayoutlibDelegate + /*package*/ static void native_moveTo(int nPath, float x, float y) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.moveTo(x, y); + } + + @LayoutlibDelegate + /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rMoveTo(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_lineTo(int nPath, float x, float y) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.lineTo(x, y); + } + + @LayoutlibDelegate + /*package*/ static void native_rLineTo(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rLineTo(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.quadTo(x1, y1, x2, y2); + } + + @LayoutlibDelegate + /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rQuadTo(dx1, dy1, dx2, dy2); + } + + @LayoutlibDelegate + /*package*/ static void native_cubicTo(int nPath, float x1, float y1, + float x2, float y2, float x3, float y3) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3); + } + + @LayoutlibDelegate + /*package*/ static void native_rCubicTo(int nPath, float x1, float y1, + float x2, float y2, float x3, float y3) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3); + } + + @LayoutlibDelegate + /*package*/ static void native_arcTo(int nPath, RectF oval, + float startAngle, float sweepAngle, boolean forceMoveTo) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo); + } + + @LayoutlibDelegate + /*package*/ static void native_close(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.close(); + } + + @LayoutlibDelegate + /*package*/ static void native_addRect(int nPath, RectF rect, int dir) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); + } + + @LayoutlibDelegate + /*package*/ static void native_addRect(int nPath, + float left, float top, float right, float bottom, int dir) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.addRect(left, top, right, bottom, dir); + } + + @LayoutlibDelegate + /*package*/ static void native_addOval(int nPath, RectF oval, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addOval is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addCircle is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addArc(int nPath, RectF oval, + float startAngle, float sweepAngle) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addArc is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addRoundRect(int nPath, RectF rect, + float rx, float ry, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addRoundRect is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addRoundRect is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src) { + native_addPath(nPath, src, 0, 0); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src, int matrix) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + // could be null if the int is 0; + Path_Delegate dstDelegate = sManager.getDelegate(dst_path); + + pathDelegate.offset(dx, dy, dstDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_offset(int nPath, float dx, float dy) { + native_offset(nPath, dx, dy, 0); + } + + @LayoutlibDelegate + /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mLastX = dx; + pathDelegate.mLastY = dy; + } + + @LayoutlibDelegate + /*package*/ static void native_transform(int nPath, int matrix, + int dst_path) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + // this can be null if dst_path is 0 + Path_Delegate dstDelegate = sManager.getDelegate(dst_path); + + pathDelegate.transform(matrixDelegate, dstDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_transform(int nPath, int matrix) { + native_transform(nPath, matrix, 0); + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nPath) { + sManager.removeJavaReferenceFor(nPath); + } + + + // ---- Private helper methods ---- + + private void set(Path_Delegate delegate) { + mPath.reset(); + setFillType(delegate.mFillType); + mPath.append(delegate.mPath, false /*connect*/); + } + + private void setFillType(FillType fillType) { + mFillType = fillType; + mPath.setWindingRule(getWindingRule(fillType)); + } + + /** + * Returns the Java2D winding rules matching a given Android {@link FillType}. + * @param type the android fill type + * @return the matching java2d winding rule. + */ + private static int getWindingRule(FillType type) { + switch (type) { + case WINDING: + case INVERSE_WINDING: + return GeneralPath.WIND_NON_ZERO; + case EVEN_ODD: + case INVERSE_EVEN_ODD: + return GeneralPath.WIND_EVEN_ODD; + } + + assert false; + throw new IllegalArgumentException(); + } + + private static Direction getDirection(int direction) { + for (Direction d : Direction.values()) { + if (direction == d.nativeInt) { + return d; + } + } + + assert false; + return null; + } + + /** + * Returns whether the path is empty. + * @return true if the path is empty. + */ + private boolean isEmpty() { + return mPath.getCurrentPoint() == null; + } + + /** + * Fills the given {@link RectF} with the path bounds. + * @param bounds the RectF to be filled. + */ + private void fillBounds(RectF bounds) { + Rectangle2D rect = mPath.getBounds2D(); + bounds.left = (float)rect.getMinX(); + bounds.right = (float)rect.getMaxX(); + bounds.top = (float)rect.getMinY(); + bounds.bottom = (float)rect.getMaxY(); + } + + /** + * Set the beginning of the next contour to the point (x,y). + * + * @param x The x-coordinate of the start of a new contour + * @param y The y-coordinate of the start of a new contour + */ + private void moveTo(float x, float y) { + mPath.moveTo(mLastX = x, mLastY = y); + } + + /** + * Set the beginning of the next contour relative to the last point on the + * previous contour. If there is no previous contour, this is treated the + * same as moveTo(). + * + * @param dx The amount to add to the x-coordinate of the end of the + * previous contour, to specify the start of a new contour + * @param dy The amount to add to the y-coordinate of the end of the + * previous contour, to specify the start of a new contour + */ + private void rMoveTo(float dx, float dy) { + dx += mLastX; + dy += mLastY; + mPath.moveTo(mLastX = dx, mLastY = dy); + } + + /** + * Add a line from the last point to the specified point (x,y). + * If no moveTo() call has been made for this contour, the first point is + * automatically set to (0,0). + * + * @param x The x-coordinate of the end of a line + * @param y The y-coordinate of the end of a line + */ + private void lineTo(float x, float y) { + mPath.lineTo(mLastX = x, mLastY = y); + } + + /** + * Same as lineTo, but the coordinates are considered relative to the last + * point on this contour. If there is no previous point, then a moveTo(0,0) + * is inserted automatically. + * + * @param dx The amount to add to the x-coordinate of the previous point on + * this contour, to specify a line + * @param dy The amount to add to the y-coordinate of the previous point on + * this contour, to specify a line + */ + private void rLineTo(float dx, float dy) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx += mLastX; + dy += mLastY; + mPath.lineTo(mLastX = dx, mLastY = dy); + } + + /** + * Add a quadratic bezier from the last point, approaching control point + * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for + * this contour, the first point is automatically set to (0,0). + * + * @param x1 The x-coordinate of the control point on a quadratic curve + * @param y1 The y-coordinate of the control point on a quadratic curve + * @param x2 The x-coordinate of the end point on a quadratic curve + * @param y2 The y-coordinate of the end point on a quadratic curve + */ + private void quadTo(float x1, float y1, float x2, float y2) { + mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); + } + + /** + * Same as quadTo, but the coordinates are considered relative to the last + * point on this contour. If there is no previous point, then a moveTo(0,0) + * is inserted automatically. + * + * @param dx1 The amount to add to the x-coordinate of the last point on + * this contour, for the control point of a quadratic curve + * @param dy1 The amount to add to the y-coordinate of the last point on + * this contour, for the control point of a quadratic curve + * @param dx2 The amount to add to the x-coordinate of the last point on + * this contour, for the end point of a quadratic curve + * @param dy2 The amount to add to the y-coordinate of the last point on + * this contour, for the end point of a quadratic curve + */ + private void rQuadTo(float dx1, float dy1, float dx2, float dy2) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx1 += mLastX; + dy1 += mLastY; + dx2 += mLastX; + dy2 += mLastY; + mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); + } + + /** + * Add a cubic bezier from the last point, approaching control points + * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been + * made for this contour, the first point is automatically set to (0,0). + * + * @param x1 The x-coordinate of the 1st control point on a cubic curve + * @param y1 The y-coordinate of the 1st control point on a cubic curve + * @param x2 The x-coordinate of the 2nd control point on a cubic curve + * @param y2 The y-coordinate of the 2nd control point on a cubic curve + * @param x3 The x-coordinate of the end point on a cubic curve + * @param y3 The y-coordinate of the end point on a cubic curve + */ + private void cubicTo(float x1, float y1, float x2, float y2, + float x3, float y3) { + mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); + } + + /** + * Same as cubicTo, but the coordinates are considered relative to the + * current point on this contour. If there is no previous point, then a + * moveTo(0,0) is inserted automatically. + */ + private void rCubicTo(float dx1, float dy1, float dx2, float dy2, + float dx3, float dy3) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx1 += mLastX; + dy1 += mLastY; + dx2 += mLastX; + dy2 += mLastY; + dx3 += mLastX; + dy3 += mLastY; + mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); + } + + /** + * Append the specified arc to the path as a new contour. If the start of + * the path is different from the path's current last point, then an + * automatic lineTo() is added to connect the current contour to the + * start of the arc. However, if the path is empty, then we call moveTo() + * with the first point of the arc. The sweep angle is tread mod 360. + * + * @param oval The bounds of oval defining shape and size of the arc + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated + * mod 360. + * @param forceMoveTo If true, always begin a new contour with the arc + */ + private void arcTo(RectF oval, float startAngle, float sweepAngle, + boolean forceMoveTo) { + Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle, + sweepAngle, Arc2D.OPEN); + mPath.append(arc, true /*connect*/); + + resetLastPointFromPath(); + } + + /** + * Close the current contour. If the current point is not equal to the + * first point of the contour, a line segment is automatically added. + */ + private void close() { + mPath.closePath(); + } + + private void resetLastPointFromPath() { + Point2D last = mPath.getCurrentPoint(); + mLastX = (float) last.getX(); + mLastY = (float) last.getY(); + } + + /** + * Add a closed rectangle contour to the path + * + * @param left The left side of a rectangle to add to the path + * @param top The top of a rectangle to add to the path + * @param right The right side of a rectangle to add to the path + * @param bottom The bottom of a rectangle to add to the path + * @param dir The direction to wind the rectangle's contour + */ + private void addRect(float left, float top, float right, float bottom, + int dir) { + moveTo(left, top); + + Direction direction = getDirection(dir); + + switch (direction) { + case CW: + lineTo(right, top); + lineTo(right, bottom); + lineTo(left, bottom); + break; + case CCW: + lineTo(left, bottom); + lineTo(right, bottom); + lineTo(right, top); + break; + } + + close(); + + resetLastPointFromPath(); + } + + /** + * Offset the path by (dx,dy), returning true on success + * + * @param dx The amount in the X direction to offset the entire path + * @param dy The amount in the Y direction to offset the entire path + * @param dst The translated path is written here. If this is null, then + * the original path is modified. + */ + public void offset(float dx, float dy, Path_Delegate dst) { + GeneralPath newPath = new GeneralPath(); + + PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); + + newPath.append(iterator, false /*connect*/); + + if (dst != null) { + dst.mPath = newPath; + } else { + mPath = newPath; + } + } + + /** + * Transform the points in this path by matrix, and write the answer + * into dst. If dst is null, then the the original path is modified. + * + * @param matrix The matrix to apply to the path + * @param dst The transformed path is written here. If dst is null, + * then the the original path is modified + */ + public void transform(Matrix_Delegate matrix, Path_Delegate dst) { + if (matrix.hasPerspective()) { + assert false; + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, + "android.graphics.Path#transform() only " + + "supports affine transformations.", null, null /*data*/); + } + + GeneralPath newPath = new GeneralPath(); + + PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform()); + + newPath.append(iterator, false /*connect*/); + + if (dst != null) { + dst.mPath = newPath; + } else { + mPath = newPath; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java new file mode 100644 index 0000000..4ab044b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java @@ -0,0 +1,70 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.PixelXorXfermode + * + * Through the layoutlib_create tool, the original native methods of PixelXorXfermode have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PixelXorXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + * @see Xfermode_Delegate + */ +public class PixelXorXfermode_Delegate extends Xfermode_Delegate { + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Composite getComposite(int alpha) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Pixel XOR Xfermodes are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int opColor) { + PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java new file mode 100644 index 0000000..c45dbaa --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter + * + * Through the layoutlib_create tool, the original native methods of PorterDuffColorFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PorterDuffColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "PorterDuff Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) { + PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nCreatePorterDuffFilter(int nativeFilter, int srcColor, + int porterDuffMode) { + // pass + return 0; + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java new file mode 100644 index 0000000..4301c1a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java @@ -0,0 +1,140 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.AlphaComposite; +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.PorterDuffXfermode + * + * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PorterDuffXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + */ +public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { + + // ---- delegate data ---- + + private final int mMode; + + // ---- Public Helper methods ---- + + public PorterDuff.Mode getMode() { + return getPorterDuffMode(mMode); + } + + @Override + public Composite getComposite(int alpha) { + return getComposite(getPorterDuffMode(mMode), alpha); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + public static PorterDuff.Mode getPorterDuffMode(int mode) { + for (PorterDuff.Mode m : PorterDuff.Mode.values()) { + if (m.nativeInt == mode) { + return m; + } + } + + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/); + 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, falpha); + case DARKEN: + break; + case DST: + return AlphaComposite.getInstance(AlphaComposite.DST, falpha); + case DST_ATOP: + return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha); + case DST_IN: + return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha); + case DST_OUT: + return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha); + case DST_OVER: + return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha); + case LIGHTEN: + break; + case MULTIPLY: + break; + case SCREEN: + break; + case SRC: + return AlphaComposite.getInstance(AlphaComposite.SRC, falpha); + case SRC_ATOP: + return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha); + case SRC_IN: + return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha); + case SRC_OUT: + return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha); + case SRC_OVER: + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); + case XOR: + return AlphaComposite.getInstance(AlphaComposite.XOR, falpha); + } + + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unsupported PorterDuff Mode: %s", mode.name()), + null, null /*data*/); + + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreateXfermode(int mode) { + PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private PorterDuffXfermode_Delegate(int mode) { + mMode = mode; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java deleted file mode 100644 index 4409a80..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2008 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; - -public class RadialGradient extends GradientShader { - - 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) { - super(colors, positions); - if (radius <= 0) { - throw new IllegalArgumentException("radius must be > 0"); - } - - 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) { - this(x, y, radius, new int[] { color0, color1 }, null /* positions */, tile); - } - - @Override - 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(); - } - - } - } - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java new file mode 100644 index 0000000..9bf78b4 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -0,0 +1,211 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.RadialGradient + * + * Through the layoutlib_create tool, the original native methods of RadialGradient have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original RadialGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class RadialGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(float x, float y, float radius, + int colors[], float positions[], int tileMode) { + RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(x, y, radius, + colors, positions, Shader_Delegate.getTileMode(tileMode)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(float x, float y, float radius, + int color0, int color1, int tileMode) { + return nativeCreate1(x, y, radius, new int[] { color0, color1 }, null /*positions*/, + tileMode); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(int native_shader, float x, float y, float radius, + int colors[], float positions[], int tileMode) { + // nothing to be done here. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(int native_shader, float x, float y, float radius, + int color0, int color1, int tileMode) { + // nothing to be done here. + return 0; + } + + // ---- Private delegate/helper methods ---- + + /** + * 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 + */ + private RadialGradient_Delegate(float x, float y, float radius, int colors[], float positions[], + TileMode tile) { + super(colors, positions); + mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile); + } + + private 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(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in RadialGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in RadialGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new RadialGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class RadialGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public RadialGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + 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; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix + pt1[0] = pt2[0] - mX; + pt1[1] = pt2[1] - mY; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + float _x = pt2[0]; + float _y = pt2[1]; + 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(); + } + + } + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java new file mode 100644 index 0000000..2812b6b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.Rasterizer + * + * Through the layoutlib_create tool, the original native methods of Rasterizer have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Rasterizer class. + * + * This also serve as a base class for all Rasterizer delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Rasterizer_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Rasterizer_Delegate> sManager = + new DelegateManager<Rasterizer_Delegate>(Rasterizer_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static Rasterizer_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java new file mode 100644 index 0000000..cb31b8f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java @@ -0,0 +1,484 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.os.Parcel; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; + +/** + * Delegate implementing the native methods of android.graphics.Region + * + * Through the layoutlib_create tool, the original native methods of Region have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Region class. + * + * This also serve as a base class for all Region delegate classes. + * + * @see DelegateManager + * + */ +public class Region_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Region_Delegate> sManager = + new DelegateManager<Region_Delegate>(Region_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private Area mArea = new Area(); + + // ---- Public Helper methods ---- + + public static Region_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public Area getJavaArea() { + return mArea; + } + + /** + * Combines two {@link Shape} into another one (actually an {@link Area}), according + * to the given {@link Region.Op}. + * + * If the Op is not one that combines two shapes, then this return null + * + * @param shape1 the firt shape to combine which can be null if there's no original clip. + * @param shape2 the 2nd shape to combine + * @param regionOp the operande for the combine + * @return a new area or null. + */ + public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) { + if (regionOp == Region.Op.DIFFERENCE.nativeInt) { + // if shape1 is null (empty), then the result is null. + if (shape1 == null) { + return null; + } + + // result is always a new area. + Area result = new Area(shape1); + result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.INTERSECT.nativeInt) { + // if shape1 is null, then the result is simply shape2. + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.UNION.nativeInt) { + // if shape1 is null, then the result is simply shape2. + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.XOR.nativeInt) { + // if shape1 is null, then the result is simply shape2 + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) { + // result is always a new area. + Area result = new Area(shape2); + + if (shape1 != null) { + result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1)); + } + + return result; + } + + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isEmpty(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isEmpty(); + } + + @LayoutlibDelegate + /*package*/ static boolean isRect(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isRectangular(); + } + + @LayoutlibDelegate + /*package*/ static boolean isComplex(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isSingular() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean contains(Region thisRegion, int x, int y) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.contains(x, y); + } + + @LayoutlibDelegate + /*package*/ static boolean quickContains(Region thisRegion, + int left, int top, int right, int bottom) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isRectangular() && + regionDelegate.mArea.contains(left, top, right - left, bottom - top); + } + + @LayoutlibDelegate + /*package*/ static boolean quickReject(Region thisRegion, + int left, int top, int right, int bottom) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isEmpty() || + regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false; + } + + @LayoutlibDelegate + /*package*/ static boolean quickReject(Region thisRegion, Region rgn) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion); + if (targetRegionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isEmpty() || + regionDelegate.mArea.getBounds().intersects( + targetRegionDelegate.mArea.getBounds()) == false; + + } + + @LayoutlibDelegate + /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion); + if (targetRegionDelegate == null) { + return; + } + + if (regionDelegate.mArea.isEmpty()) { + targetRegionDelegate.mArea = new Area(); + } else { + targetRegionDelegate.mArea = new Area(regionDelegate.mArea); + AffineTransform mtx = new AffineTransform(); + mtx.translate(dx, dy); + targetRegionDelegate.mArea.transform(mtx); + } + } + + @LayoutlibDelegate + /*package*/ static void scale(Region thisRegion, float scale, Region dst) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion); + if (targetRegionDelegate == null) { + return; + } + + if (regionDelegate.mArea.isEmpty()) { + targetRegionDelegate.mArea = new Area(); + } else { + targetRegionDelegate.mArea = new Area(regionDelegate.mArea); + AffineTransform mtx = new AffineTransform(); + mtx.scale(scale, scale); + targetRegionDelegate.mArea.transform(mtx); + } + } + + @LayoutlibDelegate + /*package*/ static int nativeConstructor() { + Region_Delegate newDelegate = new Region_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_region) { + sManager.removeJavaReferenceFor(native_region); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetRegion(int native_dst, int native_src) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Region_Delegate srcRegion = sManager.getDelegate(native_src); + if (srcRegion == null) { + return true; + } + + dstRegion.mArea.reset(); + dstRegion.mArea.add(srcRegion.mArea); + + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetRect(int native_dst, + int left, int top, int right, int bottom) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top)); + return dstRegion.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetPath(int native_dst, int native_path, int native_clip) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Path_Delegate path = Path_Delegate.getDelegate(native_path); + if (path == null) { + return true; + } + + dstRegion.mArea = new Area(path.getJavaShape()); + + Region_Delegate clip = sManager.getDelegate(native_clip); + if (clip != null) { + dstRegion.mArea.subtract(clip.getJavaArea()); + } + + return dstRegion.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetBounds(int native_region, Rect rect) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return true; + } + + Rectangle bounds = region.mArea.getBounds(); + if (bounds.isEmpty()) { + rect.left = rect.top = rect.right = rect.bottom = 0; + return false; + } + + rect.left = bounds.x; + rect.top = bounds.y; + rect.right = bounds.x + bounds.width; + rect.bottom = bounds.y + bounds.height; + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetBoundaryPath(int native_region, int native_path) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return false; + } + + Path_Delegate path = Path_Delegate.getDelegate(native_path); + if (path == null) { + return false; + } + + if (region.mArea.isEmpty()) { + path.reset(); + return false; + } + + path.setPathIterator(region.mArea.getPathIterator(new AffineTransform())); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, + int left, int top, int right, int bottom, int op) { + Region_Delegate region = sManager.getDelegate(native_dst); + if (region == null) { + return false; + } + + region.mArea = combineShapes(region.mArea, + new Rectangle2D.Float(left, top, right - left, bottom - top), op); + + assert region.mArea != null; + if (region.mArea != null) { + region.mArea = new Area(); + } + + return region.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, Rect rect, int native_region, int op) { + Region_Delegate region = sManager.getDelegate(native_dst); + if (region == null) { + return false; + } + + region.mArea = combineShapes(region.mArea, + new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op); + + assert region.mArea != null; + if (region.mArea != null) { + region.mArea = new Area(); + } + + return region.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, + int native_region1, int native_region2, int op) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Region_Delegate region1 = sManager.getDelegate(native_region1); + if (region1 == null) { + return false; + } + + Region_Delegate region2 = sManager.getDelegate(native_region2); + if (region2 == null) { + return false; + } + + dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op); + + assert dstRegion.mArea != null; + if (dstRegion.mArea != null) { + dstRegion.mArea = new Area(); + } + + return dstRegion.mArea.getBounds().isEmpty() == false; + + } + + @LayoutlibDelegate + /*package*/ static int nativeCreateFromParcel(Parcel p) { + // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only + // used during aidl call so really this should not be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Regions cannot be created from parcels.", + null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeWriteToParcel(int native_region, + Parcel p) { + // This is only called when sending a region through aidl, so really this should not + // be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Regions cannot be written to parcels.", + null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeEquals(int native_r1, int native_r2) { + Region_Delegate region1 = sManager.getDelegate(native_r1); + if (region1 == null) { + return false; + } + + Region_Delegate region2 = sManager.getDelegate(native_r2); + if (region2 == null) { + return false; + } + + return region1.mArea.equals(region2.mArea); + } + + @LayoutlibDelegate + /*package*/ static String nativeToString(int native_region) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return "not found"; + } + + return region.mArea.toString(); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader.java b/tools/layoutlib/bridge/src/android/graphics/Shader.java deleted file mode 100644 index 0cc5940..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Shader.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2008 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; - - - -/** - * Shader is the based class for objects that return horizontal spans of colors - * during drawing. A subclass of Shader is installed in a Paint calling - * paint.setShader(shader). After that any object (other than a bitmap) that is - * drawn with that paint will get its color(s) from the shader. - */ -public abstract class Shader { - - private final Matrix mMatrix = new Matrix(); - - public enum TileMode { - /** - * replicate the edge color if the shader draws outside of its - * original bounds - */ - CLAMP (0), - /** - * repeat the shader's image horizontally and vertically - */ - REPEAT (1), - /** - * repeat the shader's image horizontally and vertically, alternating - * mirror images so that adjacent images always seam - */ - MIRROR (2); - - TileMode(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * Return true if the shader has a non-identity local matrix. - * @param localM If not null, it is set to the shader's local matrix. - * @return true if the shader has a non-identity local matrix - */ - public boolean getLocalMatrix(Matrix localM) { - if (localM != null) { - localM.set(mMatrix); - } - - return !mMatrix.isIdentity(); - } - - /** - * Set the shader's local matrix. Passing null will reset the shader's - * matrix to identity - * @param localM The shader's new local matrix, or null to specify identity - */ - public void setLocalMatrix(Matrix localM) { - if (localM != null) { - mMatrix.set(localM); - } else { - mMatrix.reset(); - } - } - - /** - * Returns a java.awt.Paint object matching this shader. - */ - abstract java.awt.Paint getJavaPaint(); -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java new file mode 100644 index 0000000..368c0384 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -0,0 +1,105 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.Shader + * + * Through the layoutlib_create tool, the original native methods of Shader have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Shader class. + * + * This also serve as a base class for all Shader delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Shader_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Shader_Delegate> sManager = + new DelegateManager<Shader_Delegate>(Shader_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private Matrix_Delegate mLocalMatrix = null; + + // ---- Public Helper methods ---- + + public static Shader_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + /** + * Returns the {@link TileMode} matching the given int. + * @param tileMode the tile mode int value + * @return the TileMode enum. + */ + public static TileMode getTileMode(int tileMode) { + for (TileMode tm : TileMode.values()) { + if (tm.nativeInt == tileMode) { + return tm; + } + } + + assert false; + return TileMode.CLAMP; + } + + public abstract java.awt.Paint getJavaPaint(); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_shader, int native_skiaShader) { + sManager.removeJavaReferenceFor(native_shader); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetLocalMatrix(int native_shader, int native_skiaShader, + int matrix_instance) { + // get the delegate from the native int. + Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); + if (shaderDelegate == null) { + return; + } + + shaderDelegate.mLocalMatrix = Matrix_Delegate.getDelegate(matrix_instance); + } + + // ---- Private delegate/helper methods ---- + + protected java.awt.geom.AffineTransform getLocalMatrix() { + if (mLocalMatrix != null) { + return mLocalMatrix.getAffineTransform(); + } + + return new java.awt.geom.AffineTransform(); + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java new file mode 100644 index 0000000..410df0c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.SumPathEffect + * + * Through the layoutlib_create tool, the original native methods of SumPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original SumPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class SumPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Sum Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int first, int second) { + SumPathEffect_Delegate newDelegate = new SumPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java deleted file mode 100644 index 87036ed..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2008 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; - -public class SweepGradient extends GradientShader { - - private SweepGradientPaint mPaint; - - /** - * A subclass of Shader that draws a sweep gradient around a center point. - * - * @param cx The x-coordinate of the center - * @param cy The y-coordinate of the center - * @param colors The colors to be distributed between around the center. - * There must be at least 2 colors in the array. - * @param positions May be NULL. The relative position of - * each corresponding color in the colors array, beginning - * with 0 and ending with 1.0. If the values are not - * monotonic, the drawing may produce unexpected results. - * If positions is NULL, then the colors are automatically - * spaced evenly. - */ - public SweepGradient(float cx, float cy, - int colors[], float positions[]) { - super(colors, positions); - - mPaint = new SweepGradientPaint(cx, cy, mColors, mPositions); - } - - /** - * A subclass of Shader that draws a sweep gradient around a center point. - * - * @param cx The x-coordinate of the center - * @param cy The y-coordinate of the center - * @param color0 The color to use at the start of the sweep - * @param color1 The color to use at the end of the sweep - */ - public SweepGradient(float cx, float cy, int color0, int color1) { - this(cx, cy, new int[] { color0, color1}, null /*positions*/); - } - - @Override - java.awt.Paint getJavaPaint() { - return mPaint; - } - - private static class SweepGradientPaint extends GradientPaint { - - private final float mCx; - private final float mCy; - - public SweepGradientPaint(float cx, float cy, int[] colors, float[] positions) { - super(colors, positions, null /*tileMode*/); - mCx = cx; - mCy = cy; - } - - 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 SweepGradientPaintContext(colorModel); - } - - private class SweepGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - public SweepGradientPaintContext(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 angle 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 dx = x + ix - mCx; - float dy = y + iy - mCy; - float angle; - if (dx == 0) { - angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2); - } else if (dy == 0) { - angle = (float) (dx < 0 ? Math.PI : 0); - } else { - angle = (float) Math.atan(dy / dx); - if (dx > 0) { - if (dy < 0) { - angle += Math.PI * 2; - } - } else { - angle += Math.PI; - } - } - - // convert to 0-1. value and get color - data[index++] = getGradientColor((float) (angle / (2 * Math.PI))); - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - - } - } - -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java new file mode 100644 index 0000000..966e06e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -0,0 +1,218 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.SweepGradient + * + * Through the layoutlib_create tool, the original native methods of SweepGradient have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original SweepGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class SweepGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(float x, float y, int colors[], float positions[]) { + SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(x, y, colors, positions); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(float x, float y, int color0, int color1) { + return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate1(int native_shader, float cx, float cy, + int[] colors, float[] positions) { + // nothing to be done here. + return 0; + } + + @LayoutlibDelegate + /*package*/ static int nativePostCreate2(int native_shader, float cx, float cy, + int color0, int color1) { + // nothing to be done here. + return 0; + } + + // ---- Private delegate/helper methods ---- + + /** + * A subclass of Shader that draws a sweep gradient around a center point. + * + * @param cx The x-coordinate of the center + * @param cy The y-coordinate of the center + * @param colors The colors to be distributed between around the center. + * There must be at least 2 colors in the array. + * @param positions May be NULL. The relative position of + * each corresponding color in the colors array, beginning + * with 0 and ending with 1.0. If the values are not + * monotonic, the drawing may produce unexpected results. + * If positions is NULL, then the colors are automatically + * spaced evenly. + */ + private SweepGradient_Delegate(float cx, float cy, + int colors[], float positions[]) { + super(colors, positions); + mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions); + } + + private class SweepGradientPaint extends GradientPaint { + + private final float mCx; + private final float mCy; + + public SweepGradientPaint(float cx, float cy, int[] colors, + float[] positions) { + super(colors, positions, null /*tileMode*/); + mCx = cx; + mCy = cy; + } + + 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(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in SweepGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in SweepGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new SweepGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class SweepGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public SweepGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + 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 angle from each point to the center, and figure out the distance from + // it. + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix + pt1[0] = pt2[0] - mCx; + pt1[1] = pt2[1] - mCy; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + float dx = pt2[0]; + float dy = pt2[1]; + + float angle; + if (dx == 0) { + angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2); + } else if (dy == 0) { + angle = (float) (dx < 0 ? Math.PI : 0); + } else { + angle = (float) Math.atan(dy / dx); + if (dx > 0) { + if (dy < 0) { + angle += Math.PI * 2; + } + } else { + angle += Math.PI; + } + } + + // convert to 0-1. value and get color + data[index++] = getGradientColor((float) (angle / (2 * Math.PI))); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface.java b/tools/layoutlib/bridge/src/android/graphics/Typeface.java deleted file mode 100644 index af3adb5..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import com.android.layoutlib.bridge.FontLoader; - -import android.content.res.AssetManager; - -import java.awt.Font; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Re-implementation of Typeface over java.awt - */ -public class Typeface { - private static final String DEFAULT_FAMILY = "sans-serif"; - private static final int[] styleBuffer = new int[1]; - - /** The default NORMAL typeface object */ - public static Typeface DEFAULT; - /** - * The default BOLD typeface object. Note: this may be not actually be - * bold, depending on what fonts are installed. Call getStyle() to know - * for sure. - */ - public static Typeface DEFAULT_BOLD; - /** The NORMAL style of the default sans serif typeface. */ - public static Typeface SANS_SERIF; - /** The NORMAL style of the default serif typeface. */ - public static Typeface SERIF; - /** The NORMAL style of the default monospace typeface. */ - public static Typeface MONOSPACE; - - private static Typeface[] sDefaults; - private static FontLoader mFontLoader; - - private final int mStyle; - private final List<Font> mFonts; - private final String mFamily; - - // Style - public static final int NORMAL = _Original_Typeface.NORMAL; - public static final int BOLD = _Original_Typeface.BOLD; - public static final int ITALIC = _Original_Typeface.ITALIC; - public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC; - - /** - * Returns the underlying {@link Font} objects. The first item in the list is the real - * font. Any other items are fallback fonts for characters not found in the first one. - */ - public List<Font> getFonts() { - return mFonts; - } - - /** Returns the typeface's intrinsic style attributes */ - public int getStyle() { - return mStyle; - } - - /** Returns true if getStyle() has the BOLD bit set. */ - public final boolean isBold() { - return (getStyle() & BOLD) != 0; - } - - /** Returns true if getStyle() has the ITALIC bit set. */ - public final boolean isItalic() { - return (getStyle() & ITALIC) != 0; - } - - /** - * Create a typeface object given a family name, and option style information. - * If null is passed for the name, then the "default" font will be chosen. - * The resulting typeface object can be queried (getStyle()) to discover what - * its "real" style characteristics are. - * - * @param familyName May be null. The name of the font family. - * @param style The style (normal, bold, italic) of the typeface. - * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC - * @return The best matching typeface. - */ - public static Typeface create(String familyName, int style) { - styleBuffer[0] = style; - Font font = mFontLoader.getFont(familyName, styleBuffer); - if (font != null) { - ArrayList<Font> list = new ArrayList<Font>(); - list.add(font); - list.addAll(mFontLoader.getFallBackFonts()); - return new Typeface(familyName, styleBuffer[0], list); - } - - return null; - } - - /** - * Create a typeface object that best matches the specified existing - * typeface and the specified Style. Use this call if you want to pick a new - * style from the same family of an existing typeface object. If family is - * null, this selects from the default font's family. - * - * @param family May be null. The name of the existing type face. - * @param style The style (normal, bold, italic) of the typeface. - * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC - * @return The best matching typeface. - */ - public static Typeface create(Typeface family, int style) { - styleBuffer[0] = style; - Font font = mFontLoader.getFont(family.mFamily, styleBuffer); - if (font != null) { - ArrayList<Font> list = new ArrayList<Font>(); - list.add(font); - list.addAll(mFontLoader.getFallBackFonts()); - return new Typeface(family.mFamily, styleBuffer[0], list); - } - - return null; - } - - /** - * Returns one of the default typeface objects, based on the specified style - * - * @return the default typeface that corresponds to the style - */ - public static Typeface defaultFromStyle(int style) { - return sDefaults[style]; - } - - /** - * Create a new typeface from the specified font data. - * @param mgr The application's asset manager - * @param path The file name of the font data in the assets directory - * @return The new typeface. - */ - public static Typeface createFromAsset(AssetManager mgr, String path) { - return null; - //return new Typeface(nativeCreateFromAsset(mgr, path)); - } - - // don't allow clients to call this directly - private Typeface(String family, int style, List<Font> fonts) { - mFamily = family; - mFonts = Collections.unmodifiableList(fonts); - mStyle = style; - } - - public static void init(FontLoader fontLoader) { - mFontLoader = fontLoader; - - DEFAULT = create(DEFAULT_FAMILY, NORMAL); - DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD); - SANS_SERIF = create("sans-serif", NORMAL); - SERIF = create("serif", NORMAL); - MONOSPACE = create("monospace", NORMAL); - sDefaults = new Typeface[] { - DEFAULT, - DEFAULT_BOLD, - create(DEFAULT_FAMILY, ITALIC), - create(DEFAULT_FAMILY, BOLD_ITALIC), - }; - - /* - DEFAULT = create((String)null, 0); - DEFAULT_BOLD = create((String)null, Typeface.BOLD); - SANS_SERIF = create("sans-serif", 0); - SERIF = create("serif", 0); - MONOSPACE = create("monospace", 0); - - sDefaults = new Typeface[] { - DEFAULT, - DEFAULT_BOLD, - create((String)null, Typeface.ITALIC), - create((String)null, Typeface.BOLD_ITALIC), - };*/ - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java new file mode 100644 index 0000000..0f084f7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -0,0 +1,191 @@ +/* + * 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; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.FontLoader; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.AssetManager; + +import java.awt.Font; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Delegate implementing the native methods of android.graphics.Typeface + * + * Through the layoutlib_create tool, the original native methods of Typeface have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Typeface class. + * + * @see DelegateManager + * + */ +public final class Typeface_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Typeface_Delegate> sManager = + new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); + + // ---- delegate helper data ---- + private static final String DEFAULT_FAMILY = "sans-serif"; + private static final int[] STYLE_BUFFER = new int[1]; + + private static FontLoader sFontLoader; + private static final List<Typeface_Delegate> sPostInitDelegate = + new ArrayList<Typeface_Delegate>(); + + // ---- delegate data ---- + + private final String mFamily; + private int mStyle; + private List<Font> mFonts; + + + // ---- Public Helper methods ---- + + public static synchronized void init(FontLoader fontLoader) { + sFontLoader = fontLoader; + + for (Typeface_Delegate delegate : sPostInitDelegate) { + delegate.init(); + } + sPostInitDelegate.clear(); + } + + public static Typeface_Delegate getDelegate(int nativeTypeface) { + return sManager.getDelegate(nativeTypeface); + } + + public static List<Font> getFonts(Typeface typeface) { + return getFonts(typeface.native_instance); + } + + public static List<Font> getFonts(int native_int) { + Typeface_Delegate delegate = sManager.getDelegate(native_int); + if (delegate == null) { + return null; + } + + return delegate.getFonts(); + } + + public List<Font> getFonts() { + return mFonts; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreate(String familyName, int style) { + if (familyName == null) { + familyName = DEFAULT_FAMILY; + } + + Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); + if (sFontLoader != null) { + newDelegate.init(); + } else { + // font loader has not been initialized yet, add the delegate to a list of delegates + // to init when the font loader is initialized. + // There won't be any rendering before this happens anyway. + sPostInitDelegate.add(newDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromTypeface(int native_instance, int style) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + return 0; + } + + Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style); + if (sFontLoader != null) { + newDelegate.init(); + } else { + // font loader has not been initialized yet, add the delegate to a list of delegates + // to init when the font loader is initialized. + // There won't be any rendering before this happens anyway. + sPostInitDelegate.add(newDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromAsset(AssetManager mgr, String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromFile(String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromFile() is not supported.", null /*throwable*/, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static void nativeUnref(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + @LayoutlibDelegate + /*package*/ static int nativeGetStyle(int native_instance) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + return 0; + } + + return delegate.mStyle; + } + + @LayoutlibDelegate + /*package*/ static void setGammaForText(float blackGamma, float whiteGamma) { + // This is for device testing only: pass + } + + // ---- Private delegate/helper methods ---- + + private Typeface_Delegate(String family, int style) { + mFamily = family; + mStyle = style; + } + + private void init() { + STYLE_BUFFER[0] = mStyle; + Font font = sFontLoader.getFont(mFamily, STYLE_BUFFER); + if (font != null) { + List<Font> list = new ArrayList<Font>(); + list.add(font); + list.addAll(sFontLoader.getFallBackFonts()); + mFonts = Collections.unmodifiableList(list); + mStyle = STYLE_BUFFER[0]; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java new file mode 100644 index 0000000..962d69c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java @@ -0,0 +1,69 @@ +/* + * 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; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.Xfermode + * + * Through the layoutlib_create tool, the original native methods of Xfermode have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Xfermode class. + * + * This also serve as a base class for all Xfermode delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Xfermode_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Xfermode_Delegate> sManager = + new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static Xfermode_Delegate getDelegate(int native_instance) { + return sManager.getDelegate(native_instance); + } + + public abstract Composite getComposite(int alpha); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java new file mode 100644 index 0000000..ff82a5e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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.os; + +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.Map; + +/** + * Delegate implementing the native methods of android.os.Build + * + * Through the layoutlib_create tool, the original native methods of Build have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class Build_Delegate { + + @LayoutlibDelegate + /*package*/ static String getString(String property) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(property); + if (value != null) { + return value; + } + + return Build.UNKNOWN; + } + +} diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java new file mode 100644 index 0000000..2152c8a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java @@ -0,0 +1,57 @@ +/* + * 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.os; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + + +/** + * Delegate overriding selected methods of android.os.Handler + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + * + */ +public class Handler_Delegate { + + // -------- Delegate methods + + @LayoutlibDelegate + /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { + // get the callback + IHandlerCallback callback = sCallbacks.get(); + if (callback != null) { + callback.sendMessageAtTime(handler, msg, uptimeMillis); + } + return true; + } + + // -------- Delegate implementation + + public interface IHandlerCallback { + void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis); + } + + private final static ThreadLocal<IHandlerCallback> sCallbacks = + new ThreadLocal<IHandlerCallback>(); + + public static void setCallback(IHandlerCallback callback) { + sCallbacks.set(callback); + } + +} diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java new file mode 100644 index 0000000..63711a7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java @@ -0,0 +1,71 @@ +/* + * 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.os; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.os.SystemClock + * + * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class SystemClock_Delegate { + private static long sBootTime = System.currentTimeMillis(); + + @LayoutlibDelegate + /*package*/ static boolean setCurrentTimeMillis(long millis) { + return true; + } + + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. + * <b>Note:</b> This value may get reset occasionally (before it would + * otherwise wrap around). + * + * @return milliseconds of non-sleep uptime since boot. + */ + @LayoutlibDelegate + /*package*/ static long uptimeMillis() { + return System.currentTimeMillis() - sBootTime; + } + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @return elapsed milliseconds since boot. + */ + @LayoutlibDelegate + /*package*/ static long elapsedRealtime() { + return System.currentTimeMillis() - sBootTime; + } + + /** + * Returns milliseconds running in the current thread. + * + * @return elapsed milliseconds in the thread + */ + @LayoutlibDelegate + /*package*/ static long currentThreadTimeMillis() { + return System.currentTimeMillis(); + } +} diff --git a/tools/layoutlib/bridge/src/android/util/FloatMath.java b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java index aae44f2..1df78c2 100644 --- a/tools/layoutlib/bridge/src/android/util/FloatMath.java +++ b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java @@ -16,20 +16,23 @@ package android.util; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + /** - * Reimplements _Original_FloatMath with the standard libraries. - * - * Math routines similar to those found in {@link java.lang.Math}. Performs - * computations on {@code float} values directly without incurring the overhead - * of conversions to and from {@code double}. + * Delegate implementing the native methods of android.util.FloatMath + * + * Through the layoutlib_create tool, the original native methods of FloatMath have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. * - * <p>On one platform, {@code FloatMath.sqrt(100)} executes in one third of the - * time required by {@code java.lang.Math.sqrt(100)}.</p> */ -public class FloatMath { +/*package*/ final class FloatMath_Delegate { /** Prevents instantiation. */ - private FloatMath() {} + private FloatMath_Delegate() {} /** * Returns the float conversion of the most positive (i.e. closest to @@ -38,7 +41,8 @@ public class FloatMath { * @param value to be converted * @return the floor of value */ - public static float floor(float value) { + @LayoutlibDelegate + /*package*/ static float floor(float value) { return (float)Math.floor(value); } @@ -49,7 +53,8 @@ public class FloatMath { * @param value to be converted * @return the ceiling of value */ - public static float ceil(float value) { + @LayoutlibDelegate + /*package*/ static float ceil(float value) { return (float)Math.ceil(value); } @@ -59,7 +64,8 @@ public class FloatMath { * @param angle to compute the cosine of, in radians * @return the sine of angle */ - public static float sin(float angle) { + @LayoutlibDelegate + /*package*/ static float sin(float angle) { return (float)Math.sin(angle); } @@ -69,7 +75,8 @@ public class FloatMath { * @param angle to compute the cosine of, in radians * @return the cosine of angle */ - public static float cos(float angle) { + @LayoutlibDelegate + /*package*/ static float cos(float angle) { return (float)Math.cos(angle); } @@ -80,7 +87,8 @@ public class FloatMath { * @param value to compute sqrt of * @return the square root of value */ - public static float sqrt(float value) { + @LayoutlibDelegate + /*package*/ static float sqrt(float value) { return (float)Math.sqrt(value); } } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java deleted file mode 100644 index 0910d79..0000000 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2008 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.view; - -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.BridgeConstants; -import com.android.layoutlib.bridge.BridgeContext; -import com.android.layoutlib.bridge.BridgeXmlBlockParser; - -import org.kxml2.io.KXmlParser; -import org.xmlpull.v1.XmlPullParser; - -import android.content.Context; -import android.util.AttributeSet; - -import java.io.File; -import java.io.FileReader; - -/** - * Custom implementation of {@link LayoutInflater} to handle custom views. - */ -public final class BridgeInflater extends LayoutInflater { - - private final IProjectCallback mProjectCallback; - - /** - * List of class prefixes which are tried first by default. - * <p/> - * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. - */ - private static final String[] sClassPrefixList = { - "android.widget.", - "android.webkit." - }; - - protected BridgeInflater(LayoutInflater original, Context newContext) { - super(original, newContext); - mProjectCallback = null; - } - - /** - * Instantiate a new BridgeInflater with an {@link IProjectCallback} object. - * - * @param context The Android application context. - * @param projectCallback the {@link IProjectCallback} object. - */ - public BridgeInflater(Context context, IProjectCallback projectCallback) { - super(context); - mProjectCallback = projectCallback; - mConstructorArgs[0] = context; - } - - @Override - public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { - View view = null; - - try { - // First try to find a class using the default Android prefixes - for (String prefix : sClassPrefixList) { - try { - view = createView(name, prefix, attrs); - if (view != null) { - break; - } - } catch (ClassNotFoundException e) { - // Ignore. We'll try again using the base class below. - } - } - - // Next try using the parent loader. This will most likely only work for - // fully-qualified class names. - try { - if (view == null) { - view = super.onCreateView(name, attrs); - } - } catch (ClassNotFoundException e) { - // Ignore. We'll try again using the custom view loader below. - } - - // Finally try again using the custom view loader - try { - if (view == null) { - view = loadCustomView(name, attrs); - } - } catch (ClassNotFoundException e) { - // If the class was not found, we throw the exception directly, because this - // method is already expected to throw it. - throw e; - } - } catch (Exception e) { - // Wrap the real exception in a ClassNotFoundException, so that the calling method - // can deal with it. - ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e); - throw exception; - } - - setupViewInContext(view, attrs); - - return view; - } - - @Override - public View createViewFromTag(String name, AttributeSet attrs) { - View view = null; - try { - view = super.createViewFromTag(name, attrs); - } catch (InflateException e) { - // try to load the class from using the custom view loader - try { - view = loadCustomView(name, attrs); - } catch (Exception e2) { - // Wrap the real exception in an InflateException so that the calling - // method can deal with it. - InflateException exception = new InflateException(); - if (e2.getClass().equals(ClassNotFoundException.class) == false) { - exception.initCause(e2); - } else { - exception.initCause(e); - } - throw exception; - } - } - - setupViewInContext(view, attrs); - - return view; - } - - @Override - public View inflate(int resource, ViewGroup root) { - Context context = getContext(); - if (context instanceof BridgeContext) { - BridgeContext bridgeContext = (BridgeContext)context; - - IResourceValue value = null; - - String[] layoutInfo = Bridge.resolveResourceValue(resource); - if (layoutInfo != null) { - value = bridgeContext.getFrameworkResource(BridgeConstants.RES_LAYOUT, - layoutInfo[0]); - } else { - layoutInfo = mProjectCallback.resolveResourceValue(resource); - - if (layoutInfo != null) { - value = bridgeContext.getProjectResource(BridgeConstants.RES_LAYOUT, - layoutInfo[0]); - } - } - - if (value != null) { - File f = new File(value.getValue()); - if (f.isFile()) { - try { - KXmlParser parser = new KXmlParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); - - BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( - parser, bridgeContext, false); - - return inflate(bridgeParser, root); - } catch (Exception e) { - bridgeContext.getLogger().error(e); - // return null below. - } - } - } - } - return null; - } - - private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException, - Exception{ - if (mProjectCallback != null) { - // first get the classname in case it's not the node name - if (name.equals("view")) { - name = attrs.getAttributeValue(null, "class"); - } - - mConstructorArgs[1] = attrs; - - Object customView = mProjectCallback.loadView(name, mConstructorSignature, - mConstructorArgs); - - if (customView instanceof View) { - return (View)customView; - } - } - - return null; - } - - - - private void setupViewInContext(View view, AttributeSet attrs) { - if (getContext() instanceof BridgeContext) { - BridgeContext bc = (BridgeContext) getContext(); - if (attrs instanceof BridgeXmlBlockParser) { - Object viewKey = ((BridgeXmlBlockParser) attrs).getViewKey(); - if (viewKey != null) { - bc.addViewKey(view, viewKey); - } - } - } - } - - @Override - public LayoutInflater cloneInContext(Context newContext) { - return new BridgeInflater(this, newContext); - } -} diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java new file mode 100644 index 0000000..0f3cf57 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 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.view; + +import com.android.layoutlib.bridge.android.BridgeInflater; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.util.AttributeSet; + +import java.io.IOException; + +/** + * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater} + * + * Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class LayoutInflater_Delegate { + + /** + * Recursive method used to descend down the xml hierarchy and instantiate + * views, instantiate their children, and then call onFinishInflate(). + */ + @LayoutlibDelegate + /*package*/ static void rInflate(LayoutInflater thisInflater, + XmlPullParser parser, View parent, final AttributeSet attrs, + boolean finishInflate) throws XmlPullParserException, IOException { + + if (finishInflate == false) { + // this is a merge rInflate! + if (thisInflater instanceof BridgeInflater) { + ((BridgeInflater) thisInflater).setIsInMerge(true); + } + } + + // ---- START DEFAULT IMPLEMENTATION. + + final int depth = parser.getDepth(); + int type; + + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + final String name = parser.getName(); + + if (LayoutInflater.TAG_REQUEST_FOCUS.equals(name)) { + thisInflater.parseRequestFocus(parser, parent); + } else if (LayoutInflater.TAG_INCLUDE.equals(name)) { + if (parser.getDepth() == 0) { + throw new InflateException("<include /> cannot be the root element"); + } + thisInflater.parseInclude(parser, parent, attrs); + } else if (LayoutInflater.TAG_MERGE.equals(name)) { + throw new InflateException("<merge /> must be the root element"); + } else { + final View view = thisInflater.createViewFromTag(parent, name, attrs); + final ViewGroup viewGroup = (ViewGroup) parent; + final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); + thisInflater.rInflate(parser, view, attrs, true); + viewGroup.addView(view, params); + } + } + + if (finishInflate) parent.onFinishInflate(); + + // ---- END DEFAULT IMPLEMENTATION. + + if (finishInflate == false) { + // this is a merge rInflate! + if (thisInflater instanceof BridgeInflater) { + ((BridgeInflater) thisInflater).setIsInMerge(false); + } + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java index 974ae49..8215f7c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode.java +++ b/tools/layoutlib/bridge/src/android/view/View_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -14,27 +14,21 @@ * limitations under the License. */ -package android.graphics; +package android.view; -import android.graphics.PorterDuff.Mode; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -public class PorterDuffXfermode extends Xfermode { - private final Mode mMode; +/** + * Delegate used to provide new implementation of a select few methods of {@link View} + * + * Through the layoutlib_create tool, the original methods of View have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class View_Delegate { - /** - * Create an xfermode that uses the specified porter-duff mode. - * - * @param mode The porter-duff mode that is applied - */ - public PorterDuffXfermode(PorterDuff.Mode mode) { - mMode = mode; - } - - //---------- Custom Methods - - public PorterDuff.Mode getMode() { - return mMode; + @LayoutlibDelegate + /*package*/ static boolean isInEditMode(View thisView) { + return true; } - - //---------- } |