diff options
Diffstat (limited to 'graphics')
17 files changed, 923 insertions, 424 deletions
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java new file mode 100644 index 0000000..a759a79 --- /dev/null +++ b/graphics/java/android/graphics/FontFamily.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 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.io.File; + +/** + * A family of typefaces with different styles. + * + * @hide + */ +public class FontFamily { + /** + * @hide + */ + public long mNativePtr; + + public FontFamily() { + mNativePtr = nCreateFamily(); + mNativePtr = nCreateFamily(); + if (mNativePtr == 0) { + throw new RuntimeException(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + nUnrefFamily(mNativePtr); + } finally { + super.finalize(); + } + } + + public boolean addFont(File path) { + return nAddFont(mNativePtr, path.getAbsolutePath()); + } + + static native long nCreateFamily(); + static native void nUnrefFamily(long nativePtr); + static native boolean nAddFont(long nativeFamily, String path); +} diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java new file mode 100644 index 0000000..f304f4e --- /dev/null +++ b/graphics/java/android/graphics/FontListParser.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2014 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.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Parser for font config files. + * + * @hide + */ +public class FontListParser { + + public static class Family { + public Family(List<String> names, List<String> fontFiles) { + this.names = names; + this.fontFiles = fontFiles; + } + + public List<String> names; + // todo: need attributes for font files + public List<String> fontFiles; + } + + /* Parse fallback list (no names) */ + public static List<Family> parse(InputStream in) throws XmlPullParserException, IOException { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parser.nextTag(); + return readFamilies(parser); + } finally { + in.close(); + } + } + + private static List<Family> readFamilies(XmlPullParser parser) + throws XmlPullParserException, IOException { + List<Family> families = new ArrayList<Family>(); + parser.require(XmlPullParser.START_TAG, null, "familyset"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + if (parser.getName().equals("family")) { + families.add(readFamily(parser)); + } else { + skip(parser); + } + } + return families; + } + + private static Family readFamily(XmlPullParser parser) + throws XmlPullParserException, IOException { + List<String> names = null; + List<String> fontFiles = new ArrayList<String>(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + String tag = parser.getName(); + if (tag.equals("fileset")) { + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + if (parser.getName().equals("file")) { + String filename = parser.nextText(); + String fullFilename = "/system/fonts/" + filename; + fontFiles.add(fullFilename); + } + } + } else if (tag.equals("nameset")) { + names = new ArrayList<String>(); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + if (parser.getName().equals("name")) { + String name = parser.nextText(); + names.add(name); + } + } + } + } + return new Family(names, fontFiles); + } + + private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + int depth = 1; + while (depth > 0) { + switch (parser.next()) { + case XmlPullParser.START_TAG: + depth++; + break; + case XmlPullParser.END_TAG: + depth--; + break; + } + } + } +} diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 062acaf..fe53a17 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -272,6 +272,7 @@ public class ImageFormat { case NV16: case YUY2: case YV12: + case JPEG: case NV21: case YUV_420_888: case RAW_SENSOR: diff --git a/graphics/java/android/graphics/LayerRasterizer.java b/graphics/java/android/graphics/LayerRasterizer.java index 5b35608..e7a24a4 100644 --- a/graphics/java/android/graphics/LayerRasterizer.java +++ b/graphics/java/android/graphics/LayerRasterizer.java @@ -16,11 +16,12 @@ package android.graphics; +@Deprecated public class LayerRasterizer extends Rasterizer { public LayerRasterizer() { native_instance = nativeConstructor(); } - + /** Add a new layer (above any previous layers) to the rasterizer. The layer will extract those fields that affect the mask from the specified paint, but will not retain a reference to the paint diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index 66bf75c..b4e6bab 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -245,6 +245,16 @@ public class Matrix { } /** + * Gets whether this matrix is affine. An affine matrix preserves + * straight lines and has no perspective. + * + * @return Whether the matrix is affine. + */ + public boolean isAffine() { + return native_isAffine(native_instance); + } + + /** * 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. @@ -828,6 +838,7 @@ public class Matrix { private static native long native_create(long native_src_or_zero); private static native boolean native_isIdentity(long native_object); + private static native boolean native_isAffine(long native_object); private static native boolean native_rectStaysRect(long native_object); private static native void native_reset(long native_object); private static native void native_set(long native_object, diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 457b3ea..92cfd6b 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1064,14 +1064,17 @@ public class Paint { mNativeTypeface = typefaceNative; return typeface; } - + /** * Get the paint's rasterizer (or null). * <p /> * The raster controls/modifies how paths/text are turned into alpha masks. * * @return the paint's rasterizer (or null) + * + * @deprecated Rasterizer is not supported by either the HW or PDF backends. */ + @Deprecated public Rasterizer getRasterizer() { return mRasterizer; } @@ -1085,7 +1088,10 @@ public class Paint { * @param rasterizer May be null. The new rasterizer to be installed in * the paint. * @return rasterizer + * + * @deprecated Rasterizer is not supported by either the HW or PDF backends. */ + @Deprecated public Rasterizer setRasterizer(Rasterizer rasterizer) { long rasterizerNative = 0; if (rasterizer != null) { @@ -1095,7 +1101,7 @@ public class Paint { mRasterizer = rasterizer; return rasterizer; } - + /** * This draws a shadow layer below the main layer, with the specified * offset and color, and blur radius. If radius is 0, then the shadow @@ -1655,12 +1661,12 @@ public class Paint { return 0; } if (!mHasCompatScaling) { - return native_getTextWidths(mNativePaint, text, index, count, mBidiFlags, widths); + return native_getTextWidths(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags, widths); } final float oldSize = getTextSize(); setTextSize(oldSize*mCompatScaling); - int res = native_getTextWidths(mNativePaint, text, index, count, mBidiFlags, widths); + int res = native_getTextWidths(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags, widths); setTextSize(oldSize); for (int i=0; i<res; i++) { widths[i] *= mInvCompatScaling; @@ -1737,12 +1743,12 @@ public class Paint { return 0; } if (!mHasCompatScaling) { - return native_getTextWidths(mNativePaint, text, start, end, mBidiFlags, widths); + return native_getTextWidths(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, widths); } final float oldSize = getTextSize(); setTextSize(oldSize*mCompatScaling); - int res = native_getTextWidths(mNativePaint, text, start, end, mBidiFlags, widths); + int res = native_getTextWidths(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, widths); setTextSize(oldSize); for (int i=0; i<res; i++) { widths[i] *= mInvCompatScaling; @@ -1832,13 +1838,13 @@ public class Paint { return 0f; } if (!mHasCompatScaling) { - return native_getTextRunAdvances(mNativePaint, chars, index, count, + return native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count, contextIndex, contextCount, flags, advances, advancesIndex); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); - float res = native_getTextRunAdvances(mNativePaint, chars, index, count, + float res = native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count, contextIndex, contextCount, flags, advances, advancesIndex); setTextSize(oldSize); @@ -1963,13 +1969,13 @@ public class Paint { } if (!mHasCompatScaling) { - return native_getTextRunAdvances(mNativePaint, text, start, end, + return native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end, contextStart, contextEnd, flags, advances, advancesIndex); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); - float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end, + float totalAdvance = native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end, contextStart, contextEnd, flags, advances, advancesIndex); setTextSize(oldSize); @@ -2234,19 +2240,19 @@ public class Paint { private static native void native_setTextLocale(long native_object, String locale); - private static native int native_getTextWidths(long native_object, + private static native int native_getTextWidths(long native_object, long native_typeface, char[] text, int index, int count, int bidiFlags, float[] widths); - private static native int native_getTextWidths(long native_object, + private static native int native_getTextWidths(long native_object, long native_typeface, String text, int start, int end, int bidiFlags, float[] widths); private static native int native_getTextGlyphs(long native_object, String text, int start, int end, int contextStart, int contextEnd, int flags, char[] glyphs); - private static native float native_getTextRunAdvances(long native_object, + private static native float native_getTextRunAdvances(long native_object, long native_typeface, char[] text, int index, int count, int contextIndex, int contextCount, int flags, float[] advances, int advancesIndex); - private static native float native_getTextRunAdvances(long native_object, + private static native float native_getTextRunAdvances(long native_object, long native_typeface, String text, int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex); diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java index 817814c..c351d94e 100644 --- a/graphics/java/android/graphics/Rasterizer.java +++ b/graphics/java/android/graphics/Rasterizer.java @@ -21,6 +21,7 @@ package android.graphics; +@Deprecated public class Rasterizer { protected void finalize() throws Throwable { diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 73e0e8d..64451c4 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -17,10 +17,21 @@ package android.graphics; import android.content.res.AssetManager; -import android.util.SparseArray; +import android.graphics.FontListParser.Family; +import android.util.Log; import android.util.LongSparseArray; +import android.util.SparseArray; + +import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * The Typeface class specifies the typeface and intrinsic style of a font. @@ -30,6 +41,8 @@ import java.io.File; */ public class Typeface { + private static String TAG = "Typeface"; + /** The default NORMAL typeface object */ public static final Typeface DEFAULT; /** @@ -49,6 +62,10 @@ public class Typeface { private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = new LongSparseArray<SparseArray<Typeface>>(3); + static Typeface sDefaultTypeface; + static Map<String, Typeface> sSystemFontMap; + static FontFamily[] sFallbackFonts; + /** * @hide */ @@ -62,6 +79,11 @@ public class Typeface { private int mStyle = 0; + private static void setDefault(Typeface t) { + sDefaultTypeface = t; + nativeSetDefault(t.native_instance); + } + /** Returns the typeface's intrinsic style attributes */ public int getStyle() { return mStyle; @@ -89,6 +111,9 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(String familyName, int style) { + if (sSystemFontMap != null) { + return create(sSystemFontMap.get(familyName), style); + } return new Typeface(nativeCreate(familyName, style)); } @@ -142,7 +167,7 @@ public class Typeface { 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 @@ -156,7 +181,7 @@ public class Typeface { /** * Create a new typeface from the specified font file. * - * @param path The path to the font data. + * @param path The path to the font data. * @return The new typeface. */ public static Typeface createFromFile(File path) { @@ -166,13 +191,45 @@ public class Typeface { /** * Create a new typeface from the specified font file. * - * @param path The full path to the font data. + * @param path The full path to the font data. * @return The new typeface. */ public static Typeface createFromFile(String path) { return new Typeface(nativeCreateFromFile(path)); } + /** + * Create a new typeface from an array of font families. + * + * @param families array of font families + * @hide + */ + public static Typeface createFromFamilies(FontFamily[] families) { + long[] ptrArray = new long[families.length]; + for (int i = 0; i < families.length; i++) { + ptrArray[i] = families[i].mNativePtr; + } + return new Typeface(nativeCreateFromArray(ptrArray)); + } + + /** + * Create a new typeface from an array of font families, including + * also the font families in the fallback list. + * + * @param families array of font families + * @hide + */ + public static Typeface createFromFamiliesWithDefault(FontFamily[] families) { + long[] ptrArray = new long[families.length + sFallbackFonts.length]; + for (int i = 0; i < families.length; i++) { + ptrArray[i] = families[i].mNativePtr; + } + for (int i = 0; i < sFallbackFonts.length; i++) { + ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr; + } + return new Typeface(nativeCreateFromArray(ptrArray)); + } + // don't allow clients to call this directly private Typeface(long ni) { if (ni == 0) { @@ -182,14 +239,76 @@ public class Typeface { native_instance = ni; mStyle = nativeGetStyle(ni); } - + + private static FontFamily makeFamilyFromParsed(FontListParser.Family family) { + // TODO: expand to handle attributes like lang and variant + FontFamily fontFamily = new FontFamily(); + for (String fontFile : family.fontFiles) { + fontFamily.addFont(new File(fontFile)); + } + return fontFamily; + } + static { + // Load font config and initialize Minikin state + String systemConfigFilename = "/system/etc/system_fonts.xml"; + String configFilename = "/system/etc/fallback_fonts.xml"; + try { + // TODO: throws an exception non-Minikin builds, to fail early; + // remove when Minikin-only + new FontFamily(); + + FileInputStream systemIn = new FileInputStream(systemConfigFilename); + List<FontListParser.Family> systemFontConfig = FontListParser.parse(systemIn); + + FileInputStream fallbackIn = new FileInputStream(configFilename); + List<FontFamily> familyList = new ArrayList<FontFamily>(); + // Note that the default typeface is always present in the fallback list; + // this is an enhancement from pre-Minikin behavior. + familyList.add(makeFamilyFromParsed(systemFontConfig.get(0))); + for (Family f : FontListParser.parse(fallbackIn)) { + familyList.add(makeFamilyFromParsed(f)); + } + sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); + setDefault(Typeface.createFromFamilies(sFallbackFonts)); + + Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); + for (int i = 0; i < systemFontConfig.size(); i++) { + Typeface typeface; + Family f = systemFontConfig.get(i); + if (i == 0) { + // The first entry is the default typeface; no sense in duplicating + // the corresponding FontFamily. + typeface = sDefaultTypeface; + } else { + FontFamily fontFamily = makeFamilyFromParsed(f); + FontFamily[] families = { fontFamily }; + typeface = Typeface.createFromFamiliesWithDefault(families); + } + for (String name : f.names) { + systemFonts.put(name, typeface); + } + } + sSystemFontMap = systemFonts; + + } catch (RuntimeException e) { + Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)"); + // TODO: normal in non-Minikin case, remove or make error when Minikin-only + } catch (FileNotFoundException e) { + Log.e(TAG, "Error opening " + configFilename); + } catch (IOException e) { + Log.e(TAG, "Error reading " + configFilename); + } catch (XmlPullParserException e) { + Log.e(TAG, "XML parse exception for " + configFilename); + } + + // Set up defaults and typefaces exposed in public API 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, @@ -198,6 +317,7 @@ public class Typeface { }; } + @Override protected void finalize() throws Throwable { try { nativeUnref(native_instance); @@ -234,4 +354,6 @@ public class Typeface { private static native int nativeGetStyle(long native_instance); private static native long nativeCreateFromAsset(AssetManager mgr, String path); private static native long nativeCreateFromFile(String path); + private static native long nativeCreateFromArray(long[] familyArray); + private static native void nativeSetDefault(long native_instance); } diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index 60b4615..6755f3e 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -67,15 +67,22 @@ import java.io.IOException; * @attr ref android.R.styleable#BitmapDrawable_tileMode */ public class BitmapDrawable extends Drawable { - private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; + + // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}. + private static final int TILE_MODE_UNDEFINED = -2; + private static final int TILE_MODE_DISABLED = -1; + private static final int TILE_MODE_CLAMP = 0; + private static final int TILE_MODE_REPEAT = 1; + private static final int TILE_MODE_MIRROR = 2; + + private final Rect mDstRect = new Rect(); // Gravity.apply() sets this + private BitmapState mBitmapState; - private Bitmap mBitmap; private PorterDuffColorFilter mTintFilter; - private int mTargetDensity; - private final Rect mDstRect = new Rect(); // Gravity.apply() sets this + private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; private boolean mApplyGravity; private boolean mMutated; @@ -100,11 +107,12 @@ public class BitmapDrawable extends Drawable { /** * Create an empty drawable, setting initial target density based on * the display metrics of the resources. + * * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} * instead to specify a bitmap to draw with. */ + @SuppressWarnings("unused") @Deprecated - @SuppressWarnings({"UnusedParameters"}) public BitmapDrawable(Resources res) { mBitmapState = new BitmapState((Bitmap) null); mBitmapState.mTargetDensity = mTargetDensity; @@ -137,7 +145,7 @@ public class BitmapDrawable extends Drawable { @Deprecated public BitmapDrawable(String filepath) { this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); - if (mBitmap == null) { + if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } } @@ -145,11 +153,11 @@ public class BitmapDrawable extends Drawable { /** * Create a drawable by opening a given file path and decoding the bitmap. */ - @SuppressWarnings({"UnusedParameters"}) + @SuppressWarnings("unused") public BitmapDrawable(Resources res, String filepath) { this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); mBitmapState.mTargetDensity = mTargetDensity; - if (mBitmap == null) { + if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } } @@ -162,7 +170,7 @@ public class BitmapDrawable extends Drawable { @Deprecated public BitmapDrawable(java.io.InputStream is) { this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); - if (mBitmap == null) { + if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } } @@ -170,11 +178,11 @@ public class BitmapDrawable extends Drawable { /** * Create a drawable by decoding a bitmap from the given input stream. */ - @SuppressWarnings({"UnusedParameters"}) + @SuppressWarnings("unused") public BitmapDrawable(Resources res, java.io.InputStream is) { this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); mBitmapState.mTargetDensity = mTargetDensity; - if (mBitmap == null) { + if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } } @@ -190,22 +198,23 @@ public class BitmapDrawable extends Drawable { * Returns the bitmap used by this drawable to render. May be null. */ public final Bitmap getBitmap() { - return mBitmap; + return mBitmapState.mBitmap; } private void computeBitmapSize() { - mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); - mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); + final Bitmap bitmap = mBitmapState.mBitmap; + if (bitmap != null) { + mBitmapWidth = bitmap.getScaledWidth(mTargetDensity); + mBitmapHeight = bitmap.getScaledHeight(mTargetDensity); + } else { + mBitmapWidth = mBitmapHeight = -1; + } } private void setBitmap(Bitmap bitmap) { - if (bitmap != mBitmap) { - mBitmap = bitmap; - if (bitmap != null) { - computeBitmapSize(); - } else { - mBitmapWidth = mBitmapHeight = -1; - } + if (mBitmapState.mBitmap != bitmap) { + mBitmapState.mBitmap = bitmap; + computeBitmapSize(); invalidateSelf(); } } @@ -247,7 +256,7 @@ public class BitmapDrawable extends Drawable { public void setTargetDensity(int density) { if (mTargetDensity != density) { mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; - if (mBitmap != null) { + if (mBitmapState.mBitmap != null) { computeBitmapSize(); } invalidateSelf(); @@ -343,8 +352,9 @@ public class BitmapDrawable extends Drawable { /** * Indicates the repeat behavior of this drawable on the X axis. * - * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, - * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. + * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, + * {@link android.graphics.Shader.TileMode#REPEAT} or + * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. */ public Shader.TileMode getTileModeX() { return mBitmapState.mTileModeX; @@ -353,8 +363,9 @@ public class BitmapDrawable extends Drawable { /** * Indicates the repeat behavior of this drawable on the Y axis. * - * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, - * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. + * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, + * {@link android.graphics.Shader.TileMode#REPEAT} or + * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. */ public Shader.TileMode getTileModeY() { return mBitmapState.mTileModeY; @@ -362,9 +373,9 @@ public class BitmapDrawable extends Drawable { /** * Sets the repeat behavior of this drawable on the X axis. By default, the drawable - * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or - * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap - * is smaller than this drawable. + * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or + * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) + * if the bitmap is smaller than this drawable. * * @param mode The repeat mode for this drawable. * @@ -377,9 +388,9 @@ public class BitmapDrawable extends Drawable { /** * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable - * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or - * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap - * is smaller than this drawable. + * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or + * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) + * if the bitmap is smaller than this drawable. * * @param mode The repeat mode for this drawable. * @@ -392,9 +403,9 @@ public class BitmapDrawable extends Drawable { /** * Sets the repeat behavior of this drawable on both axis. By default, the drawable - * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or - * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap - * is smaller than this drawable. + * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or + * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) + * if the bitmap is smaller than this drawable. * * @param xmode The X repeat mode for this drawable. * @param ymode The Y repeat mode for this drawable. @@ -462,7 +473,7 @@ public class BitmapDrawable extends Drawable { @Override public void draw(Canvas canvas) { - final Bitmap bitmap = mBitmap; + final Bitmap bitmap = mBitmapState.mBitmap; if (bitmap == null) { return; } @@ -589,7 +600,7 @@ public class BitmapDrawable extends Drawable { public void setTint(ColorStateList tint) { if (mBitmapState.mTint != tint) { mBitmapState.mTint = tint; - updateTintFilter(); + computeTintFilter(); invalidateSelf(); } } @@ -612,7 +623,7 @@ public class BitmapDrawable extends Drawable { public void setTintMode(Mode tintMode) { if (mBitmapState.mTintMode != tintMode) { mBitmapState.mTintMode = tintMode; - updateTintFilter(); + computeTintFilter(); invalidateSelf(); } } @@ -624,18 +635,15 @@ public class BitmapDrawable extends Drawable { return mBitmapState.mTintMode; } - /** - * Ensures the tint filter is consistent with the current tint color and - * mode. - */ - private void updateTintFilter() { - final ColorStateList tint = mBitmapState.mTint; - final Mode tintMode = mBitmapState.mTintMode; - if (tint != null && tintMode != null) { - if (mTintFilter == null) { - mTintFilter = new PorterDuffColorFilter(0, tintMode); + private void computeTintFilter() { + final BitmapState state = mBitmapState; + if (state.mTint != null && state.mTintMode != null) { + final int color = state.mTint.getColorForState(getState(), 0); + if (mTintFilter != null) { + mTintFilter.setColor(color); + mTintFilter.setMode(state.mTintMode); } else { - mTintFilter.setMode(tintMode); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); } } else { mTintFilter = null; @@ -693,16 +701,15 @@ public class BitmapDrawable extends Drawable { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes( - r, theme, attrs, R.styleable.BitmapDrawable); - inflateStateFromTypedArray(a); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable); + updateStateFromTypedArray(a); a.recycle(); } /** - * Initializes the constant state from the values in the typed array. + * Updates the constant state from the values in the typed array. */ - private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { final Resources r = a.getResources(); final BitmapState state = mBitmapState; @@ -710,86 +717,52 @@ public class BitmapDrawable extends Drawable { final int[] themeAttrs = a.extractThemeAttrs(); state.mThemeAttrs = themeAttrs; - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_src] == 0) { - final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0); - if (id == 0) { - throw new XmlPullParserException(a.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); - } - - final Bitmap bitmap = BitmapFactory.decodeResource(r, id); + final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0); + if (srcResId != 0) { + final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId); if (bitmap == null) { throw new XmlPullParserException(a.getPositionDescription() + ": <bitmap> requires a valid src attribute"); } + state.mBitmap = bitmap; - setBitmap(bitmap); } - setTargetDensity(r.getDisplayMetrics()); + state.mTargetDensity = r.getDisplayMetrics().densityDpi; - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_mipMap] == 0) { - final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; - final boolean mipMap = a.getBoolean( - R.styleable.BitmapDrawable_mipMap, defMipMap); - setMipMap(mipMap); - } + final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; + setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap)); - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_autoMirrored] == 0) { - final boolean autoMirrored = a.getBoolean( - R.styleable.BitmapDrawable_autoMirrored, false); - setAutoMirrored(autoMirrored); - } + state.mAutoMirrored = a.getBoolean( + R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored); + state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tintMode] == 0) { - final int tintModeValue = a.getInt( - R.styleable.BitmapDrawable_tintMode, -1); - state.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); + final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); + if (tintMode != -1) { + state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); } - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tint] == 0) { - state.mTint = a.getColorStateList(R.styleable.BitmapDrawable_tint); - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, mBitmapState.mTintMode); - } + final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); + if (tint != null) { + state.mTint = tint; } final Paint paint = mBitmapState.mPaint; + paint.setAntiAlias(a.getBoolean( + R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); + paint.setFilterBitmap(a.getBoolean( + R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); + paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither())); - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_antialias] == 0) { - final boolean antiAlias = a.getBoolean( - R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()); - paint.setAntiAlias(antiAlias); - } - - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_filter] == 0) { - final boolean filter = a.getBoolean( - R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()); - paint.setFilterBitmap(filter); - } + setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity)); - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_dither] == 0) { - final boolean dither = a.getBoolean( - R.styleable.BitmapDrawable_dither, paint.isDither()); - paint.setDither(dither); - } - - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_alpha] == 0) { - state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, 1.0f); - } - - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_gravity] == 0) { - final int gravity = a.getInt( - R.styleable.BitmapDrawable_gravity, Gravity.FILL); - setGravity(gravity); - } - - if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tileMode] == 0) { - final int tileMode = a.getInt( - R.styleable.BitmapDrawable_tileMode, -1); + final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); + if (tileMode != TILE_MODE_UNDEFINED) { setTileModeInternal(tileMode); } + + // Update local properties. + initializeWithState(state, r); } @Override @@ -797,120 +770,32 @@ public class BitmapDrawable extends Drawable { super.applyTheme(t); final BitmapState state = mBitmapState; - if (state == null) { - throw new RuntimeException("Can't apply theme to <bitmap> with no constant state"); + if (state == null || state.mThemeAttrs == null) { + return; } - final int[] themeAttrs = state.mThemeAttrs; - if (themeAttrs != null) { - final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.BitmapDrawable, 0, 0); + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable); + try { updateStateFromTypedArray(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { a.recycle(); } } - /** - * Updates the constant state from the values in the typed array. - */ - private void updateStateFromTypedArray(TypedArray a) { - final Resources r = a.getResources(); - final BitmapState state = mBitmapState; - final Paint paint = mBitmapState.mPaint; - - if (a.hasValue(R.styleable.BitmapDrawable_antialias)) { - final boolean antiAlias = a.getBoolean( - R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()); - paint.setAntiAlias(antiAlias); - } - - if (a.hasValue(R.styleable.BitmapDrawable_filter)) { - final boolean filter = a.getBoolean( - R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()); - paint.setFilterBitmap(filter); - } - - if (a.hasValue(R.styleable.BitmapDrawable_dither)) { - final boolean dither = a.getBoolean( - R.styleable.BitmapDrawable_dither, paint.isDither()); - paint.setDither(dither); - } - - if (a.hasValue(R.styleable.BitmapDrawable_alpha)) { - state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); - } - - if (a.hasValue(R.styleable.BitmapDrawable_gravity)) { - final int gravity = a.getInt( - R.styleable.BitmapDrawable_gravity, Gravity.FILL); - setGravity(gravity); - } - - if (a.hasValue(R.styleable.BitmapDrawable_tileMode)) { - final int tileMode = a.getInt( - R.styleable.BitmapDrawable_tileMode, -1); - setTileModeInternal(tileMode); - } - - if (a.hasValue(R.styleable.BitmapDrawable_src)) { - final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0); - if (id == 0) { - throw new RuntimeException(a.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); - } - - final Bitmap bitmap = BitmapFactory.decodeResource(r, id); - if (bitmap == null) { - throw new RuntimeException(a.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); - } - - setBitmap(bitmap); - } - - setTargetDensity(r.getDisplayMetrics()); - - if (a.hasValue(R.styleable.BitmapDrawable_mipMap)) { - final boolean mipMap = a.getBoolean( - R.styleable.BitmapDrawable_mipMap, - state.mBitmap.hasMipMap()); - setMipMap(mipMap); - } - - if (a.hasValue(R.styleable.BitmapDrawable_autoMirrored)) { - final boolean autoMirrored = a.getBoolean( - R.styleable.BitmapDrawable_autoMirrored, false); - setAutoMirrored(autoMirrored); - } - - if (a.hasValue(R.styleable.BitmapDrawable_tintMode)) { - final int modeValue = a.getInt( - R.styleable.BitmapDrawable_tintMode, -1); - state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); - } - - if (a.hasValue(R.styleable.BitmapDrawable_tint)) { - final ColorStateList tint = a.getColorStateList( - R.styleable.BitmapDrawable_tint); - if (tint != null) { - state.mTint = tint; - final int color = tint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - } - } - private void setTileModeInternal(final int tileMode) { switch (tileMode) { - case -1: - // Do nothing. + case TILE_MODE_DISABLED: + setTileModeXY(null, null); break; - case 0: + case TILE_MODE_CLAMP: setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); break; - case 1: + case TILE_MODE_REPEAT: setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); break; - case 2: + case TILE_MODE_MIRROR: setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); break; } @@ -936,8 +821,9 @@ public class BitmapDrawable extends Drawable { if (mBitmapState.mGravity != Gravity.FILL) { return PixelFormat.TRANSLUCENT; } - Bitmap bm = mBitmap; - return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? + + final Bitmap bitmap = mBitmapState.mBitmap; + return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; } @@ -948,22 +834,26 @@ public class BitmapDrawable extends Drawable { } final static class BitmapState extends ConstantState { - Bitmap mBitmap; - ColorStateList mTint; + final Paint mPaint; + + // Values loaded during inflation. + int[] mThemeAttrs = null; + Bitmap mBitmap = null; + ColorStateList mTint = null; Mode mTintMode = Mode.SRC_IN; - int[] mThemeAttrs; - int mChangingConfigurations; int mGravity = Gravity.FILL; float mBaseAlpha = 1.0f; - Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); Shader.TileMode mTileModeX = null; Shader.TileMode mTileModeY = null; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; + boolean mAutoMirrored = false; + + int mChangingConfigurations; boolean mRebuildShader; - boolean mAutoMirrored; BitmapState(Bitmap bitmap) { mBitmap = bitmap; + mPaint = new Paint(DEFAULT_PAINT_FLAGS); } BitmapState(BitmapState bitmapState) { @@ -1013,6 +903,10 @@ public class BitmapDrawable extends Drawable { } } + /** + * The one constructor to rule them all. This is called by all public + * constructors to set the state and initialize local properties. + */ private BitmapDrawable(BitmapState state, Resources res, Theme theme) { if (theme != null && state.canApplyTheme()) { mBitmapState = new BitmapState(state); @@ -1034,11 +928,7 @@ public class BitmapDrawable extends Drawable { mTargetDensity = state.mTargetDensity; } - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - - setBitmap(state.mBitmap); + computeTintFilter(); + computeBitmapSize(); } } diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index 8243b7c..df5ca33 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -199,7 +199,7 @@ public class ColorDrawable extends Drawable { final int[] themeAttrs = state.mThemeAttrs; if (themeAttrs != null) { - final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.ColorDrawable, 0, 0); + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.ColorDrawable); updateStateFromTypedArray(a); a.recycle(); } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index dc06350..8fe1544 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -1053,7 +1053,7 @@ public class GradientDrawable extends Drawable { final int[] themeAttrs = state.mThemeAttrs; if (themeAttrs != null) { final TypedArray a = t.resolveAttributes( - themeAttrs, R.styleable.GradientDrawable, 0, 0); + themeAttrs, R.styleable.GradientDrawable); updateStateFromTypedArray(a); a.recycle(); @@ -1123,37 +1123,37 @@ public class GradientDrawable extends Drawable { TypedArray a; if (state.mAttrSize != null) { - a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize, 0, 0); + a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize); // TODO: updateGradientDrawableSize(a); a.recycle(); } if (state.mAttrGradient != null) { - a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient, 0, 0); + a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient); // TODO: updateGradientDrawableGradient(a); a.recycle(); } if (state.mAttrSolid != null) { - a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid, 0, 0); + a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid); // TODO: updateGradientDrawableSolid(a); a.recycle(); } if (state.mAttrStroke != null) { - a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke, 0, 0); + a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke); // TODO: updateGradientDrawableStroke(a); a.recycle(); } if (state.mAttrCorners != null) { - a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners, 0, 0); + a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners); // TODO: updateDrawableCorners(a); a.recycle(); } if (state.mAttrPadding != null) { - a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding, 0, 0); + a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding); // TODO: updateGradientDrawablePadding(a); a.recycle(); } diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 639d719..7847aad 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -229,7 +229,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final int[] themeAttrs = state.mThemeAttrs; if (themeAttrs != null) { - final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable, 0, 0); + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable); updateStateFromTypedArray(a); a.recycle(); } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 21992ce..3e09707 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -72,8 +72,8 @@ public class NinePatchDrawable extends Drawable { private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; // These are scaled to match the target density. - private int mBitmapWidth; - private int mBitmapHeight; + private int mBitmapWidth = -1; + private int mBitmapHeight = -1; NinePatchDrawable() { mNinePatchState = new NinePatchState(); @@ -209,7 +209,7 @@ public class NinePatchDrawable extends Drawable { } private void setNinePatch(NinePatch ninePatch) { - if (ninePatch != mNinePatch) { + if (mNinePatch != ninePatch) { mNinePatch = ninePatch; if (ninePatch != null) { computeBitmapSize(); @@ -339,7 +339,7 @@ public class NinePatchDrawable extends Drawable { public void setTint(ColorStateList tint) { if (mNinePatchState.mTint != tint) { mNinePatchState.mTint = tint; - updateTintFilter(); + computeTintFilter(); invalidateSelf(); } } @@ -362,23 +362,20 @@ public class NinePatchDrawable extends Drawable { public void setTintMode(Mode tintMode) { if (mNinePatchState.mTintMode != tintMode) { mNinePatchState.mTintMode = tintMode; - updateTintFilter(); + computeTintFilter(); invalidateSelf(); } } - /** - * Ensures the tint filter is consistent with the current tint color and - * mode. - */ - private void updateTintFilter() { - final ColorStateList tint = mNinePatchState.mTint; - final Mode tintMode = mNinePatchState.mTintMode; - if (tint != null && tintMode != null) { - if (mTintFilter == null) { - mTintFilter = new PorterDuffColorFilter(0, tintMode); + private void computeTintFilter() { + final NinePatchState state = mNinePatchState; + if (state.mTint != null && state.mTintMode != null) { + final int color = state.mTint.getColorForState(getState(), 0); + if (mTintFilter != null) { + mTintFilter.setColor(color); + mTintFilter.setMode(state.mTintMode); } else { - mTintFilter.setMode(tintMode); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); } } else { mTintFilter = null; @@ -422,38 +419,28 @@ public class NinePatchDrawable extends Drawable { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes( - r, theme, attrs, R.styleable.NinePatchDrawable); - inflateStateFromTypedArray(a); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.NinePatchDrawable); + updateStateFromTypedArray(a); a.recycle(); } /** - * Initializes the constant state from the values in the typed array. + * Updates the constant state from the values in the typed array. */ - private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { final Resources r = a.getResources(); - final NinePatchState ninePatchState = mNinePatchState; + final NinePatchState state = mNinePatchState; // Extract the theme attributes, if any. final int[] themeAttrs = a.extractThemeAttrs(); - ninePatchState.mThemeAttrs = themeAttrs; + state.mThemeAttrs = themeAttrs; - if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_dither] == 0) { - final boolean dither = a.getBoolean( - R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); - ninePatchState.mDither = dither; - } - - if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_src] == 0) { - final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); - if (id == 0) { - throw new XmlPullParserException(a.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); - } + state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither); + final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); + if (srcResId != 0) { final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inDither = !ninePatchState.mDither; + options.inDither = !state.mDither; options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; final Rect padding = new Rect(); @@ -462,7 +449,7 @@ public class NinePatchDrawable extends Drawable { try { final TypedValue value = new TypedValue(); - final InputStream is = r.openRawResource(id, value); + final InputStream is = r.openRawResource(srcResId, value); bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); @@ -479,40 +466,30 @@ public class NinePatchDrawable extends Drawable { ": <nine-patch> requires a valid 9-patch source image"); } - final NinePatch ninePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); - ninePatchState.mNinePatch = ninePatch; - ninePatchState.mPadding = padding; - ninePatchState.mOpticalInsets = Insets.of(opticalInsets); - } - - if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_autoMirrored] == 0) { - final boolean autoMirrored = a.getBoolean( - R.styleable.NinePatchDrawable_autoMirrored, false); - ninePatchState.mAutoMirrored = autoMirrored; + state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); + state.mPadding = padding; + state.mOpticalInsets = Insets.of(opticalInsets); } - if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tintMode] == 0) { - final int tintModeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); - ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); - } + state.mAutoMirrored = a.getBoolean( + R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored); + state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha); - if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tint] == 0) { - ninePatchState.mTint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); - if (ninePatchState.mTint != null) { - final int color = ninePatchState.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode); - } + final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); + if (tintMode != -1) { + state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); } - if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_alpha] == 0) { - ninePatchState.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, 1.0f); + final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); + if (tint != null) { + state.mTint = tint; } - // Apply the constant state to the paint. - initializeWithState(ninePatchState, r); + // Update local properties. + initializeWithState(state, r); // Push density applied by setNinePatchState into state. - ninePatchState.mTargetDensity = mTargetDensity; + state.mTargetDensity = mTargetDensity; } @Override @@ -520,98 +497,20 @@ public class NinePatchDrawable extends Drawable { super.applyTheme(t); final NinePatchState state = mNinePatchState; - if (state == null) { - throw new RuntimeException("Can't apply theme to <nine-patch> with no constant state"); + if (state == null || state.mThemeAttrs == null) { + return; } - final int[] themeAttrs = state.mThemeAttrs; - if (themeAttrs != null) { - final TypedArray a = t.resolveAttributes( - themeAttrs, R.styleable.NinePatchDrawable, 0, 0); + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.NinePatchDrawable); + try { updateStateFromTypedArray(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { a.recycle(); } } - /** - * Updates the constant state from the values in the typed array. - */ - private void updateStateFromTypedArray(TypedArray a) { - final Resources r = a.getResources(); - final NinePatchState state = mNinePatchState; - - if (a.hasValue(R.styleable.NinePatchDrawable_dither)) { - state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); - } - - if (a.hasValue(R.styleable.NinePatchDrawable_autoMirrored)) { - state.mAutoMirrored = a.getBoolean(R.styleable.NinePatchDrawable_autoMirrored, false); - } - - if (a.hasValue(R.styleable.NinePatchDrawable_src)) { - final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); - if (id == 0) { - throw new RuntimeException(a.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); - } - - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inDither = !state.mDither; - options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; - - final Rect padding = new Rect(); - final Rect opticalInsets = new Rect(); - Bitmap bitmap = null; - - try { - final TypedValue value = new TypedValue(); - final InputStream is = r.openRawResource(id, value); - - bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); - - is.close(); - } catch (IOException e) { - // Ignore - } - - if (bitmap == null) { - throw new RuntimeException(a.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); - } else if (bitmap.getNinePatchChunk() == null) { - throw new RuntimeException(a.getPositionDescription() + - ": <nine-patch> requires a valid 9-patch source image"); - } - - state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); - state.mPadding = padding; - state.mOpticalInsets = Insets.of(opticalInsets); - } - - if (a.hasValue(R.styleable.NinePatchDrawable_tintMode)) { - final int modeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); - state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); - } - - if (a.hasValue(R.styleable.NinePatchDrawable_tint)) { - final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); - if (tint != null) { - state.mTint = tint; - final int color = tint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - } - - if (a.hasValue(R.styleable.NinePatchDrawable_alpha)) { - state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, 1.0f); - } - - // Apply the constant state to the paint. - initializeWithState(state, r); - - // Push density applied by setNinePatchState into state. - state.mTargetDensity = mTargetDensity; - } - @Override public boolean canApplyTheme() { return mNinePatchState != null && mNinePatchState.mThemeAttrs != null; @@ -705,17 +604,19 @@ public class NinePatchDrawable extends Drawable { } final static class NinePatchState extends ConstantState { - NinePatch mNinePatch; - ColorStateList mTint; + // Values loaded during inflation. + int[] mThemeAttrs = null; + NinePatch mNinePatch = null; + ColorStateList mTint = null; Mode mTintMode = Mode.SRC_IN; - Rect mPadding; - Insets mOpticalInsets; + Rect mPadding = null; + Insets mOpticalInsets = Insets.NONE; float mBaseAlpha = 1.0f; - boolean mDither; - int[] mThemeAttrs; - int mChangingConfigurations; + boolean mDither = DEFAULT_DITHER; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; - boolean mAutoMirrored; + boolean mAutoMirrored = false; + + int mChangingConfigurations; NinePatchState() { // Empty constructor. @@ -786,6 +687,10 @@ public class NinePatchDrawable extends Drawable { } } + /** + * The one constructor to rule them all. This is called by all public + * constructors to set the state and initialize local properties. + */ private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) { if (theme != null && state.canApplyTheme()) { mNinePatchState = new NinePatchState(state); @@ -812,14 +717,12 @@ public class NinePatchDrawable extends Drawable { setDither(state.mDither); } - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); + // Make a local copy of the padding. + if (state.mPadding != null) { + mPadding = new Rect(state.mPadding); } - final Rect statePadding = state.mPadding; - mPadding = statePadding != null ? new Rect(statePadding) : null; - + computeTintFilter(); setNinePatch(state.mNinePatch); } } diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index 0097183..8128b5f 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -291,7 +291,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { final int[] themeAttrs = state.mTouchThemeAttrs; if (themeAttrs != null) { final TypedArray a = t.resolveAttributes( - themeAttrs, R.styleable.TouchFeedbackDrawable, 0, 0); + themeAttrs, R.styleable.TouchFeedbackDrawable); updateStateFromTypedArray(a); a.recycle(); } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 2da8615..05658f5 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -753,7 +753,7 @@ public class VectorDrawable extends Drawable { } final TypedArray a = t.resolveAttributes( - mThemeAttrs, R.styleable.VectorDrawablePath, 0, 0); + mThemeAttrs, R.styleable.VectorDrawablePath); mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip); diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java index f5b07c1..d603436 100644 --- a/graphics/java/android/graphics/pdf/PdfDocument.java +++ b/graphics/java/android/graphics/pdf/PdfDocument.java @@ -32,7 +32,7 @@ import java.util.List; /** * <p> * This class enables generating a PDF document from native Android content. You - * open a new document and then for every page you want to add you start a page, + * create a new document and then for every page you want to add you start a page, * write content to the page, and finish the page. After you are done with all * pages, you write the document to an output stream and close the document. * After a document is closed you should not use it anymore. Note that pages are @@ -64,7 +64,7 @@ import java.util.List; * // write the document content * document.writeTo(getOutputStream()); * - * //close the document + * // close the document * document.close(); * </pre> */ diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java new file mode 100644 index 0000000..3fa3b9f --- /dev/null +++ b/graphics/java/android/graphics/pdf/PdfRenderer.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2014 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.pdf; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.OsConstants; +import dalvik.system.CloseGuard; +import libcore.io.Libcore; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * <p> + * This class enables rendering a PDF document. This class is not thread safe. + * </p> + * <p> + * If you want to render a PDF, you create a renderer and for every page you want + * to render, you open the page, render it, and close the page. After you are done + * with rendering, you close the renderer. After the renderer is closed it should not + * be used anymore. Note that the pages are rendered one by one, i.e. you can have + * only a single page opened at any given time. + * </p> + * <p> + * A typical use of the APIs to render a PDF looks like this: + * </p> + * <pre> + * // create a new renderer + * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor()); + * + * // let us just render all pages + * final int pageCount = renderer.getPageCount(); + * for (int i = 0; i < pageCount; i++) { + * Page page = renderer.openPage(i); + * Bitmap bitmap = getBitmapReuseIfPossible(page); + * + * // say we render for showing on the screen + * page.render(bitmap, getContentBoundsInBitmap(), + * getDesiredTransformation(), Page.RENDER_MODE_FOR_DISPLAY); + * + * // do stuff with the bitmap + * + * renderer.closePage(page); + * } + * + * // close the renderer + * renderer.close(); + * </pre> + * + * @see #close() + */ +public final class PdfRenderer implements AutoCloseable { + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private final Point mTempPoint = new Point(); + + private final long mNativeDocument; + + private final int mPageCount; + + private ParcelFileDescriptor mInput; + + private Page mCurrentPage; + + /** @hide */ + @IntDef({ + Page.RENDER_MODE_FOR_DISPLAY, + Page.RENDER_MODE_FOR_PRINT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RenderMode {} + + /** + * Creates a new instance. + * <p> + * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>, + * i.e. its data being randomly accessed, e.g. pointing to a file. + * </p> + * <p> + * <strong>Note:</strong> This class takes ownership of the passed in file descriptor + * and is responsible for closing it when the renderer is closed. + * </p> + * + * @param input Seekable file descriptor to read from. + */ + public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException { + if (input == null) { + throw new NullPointerException("input cannot be null"); + } + + final long size; + try { + Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET); + size = Libcore.os.fstat(input.getFileDescriptor()).st_size; + } catch (ErrnoException ee) { + throw new IllegalArgumentException("file descriptor not seekable"); + } + + mInput = input; + mNativeDocument = nativeCreate(mInput.getFd(), size); + mPageCount = nativeGetPageCount(mNativeDocument); + mCloseGuard.open("close"); + } + + /** + * Closes this renderer. You should not use this instance + * after this method is called. + */ + public void close() { + throwIfClosed(); + throwIfPageOpened(); + doClose(); + } + + /** + * Gets the number of pages in the document. + * + * @return The page count. + */ + public int getPageCount() { + throwIfClosed(); + return mPageCount; + } + + /** + * Gets whether the document prefers to be scaled for printing. + * You should take this info account if the document is rendered + * for printing and the target media size differs from the page + * size. + * + * @return If to scale the document. + */ + public boolean shouldScaleForPrinting() { + throwIfClosed(); + return nativeScaleForPrinting(mNativeDocument); + } + + /** + * Opens a page for rendering. + * + * @param index The page index. + * @return A page that can be rendered. + * + * @see #closePage(PdfRenderer.Page) + */ + public Page openPage(int index) { + throwIfClosed(); + throwIfPageOpened(); + mCurrentPage = new Page(index); + return mCurrentPage; + } + + /** + * Closes a page opened for rendering. + * + * @param page The page to close. + * + * @see #openPage(int) + */ + public void closePage(@NonNull Page page) { + throwIfClosed(); + throwIfNotCurrentPage(page); + throwIfCurrentPageClosed(); + mCurrentPage.close(); + mCurrentPage = null; + } + + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + if (mInput != null) { + doClose(); + } + } finally { + super.finalize(); + } + } + + private void doClose() { + if (mCurrentPage != null) { + mCurrentPage.close(); + mCurrentPage = null; + } + nativeClose(mNativeDocument); + try { + mInput.close(); + } catch (IOException ioe) { + /* ignore - best effort */ + } + mInput = null; + mCloseGuard.close(); + } + + private void throwIfClosed() { + if (mInput == null) { + throw new IllegalStateException("Already closed"); + } + } + + private void throwIfPageOpened() { + if (mCurrentPage != null) { + throw new IllegalStateException("Current page not closed"); + } + } + + private void throwIfCurrentPageClosed() { + if (mCurrentPage == null) { + throw new IllegalStateException("Already closed"); + } + } + + private void throwIfNotCurrentPage(Page page) { + if (page != mCurrentPage) { + throw new IllegalArgumentException("Page not from document"); + } + } + + /** + * This class represents a PDF document page for rendering. + */ + public final class Page { + + /** + * Mode to render the content for display on a screen. + */ + public static final int RENDER_MODE_FOR_DISPLAY = 1; + + /** + * Mode to render the content for printing. + */ + public static final int RENDER_MODE_FOR_PRINT = 2; + + private final int mIndex; + private final int mWidth; + private final int mHeight; + + private long mNativePage; + + private Page(int index) { + Point size = mTempPoint; + mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size); + mIndex = index; + mWidth = size.x; + mHeight = size.y; + } + + /** + * Gets the page index. + * + * @return The index. + */ + public int getIndex() { + return mIndex; + } + + /** + * Gets the page width in points (1/72"). + * + * @return The width in points. + */ + public int getWidth() { + return mWidth; + } + + /** + * Gets the page height in points (1/72"). + * + * @return The height in points. + */ + public int getHeight() { + return mHeight; + } + + /** + * Renders a page to a bitmap. + * <p> + * You may optionally specify a rectangular clip in the bitmap bounds. No rendering + * outside the clip will be performed, hence it is your responsibility to initialize + * the bitmap outside the clip. + * </p> + * <p> + * You may optionally specify a matrix to transform the content from page coordinates + * which are in points (1/72") to bitmap coordintates which are in pixels. If this + * matrix is not provided this method will apply a transformation that will fit the + * whole page to the destination clip if profided or the destination bitmap if no + * clip is provided. + * </p> + * <p> + * The clip and transformation are useful for implementing tile rendering where the + * destination bitmap contains a portion of the image, for example when zooming. + * Another useful application is for printing where the size of the bitmap holding + * the page is too large and a client can render the page in stripes. + * </p> + * <p> + * <strong>Note: </strong> The destination bitmap format must be + * {@link Config#ARGB_8888 ARGB}. + * </p> + * <p> + * <strong>Note: </strong> The optional transformation matrix must be affine as per + * {@link android.graphics.Matrix#isAffine()}. Hence, you can specify rotation, scaling, + * translation but not a perspective transformation. + * </p> + * + * @param destination Destination bitmap to which to render. + * @param destClip Optional clip in the bitmap bounds. + * @param transform Optional transformation to apply when rendering. + * @param renderMode The render mode. + * + * @see #RENDER_MODE_FOR_DISPLAY + * @see #RENDER_MODE_FOR_PRINT + */ + public void render(@NonNull Bitmap destination, @Nullable Rect destClip, + @Nullable Matrix transform, @RenderMode int renderMode) { + if (destination.getConfig() != Config.ARGB_8888) { + throw new IllegalArgumentException("Unsupported pixel format"); + } + + if (destClip != null) { + if (destClip.left < 0 || destClip.top < 0 + || destClip.right > destination.getWidth() + || destClip.bottom > destination.getHeight()) { + throw new IllegalArgumentException("destBounds not in destination"); + } + } + + if (transform != null && !transform.isAffine()) { + throw new IllegalArgumentException("transform not affine"); + } + + if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) { + throw new IllegalArgumentException("Unsupported render mode"); + } + + if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) { + throw new IllegalArgumentException("Only single render mode supported"); + } + + final int contentLeft = (destClip != null) ? destClip.left : 0; + final int contentTop = (destClip != null) ? destClip.top : 0; + final int contentRight = (destClip != null) ? destClip.right + : destination.getWidth(); + final int contentBottom = (destClip != null) ? destClip.bottom + : destination.getHeight(); + + final long transformPtr = (transform != null) ? transform.native_instance : 0; + + nativeRenderPage(mNativeDocument, mNativePage, destination.mNativeBitmap, contentLeft, + contentTop, contentRight, contentBottom, transformPtr, renderMode); + } + + void close() { + nativeClosePage(mNativePage); + mNativePage = 0; + } + } + + private static native long nativeCreate(int fd, long size); + private static native void nativeClose(long documentPtr); + private static native int nativeGetPageCount(long documentPtr); + private static native boolean nativeScaleForPrinting(long documentPtr); + private static native void nativeRenderPage(long documentPtr, long pagePtr, long destPtr, + int destLeft, int destTop, int destRight, int destBottom, long matrixPtr, int renderMode); + private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex, + Point outSize); + private static native void nativeClosePage(long pagePtr); +} |