diff options
Diffstat (limited to 'graphics/java/android')
51 files changed, 3469 insertions, 951 deletions
diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java new file mode 100644 index 0000000..39a5a53 --- /dev/null +++ b/graphics/java/android/graphics/Atlas.java @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2013 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; + +/** + * @hide + */ +public class Atlas { + /** + * This flag indicates whether the packing algorithm will attempt + * to rotate entries to make them fit better in the atlas. + */ + public static final int FLAG_ALLOW_ROTATIONS = 0x1; + /** + * This flag indicates whether the packing algorithm should leave + * an empty 1 pixel wide border around each bitmap. This border can + * be useful if the content of the atlas will be used in OpenGL using + * bilinear filtering. + */ + public static final int FLAG_ADD_PADDING = 0x2; + /** + * Default flags: allow rotations and add padding. + */ + public static final int FLAG_DEFAULTS = FLAG_ADD_PADDING; + + /** + * Each type defines a different packing algorithm that can + * be used by an {@link Atlas}. The best algorithm to use + * will depend on the source dataset and the dimensions of + * the atlas. + */ + public enum Type { + SliceMinArea, + SliceMaxArea, + SliceShortAxis, + SliceLongAxis + } + + /** + * Represents a bitmap packed in the atlas. Each entry has a location in + * pixels in the atlas and a rotation flag. If the entry was rotated, the + * bitmap must be rotated by 90 degrees (in either direction as long as + * the origin remains the same) before being rendered into the atlas. + */ + public static class Entry { + /** + * Location, in pixels, of the bitmap on the X axis in the atlas. + */ + public int x; + /** + * Location, in pixels, of the bitmap on the Y axis in the atlas. + */ + public int y; + + /** + * If true, the bitmap must be rotated 90 degrees in the atlas. + */ + public boolean rotated; + } + + private final Policy mPolicy; + + /** + * Creates a new atlas with the specified algorithm and dimensions + * in pixels. Calling this constructor is equivalent to calling + * {@link #Atlas(Atlas.Type, int, int, int)} with {@link #FLAG_DEFAULTS}. + * + * @param type The algorithm to use to pack rectangles in the atlas + * @param width The width of the atlas in pixels + * @param height The height of the atlas in pixels + * + * @see #Atlas(Atlas.Type, int, int, int) + */ + public Atlas(Type type, int width, int height) { + this(type, width, height, FLAG_DEFAULTS); + } + + /** + * Creates a new atlas with the specified algorithm and dimensions + * in pixels. A set of flags can also be specified to control the + * behavior of the atlas. + * + * @param type The algorithm to use to pack rectangles in the atlas + * @param width The width of the atlas in pixels + * @param height The height of the atlas in pixels + * @param flags Optional flags to control the behavior of the atlas: + * {@link #FLAG_ADD_PADDING}, {@link #FLAG_ALLOW_ROTATIONS} + * + * @see #Atlas(Atlas.Type, int, int) + */ + public Atlas(Type type, int width, int height, int flags) { + mPolicy = findPolicy(type, width, height, flags); + } + + /** + * Packs a rectangle of the specified dimensions in this atlas. + * + * @param width The width of the rectangle to pack in the atlas + * @param height The height of the rectangle to pack in the atlas + * + * @return An {@link Entry} instance if the rectangle was packed in + * the atlas, or null if the rectangle could not fit + * + * @see #pack(int, int, Atlas.Entry) + */ + public Entry pack(int width, int height) { + return pack(width, height, null); + } + + /** + * Packs a rectangle of the specified dimensions in this atlas. + * + * @param width The width of the rectangle to pack in the atlas + * @param height The height of the rectangle to pack in the atlas + * @param entry Out parameter that will be filled in with the location + * and attributes of the packed rectangle, can be null + * + * @return An {@link Entry} instance if the rectangle was packed in + * the atlas, or null if the rectangle could not fit + * + * @see #pack(int, int) + */ + public Entry pack(int width, int height, Entry entry) { + if (entry == null) entry = new Entry(); + return mPolicy.pack(width, height, entry); + } + + private static Policy findPolicy(Type type, int width, int height, int flags) { + switch (type) { + case SliceMinArea: + return new SlicePolicy(width, height, flags, + new SlicePolicy.MinAreaSplitDecision()); + case SliceMaxArea: + return new SlicePolicy(width, height, flags, + new SlicePolicy.MaxAreaSplitDecision()); + case SliceShortAxis: + return new SlicePolicy(width, height, flags, + new SlicePolicy.ShorterFreeAxisSplitDecision()); + case SliceLongAxis: + return new SlicePolicy(width, height, flags, + new SlicePolicy.LongerFreeAxisSplitDecision()); + } + return null; + } + + /** + * A policy defines how the atlas performs the packing operation. + */ + private static abstract class Policy { + abstract Entry pack(int width, int height, Entry entry); + } + + /** + * The Slice algorightm divides the remaining empty space either + * horizontally or vertically after a bitmap is placed in the atlas. + * + * NOTE: the algorithm is explained below using a tree but is + * implemented using a linked list instead for performance reasons. + * + * The algorithm starts with a single empty cell covering the entire + * atlas: + * + * ----------------------- + * | | + * | | + * | | + * | Empty space | + * | (C0) | + * | | + * | | + * | | + * ----------------------- + * + * The tree of cells looks like this: + * + * N0(free) + * + * The algorithm then places a bitmap B1, if possible: + * + * ----------------------- + * | | | + * | B1 | | + * | | | + * |-------- | + * | | + * | | + * | | + * | | + * ----------------------- + * + * After placing a bitmap in an empty cell, the algorithm splits + * the remaining space in two new empty cells. The split can occur + * vertically or horizontally (this is controlled by the "split + * decision" parameter of the algorithm.) + * + * Here is for the instance the result of a vertical split: + * + * ----------------------- + * | | | + * | B1 | | + * | | | + * |--------| C2 | + * | | | + * | | | + * | C1 | | + * | | | + * ----------------------- + * + * The cells tree now looks like this: + * + * C0(occupied) + * / \ + * / \ + * / \ + * / \ + * C1(free) C2(free) + * + * For each bitmap to place in the atlas, the Slice algorithm + * will visit the free cells until it finds one where a bitmap can + * fit. It will then split the now occupied cell and proceed onto + * the next bitmap. + */ + private static class SlicePolicy extends Policy { + private final Cell mRoot = new Cell(); + + private final SplitDecision mSplitDecision; + + private final boolean mAllowRotation; + private final int mPadding; + + /** + * A cell represents a sub-rectangle of the atlas. A cell is + * a node in a linked list representing the available free + * space in the atlas. + */ + private static class Cell { + int x; + int y; + + int width; + int height; + + Cell next; + + @Override + public String toString() { + return String.format("cell[x=%d y=%d width=%d height=%d", x, y, width, height); + } + } + + SlicePolicy(int width, int height, int flags, SplitDecision splitDecision) { + mAllowRotation = (flags & FLAG_ALLOW_ROTATIONS) != 0; + mPadding = (flags & FLAG_ADD_PADDING) != 0 ? 1 : 0; + + // The entire atlas is empty at first, minus padding + Cell first = new Cell(); + first.x = first.y = mPadding; + first.width = width - 2 * mPadding; + first.height = height - 2 * mPadding; + + mRoot.next = first; + mSplitDecision = splitDecision; + } + + @Override + Entry pack(int width, int height, Entry entry) { + Cell cell = mRoot.next; + Cell prev = mRoot; + + while (cell != null) { + if (insert(cell, prev, width, height, entry)) { + return entry; + } + + prev = cell; + cell = cell.next; + } + + return null; + } + + /** + * Defines how the remaining empty space should be split up: + * vertically or horizontally. + */ + private static interface SplitDecision { + /** + * Returns true if the remaining space defined by + * <code>freeWidth</code> and <code>freeHeight</code> + * should be split horizontally. + * + * @param freeWidth The rectWidth of the free space after packing a rectangle + * @param freeHeight The rectHeight of the free space after packing a rectangle + * @param rectWidth The rectWidth of the rectangle that was packed in a cell + * @param rectHeight The rectHeight of the rectangle that was packed in a cell + */ + boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight); + } + + // Splits the free area horizontally to minimize the horizontal section area + private static class MinAreaSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return rectWidth * freeHeight > freeWidth * rectHeight; + } + } + + // Splits the free area horizontally to maximize the horizontal section area + private static class MaxAreaSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return rectWidth * freeHeight <= freeWidth * rectHeight; + } + } + + // Splits the free area horizontally if the horizontal axis is shorter + private static class ShorterFreeAxisSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return freeWidth <= freeHeight; + } + } + + // Splits the free area horizontally if the vertical axis is shorter + private static class LongerFreeAxisSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return freeWidth > freeHeight; + } + } + + /** + * Attempts to pack a rectangle of specified dimensions in the available + * empty space. + * + * @param cell The cell representing free space in which to pack the rectangle + * @param prev The previous cell in the free space linked list + * @param width The width of the rectangle to pack + * @param height The height of the rectangle to pack + * @param entry Stores the location of the packged rectangle, if it fits + * + * @return True if the rectangle was packed in the atlas, false otherwise + */ + @SuppressWarnings("SuspiciousNameCombination") + private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) { + boolean rotated = false; + + // If the rectangle doesn't fit we'll try to rotate it + // if possible before giving up + if (cell.width < width || cell.height < height) { + if (mAllowRotation) { + if (cell.width < height || cell.height < width) { + return false; + } + + // Rotate the rectangle + int temp = width; + width = height; + height = temp; + rotated = true; + } else { + return false; + } + } + + // Remaining free space after packing the rectangle + int deltaWidth = cell.width - width; + int deltaHeight = cell.height - height; + + // Split the remaining free space into two new cells + Cell first = new Cell(); + Cell second = new Cell(); + + first.x = cell.x + width + mPadding; + first.y = cell.y; + first.width = deltaWidth - mPadding; + + second.x = cell.x; + second.y = cell.y + height + mPadding; + second.height = deltaHeight - mPadding; + + if (mSplitDecision.splitHorizontal(deltaWidth, deltaHeight, + width, height)) { + first.height = height; + second.width = cell.width; + } else { + first.height = cell.height; + second.width = width; + + // The order of the cells matters for efficient packing + // We want to give priority to the cell chosen by the + // split decision heuristic + Cell temp = first; + first = second; + second = temp; + } + + // Remove degenerate cases to keep the free list as small as possible + if (first.width > 0 && first.height > 0) { + prev.next = first; + prev = first; + } + + if (second.width > 0 && second.height > 0) { + prev.next = second; + second.next = cell.next; + } else { + prev.next = cell.next; + } + + // The cell is now completely removed from the free list + cell.next = null; + + // Return the location and rotation of the packed rectangle + entry.x = cell.x; + entry.y = cell.y; + entry.rotated = rotated; + + return true; + } + } +} diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 89abdef..74838ee 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -46,20 +46,32 @@ public final class Bitmap implements Parcelable { /** * Backing buffer for the Bitmap. * Made public for quick access from drawing methods -- do NOT modify - * from outside this class. + * from outside this class * * @hide */ + @SuppressWarnings("UnusedDeclaration") // native code only public byte[] mBuffer; @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // Keep to finalize native resources private final BitmapFinalizer mFinalizer; private final boolean mIsMutable; + + /** + * Represents whether the Bitmap's content is expected to be pre-multiplied. + * Note that isPremultiplied() does not directly return this value, because + * isPremultiplied() may never return true for a 565 Bitmap. + * + * setPremultiplied() does directly set the value so that setConfig() and + * setPremultiplied() aren't order dependent, despite being setters. + */ + private boolean mIsPremultiplied; + private byte[] mNinePatchChunk; // may be null private int[] mLayoutBounds; // may be null - private int mWidth = -1; - private int mHeight = -1; + private int mWidth; + private int mHeight; private boolean mRecycled; // Package-scoped for fast access. @@ -88,38 +100,26 @@ public final class Bitmap implements Parcelable { } /** - * @noinspection UnusedDeclaration - */ - /* Private constructor that must received an already allocated native - bitmap int (pointer). - - This can be called from JNI code. - */ - Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, - int density) { - this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density); - } - - /** - * @noinspection UnusedDeclaration + * Private constructor that must received an already allocated native bitmap + * int (pointer). */ - /* Private constructor that must received an already allocated native - bitmap int (pointer). - - This can be called from JNI code. - */ - Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, - int[] layoutBounds, int density) { + @SuppressWarnings({"UnusedDeclaration"}) // called from JNI + Bitmap(int nativeBitmap, byte[] buffer, int width, int height, int density, + boolean isMutable, boolean isPremultiplied, + byte[] ninePatchChunk, int[] layoutBounds) { if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } + mWidth = width; + mHeight = height; + mIsMutable = isMutable; + mIsPremultiplied = isPremultiplied; mBuffer = buffer; // we delete this in our finalizer mNativeBitmap = nativeBitmap; mFinalizer = new BitmapFinalizer(nativeBitmap); - mIsMutable = isMutable; mNinePatchChunk = ninePatchChunk; mLayoutBounds = layoutBounds; if (density >= 0) { @@ -128,6 +128,17 @@ public final class Bitmap implements Parcelable { } /** + * Native bitmap has been reconfigured, so set premult and cached + * width/height values + */ + @SuppressWarnings({"UnusedDeclaration"}) // called from JNI + void reinit(int width, int height, boolean isPremultiplied) { + mWidth = width; + mHeight = height; + mIsPremultiplied = isPremultiplied; + } + + /** * <p>Returns the density for this bitmap.</p> * * <p>The default density is the same density as the current display, @@ -167,7 +178,98 @@ public final class Bitmap implements Parcelable { public void setDensity(int density) { mDensity = density; } - + + /** + * <p>Modifies the bitmap to have a specified width, height, and {@link + * Config}, without affecting the underlying allocation backing the bitmap. + * Bitmap pixel data is not re-initialized for the new configuration.</p> + * + * <p>This method can be used to avoid allocating a new bitmap, instead + * reusing an existing bitmap's allocation for a new configuration of equal + * or lesser size. If the Bitmap's allocation isn't large enough to support + * the new configuration, an IllegalArgumentException will be thrown and the + * bitmap will not be modified.</p> + * + * <p>The result of {@link #getByteCount()} will reflect the new configuration, + * while {@link #getAllocationByteCount()} will reflect that of the initial + * configuration.</p> + * + * <p>WARNING: This method should NOT be called on a bitmap currently used + * by the view system. It does not make guarantees about how the underlying + * pixel buffer is remapped to the new config, just that the allocation is + * reused. Additionally, the view system does not account for bitmap + * properties being modifying during use, e.g. while attached to + * drawables.</p> + * + * @see #setWidth(int) + * @see #setHeight(int) + * @see #setConfig(Config) + */ + public void reconfigure(int width, int height, Config config) { + checkRecycled("Can't call reconfigure() on a recycled bitmap"); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width and height must be > 0"); + } + if (!isMutable()) { + throw new IllegalStateException("only mutable bitmaps may be reconfigured"); + } + if (mBuffer == null) { + throw new IllegalStateException("native-backed bitmaps may not be reconfigured"); + } + + nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length); + mWidth = width; + mHeight = height; + } + + /** + * <p>Convenience method for calling {@link #reconfigure(int, int, Config)} + * with the current height and config.</p> + * + * <p>WARNING: this method should not be used on bitmaps currently used by + * the view system, see {@link #reconfigure(int, int, Config)} for more + * details.</p> + * + * @see #reconfigure(int, int, Config) + * @see #setHeight(int) + * @see #setConfig(Config) + */ + public void setWidth(int width) { + reconfigure(width, getHeight(), getConfig()); + } + + /** + * <p>Convenience method for calling {@link #reconfigure(int, int, Config)} + * with the current width and config.</p> + * + * <p>WARNING: this method should not be used on bitmaps currently used by + * the view system, see {@link #reconfigure(int, int, Config)} for more + * details.</p> + * + * @see #reconfigure(int, int, Config) + * @see #setWidth(int) + * @see #setConfig(Config) + */ + public void setHeight(int height) { + reconfigure(getWidth(), height, getConfig()); + } + + /** + * <p>Convenience method for calling {@link #reconfigure(int, int, Config)} + * with the current height and width.</p> + * + * <p>WARNING: this method should not be used on bitmaps currently used by + * the view system, see {@link #reconfigure(int, int, Config)} for more + * details.</p> + * + * @see #reconfigure(int, int, Config) + * @see #setWidth(int) + * @see #setHeight(int) + */ + public void setConfig(Config config) { + reconfigure(getWidth(), getHeight(), config); + } + /** * Sets the nine patch chunk. * @@ -289,7 +391,7 @@ public final class Bitmap implements Parcelable { * No color information is stored. * With this configuration, each pixel requires 1 byte of memory. */ - ALPHA_8 (2), + ALPHA_8 (1), /** * Each pixel is stored on 2 bytes and only the RGB channels are @@ -305,7 +407,7 @@ public final class Bitmap implements Parcelable { * This configuration may be useful when using opaque bitmaps * that do not require high color fidelity. */ - RGB_565 (4), + RGB_565 (3), /** * Each pixel is stored on 2 bytes. The three RGB color channels @@ -318,12 +420,16 @@ public final class Bitmap implements Parcelable { * * It is recommended to use {@link #ARGB_8888} instead of this * configuration. + * + * Note: as of {@link android.os.Build.VERSION_CODES#KITKAT}, + * any bitmap created with this configuration will be created + * using {@link #ARGB_8888} instead. * * @deprecated Because of the poor quality of this configuration, * it is advised to use {@link #ARGB_8888} instead. */ @Deprecated - ARGB_4444 (5), + ARGB_4444 (4), /** * Each pixel is stored on 4 bytes. Each channel (RGB and alpha @@ -333,13 +439,13 @@ public final class Bitmap implements Parcelable { * This configuration is very flexible and offers the best * quality. It should be used whenever possible. */ - ARGB_8888 (6); + ARGB_8888 (5); final int nativeInt; @SuppressWarnings({"deprecation"}) private static Config sConfigs[] = { - null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 + null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 }; Config(int ni) { @@ -449,6 +555,7 @@ public final class Bitmap implements Parcelable { checkRecycled("Can't copy a recycled bitmap"); Bitmap b = nativeCopy(mNativeBitmap, config.nativeInt, isMutable); if (b != null) { + b.setAlphaAndPremultiplied(hasAlpha(), mIsPremultiplied); b.mDensity = mDensity; } return b; @@ -457,7 +564,7 @@ public final class Bitmap implements Parcelable { /** * Creates a new bitmap, scaled from an existing bitmap, when possible. If the * specified width and height are the same as the current width and height of - * the source btimap, the source bitmap is returned and now new bitmap is + * the source bitmap, the source bitmap is returned and no new bitmap is * created. * * @param src The source bitmap. @@ -465,6 +572,7 @@ public final class Bitmap implements Parcelable { * @param dstHeight The new bitmap's desired height. * @param filter true if the source should be filtered. * @return The new scaled bitmap or the source bitmap if no scaling is required. + * @throws IllegalArgumentException if width is <= 0, or height is <= 0 */ public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) { @@ -517,6 +625,9 @@ public final class Bitmap implements Parcelable { * @param width The number of pixels in each row * @param height The number of rows * @return A copy of a subset of the source bitmap or the source bitmap itself. + * @throws IllegalArgumentException if the x, y, width, height values are + * outside of the dimensions of the source bitmap, or width is <= 0, + * or height is <= 0 */ public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) { return createBitmap(source, x, y, width, height, null, false); @@ -543,7 +654,8 @@ public final class Bitmap implements Parcelable { * 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. + * outside of the dimensions of the source bitmap, or width is <= 0, + * or height is <= 0 */ public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) { @@ -616,11 +728,12 @@ public final class Bitmap implements Parcelable { paint.setAntiAlias(true); } } - + // The new bitmap was created from a known bitmap source so assume that // they use the same density bitmap.mDensity = source.mDensity; - + bitmap.setAlphaAndPremultiplied(source.hasAlpha(), source.mIsPremultiplied); + canvas.setBitmap(bitmap); canvas.drawBitmap(source, srcR, dstR, paint); canvas.setBitmap(null); @@ -698,15 +811,13 @@ public final class Bitmap implements Parcelable { if (display != null) { bm.mDensity = display.densityDpi; } + bm.setHasAlpha(hasAlpha); if (config == Config.ARGB_8888 && !hasAlpha) { nativeErase(bm.mNativeBitmap, 0xff000000); - nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha); - } else { - // No need to initialize it to zeroes; it is backed by a VM byte array - // which is by definition preinitialized to all zeroes. - // - //nativeErase(bm.mNativeBitmap, 0); } + // No need to initialize the bitmap to zeroes with other configs; + // it is backed by a VM byte array which is by definition preinitialized + // to all zeroes. return bm; } @@ -904,22 +1015,60 @@ public final class Bitmap implements Parcelable { * <p>This method only returns true if {@link #hasAlpha()} returns true. * A bitmap with no alpha channel can be used both as a pre-multiplied and * as a non pre-multiplied bitmap.</p> - * + * + * <p>Only pre-multiplied bitmaps may be drawn by the view system or + * {@link Canvas}. If a non-pre-multiplied bitmap with an alpha channel is + * drawn to a Canvas, a RuntimeException will be thrown.</p> + * * @return true if the underlying pixels have been pre-multiplied, false * otherwise + * + * @see Bitmap#setPremultiplied(boolean) + * @see BitmapFactory.Options#inPremultiplied */ public final boolean isPremultiplied() { - return getConfig() != Config.RGB_565 && hasAlpha(); + return mIsPremultiplied && getConfig() != Config.RGB_565 && hasAlpha(); + } + + /** + * Sets whether the bitmap should treat its data as pre-multiplied. + * + * <p>Bitmaps are always treated as pre-multiplied by the view system and + * {@link Canvas} for performance reasons. Storing un-pre-multiplied data in + * a Bitmap (through {@link #setPixel}, {@link #setPixels}, or {@link + * BitmapFactory.Options#inPremultiplied BitmapFactory.Options.inPremultiplied}) + * can lead to incorrect blending if drawn by the framework.</p> + * + * <p>This method will not affect the behavior of a bitmap without an alpha + * channel, or if {@link #hasAlpha()} returns false.</p> + * + * <p>Calling createBitmap() or createScaledBitmap() with a source + * Bitmap whose colors are not pre-multiplied may result in a RuntimeException, + * since those functions require drawing the source, which is not supported for + * un-pre-multiplied Bitmaps.</p> + * + * @see Bitmap#isPremultiplied() + * @see BitmapFactory.Options#inPremultiplied + */ + public final void setPremultiplied(boolean premultiplied) { + mIsPremultiplied = premultiplied; + nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha(), premultiplied); + } + + /** Helper function to set both alpha and premultiplied. **/ + private final void setAlphaAndPremultiplied(boolean hasAlpha, boolean premultiplied) { + mIsPremultiplied = premultiplied; + nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha, premultiplied); } /** Returns the bitmap's width */ public final int getWidth() { - return mWidth == -1 ? mWidth = nativeWidth(mNativeBitmap) : mWidth; + return mWidth; } /** Returns the bitmap's height */ public final int getHeight() { - return mHeight == -1 ? mHeight = nativeHeight(mNativeBitmap) : mHeight; + return mHeight; } /** @@ -994,6 +1143,10 @@ public final class Bitmap implements Parcelable { * getPixels() or setPixels(), then the pixels are uniformly treated as * 32bit values, packed according to the Color class. * + * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, this method + * should not be used to calculate the memory usage of the bitmap. Instead, + * see {@link #getAllocationByteCount()}. + * * @return number of bytes between rows of the native bitmap pixels. */ public final int getRowBytes() { @@ -1001,7 +1154,11 @@ public final class Bitmap implements Parcelable { } /** - * Returns the number of bytes used to store this bitmap's pixels. + * Returns the minimum number of bytes that can be used to store this bitmap's pixels. + * + * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, the result of this method can + * no longer be used to determine memory usage of a bitmap. See {@link + * #getAllocationByteCount()}.</p> */ public final int getByteCount() { // int result permits bitmaps up to 46,340 x 46,340 @@ -1009,6 +1166,29 @@ public final class Bitmap implements Parcelable { } /** + * Returns the size of the allocated memory used to store this bitmap's pixels. + * + * <p>This can be larger than the result of {@link #getByteCount()} if a bitmap is reused to + * decode other bitmaps of smaller size, or by manual reconfiguration. See {@link + * #reconfigure(int, int, Config)}, {@link #setWidth(int)}, {@link #setHeight(int)}, {@link + * #setConfig(Bitmap.Config)}, and {@link BitmapFactory.Options#inBitmap + * BitmapFactory.Options.inBitmap}. If a bitmap is not modified in this way, this value will be + * the same as that returned by {@link #getByteCount()}.</p> + * + * <p>This value will not change over the lifetime of a Bitmap.</p> + * + * @see #reconfigure(int, int, Config) + */ + public final int getAllocationByteCount() { + if (mBuffer == null) { + // native backed bitmaps don't support reconfiguration, + // so alloc size is always content size + return getByteCount(); + } + return mBuffer.length; + } + + /** * If the bitmap's internal config is in one of the public formats, return * that config, otherwise return null. */ @@ -1039,7 +1219,7 @@ public final class Bitmap implements Parcelable { * non-opaque per-pixel alpha values. */ public void setHasAlpha(boolean hasAlpha) { - nativeSetHasAlpha(mNativeBitmap, hasAlpha); + nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha, mIsPremultiplied); } /** @@ -1113,7 +1293,7 @@ public final class Bitmap implements Parcelable { public int getPixel(int x, int y) { checkRecycled("Can't call getPixel() on a recycled bitmap"); checkPixelAccess(x, y); - return nativeGetPixel(mNativeBitmap, x, y); + return nativeGetPixel(mNativeBitmap, x, y, mIsPremultiplied); } /** @@ -1147,7 +1327,7 @@ public final class Bitmap implements Parcelable { } checkPixelsAccess(x, y, width, height, offset, stride, pixels); nativeGetPixels(mNativeBitmap, pixels, offset, stride, - x, y, width, height); + x, y, width, height, mIsPremultiplied); } /** @@ -1227,7 +1407,7 @@ public final class Bitmap implements Parcelable { throw new IllegalStateException(); } checkPixelAccess(x, y); - nativeSetPixel(mNativeBitmap, x, y, color); + nativeSetPixel(mNativeBitmap, x, y, color, mIsPremultiplied); } /** @@ -1264,7 +1444,7 @@ public final class Bitmap implements Parcelable { } checkPixelsAccess(x, y, width, height, offset, stride, pixels); nativeSetPixels(mNativeBitmap, pixels, offset, stride, - x, y, width, height); + x, y, width, height, mIsPremultiplied); } public static final Parcelable.Creator<Bitmap> CREATOR @@ -1400,31 +1580,32 @@ public final class Bitmap implements Parcelable { private static native Bitmap nativeCreate(int[] colors, int offset, int stride, int width, int height, - int nativeConfig, boolean mutable); + int nativeConfig, boolean mutable); private static native Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable); private static native void nativeDestructor(int nativeBitmap); private static native boolean nativeRecycle(int nativeBitmap); + private static native void nativeReconfigure(int nativeBitmap, int width, int height, + int config, int allocSize); private static native boolean nativeCompress(int nativeBitmap, int format, int quality, OutputStream stream, byte[] tempStorage); private static native void nativeErase(int nativeBitmap, int color); - private static native int nativeWidth(int nativeBitmap); - private static native int nativeHeight(int nativeBitmap); private static native int nativeRowBytes(int nativeBitmap); private static native int nativeConfig(int nativeBitmap); - private static native int nativeGetPixel(int nativeBitmap, int x, int y); + private static native int nativeGetPixel(int nativeBitmap, int x, int y, + boolean isPremultiplied); private static native void nativeGetPixels(int nativeBitmap, int[] pixels, - int offset, int stride, int x, - int y, int width, int height); + int offset, int stride, int x, int y, + int width, int height, boolean isPremultiplied); private static native void nativeSetPixel(int nativeBitmap, int x, int y, - int color); + int color, boolean isPremultiplied); private static native void nativeSetPixels(int nativeBitmap, int[] colors, - int offset, int stride, int x, - int y, int width, int height); + int offset, int stride, int x, int y, + int width, int height, boolean isPremultiplied); private static native void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst); private static native void nativeCopyPixelsFromBuffer(int nb, Buffer src); @@ -1443,7 +1624,8 @@ public final class Bitmap implements Parcelable { private static native void nativePrepareToDraw(int nativeBitmap); private static native boolean nativeHasAlpha(int nativeBitmap); - private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha); + private static native void nativeSetAlphaAndPremultiplied(int nBitmap, boolean hasAlpha, + boolean isPremul); private static native boolean nativeHasMipMap(int nativeBitmap); private static native void nativeSetHasMipMap(int nBitmap, boolean hasMipMap); private static native boolean nativeSameAs(int nb0, int nb1); diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index f155cd2..9b07da9 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -18,11 +18,11 @@ package android.graphics; import android.content.res.AssetManager; import android.content.res.Resources; +import android.os.Trace; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import java.io.BufferedInputStream; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; @@ -43,28 +43,59 @@ public class BitmapFactory { public Options() { inDither = false; inScaled = true; + inPremultiplied = true; } /** * If set, decode methods that take the Options object will attempt to - * reuse this bitmap when loading content. If the decode operation cannot - * use this bitmap, the decode method will return <code>null</code> and - * will throw an IllegalArgumentException. The current implementation - * necessitates that the reused bitmap be mutable and of the same size as the - * source content. The source content must be in jpeg or png format (whether as - * a resource or as a stream). The {@link android.graphics.Bitmap.Config - * configuration} of the reused bitmap will override the setting of - * {@link #inPreferredConfig}, if set. The reused bitmap will continue to - * remain mutable even when decoding a resource which would normally result - * in an immutable bitmap. + * reuse this bitmap when loading content. If the decode operation + * cannot use this bitmap, the decode method will return + * <code>null</code> and will throw an IllegalArgumentException. The + * current implementation necessitates that the reused bitmap be + * mutable, and the resulting reused bitmap will continue to remain + * mutable even when decoding a resource which would normally result in + * an immutable bitmap.</p> * * <p>You should still always use the returned Bitmap of the decode * method and not assume that reusing the bitmap worked, due to the * constraints outlined above and failure situations that can occur. * Checking whether the return value matches the value of the inBitmap - * set in the Options structure is a way to see if the bitmap was reused, - * but in all cases you should use the returned Bitmap to make sure - * that you are using the bitmap that was used as the decode destination.</p> + * set in the Options structure will indicate if the bitmap was reused, + * but in all cases you should use the Bitmap returned by the decoding + * function to ensure that you are using the bitmap that was used as the + * decode destination.</p> + * + * <h3>Usage with BitmapFactory</h3> + * + * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, any + * mutable bitmap can be reused by {@link BitmapFactory} to decode any + * other bitmaps as long as the resulting {@link Bitmap#getByteCount() + * byte count} of the decoded bitmap is less than or equal to the {@link + * Bitmap#getAllocationByteCount() allocated byte count} of the reused + * bitmap. This can be because the intrinsic size is smaller, or its + * size post scaling (for density / sample size) is smaller.</p> + * + * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT} + * additional constraints apply: The image being decoded (whether as a + * resource or as a stream) must be in jpeg or png format. Only equal + * sized bitmaps are supported, with {@link #inSampleSize} set to 1. + * Additionally, the {@link android.graphics.Bitmap.Config + * configuration} of the reused bitmap will override the setting of + * {@link #inPreferredConfig}, if set.</p> + * + * <h3>Usage with BitmapRegionDecoder</h3> + * + * <p>BitmapRegionDecoder will draw its requested content into the Bitmap + * provided, clipping if the output content size (post scaling) is larger + * than the provided Bitmap. The provided Bitmap's width, height, and + * {@link Bitmap.Config} will not be changed. + * + * <p class="note">BitmapRegionDecoder support for {@link #inBitmap} was + * introduced in {@link android.os.Build.VERSION_CODES#JELLY_BEAN}. All + * formats supported by BitmapRegionDecoder support Bitmap reuse via + * {@link #inBitmap}.</p> + * + * @see Bitmap#reconfigure(int,int, android.graphics.Bitmap.Config) */ public Bitmap inBitmap; @@ -108,6 +139,30 @@ public class BitmapFactory { public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; /** + * If true (which is the default), the resulting bitmap will have its + * color channels pre-multipled by the alpha channel. + * + * <p>This should NOT be set to false for images to be directly drawn by + * the view system or through a {@link Canvas}. The view system and + * {@link Canvas} assume all drawn images are pre-multiplied to simplify + * draw-time blending, and will throw a RuntimeException when + * un-premultiplied are drawn.</p> + * + * <p>This is likely only useful if you want to manipulate raw encoded + * image data, e.g. with RenderScript or custom OpenGL.</p> + * + * <p>This does not affect bitmaps without an alpha channel.</p> + * + * <p>Setting this flag to false while setting {@link #inScaled} to true + * may result in incorrect colors.</p> + * + * @see Bitmap#hasAlpha() + * @see Bitmap#isPremultiplied() + * @see #inScaled + */ + public boolean inPremultiplied; + + /** * If dither is true, the decoder will attempt to dither the decoded * image. */ @@ -192,9 +247,15 @@ public class BitmapFactory { * rather than relying on the graphics system scaling it each time it * is drawn to a Canvas. * + * <p>BitmapRegionDecoder ignores this flag, and will not scale output + * based on density. (though {@link #inSampleSize} is supported)</p> + * * <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. + * + * <p>If {@link #inPremultiplied} is set to false, and the image has alpha, + * setting this flag to true may result in incorrect colors. */ public boolean inScaled; @@ -205,14 +266,26 @@ public class BitmapFactory { * (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 + * <p>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. + * deep copy of the input data.</p> + * + * <p>While inPurgeable can help avoid big Dalvik heap allocations (from + * API level 11 onward), it sacrifices performance predictability since any + * image that the view system tries to draw may incur a decode delay which + * can lead to dropped frames. Therefore, most apps should avoid using + * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap + * allocations use the {@link #inBitmap} flag instead.</p> + * + * <p class="note"><strong>Note:</strong> This flag is ignored when used + * with {@link #decodeResource(Resources, int, + * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String, + * android.graphics.BitmapFactory.Options)}.</p> */ public boolean inPurgeable; @@ -426,11 +499,21 @@ public class BitmapFactory { if ((offset | length) < 0 || data.length < offset + length) { throw new ArrayIndexOutOfBoundsException(); } - Bitmap bm = nativeDecodeByteArray(data, offset, length, opts); - if (bm == null && opts != null && opts.inBitmap != null) { - throw new IllegalArgumentException("Problem decoding into existing bitmap"); + Bitmap bm; + + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); + try { + bm = nativeDecodeByteArray(data, offset, length, opts); + + if (bm == null && opts != null && opts.inBitmap != null) { + throw new IllegalArgumentException("Problem decoding into existing bitmap"); + } + setDensityFromOptions(bm, opts); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } + return bm; } @@ -448,6 +531,31 @@ public class BitmapFactory { } /** + * Set the newly decoded bitmap's density based on the Options. + */ + private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) { + if (outputBitmap == null || opts == null) return; + + final int density = opts.inDensity; + if (density != 0) { + outputBitmap.setDensity(density); + final int targetDensity = opts.inTargetDensity; + if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { + return; + } + + byte[] np = outputBitmap.getNinePatchChunk(); + final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); + if (opts.inScaled || isNinePatch) { + outputBitmap.setDensity(targetDensity); + } + } else if (opts.inBitmap != null) { + // bitmap was reused, ensure density is reset + outputBitmap.setDensity(Bitmap.getDefaultDensity()); + } + } + + /** * 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 @@ -464,6 +572,11 @@ public class BitmapFactory { * @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) + * + * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}, + * if {@link InputStream#markSupported is.markSupported()} returns true, + * <code>is.mark(1024)</code> would be called. As of + * {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p> */ public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { // we don't throw in this case, thus allowing the caller to only check @@ -472,123 +585,41 @@ public class BitmapFactory { return null; } - // we need mark/reset to work properly - - if (!is.markSupported()) { - is = new BufferedInputStream(is, DECODE_BUFFER_SIZE); - } - - // 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; - boolean finish = true; - - if (is instanceof AssetManager.AssetInputStream) { - final int asset = ((AssetManager.AssetInputStream) is).getAssetInt(); - - if (opts == null || (opts.inScaled && opts.inBitmap == null)) { - float scale = 1.0f; - int targetDensity = 0; - if (opts != null) { - final int density = opts.inDensity; - targetDensity = opts.inTargetDensity; - if (density != 0 && targetDensity != 0) { - scale = targetDensity / (float) density; - } - } - - bm = nativeDecodeAsset(asset, outPadding, opts, true, scale); - if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); + Bitmap bm = null; - finish = false; - } else { + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); + try { + if (is instanceof AssetManager.AssetInputStream) { + final int asset = ((AssetManager.AssetInputStream) is).getAssetInt(); bm = nativeDecodeAsset(asset, outPadding, opts); - } - } 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. - byte [] tempStorage = null; - if (opts != null) tempStorage = opts.inTempStorage; - if (tempStorage == null) tempStorage = new byte[16 * 1024]; - - if (opts == null || (opts.inScaled && opts.inBitmap == null)) { - float scale = 1.0f; - int targetDensity = 0; - if (opts != null) { - final int density = opts.inDensity; - targetDensity = opts.inTargetDensity; - if (density != 0 && targetDensity != 0) { - scale = targetDensity / (float) density; - } - } - - bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale); - if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); - - finish = false; } else { - bm = nativeDecodeStream(is, tempStorage, outPadding, opts); + bm = decodeStreamInternal(is, outPadding, opts); } - } - - if (bm == null && opts != null && opts.inBitmap != null) { - throw new IllegalArgumentException("Problem decoding into existing bitmap"); - } - - return finish ? finishDecode(bm, outPadding, opts) : bm; - } - 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(); - int[] lb = bm.getLayoutBounds(); - final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); - if (opts.inScaled || isNinePatch) { - float scale = targetDensity / (float) density; - if (scale != 1.0f) { - final Bitmap oldBitmap = bm; - bm = Bitmap.createScaledBitmap(oldBitmap, - Math.max(1, (int) (bm.getWidth() * scale + 0.5f)), - Math.max(1, (int) (bm.getHeight() * scale + 0.5f)), true); - if (bm != oldBitmap) oldBitmap.recycle(); - - if (isNinePatch) { - np = nativeScaleNinePatch(np, scale, outPadding); - bm.setNinePatchChunk(np); - } - if (lb != null) { - int[] newLb = new int[lb.length]; - for (int i=0; i<lb.length; i++) { - newLb[i] = (int)((lb[i]*scale)+.5f); - } - bm.setLayoutBounds(newLb); - } + if (bm == null && opts != null && opts.inBitmap != null) { + throw new IllegalArgumentException("Problem decoding into existing bitmap"); } - bm.setDensity(targetDensity); + setDensityFromOptions(bm, opts); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } return bm; } - + + /** + * Private helper function for decoding an InputStream natively. Buffers the input enough to + * do a rewind as needed, and supplies temporary storage if necessary. is MUST NOT be null. + */ + private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) { + // ASSERT(is != null); + byte [] tempStorage = null; + if (opts != null) tempStorage = opts.inTempStorage; + if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; + return nativeDecodeStream(is, tempStorage, outPadding, opts); + } + /** * 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. @@ -614,26 +645,36 @@ public class BitmapFactory { * 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. + * image should be completely decoded, or just its size returned. * @return the decoded bitmap, or null */ public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { - if (nativeIsSeekable(fd)) { - Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts); + Bitmap bm; + + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor"); + try { + if (nativeIsSeekable(fd)) { + bm = nativeDecodeFileDescriptor(fd, outPadding, opts); + } else { + FileInputStream fis = new FileInputStream(fd); + try { + bm = decodeStreamInternal(fis, outPadding, opts); + } finally { + try { + fis.close(); + } catch (Throwable t) {/* ignore */} + } + } + if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } - return finishDecode(bm, outPadding, opts); - } else { - FileInputStream fis = new FileInputStream(fd); - try { - return decodeStream(fis, outPadding, opts); - } finally { - try { - fis.close(); - } catch (Throwable t) {/* ignore */} - } + + setDensityFromOptions(bm, opts); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } + return bm; } /** @@ -650,15 +691,10 @@ public class BitmapFactory { private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); - private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, - Rect padding, Options opts, boolean applyScale, float scale); private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, Rect padding, Options opts); private static native Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts); - private static native Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts, - boolean applyScale, float scale); private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts); - private static native byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad); private static native boolean nativeIsSeekable(FileDescriptor fd); } diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java index b38d107..3a99977 100644 --- a/graphics/java/android/graphics/BitmapRegionDecoder.java +++ b/graphics/java/android/graphics/BitmapRegionDecoder.java @@ -17,7 +17,6 @@ package android.graphics; import android.content.res.AssetManager; -import java.io.BufferedInputStream; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; @@ -105,15 +104,14 @@ public final class BitmapRegionDecoder { * allowing sharing may degrade the decoding speed. * @return BitmapRegionDecoder, or null if the image data could not be decoded. * @throws IOException if the image format is not supported or can not be decoded. + * + * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}, + * if {@link InputStream#markSupported is.markSupported()} returns true, + * <code>is.mark(1024)</code> would be called. As of + * {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p> */ public static BitmapRegionDecoder newInstance(InputStream is, boolean isShareable) throws IOException { - // we need mark/reset to work properly in JNI - - if (!is.markSupported()) { - is = new BufferedInputStream(is, 16 * 1024); - } - if (is instanceof AssetManager.AssetInputStream) { return nativeNewInstance( ((AssetManager.AssetInputStream) is).getAssetInt(), diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index f74d0ef..a4f75b9 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -28,6 +28,9 @@ public class BitmapShader extends Shader { @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) public final Bitmap mBitmap; + private TileMode mTileX; + private TileMode mTileY; + /** * Call this to create a new shader that will draw with a bitmap. * @@ -37,11 +40,23 @@ public class BitmapShader extends Shader { */ public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) { mBitmap = bitmap; + mTileX = tileX; + mTileY = tileY; final int b = bitmap.ni(); native_instance = nativeCreate(b, tileX.nativeInt, tileY.nativeInt); native_shader = nativePostCreate(native_instance, b, tileX.nativeInt, tileY.nativeInt); } + /** + * @hide + */ + @Override + protected Shader copy() { + final BitmapShader copy = new BitmapShader(mBitmap, mTileX, mTileY); + copyLocalMatrix(copy); + return copy; + } + private static native int nativeCreate(int native_bitmap, int shaderTileModeX, int shaderTileModeY); private static native int nativePostCreate(int native_shader, int native_bitmap, diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index 6f71a2b..9e07bd4 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -22,6 +22,8 @@ package android.graphics; * {@link Canvas}. */ public class Camera { + private Matrix mMatrix; + /** * Creates a new camera, with empty transformations. */ @@ -147,7 +149,13 @@ public class Camera { * @param canvas The Canvas to set the transform matrix onto */ public void applyToCanvas(Canvas canvas) { - nativeApplyToCanvas(canvas.mNativeCanvas); + if (canvas.isHardwareAccelerated()) { + if (mMatrix == null) mMatrix = new Matrix(); + getMatrix(mMatrix); + canvas.concat(mMatrix); + } else { + nativeApplyToCanvas(canvas.mNativeCanvas); + } } public native float dotWithNormal(float dx, float dy, float dz); diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index c851844..d46238f 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -37,12 +37,14 @@ import javax.microedition.khronos.opengles.GL; * Canvas and Drawables</a> developer guide.</p></div> */ public class Canvas { + // assigned in constructors or setBitmap, freed in finalizer - int mNativeCanvas; - + /** @hide */ + public int mNativeCanvas; + // may be null private Bitmap mBitmap; - + // optional field set by the caller private DrawFilter mDrawFilter; @@ -59,7 +61,7 @@ public class Canvas { protected int mScreenDensity = Bitmap.DENSITY_NONE; // Used by native code - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") private int mSurfaceFormat; /** @@ -79,10 +81,9 @@ public class Canvas { private static final int MAXMIMUM_BITMAP_SIZE = 32766; // This field is used to finalize the native Canvas properly - @SuppressWarnings({"UnusedDeclaration"}) private final CanvasFinalizer mFinalizer; - private static class CanvasFinalizer { + private static final class CanvasFinalizer { private int mNativeCanvas; public CanvasFinalizer(int nativeCanvas) { @@ -92,13 +93,18 @@ public class Canvas { @Override protected void finalize() throws Throwable { try { - if (mNativeCanvas != 0) { - finalizer(mNativeCanvas); - } + dispose(); } finally { super.finalize(); } } + + public void dispose() { + if (mNativeCanvas != 0) { + finalizer(mNativeCanvas); + mNativeCanvas = 0; + } + } } /** @@ -108,9 +114,13 @@ public class Canvas { * canvas. */ public Canvas() { - // 0 means no native bitmap - mNativeCanvas = initRaster(0); - mFinalizer = new CanvasFinalizer(mNativeCanvas); + if (!isHardwareAccelerated()) { + // 0 means no native bitmap + mNativeCanvas = initRaster(0); + mFinalizer = new CanvasFinalizer(mNativeCanvas); + } else { + mFinalizer = null; + } } /** @@ -126,19 +136,20 @@ public class Canvas { if (!bitmap.isMutable()) { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); mNativeCanvas = initRaster(bitmap.ni()); mFinalizer = new CanvasFinalizer(mNativeCanvas); mBitmap = bitmap; mDensity = bitmap.mDensity; } - - Canvas(int nativeCanvas) { + + /** @hide */ + public Canvas(int nativeCanvas) { if (nativeCanvas == 0) { throw new IllegalStateException(); } mNativeCanvas = nativeCanvas; - mFinalizer = new CanvasFinalizer(nativeCanvas); + mFinalizer = new CanvasFinalizer(mNativeCanvas); mDensity = Bitmap.getDefaultDensity(); } @@ -155,7 +166,18 @@ public class Canvas { } finalizer(oldCanvas); } - + + /** + * Gets the native canvas pointer. + * + * @return The native pointer. + * + * @hide + */ + public int getNativeCanvas() { + return mNativeCanvas; + } + /** * Returns null. * @@ -203,7 +225,7 @@ public class Canvas { if (!bitmap.isMutable()) { throw new IllegalStateException(); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); safeCanvasSwap(initRaster(bitmap.ni()), true); mDensity = bitmap.mDensity; @@ -364,8 +386,8 @@ public class Canvas { */ public int saveLayer(RectF bounds, Paint paint, int saveFlags) { return native_saveLayer(mNativeCanvas, bounds, - paint != null ? paint.mNativePaint : 0, - saveFlags); + paint != null ? paint.mNativePaint : 0, + saveFlags); } /** @@ -374,8 +396,8 @@ public class Canvas { public int saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags) { return native_saveLayer(mNativeCanvas, left, top, right, bottom, - paint != null ? paint.mNativePaint : 0, - saveFlags); + paint != null ? paint.mNativePaint : 0, + saveFlags); } /** @@ -495,12 +517,13 @@ public class Canvas { public native void skew(float sx, float sy); /** - * Preconcat the current matrix with the specified matrix. + * Preconcat the current matrix with the specified matrix. If the specified + * matrix is null, this method does nothing. * * @param matrix The matrix to preconcatenate with the current matrix */ public void concat(Matrix matrix) { - native_concat(mNativeCanvas, matrix.native_instance); + if (matrix != null) native_concat(mNativeCanvas, matrix.native_instance); } /** @@ -1022,7 +1045,7 @@ public class Canvas { throw new NullPointerException(); } native_drawArc(mNativeCanvas, oval, startAngle, sweepAngle, - useCenter, paint.mNativePaint); + useCenter, paint.mNativePaint); } /** @@ -1052,28 +1075,47 @@ public class Canvas { public void drawPath(Path path, Paint paint) { native_drawPath(mNativeCanvas, path.ni(), paint.mNativePaint); } - - private static void throwIfRecycled(Bitmap bitmap) { + + /** + * @hide + */ + protected static void throwIfCannotDraw(Bitmap bitmap) { if (bitmap.isRecycled()) { throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap); } + if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 && + bitmap.hasAlpha()) { + throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap " + + bitmap); + } } /** * Draws the specified bitmap as an N-patch (most often, a 9-patches.) * - * Note: Only supported by hardware accelerated canvas at the moment. - * - * @param bitmap The bitmap to draw as an N-patch - * @param chunks The patches information (matches the native struct Res_png_9patch) + * @param patch The ninepatch object to render * @param dst The destination rectangle. * @param paint The paint to draw the bitmap with. may be null * * @hide */ - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { - } - + public void drawPatch(NinePatch patch, Rect dst, Paint paint) { + patch.drawSoftware(this, dst, paint); + } + + /** + * Draws the specified bitmap as an N-patch (most often, a 9-patches.) + * + * @param patch The ninepatch object to render + * @param dst The destination rectangle. + * @param paint The paint to draw the bitmap with. may be null + * + * @hide + */ + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + patch.drawSoftware(this, dst, paint); + } + /** * Draw the specified bitmap, with its top/left corner at (x,y), using * the specified paint, transformed by the current matrix. @@ -1094,7 +1136,7 @@ public class Canvas { * @param paint The paint used to draw the bitmap (may be null) */ public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top, paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity, bitmap.mDensity); } @@ -1125,7 +1167,7 @@ public class Canvas { if (dst == null) { throw new NullPointerException(); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } @@ -1156,9 +1198,9 @@ public class Canvas { if (dst == null) { throw new NullPointerException(); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, - paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); + paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } /** @@ -1279,8 +1321,8 @@ public class Canvas { checkRange(colors.length, colorOffset, count); } nativeDrawBitmapMesh(mNativeCanvas, bitmap.ni(), meshWidth, meshHeight, - verts, vertOffset, colors, colorOffset, - paint != null ? paint.mNativePaint : 0); + verts, vertOffset, colors, colorOffset, + paint != null ? paint.mNativePaint : 0); } public enum VertexMode { @@ -1342,8 +1384,8 @@ public class Canvas { checkRange(indices.length, indexOffset, indexCount); } nativeDrawVertices(mNativeCanvas, mode.nativeInt, vertexCount, verts, - vertOffset, texs, texOffset, colors, colorOffset, - indices, indexOffset, indexCount, paint.mNativePaint); + vertOffset, texs, texOffset, colors, colorOffset, + indices, indexOffset, indexCount, paint.mNativePaint); } /** @@ -1414,10 +1456,10 @@ public class Canvas { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { native_drawText(mNativeCanvas, text.toString(), start, end, x, y, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawText(this, start, end, x, y, - paint); + paint); } else { char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); @@ -1538,7 +1580,7 @@ public class Canvas { throw new IndexOutOfBoundsException(); } native_drawPosText(mNativeCanvas, text, index, count, pos, - paint.mNativePaint); + paint.mNativePaint); } /** @@ -1579,8 +1621,8 @@ public class Canvas { throw new ArrayIndexOutOfBoundsException(); } native_drawTextOnPath(mNativeCanvas, text, index, count, - path.ni(), hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + path.ni(), hOffset, vOffset, + paint.mBidiFlags, paint.mNativePaint); } /** @@ -1616,7 +1658,9 @@ public class Canvas { */ public void drawPicture(Picture picture) { picture.endRecording(); - native_drawPicture(mNativeCanvas, picture.ni()); + int restoreCount = save(); + picture.draw(this); + restoreToCount(restoreCount); } /** @@ -1647,6 +1691,15 @@ public class Canvas { } /** + * Releases the resources associated with this canvas. + * + * @hide + */ + public void release() { + mFinalizer.dispose(); + } + + /** * Free up as much memory as possible from private caches (e.g. fonts, images) * * @hide @@ -1792,7 +1845,5 @@ public class Canvas { float hOffset, float vOffset, int flags, int paint); - private static native void native_drawPicture(int nativeCanvas, - int nativePicture); private static native void finalizer(int nativeCanvas); } diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java index 933948d..8fbedae 100644 --- a/graphics/java/android/graphics/Color.java +++ b/graphics/java/android/graphics/Color.java @@ -406,18 +406,18 @@ public class Color { sColorNameMap.put("yellow", YELLOW); sColorNameMap.put("cyan", CYAN); sColorNameMap.put("magenta", MAGENTA); - sColorNameMap.put("aqua", 0x00FFFF); - sColorNameMap.put("fuchsia", 0xFF00FF); + sColorNameMap.put("aqua", 0xFF00FFFF); + sColorNameMap.put("fuchsia", 0xFFFF00FF); sColorNameMap.put("darkgrey", DKGRAY); sColorNameMap.put("grey", GRAY); sColorNameMap.put("lightgrey", LTGRAY); - sColorNameMap.put("lime", 0x00FF00); - sColorNameMap.put("maroon", 0x800000); - sColorNameMap.put("navy", 0x000080); - sColorNameMap.put("olive", 0x808000); - sColorNameMap.put("purple", 0x800080); - sColorNameMap.put("silver", 0xC0C0C0); - sColorNameMap.put("teal", 0x008080); + sColorNameMap.put("lime", 0xFF00FF00); + sColorNameMap.put("maroon", 0xFF800000); + sColorNameMap.put("navy", 0xFF000080); + sColorNameMap.put("olive", 0xFF808000); + sColorNameMap.put("purple", 0xFF800080); + sColorNameMap.put("silver", 0xFFC0C0C0); + sColorNameMap.put("teal", 0xFF008080); } } diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java index 241ab17..de0d3d6 100644 --- a/graphics/java/android/graphics/ComposeShader.java +++ b/graphics/java/android/graphics/ComposeShader.java @@ -16,10 +16,22 @@ package android.graphics; -/** A subclass of shader that returns the coposition of two other shaders, combined by +/** 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 { + + private static final int TYPE_XFERMODE = 1; + private static final int TYPE_PORTERDUFFMODE = 2; + + /** + * Type of the ComposeShader: can be either TYPE_XFERMODE or TYPE_PORTERDUFFMODE + */ + private int mType; + + private Xfermode mXferMode; + private PorterDuff.Mode mPorterDuffMode; + /** * Hold onto the shaders to avoid GC. */ @@ -37,8 +49,10 @@ public class ComposeShader extends Shader { is null, then SRC_OVER is assumed. */ public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) { + mType = TYPE_XFERMODE; mShaderA = shaderA; mShaderB = shaderB; + mXferMode = mode; native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance, (mode != null) ? mode.native_instance : 0); if (mode instanceof PorterDuffXfermode) { @@ -59,14 +73,37 @@ public class ComposeShader extends Shader { @param mode The PorterDuff mode that combines the colors from the two shaders. */ public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) { + mType = TYPE_PORTERDUFFMODE; mShaderA = shaderA; mShaderB = shaderB; + mPorterDuffMode = mode; native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance, mode.nativeInt); native_shader = nativePostCreate2(native_instance, shaderA.native_shader, shaderB.native_shader, mode.nativeInt); } + /** + * @hide + */ + @Override + protected Shader copy() { + final ComposeShader copy; + switch (mType) { + case TYPE_XFERMODE: + copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mXferMode); + break; + case TYPE_PORTERDUFFMODE: + copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mPorterDuffMode); + break; + default: + throw new IllegalArgumentException( + "ComposeShader should be created with either Xfermode or PorterDuffMode"); + } + copyLocalMatrix(copy); + return copy; + } + private static native int nativeCreate1(int native_shaderA, int native_shaderB, int native_mode); private static native int nativeCreate2(int native_shaderA, int native_shaderB, diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index f6b747a..1bcfc18 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -73,6 +73,66 @@ public class ImageFormat { public static final int YV12 = 0x32315659; /** + * <p>Android Y8 format.</p> + * + * <p>Y8 is a YUV planar format comprised of a WxH Y plane only, with each pixel + * being represented by 8 bits. It is equivalent to just the Y plane from {@link #YV12} + * format.</p> + * + * <p>This format assumes + * <ul> + * <li>an even width</li> + * <li>an even height</li> + * <li>a horizontal stride multiple of 16 pixels</li> + * </ul> + * </p> + * + * <pre> y_size = stride * height </pre> + * + * <p>For example, the {@link android.media.Image} object can provide data + * in this format from a {@link android.hardware.camera2.CameraDevice} + * through a {@link android.media.ImageReader} object if this format is + * supported by {@link android.hardware.camera2.CameraDevice}.</p> + * + * @see android.media.Image + * @see android.media.ImageReader + * @see android.hardware.camera2.CameraDevice + * + * @hide + */ + public static final int Y8 = 0x20203859; + + /** + * <p>Android Y16 format.</p> + * + * Y16 is a YUV planar format comprised of a WxH Y plane, with each pixel + * being represented by 16 bits. It is just like {@link #Y8}, but has 16 + * bits per pixel (little endian).</p> + * + * <p>This format assumes + * <ul> + * <li>an even width</li> + * <li>an even height</li> + * <li>a horizontal stride multiple of 16 pixels</li> + * </ul> + * </p> + * + * <pre> y_size = stride * height </pre> + * + * <p>For example, the {@link android.media.Image} object can provide data + * in this format from a {@link android.hardware.camera2.CameraDevice} + * through a {@link android.media.ImageReader} object if this format is + * supported by {@link android.hardware.camera2.CameraDevice}.</p> + * + * @see android.media.Image + * @see android.media.ImageReader + * @see android.hardware.camera2.CameraDevice + * + * @hide + */ + public static final int Y16 = 0x20363159; + + /** * YCbCr format, used for video. Whether this format is supported by the * camera hardware can be determined by * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}. @@ -100,6 +160,55 @@ public class ImageFormat { public static final int JPEG = 0x100; /** + * <p>Multi-plane Android YUV format</p> + * + * <p>This format is a generic YCbCr format, capable of describing any 4:2:0 + * chroma-subsampled planar or semiplanar buffer (but not fully interleaved), + * with 8 bits per color sample.</p> + * + * <p>Images in this format are always represented by three separate buffers + * of data, one for each color plane. Additional information always + * accompanies the buffers, describing the row stride and the pixel stride + * for each plane.</p> + * + * <p>The order of planes in the array returned by + * {@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that + * plane #0 is always Y, plane #1 is always U (Cb), and plane #2 is always V (Cr).</p> + * + * <p>The Y-plane is guaranteed not to be interleaved with the U/V planes + * (in particular, pixel stride is always 1 in + * {@link android.media.Image.Plane#getPixelStride() yPlane.getPixelStride()}).</p> + * + * <p>The U/V planes are guaranteed to have the same row stride and pixel stride + * (in particular, + * {@link android.media.Image.Plane#getRowStride() uPlane.getRowStride()} + * == {@link android.media.Image.Plane#getRowStride() vPlane.getRowStride()} and + * {@link android.media.Image.Plane#getPixelStride() uPlane.getPixelStride()} + * == {@link android.media.Image.Plane#getPixelStride() vPlane.getPixelStride()}; + * ).</p> + * + * @see android.media.Image + * @see android.media.ImageReader + * @see android.hardware.camera2.CameraDevice + */ + public static final int YUV_420_888 = 0x23; + + /** + * <p>General raw camera sensor image format, usually representing a + * single-channel Bayer-mosaic image. Each pixel color sample is stored with + * 16 bits of precision.</p> + * + * <p>The layout of the color mosaic, the maximum and minimum encoding + * values of the raw pixel data, the color space of the image, and all other + * needed information to interpret a raw sensor image must be queried from + * the {@link android.hardware.camera2.CameraDevice} which produced the + * image.</p> + * + * @hide + */ + public static final int RAW_SENSOR = 0x20; + + /** * Raw bayer format used for images, which is 10 bit precision samples * stored in 16 bit words. The filter pattern is RGGB. Whether this format * is supported by the camera hardware can be determined by @@ -127,8 +236,16 @@ public class ImageFormat { return 16; case YV12: return 12; + case Y8: + return 8; + case Y16: + return 16; case NV21: return 12; + case YUV_420_888: + return 12; + case RAW_SENSOR: + return 16; case BAYER_RGGB: return 16; } diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 96a71e3..4c88de3 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -17,6 +17,27 @@ package android.graphics; public class LinearGradient extends Shader { + + private static final int TYPE_COLORS_AND_POSITIONS = 1; + private static final int TYPE_COLOR_START_AND_COLOR_END = 2; + + /** + * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or + * TYPE_COLOR_START_AND_COLOR_END. + */ + private int mType; + + private float mX0; + private float mY0; + private float mX1; + private float mY1; + private int[] mColors; + private float[] mPositions; + private int mColor0; + private int mColor1; + + private TileMode mTileMode; + /** 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 @@ -36,6 +57,14 @@ public class LinearGradient extends Shader { if (positions != null && colors.length != positions.length) { throw new IllegalArgumentException("color and position arrays must be of equal length"); } + mType = TYPE_COLORS_AND_POSITIONS; + mX0 = x0; + mY0 = y0; + mX1 = x1; + mY1 = y1; + mColors = colors; + mPositions = positions; + mTileMode = tile; native_instance = nativeCreate1(x0, y0, x1, y1, colors, positions, tile.nativeInt); native_shader = nativePostCreate1(native_instance, x0, y0, x1, y1, colors, positions, tile.nativeInt); @@ -52,12 +81,42 @@ public class LinearGradient extends Shader { */ public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, TileMode tile) { + mType = TYPE_COLOR_START_AND_COLOR_END; + mX0 = x0; + mY0 = y0; + mX1 = x1; + mY1 = y1; + mColor0 = color0; + mColor1 = color1; + mTileMode = tile; native_instance = nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt); native_shader = nativePostCreate2(native_instance, x0, y0, x1, y1, color0, color1, tile.nativeInt); } - private native int nativeCreate1(float x0, float y0, float x1, float y1, + /** + * @hide + */ + @Override + protected Shader copy() { + final LinearGradient copy; + switch (mType) { + case TYPE_COLORS_AND_POSITIONS: + copy = new LinearGradient(mX0, mY0, mX1, mY1, mColors.clone(), + mPositions != null ? mPositions.clone() : null, mTileMode); + break; + case TYPE_COLOR_START_AND_COLOR_END: + copy = new LinearGradient(mX0, mY0, mX1, mY1, mColor0, mColor1, mTileMode); + break; + default: + throw new IllegalArgumentException("LinearGradient should be created with either " + + "colors and positions or start color and end color"); + } + copyLocalMatrix(copy); + return copy; + } + + private native int nativeCreate1(float x0, float y0, float x1, float y1, int colors[], float positions[], int tileMode); private native int nativeCreate2(float x0, float y0, float x1, float y1, int color0, int color1, int tileMode); diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index a837294..32e0c01 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -21,9 +21,6 @@ import java.io.PrintWriter; /** * The Matrix class holds a 3x3 matrix for transforming coordinates. - * Matrix does not have a constructor, so it must be explicitly initialized - * using either reset() - to construct an identity matrix, or one of the set..() - * functions (e.g. setTranslate, setRotate, etc.). */ public class Matrix { @@ -267,13 +264,23 @@ public class Matrix { native_set(native_instance, src.native_instance); } } - + /** Returns true iff obj is a Matrix and its values equal our values. */ + @Override public boolean equals(Object obj) { - return obj != null && - obj instanceof Matrix && - native_equals(native_instance, ((Matrix)obj).native_instance); + //if (obj == this) return true; -- NaN value would mean matrix != itself + if (!(obj instanceof Matrix)) return false; + return native_equals(native_instance, ((Matrix)obj).native_instance); + } + + @Override + public int hashCode() { + // This should generate the hash code by performing some arithmetic operation on all + // the matrix elements -- our equals() does an element-by-element comparison, and we + // need to ensure that the hash code for two equal objects is the same. We're not + // really using this at the moment, so we take the easy way out. + return 44; } /** Set the matrix to identity */ @@ -512,7 +519,7 @@ public class Matrix { */ END (3); - // the native values must match those in SkMatrix.h + // the native values must match those in SkMatrix.h ScaleToFit(int nativeInt) { this.nativeInt = nativeInt; } @@ -535,7 +542,7 @@ public class Matrix { } return native_setRectToRect(native_instance, src, dst, stf.nativeInt); } - + // private helper to perform range checks on arrays of "points" private static void checkPointArrays(float[] src, int srcIndex, float[] dst, int dstIndex, @@ -598,7 +605,7 @@ public class Matrix { native_mapPoints(native_instance, dst, dstIndex, src, srcIndex, pointCount, true); } - + /** * 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 @@ -620,7 +627,7 @@ public class Matrix { native_mapPoints(native_instance, dst, dstIndex, src, srcIndex, vectorCount, false); } - + /** * 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 @@ -713,7 +720,7 @@ public class Matrix { public float mapRadius(float radius) { return native_mapRadius(native_instance, radius); } - + /** Copy 9 values from the matrix into the array. */ public void getValues(float[] values) { @@ -736,13 +743,14 @@ public class Matrix { native_setValues(native_instance, values); } + @Override public String toString() { StringBuilder sb = new StringBuilder(64); sb.append("Matrix{"); toShortString(sb); sb.append('}'); return sb.toString(); - + } public String toShortString() { @@ -780,13 +788,18 @@ public class Matrix { pw.print(values[5]); pw.print("]["); pw.print(values[6]); pw.print(", "); pw.print(values[7]); pw.print(", "); pw.print(values[8]); pw.print(']'); - + } + @Override protected void finalize() throws Throwable { - finalizer(native_instance); + try { + finalizer(native_instance); + } finally { + super.finalize(); + } } - + /*package*/ final int ni() { return native_instance; } diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java index 4a33453..9419faf 100644 --- a/graphics/java/android/graphics/Movie.java +++ b/graphics/java/android/graphics/Movie.java @@ -16,12 +16,13 @@ package android.graphics; +import android.content.res.AssetManager; import java.io.InputStream; import java.io.FileInputStream; public class Movie { private final int mNativeMovie; - + private Movie(int nativeMovie) { if (nativeMovie == 0) { throw new RuntimeException("native movie creation failed"); @@ -42,7 +43,20 @@ public class Movie { draw(canvas, x, y, null); } - public static native Movie decodeStream(InputStream is); + public static Movie decodeStream(InputStream is) { + if (is == null) { + return null; + } + if (is instanceof AssetManager.AssetInputStream) { + final int asset = ((AssetManager.AssetInputStream) is).getAssetInt(); + return nativeDecodeAsset(asset); + } + + return nativeDecodeStream(is); + } + + private static native Movie nativeDecodeAsset(int asset); + private static native Movie nativeDecodeStream(InputStream is); public static native Movie decodeByteArray(byte[] data, int offset, int length); diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 6de4d84..528d9de 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -18,11 +18,8 @@ package android.graphics; /** - * The NinePatch class permits drawing a bitmap in nine sections. - * The four corners are unscaled; the four edges are scaled in one axis, - * and the middle is scaled in both axes. Normally, the middle is - * transparent so that the patch can provide a selection about a rectangle. - * Essentially, it allows the creation of custom graphics that will scale the + * The NinePatch class permits drawing a bitmap in nine or more sections. + * Essentially, it allows the creation of custom graphics that will scale the * way that you define, when content added within the image exceeds the normal * bounds of the graphic. For a thorough explanation of a NinePatch image, * read the discussion in the @@ -36,24 +33,40 @@ package android.graphics; */ public class NinePatch { private final Bitmap mBitmap; - private final byte[] mChunk; + + /** + * Used by native code. This pointer is an instance of Res_png_9patch*. + * + * @hide + */ + public final int mNativeChunk; + private Paint mPaint; - private String mSrcName; // Useful for debugging - private final RectF mRect = new RectF(); - + private String mSrcName; + + /** + * Create a drawable projection from a bitmap to nine patches. + * + * @param bitmap The bitmap describing the patches. + * @param chunk The 9-patch data chunk describing how the underlying bitmap + * is split apart and drawn. + */ + public NinePatch(Bitmap bitmap, byte[] chunk) { + this(bitmap, chunk, null); + } + /** * Create a drawable projection from a bitmap to nine patches. * - * @param bitmap The bitmap describing the patches. - * @param chunk The 9-patch data chunk describing how the underlying - * bitmap is split apart and drawn. - * @param srcName The name of the source for the bitmap. Might be null. + * @param bitmap The bitmap describing the patches. + * @param chunk The 9-patch data chunk describing how the underlying + * bitmap is split apart and drawn. + * @param srcName The name of the source for the bitmap. Might be null. */ public NinePatch(Bitmap bitmap, byte[] chunk, String srcName) { mBitmap = bitmap; - mChunk = chunk; mSrcName = srcName; - validateNinePatchChunk(mBitmap.ni(), chunk); + mNativeChunk = validateNinePatchChunk(mBitmap.ni(), chunk); } /** @@ -61,69 +74,103 @@ public class NinePatch { */ public NinePatch(NinePatch patch) { mBitmap = patch.mBitmap; - mChunk = patch.mChunk; mSrcName = patch.mSrcName; if (patch.mPaint != null) { mPaint = new Paint(patch.mPaint); } - validateNinePatchChunk(mBitmap.ni(), mChunk); + // No need to validate the 9patch chunk again, it was done by + // the instance we're copying from + mNativeChunk = patch.mNativeChunk; + } + + @Override + protected void finalize() throws Throwable { + try { + nativeFinalize(mNativeChunk); + } finally { + super.finalize(); + } + } + + /** + * Returns the name of this NinePatch object if one was specified + * when calling the constructor. + */ + public String getName() { + return mSrcName; + } + + /** + * Returns the paint used to draw this NinePatch. The paint can be null. + * + * @see #setPaint(Paint) + * @see #draw(Canvas, Rect) + * @see #draw(Canvas, RectF) + */ + public Paint getPaint() { + return mPaint; } + /** + * Sets the paint to use when drawing the NinePatch. + * + * @param p The paint that will be used to draw this NinePatch. + * + * @see #getPaint() + * @see #draw(Canvas, Rect) + * @see #draw(Canvas, RectF) + */ public void setPaint(Paint p) { mPaint = p; } + + /** + * Returns the bitmap used to draw this NinePatch. + */ + public Bitmap getBitmap() { + return mBitmap; + } /** - * Draw a bitmap of nine patches. + * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}. * - * @param canvas A container for the current matrix and clip used to draw the bitmap. - * @param location Where to draw the bitmap. + * @param canvas A container for the current matrix and clip used to draw the NinePatch. + * @param location Where to draw the NinePatch. */ public void draw(Canvas canvas, RectF location) { - if (!canvas.isHardwareAccelerated()) { - nativeDraw(canvas.mNativeCanvas, location, - mBitmap.ni(), mChunk, - mPaint != null ? mPaint.mNativePaint : 0, - canvas.mDensity, mBitmap.mDensity); - } else { - canvas.drawPatch(mBitmap, mChunk, location, mPaint); - } + canvas.drawPatch(this, location, mPaint); } - + /** - * Draw a bitmap of nine patches. + * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}. * - * @param canvas A container for the current matrix and clip used to draw the bitmap. - * @param location Where to draw the bitmap. + * @param canvas A container for the current matrix and clip used to draw the NinePatch. + * @param location Where to draw the NinePatch. */ public void draw(Canvas canvas, Rect location) { - if (!canvas.isHardwareAccelerated()) { - nativeDraw(canvas.mNativeCanvas, location, - mBitmap.ni(), mChunk, - mPaint != null ? mPaint.mNativePaint : 0, - canvas.mDensity, mBitmap.mDensity); - } else { - mRect.set(location); - canvas.drawPatch(mBitmap, mChunk, mRect, mPaint); - } + canvas.drawPatch(this, location, mPaint); } /** - * Draw a bitmap of nine patches. + * Draws the NinePatch. This method will ignore the paint returned + * by {@link #getPaint()} and use the specified paint instead. * - * @param canvas A container for the current matrix and clip used to draw the bitmap. - * @param location Where to draw the bitmap. - * @param paint The Paint to draw through. + * @param canvas A container for the current matrix and clip used to draw the NinePatch. + * @param location Where to draw the NinePatch. + * @param paint The Paint to draw through. */ public void draw(Canvas canvas, Rect location, Paint paint) { - if (!canvas.isHardwareAccelerated()) { - nativeDraw(canvas.mNativeCanvas, location, - mBitmap.ni(), mChunk, paint != null ? paint.mNativePaint : 0, - canvas.mDensity, mBitmap.mDensity); - } else { - mRect.set(location); - canvas.drawPatch(mBitmap, mChunk, mRect, paint); - } + canvas.drawPatch(this, location, paint); + } + + void drawSoftware(Canvas canvas, RectF location, Paint paint) { + nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mNativeChunk, + paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); + } + + void drawSoftware(Canvas canvas, Rect location, Paint paint) { + nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mNativeChunk, + paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } /** @@ -133,33 +180,67 @@ public class NinePatch { public int getDensity() { return mBitmap.mDensity; } - + + /** + * Returns the intrinsic width, in pixels, of this NinePatch. This is equivalent + * to querying the width of the underlying bitmap returned by {@link #getBitmap()}. + */ public int getWidth() { return mBitmap.getWidth(); } + /** + * Returns the intrinsic height, in pixels, of this NinePatch. This is equivalent + * to querying the height of the underlying bitmap returned by {@link #getBitmap()}. + */ public int getHeight() { return mBitmap.getHeight(); } + /** + * Indicates whether this NinePatch contains transparent or translucent pixels. + * This is equivalent to calling <code>getBitmap().hasAlpha()</code> on this + * NinePatch. + */ public final boolean hasAlpha() { return mBitmap.hasAlpha(); } - public final Region getTransparentRegion(Rect location) { - int r = nativeGetTransparentRegion(mBitmap.ni(), mChunk, location); + /** + * Returns a {@link Region} representing the parts of the NinePatch that are + * completely transparent. + * + * @param bounds The location and size of the NinePatch. + * + * @return null if the NinePatch has no transparent region to + * report, else a {@link Region} holding the parts of the specified bounds + * that are transparent. + */ + public final Region getTransparentRegion(Rect bounds) { + int r = nativeGetTransparentRegion(mBitmap.ni(), mNativeChunk, bounds); return r != 0 ? new Region(r) : null; } - + + /** + * Verifies that the specified byte array is a valid 9-patch data chunk. + * + * @param chunk A byte array representing a 9-patch data chunk. + * + * @return True if the specified byte array represents a 9-patch data chunk, + * false otherwise. + */ public native static boolean isNinePatchChunk(byte[] chunk); - private static native void validateNinePatchChunk(int bitmap, byte[] chunk); + /** + * Validates the 9-patch chunk and throws an exception if the chunk is invalid. + * If validation is successful, this method returns a native Res_png_9patch* + * object used by the renderers. + */ + private static native int validateNinePatchChunk(int bitmap, byte[] chunk); + private static native void nativeFinalize(int chunk); private static native void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance, - byte[] c, int paint_instance_or_null, - int destDensity, int srcDensity); + int c, int paint_instance_or_null, int destDensity, int srcDensity); private static native void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance, - byte[] c, int paint_instance_or_null, - int destDensity, int srcDensity); - private static native int nativeGetTransparentRegion( - int bitmap, byte[] chunk, Rect location); + int c, int paint_instance_or_null, int destDensity, int srcDensity); + private static native int nativeGetTransparentRegion(int bitmap, int chunk, Rect location); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 1485d8d..5fc2588 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -87,35 +87,133 @@ public class Paint { Align.LEFT, Align.CENTER, Align.RIGHT }; - /** bit mask for the flag enabling antialiasing */ + /** + * Paint flag that enables antialiasing when drawing. + * + * <p>Enabling this flag will cause all draw operations that support + * antialiasing to use it.</p> + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int ANTI_ALIAS_FLAG = 0x01; - /** bit mask for the flag enabling bitmap filtering */ + /** + * Paint flag that enables bilinear sampling on scaled bitmaps. + * + * <p>If cleared, scaled bitmaps will be drawn with nearest neighbor + * sampling, likely resulting in artifacts. This should generally be on + * when drawing bitmaps, unless performance-bound (rendering to software + * canvas) or preferring pixelation artifacts to blurriness when scaling + * significantly.</p> + * + * <p>If bitmaps are scaled for device density at creation time (as + * resource bitmaps often are) the filtering will already have been + * done.</p> + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int FILTER_BITMAP_FLAG = 0x02; - /** bit mask for the flag enabling dithering */ + /** + * Paint flag that enables dithering when blitting. + * + * <p>Enabling this flag applies a dither to any blit operation where the + * target's colour space is more constrained than the source. + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int DITHER_FLAG = 0x04; - /** bit mask for the flag enabling underline text */ + /** + * Paint flag that applies an underline decoration to drawn text. + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int UNDERLINE_TEXT_FLAG = 0x08; - /** bit mask for the flag enabling strike-thru text */ + /** + * Paint flag that applies a strike-through decoration to drawn text. + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int STRIKE_THRU_TEXT_FLAG = 0x10; - /** bit mask for the flag enabling fake-bold text */ + /** + * Paint flag that applies a synthetic bolding effect to drawn text. + * + * <p>Enabling this flag will cause text draw operations to apply a + * simulated bold effect when drawing a {@link Typeface} that is not + * already bold.</p> + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int FAKE_BOLD_TEXT_FLAG = 0x20; - /** bit mask for the flag enabling linear-text (no caching) */ + /** + * Paint flag that enables smooth linear scaling of text. + * + * <p>Enabling this flag does not actually scale text, but rather adjusts + * text draw operations to deal gracefully with smooth adjustment of scale. + * When this flag is enabled, font hinting is disabled to prevent shape + * deformation between scale factors, and glyph caching is disabled due to + * the large number of glyph images that will be generated.</p> + * + * <p>{@link #SUBPIXEL_TEXT_FLAG} should be used in conjunction with this + * flag to prevent glyph positions from snapping to whole pixel values as + * scale factor is adjusted.</p> + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int LINEAR_TEXT_FLAG = 0x40; - /** bit mask for the flag enabling subpixel-text */ + /** + * Paint flag that enables subpixel positioning of text. + * + * <p>Enabling this flag causes glyph advances to be computed with subpixel + * accuracy.</p> + * + * <p>This can be used with {@link #LINEAR_TEXT_FLAG} to prevent text from + * jittering during smooth scale transitions.</p> + * + * @see #Paint(int) + * @see #setFlags(int) + */ public static final int SUBPIXEL_TEXT_FLAG = 0x80; - /** bit mask for the flag enabling device kerning for text */ + /** Legacy Paint flag, no longer used. */ public static final int DEV_KERN_TEXT_FLAG = 0x100; + /** @hide bit mask for the flag enabling subpixel glyph rendering for text */ + public static final int LCD_RENDER_TEXT_FLAG = 0x200; + /** + * Paint flag that enables the use of bitmap fonts when drawing text. + * + * <p>Disabling this flag will prevent text draw operations from using + * embedded bitmap strikes in fonts, causing fonts with both scalable + * outlines and bitmap strikes to draw only the scalable outlines, and + * fonts with only bitmap strikes to not draw at all.</p> + * + * @see #Paint(int) + * @see #setFlags(int) + */ + public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400; + /** @hide bit mask for the flag forcing freetype's autohinter on for text */ + public static final int AUTO_HINTING_TEXT_FLAG = 0x800; + /** @hide bit mask for the flag enabling vertical rendering for text */ + public static final int VERTICAL_TEXT_FLAG = 0x1000; // we use this when we first create a paint - static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG; + static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG; /** - * Option for {@link #setHinting}: disable hinting. + * Font hinter option that disables font hinting. + * + * @see #setHinting(int) */ public static final int HINTING_OFF = 0x0; /** - * Option for {@link #setHinting}: enable hinting. + * Font hinter option that enables font hinting. + * + * @see #setHinting(int) */ public static final int HINTING_ON = 0x1; @@ -421,7 +519,11 @@ public class Paint { mMaskFilter = paint.mMaskFilter; mPathEffect = paint.mPathEffect; mRasterizer = paint.mRasterizer; - mShader = paint.mShader; + if (paint.mShader != null) { + mShader = paint.mShader.copy(); + } else { + mShader = null; + } mTypeface = paint.mTypeface; mXfermode = paint.mXfermode; diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 157c7d1..5b04a91 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -78,7 +78,11 @@ public class Path { mLastDirection = null; if (rects != null) rects.setEmpty(); } + // We promised not to change this, so preserve it around the native + // call, which does now reset fill type. + final FillType fillType = getFillType(); native_reset(mNativePath); + setFillType(fillType); } /** @@ -103,21 +107,106 @@ public class Path { } } - /** Enum for the ways a path may be filled - */ + /** + * The logical operations that can be performed when combining two paths. + * + * @see #op(Path, android.graphics.Path.Op) + * @see #op(Path, Path, android.graphics.Path.Op) + */ + public enum Op { + /** + * Subtract the second path from the first path. + */ + DIFFERENCE, + /** + * Intersect the two paths. + */ + INTERSECT, + /** + * Union (inclusive-or) the two paths. + */ + UNION, + /** + * Exclusive-or the two paths. + */ + XOR, + /** + * Subtract the first path from the second path. + */ + REVERSE_DIFFERENCE + } + + /** + * Set this path to the result of applying the Op to this path and the specified path. + * The resulting path will be constructed from non-overlapping contours. + * The curve order is reduced where possible so that cubics may be turned + * into quadratics, and quadratics maybe turned into lines. + * + * @param path The second operand (for difference, the subtrahend) + * + * @return True if operation succeeded, false otherwise and this path remains unmodified. + * + * @see Op + * @see #op(Path, Path, android.graphics.Path.Op) + */ + public boolean op(Path path, Op op) { + return op(this, path, op); + } + + /** + * Set this path to the result of applying the Op to the two specified paths. + * The resulting path will be constructed from non-overlapping contours. + * The curve order is reduced where possible so that cubics may be turned + * into quadratics, and quadratics maybe turned into lines. + * + * @param path1 The first operand (for difference, the minuend) + * @param path2 The second operand (for difference, the subtrahend) + * + * @return True if operation succeeded, false otherwise and this path remains unmodified. + * + * @see Op + * @see #op(Path, android.graphics.Path.Op) + */ + public boolean op(Path path1, Path path2, Op op) { + if (native_op(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) { + isSimplePath = false; + rects = null; + return true; + } + return false; + } + + /** + * Enum for the ways a path may be filled. + */ public enum FillType { // these must match the values in SkPath.h + /** + * Specifies that "inside" is computed by a non-zero sum of signed + * edge crossings. + */ WINDING (0), + /** + * Specifies that "inside" is computed by an odd number of edge + * crossings. + */ EVEN_ODD (1), + /** + * Same as {@link #WINDING}, but draws outside of the path, rather than inside. + */ INVERSE_WINDING (2), + /** + * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside. + */ INVERSE_EVEN_ODD(3); FillType(int ni) { nativeInt = ni; } + final int nativeInt; } - + // these must be in the same order as their native values static final FillType[] sFillTypeArray = { FillType.WINDING, @@ -644,24 +733,20 @@ public class Path { private static native void native_addRect(int nPath, float left, float top, float right, float bottom, int dir); private static native void native_addOval(int nPath, RectF oval, int dir); - private static native void native_addCircle(int nPath, float x, float y, - float radius, int dir); + private static native void native_addCircle(int nPath, float x, float y, float radius, int dir); private static native void native_addArc(int nPath, RectF oval, float startAngle, float sweepAngle); private static native void native_addRoundRect(int nPath, RectF rect, float rx, float ry, int dir); - private static native void native_addRoundRect(int nPath, RectF r, - float[] radii, int dir); - private static native void native_addPath(int nPath, int src, float dx, - float dy); + private static native void native_addRoundRect(int nPath, RectF r, float[] radii, int dir); + private static native void native_addPath(int nPath, int src, float dx, float dy); private static native void native_addPath(int nPath, int src); private static native void native_addPath(int nPath, int src, int matrix); - private static native void native_offset(int nPath, float dx, float dy, - int dst_path); + private static native void native_offset(int nPath, float dx, float dy, int dst_path); private static native void native_offset(int nPath, float dx, float dy); private static native void native_setLastPoint(int nPath, float dx, float dy); - private static native void native_transform(int nPath, int matrix, - int dst_path); + private static native void native_transform(int nPath, int matrix, int dst_path); private static native void native_transform(int nPath, int matrix); + private static native boolean native_op(int path1, int path2, int op, int result); private static native void finalizer(int nPath); } diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index f7c202f..d96d6d8 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -19,16 +19,16 @@ package android.graphics; public class PixelFormat { /* these constants need to match those in hardware/hardware.h */ - + public static final int UNKNOWN = 0; /** System chooses a format that supports translucency (many alpha bits) */ public static final int TRANSLUCENT = -3; - /** + /** * System chooses a format that supports transparency - * (at least 1 alpha bit) - */ + * (at least 1 alpha bit) + */ public static final int TRANSPARENT = -2; /** System chooses an opaque format (no alpha bits required) */ @@ -43,7 +43,9 @@ public class PixelFormat public static final int RGBA_5551 = 6; @Deprecated public static final int RGBA_4444 = 7; + @Deprecated public static final int A_8 = 8; + @Deprecated public static final int L_8 = 9; @Deprecated public static final int LA_88 = 0xA; @@ -52,41 +54,71 @@ public class PixelFormat /** - * @deprecated use {@link android.graphics.ImageFormat#NV16 + * @deprecated use {@link android.graphics.ImageFormat#NV16 * ImageFormat.NV16} instead. */ @Deprecated public static final int YCbCr_422_SP= 0x10; /** - * @deprecated use {@link android.graphics.ImageFormat#NV21 + * @deprecated use {@link android.graphics.ImageFormat#NV21 * ImageFormat.NV21} instead. */ @Deprecated public static final int YCbCr_420_SP= 0x11; /** - * @deprecated use {@link android.graphics.ImageFormat#YUY2 + * @deprecated use {@link android.graphics.ImageFormat#YUY2 * ImageFormat.YUY2} instead. */ @Deprecated public static final int YCbCr_422_I = 0x14; /** - * @deprecated use {@link android.graphics.ImageFormat#JPEG + * @deprecated use {@link android.graphics.ImageFormat#JPEG * ImageFormat.JPEG} instead. */ @Deprecated public static final int JPEG = 0x100; - /* - * We use a class initializer to allow the native code to cache some - * field offsets. - */ - native private static void nativeClassInit(); - static { nativeClassInit(); } + public static void getPixelFormatInfo(int format, PixelFormat info) { + switch (format) { + case RGBA_8888: + case RGBX_8888: + info.bitsPerPixel = 32; + info.bytesPerPixel = 4; + break; + case RGB_888: + info.bitsPerPixel = 24; + info.bytesPerPixel = 3; + break; + case RGB_565: + case RGBA_5551: + case RGBA_4444: + case LA_88: + info.bitsPerPixel = 16; + info.bytesPerPixel = 2; + break; + case A_8: + case L_8: + case RGB_332: + info.bitsPerPixel = 8; + info.bytesPerPixel = 1; + break; + case YCbCr_422_SP: + case YCbCr_422_I: + info.bitsPerPixel = 16; + info.bytesPerPixel = 1; + break; + case YCbCr_420_SP: + info.bitsPerPixel = 12; + info.bytesPerPixel = 1; + break; + default: + throw new IllegalArgumentException("unkonwon pixel format " + format); + } + } - public static native void getPixelFormatInfo(int format, PixelFormat info); public static boolean formatHasAlpha(int format) { switch (format) { case PixelFormat.A_8: @@ -100,7 +132,7 @@ public class PixelFormat } return false; } - + public int bytesPerPixel; public int bitsPerPixel; } diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java index 897762c..f011e5c 100644 --- a/graphics/java/android/graphics/RadialGradient.java +++ b/graphics/java/android/graphics/RadialGradient.java @@ -18,6 +18,25 @@ package android.graphics; public class RadialGradient extends Shader { + private static final int TYPE_COLORS_AND_POSITIONS = 1; + private static final int TYPE_COLOR_CENTER_AND_COLOR_EDGE = 2; + + /** + * Type of the RadialGradient: can be either TYPE_COLORS_AND_POSITIONS or + * TYPE_COLOR_CENTER_AND_COLOR_EDGE. + */ + private int mType; + + private float mX; + private float mY; + private float mRadius; + private int[] mColors; + private float[] mPositions; + private int mColor0; + private int mColor1; + + private TileMode mTileMode; + /** 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 @@ -39,6 +58,13 @@ public class RadialGradient extends Shader { if (positions != null && colors.length != positions.length) { throw new IllegalArgumentException("color and position arrays must be of equal length"); } + mType = TYPE_COLORS_AND_POSITIONS; + mX = x; + mY = y; + mRadius = radius; + mColors = colors; + mPositions = positions; + mTileMode = tile; native_instance = nativeCreate1(x, y, radius, colors, positions, tile.nativeInt); native_shader = nativePostCreate1(native_instance, x, y, radius, colors, positions, tile.nativeInt); @@ -57,12 +83,41 @@ public class RadialGradient extends Shader { if (radius <= 0) { throw new IllegalArgumentException("radius must be > 0"); } + mType = TYPE_COLOR_CENTER_AND_COLOR_EDGE; + mX = x; + mY = y; + mRadius = radius; + mColor0 = color0; + mColor1 = color1; + mTileMode = tile; native_instance = nativeCreate2(x, y, radius, color0, color1, tile.nativeInt); native_shader = nativePostCreate2(native_instance, x, y, radius, color0, color1, tile.nativeInt); } - private static native int nativeCreate1(float x, float y, float radius, + /** + * @hide + */ + @Override + protected Shader copy() { + final RadialGradient copy; + switch (mType) { + case TYPE_COLORS_AND_POSITIONS: + copy = new RadialGradient(mX, mY, mRadius, mColors.clone(), + mPositions != null ? mPositions.clone() : null, mTileMode); + break; + case TYPE_COLOR_CENTER_AND_COLOR_EDGE: + copy = new RadialGradient(mX, mY, mRadius, mColor0, mColor1, mTileMode); + break; + default: + throw new IllegalArgumentException("RadialGradient should be created with either " + + "colors and positions or center color and edge color"); + } + copyLocalMatrix(copy); + return copy; + } + + private static native int nativeCreate1(float x, float y, float radius, int colors[], float positions[], int tileMode); private static native int nativeCreate2(float x, float y, float radius, int color0, int color1, int tileMode); diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index 43758e7..afc68d8 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -90,6 +90,28 @@ public class Shader { } } + /** + * @hide + */ + protected Shader copy() { + final Shader copy = new Shader(); + copyLocalMatrix(copy); + return copy; + } + + /** + * @hide + */ + protected void copyLocalMatrix(Shader dest) { + if (mLocalMatrix != null) { + final Matrix lm = new Matrix(); + getLocalMatrix(lm); + dest.setLocalMatrix(lm); + } else { + dest.setLocalMatrix(null); + } + } + private static native void nativeDestructor(int native_shader, int native_skiaShader); private static native void nativeSetLocalMatrix(int native_shader, int native_skiaShader, int matrix_instance); diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index af1a447..b910a24 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -21,6 +21,7 @@ import java.lang.ref.WeakReference; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.view.Surface; /** * Captures frames from an image stream as an OpenGL ES texture. @@ -69,6 +70,7 @@ public class SurfaceTexture { * These fields are used by native code, do not access or modify. */ private int mSurfaceTexture; + private int mBufferQueue; private int mFrameAvailableListener; /** @@ -79,8 +81,12 @@ public class SurfaceTexture { } /** - * Exception thrown when a surface couldn't be created or resized + * Exception thrown when a SurfaceTexture couldn't be created or resized. + * + * @deprecated No longer thrown. {@link Surface.OutOfResourcesException} is used instead. */ + @SuppressWarnings("serial") + @Deprecated public static class OutOfResourcesException extends Exception { public OutOfResourcesException() { } @@ -93,32 +99,32 @@ public class SurfaceTexture { * Construct a new SurfaceTexture to stream images to a given OpenGL texture. * * @param texName the OpenGL texture object name (e.g. generated via glGenTextures) + * + * @throws OutOfResourcesException If the SurfaceTexture cannot be created. */ public SurfaceTexture(int texName) { - this(texName, false); + init(texName, false); } /** * Construct a new SurfaceTexture to stream images to a given OpenGL texture. * + * In single buffered mode the application is responsible for serializing access to the image + * content buffer. Each time the image content is to be updated, the + * {@link #releaseTexImage()} method must be called before the image content producer takes + * ownership of the buffer. For example, when producing image content with the NDK + * ANativeWindow_lock and ANativeWindow_unlockAndPost functions, {@link #releaseTexImage()} + * must be called before each ANativeWindow_lock, or that call will fail. When producing + * image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first + * OpenGL ES function call each frame. + * * @param texName the OpenGL texture object name (e.g. generated via glGenTextures) - * @param allowSynchronousMode whether the SurfaceTexture can run in the synchronous mode. - * When the image stream comes from OpenGL, SurfaceTexture may run in the synchronous - * mode where the producer side may be blocked to avoid skipping frames. To avoid the - * thread block, set allowSynchronousMode to false. + * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode. * - * @hide + * @throws throws OutOfResourcesException If the SurfaceTexture cannot be created. */ - public SurfaceTexture(int texName, boolean allowSynchronousMode) { - Looper looper; - if ((looper = Looper.myLooper()) != null) { - mEventHandler = new EventHandler(looper); - } else if ((looper = Looper.getMainLooper()) != null) { - mEventHandler = new EventHandler(looper); - } else { - mEventHandler = null; - } - nativeInit(texName, new WeakReference<SurfaceTexture>(this), allowSynchronousMode); + public SurfaceTexture(int texName, boolean singleBufferMode) { + init(texName, singleBufferMode); } /** @@ -143,9 +149,9 @@ public class SurfaceTexture { * android.view.Surface#lockCanvas} is called. For OpenGL ES, the EGLSurface should be * destroyed (via eglDestroySurface), made not-current (via eglMakeCurrent), and then recreated * (via eglCreateWindowSurface) to ensure that the new default size has taken effect. - * + * * The width and height parameters must be no greater than the minimum of - * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see + * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see * {@link javax.microedition.khronos.opengles.GL10#glGetIntegerv glGetIntegerv}). * An error due to invalid dimensions might not be reported until * updateTexImage() is called. @@ -164,6 +170,15 @@ public class SurfaceTexture { } /** + * Releases the the texture content. This is needed in single buffered mode to allow the image + * content producer to take ownership of the image buffer. + * For more information see {@link #SurfaceTexture(int, boolean)}. + */ + public void releaseTexImage() { + nativeReleaseTexImage(); + } + + /** * Detach the SurfaceTexture from the OpenGL ES context that owns the OpenGL ES texture object. * This call must be made with the OpenGL ES context current on the calling thread. The OpenGL * ES texture object will be deleted as a result of this call. After calling this method all @@ -261,6 +276,7 @@ public class SurfaceTexture { nativeRelease(); } + @Override protected void finalize() throws Throwable { try { nativeFinalize(); @@ -299,12 +315,26 @@ public class SurfaceTexture { } } - private native void nativeInit(int texName, Object weakSelf, boolean allowSynchronousMode); + private void init(int texName, boolean singleBufferMode) throws Surface.OutOfResourcesException { + Looper looper; + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(looper); + } else { + mEventHandler = null; + } + nativeInit(texName, singleBufferMode, new WeakReference<SurfaceTexture>(this)); + } + + private native void nativeInit(int texName, boolean singleBufferMode, Object weakSelf) + throws Surface.OutOfResourcesException; private native void nativeFinalize(); private native void nativeGetTransformMatrix(float[] mtx); private native long nativeGetTimestamp(); private native void nativeSetDefaultBufferSize(int width, int height); private native void nativeUpdateTexImage(); + private native void nativeReleaseTexImage(); private native int nativeDetachFromGLContext(); private native int nativeAttachToGLContext(int texName); private native int nativeGetQueuedCount(); diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java index 2afdd4d..e9cda39 100644 --- a/graphics/java/android/graphics/SweepGradient.java +++ b/graphics/java/android/graphics/SweepGradient.java @@ -18,6 +18,22 @@ package android.graphics; public class SweepGradient extends Shader { + private static final int TYPE_COLORS_AND_POSITIONS = 1; + private static final int TYPE_COLOR_START_AND_COLOR_END = 2; + + /** + * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or + * TYPE_COLOR_START_AND_COLOR_END. + */ + private int mType; + + private float mCx; + private float mCy; + private int[] mColors; + private float[] mPositions; + private int mColor0; + private int mColor1; + /** * A subclass of Shader that draws a sweep gradient around a center point. * @@ -41,6 +57,11 @@ public class SweepGradient extends Shader { throw new IllegalArgumentException( "color and position arrays must be of equal length"); } + mType = TYPE_COLORS_AND_POSITIONS; + mCx = cx; + mCy = cy; + mColors = colors; + mPositions = positions; native_instance = nativeCreate1(cx, cy, colors, positions); native_shader = nativePostCreate1(native_instance, cx, cy, colors, positions); } @@ -54,10 +75,37 @@ public class SweepGradient extends Shader { * @param color1 The color to use at the end of the sweep */ public SweepGradient(float cx, float cy, int color0, int color1) { + mType = TYPE_COLOR_START_AND_COLOR_END; + mCx = cx; + mCy = cy; + mColor0 = color0; + mColor1 = color1; native_instance = nativeCreate2(cx, cy, color0, color1); native_shader = nativePostCreate2(native_instance, cx, cy, color0, color1); } + /** + * @hide + */ + @Override + protected Shader copy() { + final SweepGradient copy; + switch (mType) { + case TYPE_COLORS_AND_POSITIONS: + copy = new SweepGradient(mCx, mCy, mColors.clone(), + mPositions != null ? mPositions.clone() : null); + break; + case TYPE_COLOR_START_AND_COLOR_END: + copy = new SweepGradient(mCx, mCy, mColor0, mColor1); + break; + default: + throw new IllegalArgumentException("SweepGradient should be created with either " + + "colors and positions or start color and end color"); + } + copyLocalMatrix(copy); + return copy; + } + private static native int nativeCreate1(float x, float y, int colors[], float positions[]); private static native int nativeCreate2(float x, float y, int color0, int color1); diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index f0e9723..9accbbc 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -153,6 +153,11 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override + public int getAlpha() { + return mState.mDrawable.getAlpha(); + } + + @Override public void setColorFilter(ColorFilter cf) { mState.mDrawable.setColorFilter(cf); } diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 7c7cd01..bde978d 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -167,7 +167,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An * @return The Drawable at the specified frame index */ public Drawable getFrame(int index) { - return mAnimationState.getChildren()[index]; + return mAnimationState.getChild(index); } /** @@ -322,7 +322,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An mDurations = orig.mDurations; mOneShot = orig.mOneShot; } else { - mDurations = new int[getChildren().length]; + mDurations = new int[getCapacity()]; mOneShot = true; } } diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index a97ed2c..98e3386 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -23,12 +23,15 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Shader; +import android.graphics.Xfermode; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.LayoutDirection; import android.view.Gravity; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -72,7 +75,10 @@ public class BitmapDrawable extends Drawable { // These are scaled to match the target density. private int mBitmapWidth; private int mBitmapHeight; - + + // Mirroring matrix for using with Shaders + private Matrix mMirrorMatrix; + /** * Create an empty drawable, not dealing with density. * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} @@ -399,14 +405,51 @@ public class BitmapDrawable extends Drawable { } @Override + public void setAutoMirrored(boolean mirrored) { + if (mBitmapState.mAutoMirrored != mirrored) { + mBitmapState.mAutoMirrored = mirrored; + invalidateSelf(); + } + } + + @Override + public final boolean isAutoMirrored() { + return mBitmapState.mAutoMirrored; + } + + @Override public int getChangingConfigurations() { return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; } - + + private boolean needMirroring() { + return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; + } + + private void updateMirrorMatrix(float dx) { + if (mMirrorMatrix == null) { + mMirrorMatrix = new Matrix(); + } + mMirrorMatrix.setTranslate(dx, 0); + mMirrorMatrix.preScale(-1.0f, 1.0f); + } + @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mApplyGravity = true; + Shader shader = mBitmapState.mPaint.getShader(); + if (shader != null) { + if (needMirroring()) { + updateMirrorMatrix(bounds.right - bounds.left); + shader.setLocalMatrix(mMirrorMatrix); + } else { + if (mMirrorMatrix != null) { + mMirrorMatrix = null; + shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); + } + } + } } @Override @@ -430,6 +473,7 @@ public class BitmapDrawable extends Drawable { } Shader shader = state.mPaint.getShader(); + final boolean needMirroring = needMirroring(); if (shader == null) { if (mApplyGravity) { final int layoutDirection = getLayoutDirection(); @@ -437,12 +481,31 @@ public class BitmapDrawable extends Drawable { getBounds(), mDstRect, layoutDirection); mApplyGravity = false; } + if (needMirroring) { + canvas.save(); + // Mirror the bitmap + canvas.translate(mDstRect.right - mDstRect.left, 0); + canvas.scale(-1.0f, 1.0f); + } canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); + if (needMirroring) { + canvas.restore(); + } } else { if (mApplyGravity) { copyBounds(mDstRect); mApplyGravity = false; } + if (needMirroring) { + // Mirror the bitmap + updateMirrorMatrix(mDstRect.right - mDstRect.left); + shader.setLocalMatrix(mMirrorMatrix); + } else { + if (mMirrorMatrix != null) { + mMirrorMatrix = null; + shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); + } + } canvas.drawRect(mDstRect, state.mPaint); } } @@ -458,12 +521,25 @@ public class BitmapDrawable extends Drawable { } @Override + public int getAlpha() { + return mBitmapState.mPaint.getAlpha(); + } + + @Override public void setColorFilter(ColorFilter cf) { mBitmapState.mPaint.setColorFilter(cf); invalidateSelf(); } /** + * @hide Candidate for future API inclusion + */ + public void setXfermode(Xfermode xfermode) { + mBitmapState.mPaint.setXfermode(xfermode); + invalidateSelf(); + } + + /** * A mutable BitmapDrawable still shares its Bitmap with any other Drawable * that comes from the same resource. * @@ -500,6 +576,8 @@ public class BitmapDrawable extends Drawable { setTargetDensity(r.getDisplayMetrics()); setMipMap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_mipMap, bitmap.hasMipMap())); + setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_autoMirrored, + false)); final Paint paint = mBitmapState.mPaint; paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, @@ -562,6 +640,7 @@ public class BitmapDrawable extends Drawable { Shader.TileMode mTileModeY = null; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; boolean mRebuildShader; + boolean mAutoMirrored; BitmapState(Bitmap bitmap) { mBitmap = bitmap; @@ -576,6 +655,12 @@ public class BitmapDrawable extends Drawable { mTargetDensity = bitmapState.mTargetDensity; mPaint = new Paint(bitmapState.mPaint); mRebuildShader = bitmapState.mRebuildShader; + mAutoMirrored = bitmapState.mAutoMirrored; + } + + @Override + public Bitmap getBitmap() { + return mBitmap; } @Override diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index eb9f046..2a9a14b 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -158,6 +158,11 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } @Override + public int getAlpha() { + return mClipState.mDrawable.getAlpha(); + } + + @Override public void setColorFilter(ColorFilter cf) { mClipState.mDrawable.setColorFilter(cf); } diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index d11e554..61dd675 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -116,6 +116,7 @@ public class ColorDrawable extends Drawable { * * @return A value between 0 and 255. */ + @Override public int getAlpha() { return mState.mUseColor >>> 24; } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index d5183d5..8a3d940 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.graphics.Insets; +import android.graphics.Xfermode; import android.os.Trace; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -411,12 +412,32 @@ public abstract class Drawable { public abstract void setAlpha(int alpha); /** + * Gets the current alpha value for the drawable. 0 means fully transparent, + * 255 means fully opaque. This method is implemented by + * Drawable subclasses and the value returned is specific to how that class treats alpha. + * The default return value is 255 if the class does not override this method to return a value + * specific to its use of alpha. + */ + public int getAlpha() { + return 0xFF; + } + + /** * Specify an optional colorFilter for the drawable. Pass null to remove * any filters. */ public abstract void setColorFilter(ColorFilter cf); /** + * @hide Consider for future API inclusion + */ + public void setXfermode(Xfermode mode) { + // Base implementation drops it on the floor for compatibility. Whee! + // TODO: For this to be included in the API proper, all framework drawables need impls. + // For right now only BitmapDrawable has it. + } + + /** * Specify a color and porterduff mode to be the colorfilter for this * drawable. */ @@ -561,6 +582,25 @@ public abstract class Drawable { } /** + * Set whether this Drawable is automatically mirrored when its layout direction is RTL + * (right-to left). See {@link android.util.LayoutDirection}. + * + * @param mirrored Set to true if the Drawable should be mirrored, false if not. + */ + public void setAutoMirrored(boolean mirrored) { + } + + /** + * Tells if this Drawable will be automatically mirrored when its layout direction is RTL + * right-to-left. See {@link android.util.LayoutDirection}. + * + * @return boolean Returns true if this Drawable will be automatically mirrored. + */ + public boolean isAutoMirrored() { + return false; + } + + /** * Return the opacity/transparency of this Drawable. The returned value is * one of the abstract format constants in * {@link android.graphics.PixelFormat}: @@ -858,10 +898,6 @@ public abstract class Drawable { drawable = new StateListDrawable(); } else if (name.equals("level-list")) { drawable = new LevelListDrawable(); - /* Probably not doing this. - } else if (name.equals("mipmap")) { - drawable = new MipmapDrawable(); - */ } else if (name.equals("layer-list")) { drawable = new LayerDrawable(); } else if (name.equals("transition")) { @@ -985,6 +1021,13 @@ public abstract class Drawable { * this drawable (and thus require completely reloading it). */ public abstract int getChangingConfigurations(); + + /** + * @hide + */ + public Bitmap getBitmap() { + return null; + } } /** diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 0f84e86..aac7876 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -23,6 +23,8 @@ import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.SystemClock; +import android.util.LayoutDirection; +import android.util.SparseArray; /** * A helper class that contains several {@link Drawable}s and selects which one to use. @@ -58,6 +60,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { private long mExitAnimationEnd; private Drawable mLastDrawable; + private Insets mInsets = Insets.NONE; + // overrides from Drawable @Override @@ -76,19 +80,32 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { | mDrawableContainerState.mChangingConfigurations | mDrawableContainerState.mChildrenChangingConfigurations; } - + + private boolean needsMirroring() { + return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; + } + @Override public boolean getPadding(Rect padding) { final Rect r = mDrawableContainerState.getConstantPadding(); + boolean result; if (r != null) { padding.set(r); - return true; - } - if (mCurrDrawable != null) { - return mCurrDrawable.getPadding(padding); + result = (r.left | r.top | r.bottom | r.right) != 0; } else { - return super.getPadding(padding); + if (mCurrDrawable != null) { + result = mCurrDrawable.getPadding(padding); + } else { + result = super.getPadding(padding); + } + } + if (needsMirroring()) { + final int left = padding.left; + final int right = padding.right; + padding.left = right; + padding.right = left; } + return result; } /** @@ -96,7 +113,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { */ @Override public Insets getOpticalInsets() { - return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getOpticalInsets(); + return mInsets; } @Override @@ -114,6 +131,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } @Override + public int getAlpha() { + return mAlpha; + } + + @Override public void setDither(boolean dither) { if (mDrawableContainerState.mDither != dither) { mDrawableContainerState.mDither = dither; @@ -132,7 +154,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } } } - + /** * Change the global fade duration when a new drawable is entering * the scene. @@ -141,7 +163,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { public void setEnterFadeDuration(int ms) { mDrawableContainerState.mEnterFadeDuration = ms; } - + /** * Change the global fade duration when a new drawable is leaving * the scene. @@ -150,7 +172,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { public void setExitFadeDuration(int ms) { mDrawableContainerState.mExitFadeDuration = ms; } - + @Override protected void onBoundsChange(Rect bounds) { if (mLastDrawable != null) { @@ -160,12 +182,25 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mCurrDrawable.setBounds(bounds); } } - + @Override public boolean isStateful() { return mDrawableContainerState.isStateful(); } - + + @Override + public void setAutoMirrored(boolean mirrored) { + mDrawableContainerState.mAutoMirrored = mirrored; + if (mCurrDrawable != null) { + mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored); + } + } + + @Override + public boolean isAutoMirrored() { + return mDrawableContainerState.mAutoMirrored; + } + @Override public void jumpToCurrentState() { boolean changed = false; @@ -228,7 +263,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1; } - + @Override public int getMinimumWidth() { if (mDrawableContainerState.isConstantSize()) { @@ -245,18 +280,21 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0; } + @Override public void invalidateDrawable(Drawable who) { if (who == mCurrDrawable && getCallback() != null) { getCallback().invalidateDrawable(this); } } + @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { if (who == mCurrDrawable && getCallback() != null) { getCallback().scheduleDrawable(this, what, when); } } + @Override public void unscheduleDrawable(Drawable who, Runnable what) { if (who == mCurrDrawable && getCallback() != null) { getCallback().unscheduleDrawable(this, what); @@ -308,10 +346,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { - Drawable d = mDrawableContainerState.mDrawables[idx]; + final Drawable d = mDrawableContainerState.getChild(idx); mCurrDrawable = d; mCurIndex = idx; if (d != null) { + mInsets = d.getOpticalInsets(); d.mutate(); if (mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; @@ -325,9 +364,13 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { d.setLevel(getLevel()); d.setBounds(getBounds()); d.setLayoutDirection(getLayoutDirection()); + d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); + } else { + mInsets = Insets.NONE; } } else { mCurrDrawable = null; + mInsets = Insets.NONE; mCurIndex = -1; } @@ -350,7 +393,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return true; } - + void animate(boolean schedule) { final long now = SystemClock.uptimeMillis(); boolean animating = false; @@ -410,11 +453,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - final int N = mDrawableContainerState.getChildCount(); - final Drawable[] drawables = mDrawableContainerState.getChildren(); - for (int i = 0; i < N; i++) { - if (drawables[i] != null) drawables[i].mutate(); - } + mDrawableContainerState.mutate(); mMutated = true; } return this; @@ -428,90 +467,108 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { */ public abstract static class DrawableContainerState extends ConstantState { final DrawableContainer mOwner; + final Resources mRes; + + SparseArray<ConstantStateFuture> mDrawableFutures; + + int mChangingConfigurations; + int mChildrenChangingConfigurations; - int mChangingConfigurations; - int mChildrenChangingConfigurations; - - Drawable[] mDrawables; - int mNumChildren; + Drawable[] mDrawables; + int mNumChildren; - boolean mVariablePadding = false; - Rect mConstantPadding = null; + boolean mVariablePadding; + boolean mPaddingChecked; + Rect mConstantPadding; - boolean mConstantSize = false; - boolean mComputedConstantSize = false; - int mConstantWidth; - int mConstantHeight; - int mConstantMinimumWidth; - int mConstantMinimumHeight; + boolean mConstantSize; + boolean mComputedConstantSize; + int mConstantWidth; + int mConstantHeight; + int mConstantMinimumWidth; + int mConstantMinimumHeight; - int mOpacity; + boolean mCheckedOpacity; + int mOpacity; - boolean mHaveStateful = false; - boolean mStateful; + boolean mCheckedStateful; + boolean mStateful; - boolean mCheckedConstantState; - boolean mCanConstantState; + boolean mCheckedConstantState; + boolean mCanConstantState; - boolean mPaddingChecked = false; - - boolean mDither = DEFAULT_DITHER; + boolean mDither = DEFAULT_DITHER; - int mEnterFadeDuration; - int mExitFadeDuration; + boolean mMutated; + int mLayoutDirection; + + int mEnterFadeDuration; + int mExitFadeDuration; + + boolean mAutoMirrored; DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { mOwner = owner; + mRes = res; if (orig != null) { mChangingConfigurations = orig.mChangingConfigurations; mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; - - final Drawable[] origDr = orig.mDrawables; - - mDrawables = new Drawable[origDr.length]; - mNumChildren = orig.mNumChildren; - final int N = mNumChildren; - for (int i=0; i<N; i++) { - if (res != null) { - mDrawables[i] = origDr[i].getConstantState().newDrawable(res); - } else { - mDrawables[i] = origDr[i].getConstantState().newDrawable(); - } - mDrawables[i].setCallback(owner); - mDrawables[i].setLayoutDirection(origDr[i].getLayoutDirection()); - } + mCheckedConstantState = true; + mCanConstantState = true; - mCheckedConstantState = mCanConstantState = true; mVariablePadding = orig.mVariablePadding; - if (orig.mConstantPadding != null) { - mConstantPadding = new Rect(orig.mConstantPadding); - } mConstantSize = orig.mConstantSize; - mComputedConstantSize = orig.mComputedConstantSize; - mConstantWidth = orig.mConstantWidth; - mConstantHeight = orig.mConstantHeight; - mConstantMinimumWidth = orig.mConstantMinimumWidth; - mConstantMinimumHeight = orig.mConstantMinimumHeight; - - mOpacity = orig.mOpacity; - mHaveStateful = orig.mHaveStateful; - mStateful = orig.mStateful; - mDither = orig.mDither; - + mMutated = orig.mMutated; + mLayoutDirection = orig.mLayoutDirection; mEnterFadeDuration = orig.mEnterFadeDuration; mExitFadeDuration = orig.mExitFadeDuration; + mAutoMirrored = orig.mAutoMirrored; + + // Cloning the following values may require creating futures. + mConstantPadding = orig.getConstantPadding(); + mPaddingChecked = true; + + mConstantWidth = orig.getConstantWidth(); + mConstantHeight = orig.getConstantHeight(); + mConstantMinimumWidth = orig.getConstantMinimumWidth(); + mConstantMinimumHeight = orig.getConstantMinimumHeight(); + mComputedConstantSize = true; + + mOpacity = orig.getOpacity(); + mCheckedOpacity = true; + + mStateful = orig.isStateful(); + mCheckedStateful = true; + + // Postpone cloning children and futures until we're absolutely + // sure that we're done computing values for the original state. + final Drawable[] origDr = orig.mDrawables; + mDrawables = new Drawable[origDr.length]; + mNumChildren = orig.mNumChildren; + + final SparseArray<ConstantStateFuture> origDf = orig.mDrawableFutures; + if (origDf != null) { + mDrawableFutures = origDf.clone(); + } else { + mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren); + } + final int N = mNumChildren; + for (int i = 0; i < N; i++) { + if (origDr[i] != null) { + mDrawableFutures.put(i, new ConstantStateFuture(origDr[i])); + } + } } else { mDrawables = new Drawable[10]; mNumChildren = 0; - mCheckedConstantState = mCanConstantState = false; } } - + @Override public int getChangingConfigurations() { return mChangingConfigurations | mChildrenChangingConfigurations; @@ -530,7 +587,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mDrawables[pos] = dr; mNumChildren++; mChildrenChangingConfigurations |= dr.getChangingConfigurations(); - mHaveStateful = false; + mCheckedStateful = false; + mCheckedOpacity = false; mConstantPadding = null; mPaddingChecked = false; @@ -539,18 +597,89 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return pos; } + final int getCapacity() { + return mDrawables.length; + } + + private final void createAllFutures() { + if (mDrawableFutures != null) { + final int futureCount = mDrawableFutures.size(); + for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) { + final int index = mDrawableFutures.keyAt(keyIndex); + mDrawables[index] = mDrawableFutures.valueAt(keyIndex).get(this); + } + + mDrawableFutures = null; + } + } + public final int getChildCount() { return mNumChildren; } + /* + * @deprecated Use {@link #getChild} instead. + */ public final Drawable[] getChildren() { + // Create all futures for backwards compatibility. + createAllFutures(); + return mDrawables; } - /** A boolean value indicating whether to use the maximum padding value of - * all frames in the set (false), or to use the padding value of the frame - * being shown (true). Default value is false. - */ + public final Drawable getChild(int index) { + final Drawable result = mDrawables[index]; + if (result != null) { + return result; + } + + // Prepare future drawable if necessary. + if (mDrawableFutures != null) { + final int keyIndex = mDrawableFutures.indexOfKey(index); + if (keyIndex >= 0) { + final Drawable prepared = mDrawableFutures.valueAt(keyIndex).get(this); + mDrawables[index] = prepared; + mDrawableFutures.removeAt(keyIndex); + return prepared; + } + } + + return null; + } + + final void setLayoutDirection(int layoutDirection) { + // No need to call createAllFutures, since future drawables will + // change layout direction when they are prepared. + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i] != null) { + drawables[i].setLayoutDirection(layoutDirection); + } + } + + mLayoutDirection = layoutDirection; + } + + final void mutate() { + // No need to call createAllFutures, since future drawables will + // mutate when they are prepared. + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i] != null) { + drawables[i].mutate(); + } + } + + mMutated = true; + } + + /** + * A boolean value indicating whether to use the maximum padding value + * of all frames in the set (false), or to use the padding value of the + * frame being shown (true). Default value is false. + */ public final void setVariablePadding(boolean variable) { mVariablePadding = variable; } @@ -559,13 +688,16 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { if (mVariablePadding) { return null; } - if (mConstantPadding != null || mPaddingChecked) { + + if ((mConstantPadding != null) || mPaddingChecked) { return mConstantPadding; } + createAllFutures(); + Rect r = null; final Rect t = new Rect(); - final int N = getChildCount(); + final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { if (drawables[i].getPadding(t)) { @@ -576,6 +708,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { if (t.bottom > r.bottom) r.bottom = t.bottom; } } + mPaddingChecked = true; return (mConstantPadding = r); } @@ -623,12 +756,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { protected void computeConstantSize() { mComputedConstantSize = true; - final int N = getChildCount(); + createAllFutures(); + + final int N = mNumChildren; final Drawable[] drawables = mDrawables; mConstantWidth = mConstantHeight = -1; mConstantMinimumWidth = mConstantMinimumHeight = 0; for (int i = 0; i < N; i++) { - Drawable dr = drawables[i]; + final Drawable dr = drawables[i]; int s = dr.getIntrinsicWidth(); if (s > mConstantWidth) mConstantWidth = s; s = dr.getIntrinsicHeight(); @@ -657,33 +792,45 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } public final int getOpacity() { - final int N = getChildCount(); + if (mCheckedOpacity) { + return mOpacity; + } + + createAllFutures(); + + mCheckedOpacity = true; + + final int N = mNumChildren; final Drawable[] drawables = mDrawables; - int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; + int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; for (int i = 1; i < N; i++) { op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); } + mOpacity = op; return op; } public final boolean isStateful() { - if (mHaveStateful) { + if (mCheckedStateful) { return mStateful; } - - boolean stateful = false; - final int N = getChildCount(); + + createAllFutures(); + + mCheckedStateful = true; + + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { - if (mDrawables[i].isStateful()) { - stateful = true; - break; + if (drawables[i].isStateful()) { + mStateful = true; + return true; } } - - mStateful = stateful; - mHaveStateful = true; - return stateful; + + mStateful = false; + return false; } public void growArray(int oldSize, int newSize) { @@ -693,24 +840,60 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } public synchronized boolean canConstantState() { - if (!mCheckedConstantState) { - mCanConstantState = true; - final int N = mNumChildren; - for (int i=0; i<N; i++) { - if (mDrawables[i].getConstantState() == null) { - mCanConstantState = false; - break; - } + if (mCheckedConstantState) { + return mCanConstantState; + } + + createAllFutures(); + + mCheckedConstantState = true; + + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i].getConstantState() == null) { + mCanConstantState = false; + return false; } - mCheckedConstantState = true; } - return mCanConstantState; + mCanConstantState = true; + return true; + } + + /** + * Class capable of cloning a Drawable from another Drawable's + * ConstantState. + */ + private static class ConstantStateFuture { + private final ConstantState mConstantState; + + private ConstantStateFuture(Drawable source) { + mConstantState = source.getConstantState(); + } + + /** + * Obtains and prepares the Drawable represented by this future. + * + * @param state the container into which this future will be placed + * @return a prepared Drawable + */ + public Drawable get(DrawableContainerState state) { + final Drawable result = (state.mRes == null) ? + mConstantState.newDrawable() : mConstantState.newDrawable(state.mRes); + result.setLayoutDirection(state.mLayoutDirection); + result.setCallback(state.mOwner); + + if (state.mMutated) { + result.mutate(); + } + + return result; + } } } - protected void setConstantState(DrawableContainerState state) - { + protected void setConstantState(DrawableContainerState state) { mDrawableContainerState = state; } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index b966bb4..b340777 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -19,6 +19,7 @@ package android.graphics.drawable; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.DashPathEffect; import android.graphics.LinearGradient; @@ -639,6 +640,11 @@ public class GradientDrawable extends Drawable { } @Override + public int getAlpha() { + return mAlpha; + } + + @Override public void setDither(boolean dither) { if (dither != mDither) { mDither = dither; @@ -742,9 +748,6 @@ public class GradientDrawable extends Drawable { mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, colors, st.mPositions, Shader.TileMode.CLAMP)); - if (!mGradientState.mHasSolidColor) { - mFillPaint.setColor(mAlpha << 24); - } } else if (st.mGradient == RADIAL_GRADIENT) { x0 = r.left + (r.right - r.left) * st.mCenterX; y0 = r.top + (r.bottom - r.top) * st.mCenterY; @@ -754,9 +757,6 @@ public class GradientDrawable extends Drawable { mFillPaint.setShader(new RadialGradient(x0, y0, level * st.mGradientRadius, colors, null, Shader.TileMode.CLAMP)); - if (!mGradientState.mHasSolidColor) { - mFillPaint.setColor(mAlpha << 24); - } } else if (st.mGradient == SWEEP_GRADIENT) { x0 = r.left + (r.right - r.left) * st.mCenterX; y0 = r.top + (r.bottom - r.top) * st.mCenterY; @@ -787,9 +787,12 @@ public class GradientDrawable extends Drawable { } mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); - if (!mGradientState.mHasSolidColor) { - mFillPaint.setColor(mAlpha << 24); - } + } + + // If we don't have a solid color, the alpha channel must be + // maxed out so that alpha modulation works correctly. + if (!st.mHasSolidColor) { + mFillPaint.setColor(Color.BLACK); } } } @@ -1276,6 +1279,9 @@ public class GradientDrawable extends Drawable { // the app is stroking the shape, set the color to the default // value of state.mSolidColor mFillPaint.setColor(0); + } else { + // Otherwise, make sure the fill alpha is maxed out. + mFillPaint.setColor(Color.BLACK); } mPadding = state.mPadding; if (state.mStrokeWidth >= 0) { diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 2576f42e..8188782 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -192,12 +192,23 @@ public class InsetDrawable extends Drawable implements Drawable.Callback public void setAlpha(int alpha) { mInsetState.mDrawable.setAlpha(alpha); } - + + @Override + public int getAlpha() { + return mInsetState.mDrawable.getAlpha(); + } + @Override public void setColorFilter(ColorFilter cf) { mInsetState.mDrawable.setColorFilter(cf); } - + + /** {@hide} */ + @Override + public void setLayoutDirection(int layoutDirection) { + mInsetState.mDrawable.setLayoutDirection(layoutDirection); + } + @Override public int getOpacity() { return mInsetState.mDrawable.getOpacity(); @@ -256,6 +267,13 @@ public class InsetDrawable extends Drawable implements Drawable.Callback return this; } + /** + * Returns the drawable wrapped by this InsetDrawable. May be null. + */ + public Drawable getDrawable() { + return mInsetState.mDrawable; + } + final static class InsetState extends ConstantState { Drawable mDrawable; int mChangingConfigurations; diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 6b59dba..81cc11b 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -119,6 +119,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); + setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.LayerDrawable_autoMirrored, + false)); + a.recycle(); final int innerDepth = parser.getDepth() + 1; @@ -200,6 +203,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { st.mChildren[i] = childDrawable; childDrawable.mId = id; childDrawable.mDrawable = layer; + childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); childDrawable.mInsetL = left; childDrawable.mInsetT = top; childDrawable.mInsetR = right; @@ -402,7 +406,18 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { array[i].mDrawable.setAlpha(alpha); } } - + + @Override + public int getAlpha() { + final ChildDrawable[] array = mLayerState.mChildren; + if (mLayerState.mNum > 0) { + // All layers should have the same alpha set on them - just return the first one + return array[0].mDrawable.getAlpha(); + } else { + return super.getAlpha(); + } + } + @Override public void setColorFilter(ColorFilter cf) { final ChildDrawable[] array = mLayerState.mChildren; @@ -437,6 +452,21 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override + public void setAutoMirrored(boolean mirrored) { + mLayerState.mAutoMirrored = mirrored; + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.mNum; + for (int i=0; i<N; i++) { + array[i].mDrawable.setAutoMirrored(mirrored); + } + } + + @Override + public boolean isAutoMirrored() { + return mLayerState.mAutoMirrored; + } + + @Override public boolean isStateful() { return mLayerState.isStateful(); } @@ -619,6 +649,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { private boolean mCheckedConstantState; private boolean mCanConstantState; + private boolean mAutoMirrored; + LayerState(LayerState orig, LayerDrawable owner, Resources res) { if (orig != null) { final ChildDrawable[] origChildDrawable = orig.mChildren; @@ -652,6 +684,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mHaveStateful = orig.mHaveStateful; mStateful = orig.mStateful; mCheckedConstantState = mCanConstantState = true; + mAutoMirrored = orig.mAutoMirrored; } else { mNum = 0; mChildren = null; diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java index 21be983..872fdce 100644 --- a/graphics/java/android/graphics/drawable/LevelListDrawable.java +++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java @@ -164,8 +164,8 @@ public class LevelListDrawable extends DrawableContainer { mLows = orig.mLows; mHighs = orig.mHighs; } else { - mLows = new int[getChildren().length]; - mHighs = new int[getChildren().length]; + mLows = new int[getCapacity()]; + mHighs = new int[getCapacity()]; } } diff --git a/graphics/java/android/graphics/drawable/MipmapDrawable.java b/graphics/java/android/graphics/drawable/MipmapDrawable.java deleted file mode 100644 index cd39719..0000000 --- a/graphics/java/android/graphics/drawable/MipmapDrawable.java +++ /dev/null @@ -1,312 +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.drawable; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.util.AttributeSet; - -import java.io.IOException; - -/** - * @hide -- we are probably moving to do MipMaps in another way (more integrated - * with the resource system). - * - * A resource that manages a number of alternate Drawables, and which actually draws the one which - * size matches the most closely the drawing bounds. Providing several pre-scaled version of the - * drawable helps minimizing the aliasing artifacts that can be introduced by the scaling. - * - * <p> - * Use {@link #addDrawable(Drawable)} to define the different Drawables that will represent the - * mipmap levels of this MipmapDrawable. The mipmap Drawable that will actually be used when this - * MipmapDrawable is drawn is the one which has the smallest intrinsic height greater or equal than - * the bounds' height. This selection ensures that the best available mipmap level is scaled down to - * draw this MipmapDrawable. - * </p> - * - * If the bounds' height is larger than the largest mipmap, the largest mipmap will be scaled up. - * Note that Drawables without intrinsic height (i.e. with a negative value, such as Color) will - * only be used if no other mipmap Drawable are provided. The Drawables' intrinsic heights should - * not be changed after the Drawable has been added to this MipmapDrawable. - * - * <p> - * The different mipmaps' parameters (opacity, padding, color filter, gravity...) should typically - * be similar to ensure a continuous visual appearance when the MipmapDrawable is scaled. The aspect - * ratio of the different mipmaps should especially be equal. - * </p> - * - * A typical example use of a MipmapDrawable would be for an image which is intended to be scaled at - * various sizes, and for which one wants to provide pre-scaled versions to precisely control its - * appearance. - * - * <p> - * The intrinsic size of a MipmapDrawable are inferred from those of the largest mipmap (in terms of - * {@link Drawable#getIntrinsicHeight()}). On the opposite, its minimum - * size is defined by the smallest provided mipmap. - * </p> - - * It can be defined in an XML file with the <code><mipmap></code> element. - * Each mipmap Drawable is defined in a nested <code><item></code>. For example: - * <pre> - * <mipmap xmlns:android="http://schemas.android.com/apk/res/android"> - * <item android:drawable="@drawable/my_image_8" /> - * <item android:drawable="@drawable/my_image_32" /> - * <item android:drawable="@drawable/my_image_128" /> - * </mipmap> - *</pre> - * <p> - * With this XML saved into the res/drawable/ folder of the project, it can be referenced as - * the drawable for an {@link android.widget.ImageView}. Assuming that the heights of the provided - * drawables are respectively 8, 32 and 128 pixels, the first one will be scaled down when the - * bounds' height is lower or equal than 8 pixels. The second drawable will then be used up to a - * height of 32 pixels and the largest drawable will be used for greater heights. - * </p> - * @attr ref android.R.styleable#MipmapDrawableItem_drawable - */ -public class MipmapDrawable extends DrawableContainer { - private final MipmapContainerState mMipmapContainerState; - private boolean mMutated; - - public MipmapDrawable() { - this(null, null); - } - - /** - * Adds a Drawable to the list of available mipmap Drawables. The Drawable actually used when - * this MipmapDrawable is drawn is determined from its bounds. - * - * This method has no effect if drawable is null. - * - * @param drawable The Drawable that will be added to list of available mipmap Drawables. - */ - - public void addDrawable(Drawable drawable) { - if (drawable != null) { - mMipmapContainerState.addDrawable(drawable); - onDrawableAdded(); - } - } - - private void onDrawableAdded() { - // selectDrawable assumes that the container content does not change. - // When a Drawable is added, the same index can correspond to a new Drawable, and since - // selectDrawable has a fast exit case when oldIndex==newIndex, the new drawable could end - // up not being used in place of the previous one if they happen to share the same index. - // This make sure the new computed index can actually replace the previous one. - selectDrawable(-1); - onBoundsChange(getBounds()); - } - - // overrides from Drawable - - @Override - protected void onBoundsChange(Rect bounds) { - final int index = mMipmapContainerState.indexForBounds(bounds); - - // Will call invalidateSelf() if needed - selectDrawable(index); - - super.onBoundsChange(bounds); - } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { - - super.inflate(r, parser, attrs); - - int type; - - final int innerDepth = parser.getDepth() + 1; - int depth; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && ((depth = parser.getDepth()) >= innerDepth - || type != XmlPullParser.END_TAG)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - - if (depth > innerDepth || !parser.getName().equals("item")) { - continue; - } - - TypedArray a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.MipmapDrawableItem); - - int drawableRes = a.getResourceId( - com.android.internal.R.styleable.MipmapDrawableItem_drawable, 0); - - a.recycle(); - - Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); - } else { - while ((type = parser.next()) == XmlPullParser.TEXT) { - } - if (type != XmlPullParser.START_TAG) { - throw new XmlPullParserException( - parser.getPositionDescription() - + ": <item> tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - dr = Drawable.createFromXmlInner(r, parser, attrs); - } - - mMipmapContainerState.addDrawable(dr); - } - - onDrawableAdded(); - } - - @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mMipmapContainerState.mMipmapHeights = mMipmapContainerState.mMipmapHeights.clone(); - mMutated = true; - } - return this; - } - - private final static class MipmapContainerState extends DrawableContainerState { - private int[] mMipmapHeights; - - MipmapContainerState(MipmapContainerState orig, MipmapDrawable owner, Resources res) { - super(orig, owner, res); - - if (orig != null) { - mMipmapHeights = orig.mMipmapHeights; - } else { - mMipmapHeights = new int[getChildren().length]; - } - - // Change the default value - setConstantSize(true); - } - - /** - * Returns the index of the child mipmap drawable that will best fit the provided bounds. - * This index is determined by comparing bounds' height and children intrinsic heights. - * The returned mipmap index is the smallest mipmap which height is greater or equal than - * the bounds' height. If the bounds' height is larger than the largest mipmap, the largest - * mipmap index is returned. - * - * @param bounds The bounds of the MipMapDrawable. - * @return The index of the child Drawable that will best fit these bounds, or -1 if there - * are no children mipmaps. - */ - public int indexForBounds(Rect bounds) { - final int boundsHeight = bounds.height(); - final int N = getChildCount(); - for (int i = 0; i < N; i++) { - if (boundsHeight <= mMipmapHeights[i]) { - return i; - } - } - - // No mipmap larger than bounds found. Use largest one which will be scaled up. - if (N > 0) { - return N - 1; - } - // No Drawable mipmap at all - return -1; - } - - /** - * Adds a Drawable to the list of available mipmap Drawables. This list can be retrieved - * using {@link DrawableContainer.DrawableContainerState#getChildren()} and this method - * ensures that it is always sorted by increasing {@link Drawable#getIntrinsicHeight()}. - * - * @param drawable The Drawable that will be added to children list - */ - public void addDrawable(Drawable drawable) { - // Insert drawable in last position, correctly resetting cached values and - // especially mComputedConstantSize - int pos = addChild(drawable); - - // Bubble sort the last drawable to restore the sort by intrinsic height - final int drawableHeight = drawable.getIntrinsicHeight(); - - while (pos > 0) { - final Drawable previousDrawable = mDrawables[pos-1]; - final int previousIntrinsicHeight = previousDrawable.getIntrinsicHeight(); - - if (drawableHeight < previousIntrinsicHeight) { - mDrawables[pos] = previousDrawable; - mMipmapHeights[pos] = previousIntrinsicHeight; - - mDrawables[pos-1] = drawable; - mMipmapHeights[pos-1] = drawableHeight; - pos--; - } else { - break; - } - } - } - - /** - * Intrinsic sizes are those of the largest available mipmap. - * Minimum sizes are those of the smallest available mipmap. - */ - @Override - protected void computeConstantSize() { - final int N = getChildCount(); - if (N > 0) { - final Drawable smallestDrawable = mDrawables[0]; - mConstantMinimumWidth = smallestDrawable.getMinimumWidth(); - mConstantMinimumHeight = smallestDrawable.getMinimumHeight(); - - final Drawable largestDrawable = mDrawables[N-1]; - mConstantWidth = largestDrawable.getIntrinsicWidth(); - mConstantHeight = largestDrawable.getIntrinsicHeight(); - } else { - mConstantWidth = mConstantHeight = -1; - mConstantMinimumWidth = mConstantMinimumHeight = 0; - } - mComputedConstantSize = true; - } - - @Override - public Drawable newDrawable() { - return new MipmapDrawable(this, null); - } - - @Override - public Drawable newDrawable(Resources res) { - return new MipmapDrawable(this, res); - } - - @Override - public void growArray(int oldSize, int newSize) { - super.growArray(oldSize, newSize); - int[] newInts = new int[newSize]; - System.arraycopy(mMipmapHeights, 0, newInts, 0, oldSize); - mMipmapHeights = newInts; - } - } - - private MipmapDrawable(MipmapContainerState state, Resources res) { - MipmapContainerState as = new MipmapContainerState(state, this, res); - mMipmapContainerState = as; - setConstantState(as); - onDrawableAdded(); - } -} diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index a9dc22b..9c57a2c 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -30,6 +30,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.LayoutDirection; import android.util.TypedValue; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -52,7 +53,7 @@ import java.io.InputStream; */ public class NinePatchDrawable extends Drawable { // dithering helps a lot, and is pretty cheap, so default is true - private static final boolean DEFAULT_DITHER = true; + private static final boolean DEFAULT_DITHER = false; private NinePatchState mNinePatchState; private NinePatch mNinePatch; private Rect mPadding; @@ -132,6 +133,7 @@ public class NinePatchDrawable extends Drawable { // lazy allocation of a paint setDither(state.mDither); } + setAutoMirrored(state.mAutoMirrored); if (mNinePatch != null) { computeBitmapSize(); } @@ -197,10 +199,8 @@ public class NinePatchDrawable extends Drawable { mBitmapHeight = mNinePatch.getHeight(); mOpticalInsets = mNinePatchState.mOpticalInsets; } else { - mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), - sdensity, tdensity); - mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), - sdensity, tdensity); + mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), sdensity, tdensity); + mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), sdensity, tdensity); if (mNinePatchState.mPadding != null && mPadding != null) { Rect dest = mPadding; Rect src = mNinePatchState.mPadding; @@ -218,7 +218,18 @@ public class NinePatchDrawable extends Drawable { @Override public void draw(Canvas canvas) { - mNinePatch.draw(canvas, getBounds(), mPaint); + final Rect bounds = getBounds(); + final boolean needsMirroring = needsMirroring(); + if (needsMirroring) { + canvas.save(); + // Mirror the 9patch + canvas.translate(bounds.right - bounds.left, 0); + canvas.scale(-1.0f, 1.0f); + } + mNinePatch.draw(canvas, bounds, mPaint); + if (needsMirroring) { + canvas.restore(); + } } @Override @@ -228,8 +239,12 @@ public class NinePatchDrawable extends Drawable { @Override public boolean getPadding(Rect padding) { - padding.set(mPadding); - return true; + if (needsMirroring()) { + padding.set(mPadding.right, mPadding.top, mPadding.left, mPadding.bottom); + } else { + padding.set(mPadding); + } + return (padding.left | padding.top | padding.right | padding.bottom) != 0; } /** @@ -237,7 +252,12 @@ public class NinePatchDrawable extends Drawable { */ @Override public Insets getOpticalInsets() { - return mOpticalInsets; + if (needsMirroring()) { + return Insets.of(mOpticalInsets.right, mOpticalInsets.top, mOpticalInsets.right, + mOpticalInsets.bottom); + } else { + return mOpticalInsets; + } } @Override @@ -251,6 +271,15 @@ public class NinePatchDrawable extends Drawable { } @Override + public int getAlpha() { + if (mPaint == null) { + // Fast common case -- normal alpha. + return 0xFF; + } + return getPaint().getAlpha(); + } + + @Override public void setColorFilter(ColorFilter cf) { if (mPaint == null && cf == null) { // Fast common case -- leave at no color filter. @@ -262,6 +291,7 @@ public class NinePatchDrawable extends Drawable { @Override public void setDither(boolean dither) { + //noinspection PointlessBooleanExpression if (mPaint == null && dither == DEFAULT_DITHER) { // Fast common case -- leave at default dither. return; @@ -271,6 +301,20 @@ public class NinePatchDrawable extends Drawable { } @Override + public void setAutoMirrored(boolean mirrored) { + mNinePatchState.mAutoMirrored = mirrored; + } + + private boolean needsMirroring() { + return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; + } + + @Override + public boolean isAutoMirrored() { + return mNinePatchState.mAutoMirrored; + } + + @Override public void setFilterBitmap(boolean filter) { getPaint().setFilterBitmap(filter); invalidateSelf(); @@ -290,8 +334,7 @@ public class NinePatchDrawable extends Drawable { } final boolean dither = a.getBoolean( - com.android.internal.R.styleable.NinePatchDrawable_dither, - DEFAULT_DITHER); + com.android.internal.R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); final BitmapFactory.Options options = new BitmapFactory.Options(); if (dither) { options.inDither = false; @@ -321,9 +364,11 @@ public class NinePatchDrawable extends Drawable { ": <nine-patch> requires a valid 9-patch source image"); } - setNinePatchState(new NinePatchState( - new NinePatch(bitmap, bitmap.getNinePatchChunk(), "XML 9-patch"), - padding, opticalInsets, dither), r); + final boolean automirrored = a.getBoolean( + com.android.internal.R.styleable.NinePatchDrawable_autoMirrored, false); + + setNinePatchState(new NinePatchState(new NinePatch(bitmap, bitmap.getNinePatchChunk()), + padding, opticalInsets, dither, automirrored), r); mNinePatchState.mTargetDensity = mTargetDensity; a.recycle(); @@ -394,39 +439,49 @@ public class NinePatchDrawable extends Drawable { return this; } - private final static class NinePatchState extends ConstantState { + final static class NinePatchState extends ConstantState { final NinePatch mNinePatch; final Rect mPadding; final Insets mOpticalInsets; final boolean mDither; int mChangingConfigurations; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; + boolean mAutoMirrored; NinePatchState(NinePatch ninePatch, Rect padding) { - this(ninePatch, padding, new Rect(), DEFAULT_DITHER); + this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false); } NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) { - this(ninePatch, padding, opticalInsets, DEFAULT_DITHER); + this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false); } - NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither) { + NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither, + boolean autoMirror) { mNinePatch = ninePatch; mPadding = rect; mOpticalInsets = Insets.of(opticalInsets); mDither = dither; + mAutoMirrored = autoMirror; } // Copy constructor NinePatchState(NinePatchState state) { - mNinePatch = new NinePatch(state.mNinePatch); + // Note we don't copy the nine patch because it is immutable. + mNinePatch = state.mNinePatch; // Note we don't copy the padding because it is immutable. mPadding = state.mPadding; mOpticalInsets = state.mOpticalInsets; mDither = state.mDither; mChangingConfigurations = state.mChangingConfigurations; mTargetDensity = state.mTargetDensity; + mAutoMirrored = state.mAutoMirrored; + } + + @Override + public Bitmap getBitmap() { + return mNinePatch.getBitmap(); } @Override diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 83d9581..aec3a4b 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -108,6 +108,11 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { mState.mDrawable.setAlpha(alpha); } + @Override + public int getAlpha() { + return mState.mDrawable.getAlpha(); + } + public void setColorFilter(ColorFilter cf) { mState.mDrawable.setColorFilter(cf); } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index 0664214..ec6b2c1 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -177,6 +177,11 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } @Override + public int getAlpha() { + return mScaleState.mDrawable.getAlpha(); + } + + @Override public void setColorFilter(ColorFilter cf) { mScaleState.mDrawable.setColorFilter(cf); } diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 1dbcddb..93f2dc6 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -252,7 +252,12 @@ public class ShapeDrawable extends Drawable { mShapeState.mAlpha = alpha; invalidateSelf(); } - + + @Override + public int getAlpha() { + return mShapeState.mAlpha; + } + @Override public void setColorFilter(ColorFilter cf) { mShapeState.mPaint.setColorFilter(cf); diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index f8f3ac9..48d66b7 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -132,6 +132,9 @@ public class StateListDrawable extends DrawableContainer { setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither, DEFAULT_DITHER)); + setAutoMirrored(a.getBoolean( + com.android.internal.R.styleable.StateListDrawable_autoMirrored, false)); + a.recycle(); int type; @@ -228,7 +231,7 @@ public class StateListDrawable extends DrawableContainer { * @see #getStateSet(int) */ public Drawable getStateDrawable(int index) { - return mStateListState.getChildren()[index]; + return mStateListState.getChild(index); } /** @@ -264,11 +267,11 @@ public class StateListDrawable extends DrawableContainer { /** @hide */ @Override public void setLayoutDirection(int layoutDirection) { - final int numStates = getStateCount(); - for (int i = 0; i < numStates; i++) { - getStateDrawable(i).setLayoutDirection(layoutDirection); - } super.setLayoutDirection(layoutDirection); + + // Let the container handle setting its own layout direction. Otherwise, + // we're accessing potentially unused states. + mStateListState.setLayoutDirection(layoutDirection); } static final class StateListState extends DrawableContainerState { @@ -278,9 +281,9 @@ public class StateListDrawable extends DrawableContainer { super(orig, owner, res); if (orig != null) { - mStateSets = orig.mStateSets; + mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length); } else { - mStateSets = new int[getChildren().length][]; + mStateSets = new int[getCapacity()][]; } } diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java new file mode 100644 index 0000000..29d14a2 --- /dev/null +++ b/graphics/java/android/graphics/pdf/PdfDocument.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2013 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.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + +import dalvik.system.CloseGuard; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +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, + * 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 + * created one by one, i.e. you can have only a single page to which you are + * writing at any given time. This class is not thread safe. + * </p> + * <p> + * A typical use of the APIs looks like this: + * </p> + * <pre> + * // create a new document + * PdfDocument document = new PdfDocument(); + * + * // crate a page description + * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1).create(); + * + * // start a page + * Page page = document.startPage(pageInfo); + * + * // draw something on the page + * View content = getContentView(); + * content.draw(page.getCanvas()); + * + * // finish the page + * document.finishPage(page); + * . . . + * // add more pages + * . . . + * // write the document content + * document.writeTo(getOutputStream()); + * + * //close the document + * document.close(); + * </pre> + */ +public class PdfDocument { + + // TODO: We need a constructor that will take an OutputStream to + // support online data serialization as opposed to the current + // on demand one. The current approach is fine until Skia starts + // to support online PDF generation at which point we need to + // handle this. + + private final byte[] mChunk = new byte[4096]; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private final List<PageInfo> mPages = new ArrayList<PageInfo>(); + + private int mNativeDocument; + + private Page mCurrentPage; + + /** + * Creates a new instance. + */ + public PdfDocument() { + mNativeDocument = nativeCreateDocument(); + mCloseGuard.open("close"); + } + + /** + * Starts a page using the provided {@link PageInfo}. After the page + * is created you can draw arbitrary content on the page's canvas which + * you can get by calling {@link Page#getCanvas()}. After you are done + * drawing the content you should finish the page by calling + * {@link #finishPage(Page)}. After the page is finished you should + * no longer access the page or its canvas. + * <p> + * <strong>Note:</strong> Do not call this method after {@link #close()}. + * Also do not call this method if the last page returned by this method + * is not finished by calling {@link #finishPage(Page)}. + * </p> + * + * @param pageInfo The page info. Cannot be null. + * @return A blank page. + * + * @see #finishPage(Page) + */ + public Page startPage(PageInfo pageInfo) { + throwIfClosed(); + throwIfCurrentPageNotFinished(); + if (pageInfo == null) { + throw new IllegalArgumentException("page cannot be null"); + } + Canvas canvas = new PdfCanvas(nativeStartPage(mNativeDocument, pageInfo.mPageWidth, + pageInfo.mPageHeight, pageInfo.mContentRect.left, pageInfo.mContentRect.top, + pageInfo.mContentRect.right, pageInfo.mContentRect.bottom)); + mCurrentPage = new Page(canvas, pageInfo); + return mCurrentPage; + } + + /** + * Finishes a started page. You should always finish the last started page. + * <p> + * <strong>Note:</strong> Do not call this method after {@link #close()}. + * You should not finish the same page more than once. + * </p> + * + * @param page The page. Cannot be null. + * + * @see #startPage(PageInfo) + */ + public void finishPage(Page page) { + throwIfClosed(); + if (page == null) { + throw new IllegalArgumentException("page cannot be null"); + } + if (page != mCurrentPage) { + throw new IllegalStateException("invalid page"); + } + if (page.isFinished()) { + throw new IllegalStateException("page already finished"); + } + mPages.add(page.getInfo()); + mCurrentPage = null; + nativeFinishPage(mNativeDocument); + page.finish(); + } + + /** + * Writes the document to an output stream. You can call this method + * multiple times. + * <p> + * <strong>Note:</strong> Do not call this method after {@link #close()}. + * Also do not call this method if a page returned by {@link #startPage( + * PageInfo)} is not finished by calling {@link #finishPage(Page)}. + * </p> + * + * @param out The output stream. Cannot be null. + * + * @throws IOException If an error occurs while writing. + */ + public void writeTo(OutputStream out) throws IOException { + throwIfClosed(); + throwIfCurrentPageNotFinished(); + if (out == null) { + throw new IllegalArgumentException("out cannot be null!"); + } + nativeWriteTo(mNativeDocument, out, mChunk); + } + + /** + * Gets the pages of the document. + * + * @return The pages or an empty list. + */ + public List<PageInfo> getPages() { + return Collections.unmodifiableList(mPages); + } + + /** + * Closes this document. This method should be called after you + * are done working with the document. After this call the document + * is considered closed and none of its methods should be called. + * <p> + * <strong>Note:</strong> Do not call this method if the page + * returned by {@link #startPage(PageInfo)} is not finished by + * calling {@link #finishPage(Page)}. + * </p> + */ + public void close() { + throwIfCurrentPageNotFinished(); + dispose(); + } + + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + dispose(); + } finally { + super.finalize(); + } + } + + private void dispose() { + if (mNativeDocument != 0) { + nativeClose(mNativeDocument); + mCloseGuard.close(); + mNativeDocument = 0; + } + } + + /** + * Throws an exception if the document is already closed. + */ + private void throwIfClosed() { + if (mNativeDocument == 0) { + throw new IllegalStateException("document is closed!"); + } + } + + /** + * Throws an exception if the last started page is not finished. + */ + private void throwIfCurrentPageNotFinished() { + if (mCurrentPage != null) { + throw new IllegalStateException("Current page not finished!"); + } + } + + private native int nativeCreateDocument(); + + private native void nativeClose(int document); + + private native void nativeFinishPage(int document); + + private native void nativeWriteTo(int document, OutputStream out, byte[] chunk); + + private static native int nativeStartPage(int documentPtr, int pageWidth, int pageHeight, + int contentLeft, int contentTop, int contentRight, int contentBottom); + + private final class PdfCanvas extends Canvas { + + public PdfCanvas(int nativeCanvas) { + super(nativeCanvas); + } + + @Override + public void setBitmap(Bitmap bitmap) { + throw new UnsupportedOperationException(); + } + } + + /** + * This class represents meta-data that describes a PDF {@link Page}. + */ + public static final class PageInfo { + private int mPageWidth; + private int mPageHeight; + private Rect mContentRect; + private int mPageNumber; + + /** + * Creates a new instance. + */ + private PageInfo() { + /* do nothing */ + } + + /** + * Gets the page width in PostScript points (1/72th of an inch). + * + * @return The page width. + */ + public int getPageWidth() { + return mPageWidth; + } + + /** + * Gets the page height in PostScript points (1/72th of an inch). + * + * @return The page height. + */ + public int getPageHeight() { + return mPageHeight; + } + + /** + * Get the content rectangle in PostScript points (1/72th of an inch). + * This is the area that contains the page content and is relative to + * the page top left. + * + * @return The content rectangle. + */ + public Rect getContentRect() { + return mContentRect; + } + + /** + * Gets the page number. + * + * @return The page number. + */ + public int getPageNumber() { + return mPageNumber; + } + + /** + * Builder for creating a {@link PageInfo}. + */ + public static final class Builder { + private final PageInfo mPageInfo = new PageInfo(); + + /** + * Creates a new builder with the mandatory page info attributes. + * + * @param pageWidth The page width in PostScript (1/72th of an inch). + * @param pageHeight The page height in PostScript (1/72th of an inch). + * @param pageNumber The page number. + */ + public Builder(int pageWidth, int pageHeight, int pageNumber) { + if (pageWidth <= 0) { + throw new IllegalArgumentException("page width must be positive"); + } + if (pageHeight <= 0) { + throw new IllegalArgumentException("page width must be positive"); + } + if (pageNumber < 0) { + throw new IllegalArgumentException("pageNumber must be non negative"); + } + mPageInfo.mPageWidth = pageWidth; + mPageInfo.mPageHeight = pageHeight; + mPageInfo.mPageNumber = pageNumber; + } + + /** + * Sets the content rectangle in PostScript point (1/72th of an inch). + * This is the area that contains the page content and is relative to + * the page top left. + * + * @param contentRect The content rectangle. Must fit in the page. + */ + public Builder setContentRect(Rect contentRect) { + if (contentRect != null && (contentRect.left < 0 + || contentRect.top < 0 + || contentRect.right > mPageInfo.mPageWidth + || contentRect.bottom > mPageInfo.mPageHeight)) { + throw new IllegalArgumentException("contentRect does not fit the page"); + } + mPageInfo.mContentRect = contentRect; + return this; + } + + /** + * Creates a new {@link PageInfo}. + * + * @return The new instance. + */ + public PageInfo create() { + if (mPageInfo.mContentRect == null) { + mPageInfo.mContentRect = new Rect(0, 0, + mPageInfo.mPageWidth, mPageInfo.mPageHeight); + } + return mPageInfo; + } + } + } + + /** + * This class represents a PDF document page. It has associated + * a canvas on which you can draw content and is acquired by a + * call to {@link #getCanvas()}. It also has associated a + * {@link PageInfo} instance that describes its attributes. Also + * a page has + */ + public static final class Page { + private final PageInfo mPageInfo; + private Canvas mCanvas; + + /** + * Creates a new instance. + * + * @param canvas The canvas of the page. + * @param pageInfo The info with meta-data. + */ + private Page(Canvas canvas, PageInfo pageInfo) { + mCanvas = canvas; + mPageInfo = pageInfo; + } + + /** + * Gets the {@link Canvas} of the page. + * + * <p> + * <strong>Note: </strong> There are some draw operations that are not yet + * supported by the canvas returned by this method. More specifically: + * <ul> + * <li>Inverse path clipping performed via {@link Canvas#clipPath(android.graphics.Path, + * android.graphics.Region.Op) Canvas.clipPath(android.graphics.Path, + * android.graphics.Region.Op)} for {@link + * android.graphics.Region.Op#REVERSE_DIFFERENCE + * Region.Op#REVERSE_DIFFERENCE} operations.</li> + * <li>{@link Canvas#drawVertices(android.graphics.Canvas.VertexMode, int, + * float[], int, float[], int, int[], int, short[], int, int, + * android.graphics.Paint) Canvas.drawVertices( + * android.graphics.Canvas.VertexMode, int, float[], int, float[], + * int, int[], int, short[], int, int, android.graphics.Paint)}</li> + * <li>Color filters set via {@link Paint#setColorFilter( + * android.graphics.ColorFilter)}</li> + * <li>Mask filters set via {@link Paint#setMaskFilter( + * android.graphics.MaskFilter)}</li> + * <li>Some XFER modes such as + * {@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC}, + * {@link android.graphics.PorterDuff.Mode#DST_ATOP PorterDuff.DST_ATOP}, + * {@link android.graphics.PorterDuff.Mode#XOR PorterDuff.XOR}, + * {@link android.graphics.PorterDuff.Mode#ADD PorterDuff.ADD}</li> + * </ul> + * + * @return The canvas if the page is not finished, null otherwise. + * + * @see PdfDocument#finishPage(Page) + */ + public Canvas getCanvas() { + return mCanvas; + } + + /** + * Gets the {@link PageInfo} with meta-data for the page. + * + * @return The page info. + * + * @see PdfDocument#finishPage(Page) + */ + public PageInfo getInfo() { + return mPageInfo; + } + + boolean isFinished() { + return mCanvas == null; + } + + private void finish() { + if (mCanvas != null) { + mCanvas.release(); + mCanvas = null; + } + } + } +} diff --git a/graphics/java/android/graphics/pdf/package.html b/graphics/java/android/graphics/pdf/package.html new file mode 100644 index 0000000..51f2460 --- /dev/null +++ b/graphics/java/android/graphics/pdf/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Contains classes for manipulation of PDF content. +</BODY> +</HTML>
\ No newline at end of file diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index b460a4d..dca934f 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -28,6 +28,7 @@ import android.graphics.SurfaceTexture; import android.util.Log; import android.util.TypedValue; import android.graphics.Canvas; +import android.os.Trace; /** * <p> This class provides the primary method through which data is passed to @@ -60,6 +61,7 @@ public class Allocation extends BaseObj { Bitmap mBitmap; int mUsage; Allocation mAdaptedAllocation; + int mSize; boolean mConstrainedLOD; boolean mConstrainedFace; @@ -78,7 +80,7 @@ public class Allocation extends BaseObj { int mCurrentCount; static HashMap<Integer, Allocation> mAllocationMap = new HashMap<Integer, Allocation>(); - IoInputNotifier mBufferNotifier; + OnBufferAvailableListener mBufferNotifier; /** * The usage of the Allocation. These signal to RenderScript where to place @@ -269,8 +271,23 @@ public class Allocation extends BaseObj { mUsage = usage; if (t != null) { + // TODO: A3D doesn't have Type info during creation, so we can't + // calculate the size ahead of time. We can possibly add a method + // to update the size in the future if it seems reasonable. + mSize = mType.getCount() * mType.getElement().getBytesSize(); updateCacheInfo(t); } + try { + RenderScript.registerNativeAllocation.invoke(RenderScript.sRuntime, mSize); + } catch (Exception e) { + Log.e(RenderScript.LOG_TAG, "Couldn't invoke registerNativeAllocation:" + e); + throw new RSRuntimeException("Couldn't invoke registerNativeAllocation:" + e); + } + } + + protected void finalize() throws Throwable { + RenderScript.registerNativeFree.invoke(RenderScript.sRuntime, mSize); + super.finalize(); } private void validateIsInt32() { @@ -352,6 +369,7 @@ public class Allocation extends BaseObj { * */ public void syncAll(int srcLocation) { + Trace.traceBegin(RenderScript.TRACE_TAG, "syncAll"); switch (srcLocation) { case USAGE_GRAPHICS_TEXTURE: case USAGE_SCRIPT: @@ -372,6 +390,7 @@ public class Allocation extends BaseObj { } mRS.validate(); mRS.nAllocationSyncAll(getIDSafe(), srcLocation); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -382,12 +401,14 @@ public class Allocation extends BaseObj { * */ public void ioSend() { + Trace.traceBegin(RenderScript.TRACE_TAG, "ioSend"); if ((mUsage & USAGE_IO_OUTPUT) == 0) { throw new RSIllegalArgumentException( "Can only send buffer if IO_OUTPUT usage specified."); } mRS.validate(); mRS.nAllocationIoSend(getID(mRS)); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -404,12 +425,14 @@ public class Allocation extends BaseObj { * */ public void ioReceive() { + Trace.traceBegin(RenderScript.TRACE_TAG, "ioReceive"); if ((mUsage & USAGE_IO_INPUT) == 0) { throw new RSIllegalArgumentException( "Can only receive if IO_INPUT usage specified."); } mRS.validate(); mRS.nAllocationIoReceive(getID(mRS)); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -418,6 +441,7 @@ public class Allocation extends BaseObj { * @param d Source array. */ public void copyFrom(BaseObj[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); validateIsObject(); if (d.length != mCurrentCount) { @@ -429,6 +453,7 @@ public class Allocation extends BaseObj { i[ct] = d[ct].getID(mRS); } copy1DRangeFromUnchecked(0, mCurrentCount, i); + Trace.traceEnd(RenderScript.TRACE_TAG); } private void validateBitmapFormat(Bitmap b) { @@ -494,6 +519,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFromUnchecked(int[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -502,7 +528,9 @@ public class Allocation extends BaseObj { } else { copy1DRangeFromUnchecked(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy into this Allocation from an array. This method does not guarantee * that the Allocation is compatible with the input buffer; it copies memory @@ -511,6 +539,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFromUnchecked(short[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -519,7 +548,9 @@ public class Allocation extends BaseObj { } else { copy1DRangeFromUnchecked(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy into this Allocation from an array. This method does not guarantee * that the Allocation is compatible with the input buffer; it copies memory @@ -528,6 +559,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFromUnchecked(byte[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -536,7 +568,9 @@ public class Allocation extends BaseObj { } else { copy1DRangeFromUnchecked(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy into this Allocation from an array. This method does not guarantee * that the Allocation is compatible with the input buffer; it copies memory @@ -545,6 +579,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFromUnchecked(float[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -553,8 +588,10 @@ public class Allocation extends BaseObj { } else { copy1DRangeFromUnchecked(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy into this Allocation from an array. This variant is type checked * and will generate exceptions if the Allocation's {@link @@ -563,6 +600,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFrom(int[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -571,6 +609,7 @@ public class Allocation extends BaseObj { } else { copy1DRangeFrom(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -581,6 +620,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFrom(short[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -589,6 +629,7 @@ public class Allocation extends BaseObj { } else { copy1DRangeFrom(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -599,6 +640,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFrom(byte[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -607,6 +649,7 @@ public class Allocation extends BaseObj { } else { copy1DRangeFrom(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -617,6 +660,7 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copyFrom(float[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); if (mCurrentDimZ > 0) { copy3DRangeFrom(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, d); @@ -625,6 +669,7 @@ public class Allocation extends BaseObj { } else { copy1DRangeFrom(0, mCurrentCount, d); } + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -641,6 +686,7 @@ public class Allocation extends BaseObj { * @param b the source bitmap */ public void copyFrom(Bitmap b) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); if (b.getConfig() == null) { Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); @@ -652,6 +698,7 @@ public class Allocation extends BaseObj { validateBitmapSize(b); validateBitmapFormat(b); mRS.nAllocationCopyFromBitmap(getID(mRS), b); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -661,14 +708,15 @@ public class Allocation extends BaseObj { * @param a the source allocation */ public void copyFrom(Allocation a) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); mRS.validate(); if (!mType.equals(a.getType())) { throw new RSIllegalArgumentException("Types of allocations must match."); } copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, a, 0, 0); + Trace.traceEnd(RenderScript.TRACE_TAG); } - /** * This is only intended to be used by auto-generated code reflected from * the RenderScript script files and should not be used by developers. @@ -759,10 +807,13 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFromUnchecked(int off, int count, int[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFromUnchecked"); int dataSize = mType.mElement.getBytesSize() * count; data1DChecks(off, count, d.length * 4, dataSize); mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize); + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy an array into part of this Allocation. This method does not * guarantee that the Allocation is compatible with the input buffer. @@ -772,10 +823,13 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFromUnchecked(int off, int count, short[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFromUnchecked"); int dataSize = mType.mElement.getBytesSize() * count; data1DChecks(off, count, d.length * 2, dataSize); mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize); + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy an array into part of this Allocation. This method does not * guarantee that the Allocation is compatible with the input buffer. @@ -785,10 +839,13 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFromUnchecked(int off, int count, byte[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFromUnchecked"); int dataSize = mType.mElement.getBytesSize() * count; data1DChecks(off, count, d.length, dataSize); mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize); + Trace.traceEnd(RenderScript.TRACE_TAG); } + /** * Copy an array into part of this Allocation. This method does not * guarantee that the Allocation is compatible with the input buffer. @@ -798,9 +855,11 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFromUnchecked(int off, int count, float[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFromUnchecked"); int dataSize = mType.mElement.getBytesSize() * count; data1DChecks(off, count, d.length * 4, dataSize); mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -813,8 +872,10 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFrom(int off, int count, int[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFrom"); validateIsInt32(); copy1DRangeFromUnchecked(off, count, d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -827,8 +888,10 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFrom(int off, int count, short[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFrom"); validateIsInt16(); copy1DRangeFromUnchecked(off, count, d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -841,8 +904,10 @@ public class Allocation extends BaseObj { * @param d the source data array */ public void copy1DRangeFrom(int off, int count, byte[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFrom"); validateIsInt8(); copy1DRangeFromUnchecked(off, count, d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -855,10 +920,11 @@ public class Allocation extends BaseObj { * @param d the source data array. */ public void copy1DRangeFrom(int off, int count, float[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFrom"); validateIsFloat32(); copy1DRangeFromUnchecked(off, count, d); + Trace.traceEnd(RenderScript.TRACE_TAG); } - /** * Copy part of an Allocation into this Allocation. * @@ -869,6 +935,7 @@ public class Allocation extends BaseObj { * be copied. */ public void copy1DRangeFrom(int off, int count, Allocation data, int dataOff) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFrom"); mRS.nAllocationData2D(getIDSafe(), off, 0, mSelectedLOD, mSelectedFace.mID, count, 1, data.getID(mRS), dataOff, 0, @@ -893,34 +960,41 @@ public class Allocation extends BaseObj { } void copy2DRangeFromUnchecked(int xoff, int yoff, int w, int h, byte[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFromUnchecked"); mRS.validate(); validate2DRange(xoff, yoff, w, h); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, data, data.length); + Trace.traceEnd(RenderScript.TRACE_TAG); } void copy2DRangeFromUnchecked(int xoff, int yoff, int w, int h, short[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFromUnchecked"); mRS.validate(); validate2DRange(xoff, yoff, w, h); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, data, data.length * 2); + Trace.traceEnd(RenderScript.TRACE_TAG); } void copy2DRangeFromUnchecked(int xoff, int yoff, int w, int h, int[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFromUnchecked"); mRS.validate(); validate2DRange(xoff, yoff, w, h); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, data, data.length * 4); + Trace.traceEnd(RenderScript.TRACE_TAG); } void copy2DRangeFromUnchecked(int xoff, int yoff, int w, int h, float[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFromUnchecked"); mRS.validate(); validate2DRange(xoff, yoff, w, h); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, data, data.length * 4); + Trace.traceEnd(RenderScript.TRACE_TAG); } - /** * Copy from an array into a rectangular region in this Allocation. The * array is assumed to be tightly packed. @@ -932,8 +1006,10 @@ public class Allocation extends BaseObj { * @param data to be placed into the Allocation */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, byte[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); validateIsInt8(); copy2DRangeFromUnchecked(xoff, yoff, w, h, data); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -947,8 +1023,10 @@ public class Allocation extends BaseObj { * @param data to be placed into the Allocation */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, short[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); validateIsInt16(); copy2DRangeFromUnchecked(xoff, yoff, w, h, data); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -962,8 +1040,10 @@ public class Allocation extends BaseObj { * @param data to be placed into the Allocation */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, int[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); validateIsInt32(); copy2DRangeFromUnchecked(xoff, yoff, w, h, data); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -977,8 +1057,10 @@ public class Allocation extends BaseObj { * @param data to be placed into the Allocation */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, float[] data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); validateIsFloat32(); copy2DRangeFromUnchecked(xoff, yoff, w, h, data); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -995,12 +1077,14 @@ public class Allocation extends BaseObj { */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, Allocation data, int dataXoff, int dataYoff) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); mRS.validate(); validate2DRange(xoff, yoff, w, h); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, data.getID(mRS), dataXoff, dataYoff, data.mSelectedLOD, data.mSelectedFace.mID); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1013,6 +1097,7 @@ public class Allocation extends BaseObj { * @param data the Bitmap to be copied */ public void copy2DRangeFrom(int xoff, int yoff, Bitmap data) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); mRS.validate(); if (data.getConfig() == null) { Bitmap newBitmap = Bitmap.createBitmap(data.getWidth(), data.getHeight(), Bitmap.Config.ARGB_8888); @@ -1024,6 +1109,7 @@ public class Allocation extends BaseObj { validateBitmapFormat(data); validate2DRange(xoff, yoff, data.getWidth(), data.getHeight()); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, data); + Trace.traceEnd(RenderScript.TRACE_TAG); } private void validate3DRange(int xoff, int yoff, int zoff, int w, int h, int d) { @@ -1166,10 +1252,12 @@ public class Allocation extends BaseObj { * @param b The bitmap to be set from the Allocation. */ public void copyTo(Bitmap b) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); mRS.validate(); validateBitmapFormat(b); validateBitmapSize(b); mRS.nAllocationCopyToBitmap(getID(mRS), b); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1180,9 +1268,11 @@ public class Allocation extends BaseObj { * @param d The array to be set from the Allocation. */ public void copyTo(byte[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); validateIsInt8(); mRS.validate(); mRS.nAllocationRead(getID(mRS), d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1193,9 +1283,11 @@ public class Allocation extends BaseObj { * @param d The array to be set from the Allocation. */ public void copyTo(short[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); validateIsInt16(); mRS.validate(); mRS.nAllocationRead(getID(mRS), d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1206,9 +1298,11 @@ public class Allocation extends BaseObj { * @param d The array to be set from the Allocation. */ public void copyTo(int[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); validateIsInt32(); mRS.validate(); mRS.nAllocationRead(getID(mRS), d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1219,9 +1313,11 @@ public class Allocation extends BaseObj { * @param d The array to be set from the Allocation. */ public void copyTo(float[] d) { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); validateIsFloat32(); mRS.validate(); mRS.nAllocationRead(getID(mRS), d); + Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1271,6 +1367,7 @@ public class Allocation extends BaseObj { * utilized */ static public Allocation createTyped(RenderScript rs, Type type, MipmapControl mips, int usage) { + Trace.traceBegin(RenderScript.TRACE_TAG, "createTyped"); rs.validate(); if (type.getID(rs) == 0) { throw new RSInvalidStateException("Bad Type"); @@ -1279,6 +1376,7 @@ public class Allocation extends BaseObj { if (id == 0) { throw new RSRuntimeException("Allocation creation failed."); } + Trace.traceEnd(RenderScript.TRACE_TAG); return new Allocation(id, rs, type, usage); } @@ -1323,6 +1421,7 @@ public class Allocation extends BaseObj { */ static public Allocation createSized(RenderScript rs, Element e, int count, int usage) { + Trace.traceBegin(RenderScript.TRACE_TAG, "createSized"); rs.validate(); Type.Builder b = new Type.Builder(rs, e); b.setX(count); @@ -1332,6 +1431,7 @@ public class Allocation extends BaseObj { if (id == 0) { throw new RSRuntimeException("Allocation creation failed."); } + Trace.traceEnd(RenderScript.TRACE_TAG); return new Allocation(id, rs, t, usage); } @@ -1391,6 +1491,7 @@ public class Allocation extends BaseObj { static public Allocation createFromBitmap(RenderScript rs, Bitmap b, MipmapControl mips, int usage) { + Trace.traceBegin(RenderScript.TRACE_TAG, "createFromBitmap"); rs.validate(); // WAR undocumented color formats @@ -1426,6 +1527,7 @@ public class Allocation extends BaseObj { if (id == 0) { throw new RSRuntimeException("Load failed."); } + Trace.traceEnd(RenderScript.TRACE_TAG); return new Allocation(id, rs, t, usage); } @@ -1739,26 +1841,22 @@ public class Allocation extends BaseObj { } /** - * @hide - * * Interface to handle notification when new buffers are available via * {@link #USAGE_IO_INPUT}. An application will receive one notification * when a buffer is available. Additional buffers will not trigger new * notifications until a buffer is processed. */ - public interface IoInputNotifier { + public interface OnBufferAvailableListener { public void onBufferAvailable(Allocation a); } /** - * @hide - * * Set a notification handler for {@link #USAGE_IO_INPUT}. * - * @param callback instance of the IoInputNotifier class to be called - * when buffer arrive. + * @param callback instance of the OnBufferAvailableListener + * class to be called when buffer arrive. */ - public void setIoInputNotificationHandler(IoInputNotifier callback) { + public void setOnBufferAvailableListener(OnBufferAvailableListener callback) { synchronized(mAllocationMap) { mAllocationMap.put(new Integer(getID(mRS)), this); mBufferNotifier = callback; diff --git a/graphics/java/android/renderscript/AllocationAdapter.java b/graphics/java/android/renderscript/AllocationAdapter.java index a6645bb..84a9ad3 100644 --- a/graphics/java/android/renderscript/AllocationAdapter.java +++ b/graphics/java/android/renderscript/AllocationAdapter.java @@ -224,7 +224,6 @@ public class AllocationAdapter extends Allocation { } static public AllocationAdapter create2D(RenderScript rs, Allocation a) { - android.util.Log.e("rs", "create2d " + a); rs.validate(); AllocationAdapter aa = new AllocationAdapter(0, rs, a); aa.mConstrainedLOD = true; diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java index e17d79a..fc54532 100644 --- a/graphics/java/android/renderscript/BaseObj.java +++ b/graphics/java/android/renderscript/BaseObj.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.util.Log; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * BaseObj is the base class for all RenderScript objects owned by a RS context. @@ -109,17 +109,31 @@ public class BaseObj { return mName; } - protected void finalize() throws Throwable { - if (!mDestroyed) { - if(mID != 0 && mRS.isAlive()) { + private void helpDestroy() { + boolean shouldDestroy = false; + synchronized(this) { + if (!mDestroyed) { + shouldDestroy = true; + mDestroyed = true; + } + } + + if (shouldDestroy) { + // must include nObjDestroy in the critical section + ReentrantReadWriteLock.ReadLock rlock = mRS.mRWLock.readLock(); + rlock.lock(); + // AllocationAdapters are BaseObjs with an ID of 0 but should not be passed to nObjDestroy + if(mRS.isAlive() && mID != 0) { mRS.nObjDestroy(mID); } + rlock.unlock(); mRS = null; mID = 0; - mDestroyed = true; - //Log.v(RenderScript.LOG_TAG, getClass() + - // " auto finalizing object without having released the RS reference."); } + } + + protected void finalize() throws Throwable { + helpDestroy(); super.finalize(); } @@ -128,12 +142,11 @@ public class BaseObj { * primary use is to force immediate cleanup of resources when it is * believed the GC will not respond quickly enough. */ - synchronized public void destroy() { + public void destroy() { if(mDestroyed) { throw new RSInvalidStateException("Object already destroyed."); } - mDestroyed = true; - mRS.nObjDestroy(mID); + helpDestroy(); } /** diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java index 3838c61..68badfa 100644 --- a/graphics/java/android/renderscript/Element.java +++ b/graphics/java/android/renderscript/Element.java @@ -725,6 +725,13 @@ public class Element extends BaseObj { return rs.mElement_LONG_4; } + public static Element YUV(RenderScript rs) { + if (rs.mElement_YUV == null) { + rs.mElement_YUV = createPixel(rs, DataType.UNSIGNED_8, DataKind.PIXEL_YUV); + } + return rs.mElement_YUV; + } + public static Element MATRIX_4X4(RenderScript rs) { if(rs.mElement_MATRIX_4X4 == null) { rs.mElement_MATRIX_4X4 = createUser(rs, DataType.MATRIX_4X4); diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 1264adc..35fe8c4 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -18,6 +18,8 @@ package android.renderscript; import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.locks.ReentrantReadWriteLock; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -29,8 +31,8 @@ import android.graphics.SurfaceTexture; import android.os.Process; import android.util.Log; import android.view.Surface; - - +import android.os.SystemProperties; +import android.os.Trace; /** * This class provides access to a RenderScript context, which controls RenderScript @@ -44,6 +46,8 @@ import android.view.Surface; * </div> **/ public class RenderScript { + static final long TRACE_TAG = Trace.TRACE_TAG_RS; + static final String LOG_TAG = "RenderScript_jni"; static final boolean DEBUG = false; @SuppressWarnings({"UnusedDeclaration", "deprecation"}) @@ -55,20 +59,35 @@ public class RenderScript { * We use a class initializer to allow the native code to cache some * field offsets. */ - @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // TODO: now used locally; remove? static boolean sInitialized; native static void _nInit(); + static Object sRuntime; + static Method registerNativeAllocation; + static Method registerNativeFree; static { sInitialized = false; - try { - System.loadLibrary("rs_jni"); - _nInit(); - sInitialized = true; - } catch (UnsatisfiedLinkError e) { - Log.e(LOG_TAG, "Error loading RS jni library: " + e); - throw new RSRuntimeException("Error loading RS jni library: " + e); + if (!SystemProperties.getBoolean("config.disable_renderscript", false)) { + try { + Class<?> vm_runtime = Class.forName("dalvik.system.VMRuntime"); + Method get_runtime = vm_runtime.getDeclaredMethod("getRuntime"); + sRuntime = get_runtime.invoke(null); + registerNativeAllocation = vm_runtime.getDeclaredMethod("registerNativeAllocation", Integer.TYPE); + registerNativeFree = vm_runtime.getDeclaredMethod("registerNativeFree", Integer.TYPE); + } catch (Exception e) { + Log.e(LOG_TAG, "Error loading GC methods: " + e); + throw new RSRuntimeException("Error loading GC methods: " + e); + } + try { + System.loadLibrary("rs_jni"); + _nInit(); + sInitialized = true; + } catch (UnsatisfiedLinkError e) { + Log.e(LOG_TAG, "Error loading RS jni library: " + e); + throw new RSRuntimeException("Error loading RS jni library: " + e); + } } } @@ -84,6 +103,20 @@ public class RenderScript { static File mCacheDir; + // this should be a monotonically increasing ID + // used in conjunction with the API version of a device + static final long sMinorID = 1; + + /** + * Returns an identifier that can be used to identify a particular + * minor version of RS. + * + * @hide + */ + public static long getMinorID() { + return sMinorID; + } + /** * Sets the directory to use as a persistent storage for the * renderscript object file cache. @@ -92,6 +125,11 @@ public class RenderScript { * @param cacheDir A directory the current process can write to */ public static void setupDiskCache(File cacheDir) { + if (!sInitialized) { + Log.e(LOG_TAG, "RenderScript.setupDiskCache() called when disabled"); + return; + } + // Defer creation of cache path to nScriptCCreate(). mCacheDir = cacheDir; } @@ -128,6 +166,7 @@ public class RenderScript { } ContextType mContextType; + ReentrantReadWriteLock mRWLock; // Methods below are wrapped to protect the non-threadsafe // lockless fifo. @@ -155,7 +194,18 @@ public class RenderScript { native void rsnContextDestroy(int con); synchronized void nContextDestroy() { validate(); - rsnContextDestroy(mContext); + + // take teardown lock + // teardown lock can only be taken when no objects are being destroyed + ReentrantReadWriteLock.WriteLock wlock = mRWLock.writeLock(); + wlock.lock(); + + int curCon = mContext; + // context is considered dead as of this point + mContext = 0; + + wlock.unlock(); + rsnContextDestroy(curCon); } native void rsnContextSetSurface(int con, int w, int h, Surface sur); synchronized void nContextSetSurface(int w, int h, Surface sur) { @@ -240,8 +290,9 @@ public class RenderScript { validate(); return rsnGetName(mContext, obj); } + // nObjDestroy is explicitly _not_ synchronous to prevent crashes in finalizers native void rsnObjDestroy(int con, int id); - synchronized void nObjDestroy(int id) { + void nObjDestroy(int id) { // There is a race condition here. The calling code may be run // by the gc while teardown is occuring. This protects againts // deleting dead objects. @@ -875,6 +926,8 @@ public class RenderScript { Element mElement_LONG_3; Element mElement_LONG_4; + Element mElement_YUV; + Element mElement_MATRIX_4X4; Element mElement_MATRIX_3X3; Element mElement_MATRIX_2X2; @@ -1111,6 +1164,7 @@ public class RenderScript { if (ctx != null) { mApplicationContext = ctx.getApplicationContext(); } + mRWLock = new ReentrantReadWriteLock(); } /** @@ -1137,6 +1191,11 @@ public class RenderScript { * @return RenderScript */ public static RenderScript create(Context ctx, int sdkVersion, ContextType ct) { + if (!sInitialized) { + Log.e(LOG_TAG, "RenderScript.create() called when disabled; someone is likely to crash"); + return null; + } + RenderScript rs = new RenderScript(ctx); rs.mDev = rs.nDeviceCreate(); @@ -1200,6 +1259,8 @@ public class RenderScript { */ public void destroy() { validate(); + nContextFinish(); + nContextDeinitToClient(mContext); mMessageThread.mRun = false; try { @@ -1208,7 +1269,6 @@ public class RenderScript { } nContextDestroy(); - mContext = 0; nDeviceDestroy(mDev); mDev = 0; diff --git a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java index 77b9385..32c3d15 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java @@ -21,15 +21,27 @@ import android.util.Log; /** * Intrinsic for applying a color matrix to allocations. * - * This has the same effect as loading each element and - * converting it to a {@link Element#F32_4}, multiplying the - * result by the 4x4 color matrix as performed by - * rsMatrixMultiply() and writing it to the output after - * conversion back to {@link Element#U8_4}. + * If the element type is {@link Element.DataType#UNSIGNED_8}, + * it is converted to {@link Element.DataType#FLOAT_32} and + * normalized from (0-255) to (0-1). If the incoming vector size + * is less than four, a {@link Element#F32_4} is created by + * filling the missing vector channels with zero. This value is + * then multiplied by the 4x4 color matrix as performed by + * rsMatrixMultiply(), adding a {@link Element#F32_4}, and then + * writing it to the output {@link Allocation}. + * + * If the ouptut type is unsigned, the value is normalized from + * (0-1) to (0-255) and converted. If the output vector size is + * less than four, the unused channels are discarded. + * + * Supported elements types are {@link Element#U8}, {@link + * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4}, + * {@link Element#F32}, {@link Element#F32_2}, {@link + * Element#F32_3}, and {@link Element#F32_4}. **/ public final class ScriptIntrinsicColorMatrix extends ScriptIntrinsic { private final Matrix4f mMatrix = new Matrix4f(); - private Allocation mInput; + private final Float4 mAdd = new Float4(); private ScriptIntrinsicColorMatrix(int id, RenderScript rs) { super(id, rs); @@ -39,18 +51,31 @@ public final class ScriptIntrinsicColorMatrix extends ScriptIntrinsic { * Create an intrinsic for applying a color matrix to an * allocation. * - * Supported elements types are {@link Element#U8_4} - * * @param rs The RenderScript context - * @param e Element type for intputs and outputs + * @param e Element type for inputs and outputs, As of API 19, + * this parameter is ignored. The Element type check is + * performed in the kernel launch. + * + * @deprecated Use the single argument version as Element is now + * ignored. * * @return ScriptIntrinsicColorMatrix */ + @Deprecated public static ScriptIntrinsicColorMatrix create(RenderScript rs, Element e) { - if (!e.isCompatible(Element.U8_4(rs))) { - throw new RSIllegalArgumentException("Unsuported element type."); - } - int id = rs.nScriptIntrinsicCreate(2, e.getID(rs)); + return create(rs); + } + + /** + * Create an intrinsic for applying a color matrix to an + * allocation. + * + * @param rs The RenderScript context + * + * @return ScriptIntrinsicColorMatrix + */ + public static ScriptIntrinsicColorMatrix create(RenderScript rs) { + int id = rs.nScriptIntrinsicCreate(2, 0); return new ScriptIntrinsicColorMatrix(id, rs); } @@ -84,6 +109,49 @@ public final class ScriptIntrinsicColorMatrix extends ScriptIntrinsic { } /** + * Set the value to be added after the color matrix has been + * applied. The default value is {0, 0, 0, 0} + * + * @param f The float4 value to be added. + */ + public void setAdd(Float4 f) { + mAdd.x = f.x; + mAdd.y = f.y; + mAdd.z = f.z; + mAdd.w = f.w; + + FieldPacker fp = new FieldPacker(4*4); + fp.addF32(f.x); + fp.addF32(f.y); + fp.addF32(f.z); + fp.addF32(f.w); + setVar(1, fp); + } + + /** + * Set the value to be added after the color matrix has been + * applied. The default value is {0, 0, 0, 0} + * + * @param r The red add value. + * @param g The green add value. + * @param b The blue add value. + * @param a The alpha add value. + */ + public void setAdd(float r, float g, float b, float a) { + mAdd.x = r; + mAdd.y = g; + mAdd.z = b; + mAdd.w = a; + + FieldPacker fp = new FieldPacker(4*4); + fp.addF32(mAdd.x); + fp.addF32(mAdd.y); + fp.addF32(mAdd.z); + fp.addF32(mAdd.w); + setVar(1, fp); + } + + /** * Set a color matrix to convert from RGB to luminance. The alpha channel * will be a copy. * @@ -142,13 +210,45 @@ public final class ScriptIntrinsicColorMatrix extends ScriptIntrinsic { /** - * Invoke the kernel and apply the matrix to each cell of ain and copy to - * aout. + * Invoke the kernel and apply the matrix to each cell of input + * {@link Allocation} and copy to the output {@link Allocation}. + * + * If the vector size of the input is less than four, the + * remaining components are treated as zero for the matrix + * multiply. + * + * If the output vector size is less than four, the unused + * vector components are discarded. + * * * @param ain Input allocation * @param aout Output allocation */ public void forEach(Allocation ain, Allocation aout) { + if (!ain.getElement().isCompatible(Element.U8(mRS)) && + !ain.getElement().isCompatible(Element.U8_2(mRS)) && + !ain.getElement().isCompatible(Element.U8_3(mRS)) && + !ain.getElement().isCompatible(Element.U8_4(mRS)) && + !ain.getElement().isCompatible(Element.F32(mRS)) && + !ain.getElement().isCompatible(Element.F32_2(mRS)) && + !ain.getElement().isCompatible(Element.F32_3(mRS)) && + !ain.getElement().isCompatible(Element.F32_4(mRS))) { + + throw new RSIllegalArgumentException("Unsuported element type."); + } + + if (!aout.getElement().isCompatible(Element.U8(mRS)) && + !aout.getElement().isCompatible(Element.U8_2(mRS)) && + !aout.getElement().isCompatible(Element.U8_3(mRS)) && + !aout.getElement().isCompatible(Element.U8_4(mRS)) && + !aout.getElement().isCompatible(Element.F32(mRS)) && + !aout.getElement().isCompatible(Element.F32_2(mRS)) && + !aout.getElement().isCompatible(Element.F32_3(mRS)) && + !aout.getElement().isCompatible(Element.F32_4(mRS))) { + + throw new RSIllegalArgumentException("Unsuported element type."); + } + forEach(0, ain, aout, null); } diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java index c9c54b2..5d3c1d3 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve3x3.java @@ -31,7 +31,10 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { } /** - * Supported elements types are {@link Element#U8_4} + * Supported elements types are {@link Element#U8}, {@link + * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4}, + * {@link Element#F32}, {@link Element#F32_2}, {@link + * Element#F32_3}, and {@link Element#F32_4} * * The default coefficients are. * @@ -48,7 +51,14 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { */ public static ScriptIntrinsicConvolve3x3 create(RenderScript rs, Element e) { float f[] = { 0, 0, 0, 0, 1, 0, 0, 0, 0}; - if (!e.isCompatible(Element.U8_4(rs))) { + if (!e.isCompatible(Element.U8(rs)) && + !e.isCompatible(Element.U8_2(rs)) && + !e.isCompatible(Element.U8_3(rs)) && + !e.isCompatible(Element.U8_4(rs)) && + !e.isCompatible(Element.F32(rs)) && + !e.isCompatible(Element.F32_2(rs)) && + !e.isCompatible(Element.F32_3(rs)) && + !e.isCompatible(Element.F32_4(rs))) { throw new RSIllegalArgumentException("Unsuported element type."); } int id = rs.nScriptIntrinsicCreate(1, e.getID(rs)); diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java index c6e1e39..ad09f95 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java @@ -31,7 +31,10 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { } /** - * Supported elements types are {@link Element#U8_4} + * Supported elements types are {@link Element#U8}, {@link + * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4}, + * {@link Element#F32}, {@link Element#F32_2}, {@link + * Element#F32_3}, and {@link Element#F32_4} * * The default coefficients are. * <code> @@ -48,6 +51,17 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { * @return ScriptIntrinsicConvolve5x5 */ public static ScriptIntrinsicConvolve5x5 create(RenderScript rs, Element e) { + if (!e.isCompatible(Element.U8(rs)) && + !e.isCompatible(Element.U8_2(rs)) && + !e.isCompatible(Element.U8_3(rs)) && + !e.isCompatible(Element.U8_4(rs)) && + !e.isCompatible(Element.F32(rs)) && + !e.isCompatible(Element.F32_2(rs)) && + !e.isCompatible(Element.F32_3(rs)) && + !e.isCompatible(Element.F32_4(rs))) { + throw new RSIllegalArgumentException("Unsuported element type."); + } + int id = rs.nScriptIntrinsicCreate(4, e.getID(rs)); return new ScriptIntrinsicConvolve5x5(id, rs); diff --git a/graphics/java/android/renderscript/ScriptIntrinsicHistogram.java b/graphics/java/android/renderscript/ScriptIntrinsicHistogram.java new file mode 100644 index 0000000..adc2d95 --- /dev/null +++ b/graphics/java/android/renderscript/ScriptIntrinsicHistogram.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2013 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.renderscript; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + +/** + * Intrinsic Histogram filter. + * + * + **/ +public final class ScriptIntrinsicHistogram extends ScriptIntrinsic { + private Allocation mOut; + + private ScriptIntrinsicHistogram(int id, RenderScript rs) { + super(id, rs); + } + + /** + * Create an intrinsic for calculating the histogram of an uchar + * or uchar4 image. + * + * Supported elements types are + * {@link Element#U8_4}, {@link Element#U8_3}, + * {@link Element#U8_2}, {@link Element#U8} + * + * @param rs The RenderScript context + * @param e Element type for inputs + * + * @return ScriptIntrinsicHistogram + */ + public static ScriptIntrinsicHistogram create(RenderScript rs, Element e) { + if ((!e.isCompatible(Element.U8_4(rs))) && + (!e.isCompatible(Element.U8_3(rs))) && + (!e.isCompatible(Element.U8_2(rs))) && + (!e.isCompatible(Element.U8(rs)))) { + throw new RSIllegalArgumentException("Unsuported element type."); + } + int id = rs.nScriptIntrinsicCreate(9, e.getID(rs)); + ScriptIntrinsicHistogram sib = new ScriptIntrinsicHistogram(id, rs); + return sib; + } + + /** + * Process an input buffer and place the histogram into the + * output allocation. The output allocation may be a narrower + * vector size than the input. In this case the vector size of + * the output is used to determine how many of the input + * channels are used in the computation. This is useful if you + * have an RGBA input buffer but only want the histogram for + * RGB. + * + * 1D and 2D input allocations are supported. + * + * @param ain The input image + */ + public void forEach(Allocation ain) { + if (ain.getType().getElement().getVectorSize() < + mOut.getType().getElement().getVectorSize()) { + + throw new RSIllegalArgumentException( + "Input vector size must be >= output vector size."); + } + if (ain.getType().getElement().isCompatible(Element.U8(mRS)) && + ain.getType().getElement().isCompatible(Element.U8_4(mRS))) { + throw new RSIllegalArgumentException("Output type must be U32 or I32."); + } + + forEach(0, ain, null, null); + } + + /** + * Set the coefficients used for the RGBA to Luminocity + * calculation. The default is {0.299f, 0.587f, 0.114f, 0.f}. + * + * Coefficients must be >= 0 and sum to 1.0 or less. + * + * @param r Red coefficient + * @param g Green coefficient + * @param b Blue coefficient + * @param a Alpha coefficient + */ + public void setDotCoefficients(float r, float g, float b, float a) { + if ((r < 0.f) || (g < 0.f) || (b < 0.f) || (a < 0.f)) { + throw new RSIllegalArgumentException("Coefficient may not be negative."); + } + if ((r + g + b + a) > 1.f) { + throw new RSIllegalArgumentException("Sum of coefficients must be 1.0 or less."); + } + + FieldPacker fp = new FieldPacker(16); + fp.addF32(r); + fp.addF32(g); + fp.addF32(b); + fp.addF32(a); + setVar(0, fp); + } + + /** + * Set the output of the histogram. 32 bit integer types are + * supported. + * + * @param aout The output allocation + */ + public void setOutput(Allocation aout) { + mOut = aout; + if (mOut.getType().getElement() != Element.U32(mRS) && + mOut.getType().getElement() != Element.U32_2(mRS) && + mOut.getType().getElement() != Element.U32_3(mRS) && + mOut.getType().getElement() != Element.U32_4(mRS) && + mOut.getType().getElement() != Element.I32(mRS) && + mOut.getType().getElement() != Element.I32_2(mRS) && + mOut.getType().getElement() != Element.I32_3(mRS) && + mOut.getType().getElement() != Element.I32_4(mRS)) { + + throw new RSIllegalArgumentException("Output type must be U32 or I32."); + } + if ((mOut.getType().getX() != 256) || + (mOut.getType().getY() != 0) || + mOut.getType().hasMipmaps() || + (mOut.getType().getYuv() != 0)) { + + throw new RSIllegalArgumentException("Output must be 1D, 256 elements."); + } + setVar(1, aout); + } + + /** + * Process an input buffer and place the histogram into the + * output allocation. The dot product of the input channel and + * the coefficients from 'setDotCoefficients' are used to + * calculate the output values. + * + * 1D and 2D input allocations are supported. + * + * @param ain The input image + */ + public void forEach_Dot(Allocation ain) { + if (mOut.getType().getElement().getVectorSize() != 1) { + throw new RSIllegalArgumentException("Output vector size must be one."); + } + if (ain.getType().getElement().isCompatible(Element.U8(mRS)) && + ain.getType().getElement().isCompatible(Element.U8_4(mRS))) { + throw new RSIllegalArgumentException("Output type must be U32 or I32."); + } + + forEach(1, ain, null, null); + } + + + + /** + * Get a KernelID for this intrinsic kernel. + * + * @return Script.KernelID The KernelID object. + */ + public Script.KernelID getKernelID_Separate() { + return createKernelID(0, 3, null, null); + } + + /** + * Get a FieldID for the input field of this intrinsic. + * + * @return Script.FieldID The FieldID object. + */ + public Script.FieldID getFieldID_Input() { + return createFieldID(1, null); + } +} + diff --git a/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java b/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java index 9b5de9b..845625d 100644 --- a/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java +++ b/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java @@ -20,9 +20,9 @@ package android.renderscript; /** * Intrinsic for converting an Android YUV buffer to RGB. * - * The input allocation is supplied in NV21 format as a U8 - * element type. The output is RGBA, the alpha channel will be - * set to 255. + * The input allocation should be supplied in a supported YUV format + * as a YUV element Allocation. The output is RGBA; the alpha channel + * will be set to 255. */ public final class ScriptIntrinsicYuvToRGB extends ScriptIntrinsic { private Allocation mInput; diff --git a/graphics/java/android/renderscript/Type.java b/graphics/java/android/renderscript/Type.java index ef08c29..e023739 100644 --- a/graphics/java/android/renderscript/Type.java +++ b/graphics/java/android/renderscript/Type.java @@ -37,10 +37,11 @@ import android.util.Log; * faces. LOD and cube map faces are booleans to indicate present or not * present. </p> * - * <p>A Type also supports YUV format information to support an {@link - * android.renderscript.Allocation} in a YUV format. The YUV formats supported - * are {@link android.graphics.ImageFormat#YV12} and {@link - * android.graphics.ImageFormat#NV21}.</p> + * <p>A Type also supports YUV format information to support an + * {@link android.renderscript.Allocation} in a YUV format. The YUV formats + * supported are {@link android.graphics.ImageFormat#YV12}, + * {@link android.graphics.ImageFormat#NV21}, and + * {@link android.graphics.ImageFormat#YUV_420_888}</p> * * <div class="special reference"> * <h3>Developer Guides</h3> @@ -284,16 +285,19 @@ public class Type extends BaseObj { /** * Set the YUV layout for a Type. * - * @param yuvFormat {@link android.graphics.ImageFormat#YV12} or {@link android.graphics.ImageFormat#NV21} + * @param yuvFormat {@link android.graphics.ImageFormat#YV12}, {@link android.graphics.ImageFormat#NV21}, or + * {@link android.graphics.ImageFormat#YUV_420_888}. */ public Builder setYuvFormat(int yuvFormat) { switch (yuvFormat) { case android.graphics.ImageFormat.NV21: case android.graphics.ImageFormat.YV12: + case android.graphics.ImageFormat.YUV_420_888: break; default: - throw new RSIllegalArgumentException("Only NV21 and YV12 are supported.."); + throw new RSIllegalArgumentException( + "Only ImageFormat.NV21, .YV12, and .YUV_420_888 are supported.."); } mYuv = yuvFormat; |
