From b49f446c98096c4790a11d9b5bc83a4e585278c9 Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Thu, 20 Mar 2014 12:44:20 -0700 Subject: [PATCH] Rework Outline API, remove isolatedZVolume remnants Change-Id: I30c2fe832dcb98fa6329b1a595b3d3aafbdcad6b --- api/current.txt | 11 ++- core/java/android/view/RenderNode.java | 38 ++++---- core/java/android/view/View.java | 106 +++++++++----------- core/java/android/view/ViewGroup.java | 51 +--------- core/jni/android_view_RenderNode.cpp | 33 ++++--- core/res/res/values/attrs.xml | 3 - graphics/java/android/graphics/Outline.java | 108 +++++++++++++++++++++ .../java/android/graphics/drawable/Drawable.java | 19 ++++ .../graphics/drawable/GradientDrawable.java | 51 ++++++++-- libs/hwui/Outline.h | 90 +++++++++++++++++ libs/hwui/RenderNode.cpp | 23 +++-- libs/hwui/RenderProperties.cpp | 2 - libs/hwui/RenderProperties.h | 33 +++---- 13 files changed, 382 insertions(+), 186 deletions(-) create mode 100644 graphics/java/android/graphics/Outline.java create mode 100644 libs/hwui/Outline.h diff --git a/api/current.txt b/api/current.txt index a3d39c8dca22..21d5cf83ce46 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9792,6 +9792,13 @@ package android.graphics { method public void setPaint(android.graphics.Paint); } + public class Outline { + ctor public Outline(); + method public final boolean isValid(); + method public void set(android.graphics.Outline); + method public void setRoundRect(int, int, int, int, float); + } + public class Paint { ctor public Paint(); ctor public Paint(int); @@ -10501,6 +10508,7 @@ package android.graphics.drawable { method public int getMinimumHeight(); method public int getMinimumWidth(); method public abstract int getOpacity(); + method public android.graphics.Outline getOutline(); method public boolean getPadding(android.graphics.Rect); method public int[] getState(); method public android.graphics.Region getTransparentRegion(); @@ -29097,7 +29105,6 @@ package android.view { method public int getNextFocusRightId(); method public int getNextFocusUpId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); - method public final void getOutline(android.graphics.Path); method public int getOverScrollMode(); method public android.view.ViewOverlay getOverlay(); method public int getPaddingBottom(); @@ -29361,7 +29368,7 @@ package android.view { method public void setOnLongClickListener(android.view.View.OnLongClickListener); method public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener); method public void setOnTouchListener(android.view.View.OnTouchListener); - method public void setOutline(android.graphics.Path); + method public void setOutline(android.graphics.Outline); method public void setOverScrollMode(int); method public void setPadding(int, int, int, int); method public void setPaddingRelative(int, int, int, int); diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 87ab20e2cba2..60fb7ac4881d 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -17,7 +17,7 @@ package android.view; import android.graphics.Matrix; -import android.graphics.Path; +import android.graphics.Outline; /** *

A display list records a series of graphics related operations and can replay @@ -311,16 +311,6 @@ public class RenderNode { } /** - * Set whether the display list should collect and Z order all 3d composited descendents, and - * draw them in order with the default Z=0 content. - * - * @param isolatedZVolume true if the display list should collect and Z order descendents. - */ - public void setIsolatedZVolume(boolean isolatedZVolume) { - nSetIsolatedZVolume(mNativeDisplayList, isolatedZVolume); - } - - /** * Sets whether the display list should be drawn immediately after the * closest ancestor display list where isolateZVolume is true. If the * display list itself satisfies this constraint, changing this attribute @@ -346,13 +336,19 @@ public class RenderNode { * Sets the outline, defining the shape that casts a shadow, and the path to * be clipped if setClipToOutline is set. * - * Deep copies the native path to simplify reference ownership. - * - * @param outline Convex, CW Path to store in the DisplayList. May be null. - */ - public void setOutline(Path outline) { - long nativePath = (outline == null) ? 0 : outline.mNativePath; - nSetOutline(mNativeDisplayList, nativePath); + * Deep copies the data into native to simplify reference ownership. + */ + public void setOutline(Outline outline) { + if (outline == null) { + nSetOutlineEmpty(mNativeDisplayList); + } else if (!outline.isValid()) { + throw new IllegalArgumentException("Outline must be valid"); + } else if (outline.mRect != null) { + nSetOutlineRoundRect(mNativeDisplayList, outline.mRect.left, outline.mRect.top, + outline.mRect.right, outline.mRect.bottom, outline.mRadius); + } else if (outline.mPath != null) { + nSetOutlineConvexPath(mNativeDisplayList, outline.mPath.mNativePath); + } } /** @@ -855,8 +851,10 @@ public class RenderNode { private static native void nSetClipToBounds(long displayList, boolean clipToBounds); private static native void nSetProjectBackwards(long displayList, boolean shouldProject); private static native void nSetProjectionReceiver(long displayList, boolean shouldRecieve); - private static native void nSetIsolatedZVolume(long displayList, boolean isolateZVolume); - private static native void nSetOutline(long displayList, long nativePath); + private static native void nSetOutlineRoundRect(long displayList, int left, int top, + int right, int bottom, float radius); + private static native void nSetOutlineConvexPath(long displayList, long nativePath); + private static native void nSetOutlineEmpty(long displayList); private static native void nSetClipToOutline(long displayList, boolean clipToOutline); private static native void nSetAlpha(long displayList, float alpha); private static native void nSetHasOverlappingRendering(long displayList, diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a8ccd49119a5..9f912db9c63e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -31,6 +31,7 @@ import android.graphics.Insets; import android.graphics.Interpolator; import android.graphics.LinearGradient; import android.graphics.Matrix; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; @@ -2377,14 +2378,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int PFLAG3_CLIP_TO_OUTLINE = 0x20; /** + * Flag indicating that a view's outline has been specifically defined. + */ + static final int PFLAG3_OUTLINE_DEFINED = 0x40; + + /** * Flag indicating that we're in the process of applying window insets. */ - static final int PFLAG3_APPLYING_INSETS = 0x40; + static final int PFLAG3_APPLYING_INSETS = 0x80; /** * Flag indicating that we're in the process of fitting system windows using the old method. */ - static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80; + static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100; /* End of masks for mPrivateFlags3 */ @@ -3335,8 +3341,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Stores the outline of the view, passed down to the DisplayList level for * defining shadow shape and clipping. + * + * TODO: once RenderNode is long-lived, remove this and rely on native copy. */ - private Path mOutline; + private Outline mOutline; /** * When this view has focus and the next focus is {@link #FOCUS_LEFT}, @@ -10802,66 +10810,44 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Copies the Outline of the View into the Path parameter. - *

- * If the outline is not set, the parameter Path is set to empty. - * - * @param outline Path into which View's outline will be copied. Must be non-null. - * - * @see #setOutline(Path) - * @see #getClipToOutline() - * @see #setClipToOutline(boolean) - */ - public final void getOutline(@NonNull Path outline) { - if (outline == null) { - throw new IllegalArgumentException("Path must be non-null"); - } - if (mOutline == null) { - outline.reset(); - } else { - outline.set(mOutline); - } - } - - /** * Sets the outline of the view, which defines the shape of the shadow it * casts, and can used for clipping. *

- * The outline path of a View must be {@link android.graphics.Path#isConvex() convex}. - *

- * If the outline is not set, or {@link Path#isEmpty()}, shadows will be - * cast from the bounds of the View, and clipToOutline will be ignored. + * If the outline is not set or is null, shadows will be cast from the + * bounds of the View, and clipToOutline will be ignored. * - * @param outline The new outline of the view. Must be non-null, and convex. + * @param outline The new outline of the view. + * Must be {@link android.view.Outline#isValid() valid.} * - * @see #getOutline(Path) * @see #getClipToOutline() * @see #setClipToOutline(boolean) */ - public void setOutline(@NonNull Path outline) { - if (outline == null) { - throw new IllegalArgumentException("Path must be non-null"); + public void setOutline(@Nullable Outline outline) { + if (outline != null && !outline.isValid()) { + throw new IllegalArgumentException("Outline must not be invalid"); } - if (!outline.isConvex()) { - throw new IllegalArgumentException("Path must be convex"); - } - // always copy the path since caller may reuse - if (mOutline == null) { - mOutline = new Path(outline); + + mPrivateFlags3 |= PFLAG3_OUTLINE_DEFINED; + + if (outline == null) { + mOutline = null; } else { + // always copy the path since caller may reuse + if (mOutline == null) { + mOutline = new Outline(); + } mOutline.set(outline); } if (mDisplayList != null) { - mDisplayList.setOutline(outline); + mDisplayList.setOutline(mOutline); } } /** * Returns whether the outline of the View will be used for clipping. * - * @see #getOutline(Path) - * @see #setOutline(Path) + * @see #setOutline(Outline) */ public final boolean getClipToOutline() { return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0); @@ -10879,8 +10865,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * If the outline of the view is not set or is empty, no clipping will be * performed. * - * @see #getOutline(Path) - * @see #setOutline(Path) + * @see #setOutline(Outline) */ public void setClipToOutline(boolean clipToOutline) { // TODO : Add a fast invalidation here. @@ -11460,7 +11445,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Damage the entire IsolatedZVolume recieving this view's shadow. if (getTranslationZ() != 0) { - damageIsolatedZVolume(); + damageShadowReceiver(); } } } @@ -11489,24 +11474,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Damage area of the screen covered by the current isolated Z volume + * Damage area of the screen that can be covered by this View's shadow. * * This method will guarantee that any changes to shadows cast by a View * are damaged on the screen for future redraw. */ - private void damageIsolatedZVolume() { + private void damageShadowReceiver() { final AttachInfo ai = mAttachInfo; if (ai != null) { ViewParent p = getParent(); - while (p != null) { - if (p instanceof ViewGroup) { - final ViewGroup vg = (ViewGroup) p; - if (vg.hasIsolatedZVolume()) { - vg.damageInParent(); - return; - } - } - p = p.getParent(); + if (p != null && p instanceof ViewGroup) { + final ViewGroup vg = (ViewGroup) p; + vg.damageInParent(); } } } @@ -11540,7 +11519,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, damageInParent(); } if (invalidateParent && getTranslationZ() != 0) { - damageIsolatedZVolume(); + damageShadowReceiver(); } } @@ -14571,10 +14550,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, displayList.setClipToBounds( (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0); } - if (this instanceof ViewGroup) { - displayList.setIsolatedZVolume( - (((ViewGroup) this).mGroupFlags & ViewGroup.FLAG_ISOLATED_Z_VOLUME) != 0); - } displayList.setOutline(mOutline); displayList.setClipToOutline(getClipToOutline()); float alpha = 1; @@ -15178,6 +15153,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; + if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) { + // Outline not currently define, query from background + mOutline = background.getOutline(); + if (mDisplayList != null) { + mDisplayList.setOutline(mOutline); + } + } } // Attempt to use a display list if requested. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 22fbbd62a058..aadaa7f0ea78 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -357,15 +357,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000; - /** - * When true, indicates that all 3d composited descendents are contained within this group, and - * will not be interleaved with other 3d composited content. - */ - static final int FLAG_ISOLATED_Z_VOLUME = 0x1000000; - - static final int FLAG_IS_TRANSITION_GROUP = 0x2000000; + static final int FLAG_IS_TRANSITION_GROUP = 0x1000000; - static final int FLAG_IS_TRANSITION_GROUP_SET = 0x4000000; + static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000; /** * Indicates which types of drawing caches are to be kept in memory. @@ -499,7 +493,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mGroupFlags |= FLAG_ANIMATION_DONE; mGroupFlags |= FLAG_ANIMATION_CACHE; mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE; - mGroupFlags |= FLAG_ISOLATED_Z_VOLUME; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; @@ -528,9 +521,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case R.styleable.ViewGroup_clipToPadding: setClipToPadding(a.getBoolean(attr, true)); break; - case R.styleable.ViewGroup_isolatedZVolume: - setIsolatedZVolume(a.getBoolean(attr, true)); - break; case R.styleable.ViewGroup_animationCache: setAnimationCacheEnabled(a.getBoolean(attr, true)); break; @@ -3159,43 +3149,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Returns whether this group's descendents are drawn in their own - * independent Z volume. Views drawn in one contained volume will not - * interleave with views in another, even if their Z values are interleaved. - * The default value is true. - * @see #setIsolatedZVolume(boolean) - * - * @return True if the ViewGroup has an isolated Z volume. - * - * @hide - */ - public boolean hasIsolatedZVolume() { - return ((mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0); - } - - /** - * By default, only direct children of a group can interleave drawing order - * by interleaving Z values. Set to false on individual groups to enable Z - * interleaving of views that aren't direct siblings. - * - * @return True if the group should be an isolated Z volume with its own Z - * ordering space, false if its decendents should inhabit the - * inherited Z ordering volume. - * @attr ref android.R.styleable#ViewGroup_isolatedZVolume - * - * @hide - */ - public void setIsolatedZVolume(boolean isolateZVolume) { - boolean previousValue = (mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0; - if (isolateZVolume != previousValue) { - setBooleanFlag(FLAG_ISOLATED_Z_VOLUME, isolateZVolume); - if (mDisplayList != null) { - mDisplayList.setIsolatedZVolume(isolateZVolume); - } - } - } - - /** * Returns whether this group's children are clipped to their bounds before drawing. * The default value is true. * @see #setClipChildren(boolean) diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index fa08a7837b3c..f96be222bb51 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -69,7 +69,7 @@ static void android_view_RenderNode_destroyDisplayList(JNIEnv* env, } // ---------------------------------------------------------------------------- -// DisplayList view properties +// RenderProperties // ---------------------------------------------------------------------------- static void android_view_RenderNode_setCaching(JNIEnv* env, @@ -98,11 +98,6 @@ static void android_view_RenderNode_setClipToBounds(JNIEnv* env, displayList->properties().setClipToBounds(clipToBounds); } -static void android_view_RenderNode_setIsolatedZVolume(JNIEnv* env, - jobject clazz, jlong displayListPtr, jboolean shouldIsolate) { - // No-op, TODO: Remove Java usage of this method -} - static void android_view_RenderNode_setProjectBackwards(JNIEnv* env, jobject clazz, jlong displayListPtr, jboolean shouldProject) { RenderNode* displayList = reinterpret_cast(displayListPtr); @@ -115,17 +110,28 @@ static void android_view_RenderNode_setProjectionReceiver(JNIEnv* env, displayList->properties().setProjectionReceiver(shouldRecieve); } -static void android_view_RenderNode_setOutline(JNIEnv* env, +static void android_view_RenderNode_setOutlineRoundRect(JNIEnv* env, + jobject clazz, jlong displayListPtr, jint left, jint top, + jint right, jint bottom, jfloat radius) { + RenderNode* displayList = reinterpret_cast(displayListPtr); + displayList->properties().outline().setRoundRect(left, top, right, bottom, radius); +} +static void android_view_RenderNode_setOutlineConvexPath(JNIEnv* env, jobject clazz, jlong displayListPtr, jlong outlinePathPtr) { RenderNode* displayList = reinterpret_cast(displayListPtr); - SkPath* outline = reinterpret_cast(outlinePathPtr); - displayList->properties().setOutline(outline); + SkPath* outlinePath = reinterpret_cast(outlinePathPtr); + displayList->properties().outline().setConvexPath(outlinePath); +} +static void android_view_RenderNode_setOutlineEmpty(JNIEnv* env, + jobject clazz, jlong displayListPtr) { + RenderNode* displayList = reinterpret_cast(displayListPtr); + displayList->properties().outline().setEmpty(); } static void android_view_RenderNode_setClipToOutline(JNIEnv* env, jobject clazz, jlong displayListPtr, jboolean clipToOutline) { RenderNode* displayList = reinterpret_cast(displayListPtr); - displayList->properties().setClipToOutline(clipToOutline); + displayList->properties().outline().setShouldClip(clipToOutline); } static void android_view_RenderNode_setAlpha(JNIEnv* env, @@ -381,11 +387,14 @@ static JNINativeMethod gMethods[] = { { "nSetStaticMatrix", "(JJ)V", (void*) android_view_RenderNode_setStaticMatrix }, { "nSetAnimationMatrix", "(JJ)V", (void*) android_view_RenderNode_setAnimationMatrix }, { "nSetClipToBounds", "(JZ)V", (void*) android_view_RenderNode_setClipToBounds }, - { "nSetIsolatedZVolume", "(JZ)V", (void*) android_view_RenderNode_setIsolatedZVolume }, { "nSetProjectBackwards", "(JZ)V", (void*) android_view_RenderNode_setProjectBackwards }, { "nSetProjectionReceiver","(JZ)V", (void*) android_view_RenderNode_setProjectionReceiver }, - { "nSetOutline", "(JJ)V", (void*) android_view_RenderNode_setOutline }, + + { "nSetOutlineRoundRect", "(JIIIIF)V", (void*) android_view_RenderNode_setOutlineRoundRect }, + { "nSetOutlineConvexPath", "(JJ)V", (void*) android_view_RenderNode_setOutlineConvexPath }, + { "nSetOutlineEmpty", "(J)V", (void*) android_view_RenderNode_setOutlineEmpty }, { "nSetClipToOutline", "(JZ)V", (void*) android_view_RenderNode_setClipToOutline }, + { "nSetAlpha", "(JF)V", (void*) android_view_RenderNode_setAlpha }, { "nSetHasOverlappingRendering", "(JZ)V", (void*) android_view_RenderNode_setHasOverlappingRendering }, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 582ed1bba803..f1b1f6ba941b 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2292,9 +2292,6 @@ - - diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java new file mode 100644 index 000000000000..a65ac875b3ef --- /dev/null +++ b/graphics/java/android/graphics/Outline.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +/** + * Defines an area of content. + * + * Can be used with a View or Drawable to drive the shape of shadows cast by a + * View, and allowing Views to clip inner content. + * + * @see View#setOutline(Outline) + * @see View#setClipToOutline(boolean) + */ +public class Outline { + /** @hide */ + public Rect mRect; + + /** @hide */ + public float mRadius; + + /** @hide */ + public Path mPath; + + /** + * Constructs an invalid Outline. Call one of the setter methods to make + * the outline valid for use with a View. + */ + public Outline() {} + + /** + * Returns whether the Outline is valid for use with a View. + *

+ * Outlines are invalid when constructed until a setter method is called. + */ + public final boolean isValid() { + return mRect != null || mPath != null; + } + + /** + * @hide + */ + public final boolean canClip() { + return mPath == null; + } + + /** + * Replace the contents of this Outline with the contents of src. + */ + public void set(Outline src) { + if (src.mPath != null) { + if (mPath == null) { + mPath = new Path(); + } + mPath.set(src.mPath); + mRect = null; + } + if (src.mRect != null) { + if (mRect == null) { + mRect = new Rect(); + } + mRect.set(src.mRect); + } + mRadius = src.mRadius; + } + + /** + * Sets the Outline to the rounded rect defined by the input rect, and corner radius. + *

+ * Outlines produced by this method support + * {@link View#setClipToOutline(boolean) View clipping.} + */ + public void setRoundRect(int left, int top, int right, int bottom, float radius) { + if (mRect == null) mRect = new Rect(); + mRect.set(left, top, right, bottom); + mRadius = radius; + mPath = null; + } + + /** + * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}. + * + * @hide + */ + public void setConvexPath(Path convexPath) { + if (!convexPath.isConvex()) { + throw new IllegalArgumentException("path must be convex"); + } + if (mPath == null) mPath = new Path(); + + mRect = null; + mRadius = -1.0f; + mPath.set(convexPath); + } +} diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 84211efd74a9..eb6b536ab64b 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -30,6 +30,7 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.NinePatch; +import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; @@ -860,6 +861,24 @@ public abstract class Drawable { } /** + * Returns the outline for this drawable if defined, null if not. + *

+ * This method will be called by a View on its background Drawable after + * bounds change, if the View's Outline isn't set explicitly. This allows + * the background Drawable to provide the shape of the shadow casting + * portion of the View. It can also serve to clip the area of the View if + * if {@link View#setClipToOutline(boolean)} is set on the View. + *

+ * The Outline queried by the View will not be modified, and is treated as + * a static shape that only needs to be requeried when the drawable's bounds + * change. + * + * @see View#setOutline(android.view.Outline) + * @see View#setClipToOutline(boolean) + */ + public Outline getOutline() { return null; } + + /** * Make this drawable mutable. This operation cannot be reversed. A mutable * drawable is guaranteed to not share its state with any other drawable. * This is especially useful when you need to modify properties of drawables diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 46d57adc0fb4..8f22addbd879 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -24,6 +24,7 @@ import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.DashPathEffect; import android.graphics.LinearGradient; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; @@ -133,6 +134,7 @@ public class GradientDrawable extends Drawable { private final Path mPath = new Path(); private final RectF mRect = new RectF(); + private Outline mOutline; private Paint mLayerPaint; // internal, used if we use saveLayer() private boolean mRectIsDirty; // internal state @@ -585,11 +587,8 @@ public class GradientDrawable extends Drawable { // to show it. If we did nothing, Skia would clamp the rad // independently along each axis, giving us a thin ellipse // if the rect were very wide but not very tall - float rad = st.mRadius; - float r = Math.min(mRect.width(), mRect.height()) * 0.5f; - if (rad > r) { - rad = r; - } + float rad = Math.min(st.mRadius, + Math.min(mRect.width(), mRect.height()) * 0.5f); canvas.drawRoundRect(mRect, rad, rad, mFillPaint); if (haveStroke) { canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); @@ -661,7 +660,7 @@ public class GradientDrawable extends Drawable { if (mRingPath == null) { mRingPath = new Path(); } else { - mRingPath.reset(); + mRingPath.reset(); } final Path ringPath = mRingPath; @@ -1243,6 +1242,46 @@ public class GradientDrawable extends Drawable { } @Override + public Outline getOutline() { + final GradientState st = mGradientState; + final Rect bounds = getBounds(); + + switch (st.mShape) { + case RECTANGLE: + if (st.mRadiusArray != null) { + return null; + } + float rad = 0; + if (st.mRadius > 0.0f) { + // clamp the radius based on width & height, matching behavior in draw() + rad = Math.min(st.mRadius, + Math.min(bounds.width(), bounds.height()) * 0.5f); + } + if (mOutline == null) { + mOutline = new Outline(); + } + mOutline.setRoundRect(bounds.left, bounds.top, + bounds.right, bounds.bottom, rad); + return mOutline; + case LINE: { + float halfStrokeWidth = mStrokePaint.getStrokeWidth() * 0.5f; + float centerY = bounds.centerY(); + int top = (int) Math.floor(centerY - halfStrokeWidth); + int bottom = (int) Math.ceil(centerY + halfStrokeWidth); + + if (mOutline == null) { + mOutline = new Outline(); + } + mOutline.setRoundRect(bounds.left, top, bounds.right, bottom, 0); + return mOutline; + } + default: + // TODO: investigate + return null; + } + } + + @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { mGradientState = new GradientState(mGradientState); diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h new file mode 100644 index 000000000000..f42be507203d --- /dev/null +++ b/libs/hwui/Outline.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef OUTLINE_H +#define OUTLINE_H + +#include + +#include "Rect.h" + +namespace android { +namespace uirenderer { + +class Outline { +public: + Outline() + : mShouldClip(false) + , mType(kOutlineType_None) + , mRadius(0) {} + + void setRoundRect(int left, int top, int right, int bottom, int radius) { + mType = kOutlineType_RoundRect; + mBounds.set(left, top, right, bottom); + mRadius = radius; + mPath.reset(); + mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom), + radius, radius); + } + + void setConvexPath(const SkPath* outline) { + if (!outline) { + setEmpty(); + return; + } + mType = kOutlineType_ConvexPath; + mPath = *outline; + mBounds.set(outline->getBounds()); + } + + void setEmpty() { + mType = kOutlineType_None; + mPath.reset(); + } + + void setShouldClip(bool clip) { + mShouldClip = clip; + } + + + bool willClip() const { + // only round rect outlines can be used for clipping + return mShouldClip && (mType == kOutlineType_RoundRect); + } + + const SkPath* getPath() { + if (mType == kOutlineType_None) return NULL; + + return &mPath; + } + +private: + enum OutlineType { + kOutlineType_None = 0, + kOutlineType_ConvexPath = 1, + kOutlineType_RoundRect = 2 + }; + + bool mShouldClip; + OutlineType mType; + Rect mBounds; + float mRadius; + SkPath mPath; +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* OUTLINE_H */ diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index e371590b8120..8aed857eb275 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -128,14 +128,14 @@ void RenderNode::outputViewProperties(const int level) { flags |= SkCanvas::kClipToLayer_SaveFlag; clipToBoundsNeeded = false; // clipping done by save layer } - ALOGD("%*sSaveLayerAlpha %.2f, %.2f, %.2f, %.2f, %d, 0x%x", level * 2, "", - (float) 0, (float) 0, (float) properties().mRight - properties().mLeft, (float) properties().mBottom - properties().mTop, + ALOGD("%*sSaveLayerAlpha %d, %d, %d, %d, %d, 0x%x", level * 2, "", + 0, 0, properties().mWidth, properties().mHeight, (int)(properties().mAlpha * 255), flags); } } if (clipToBoundsNeeded) { - ALOGD("%*sClipRect %.2f, %.2f, %.2f, %.2f", level * 2, "", 0.0f, 0.0f, - (float) properties().mRight - properties().mLeft, (float) properties().mBottom - properties().mTop); + ALOGD("%*sClipRect %d, %d, %d, %d", level * 2, "", + 0, 0, properties().mWidth, properties().mHeight); } } @@ -185,17 +185,20 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler, } SaveLayerOp* op = new (handler.allocator()) SaveLayerOp( - 0, 0, properties().mRight - properties().mLeft, properties().mBottom - properties().mTop, properties().mAlpha * 255, saveFlags); + 0, 0, properties().mWidth, properties().mHeight, + properties().mAlpha * 255, saveFlags); handler(op, PROPERTY_SAVECOUNT, properties().mClipToBounds); } } if (clipToBoundsNeeded) { - ClipRectOp* op = new (handler.allocator()) ClipRectOp(0, 0, - properties().mRight - properties().mLeft, properties().mBottom - properties().mTop, SkRegion::kIntersect_Op); + ClipRectOp* op = new (handler.allocator()) ClipRectOp( + 0, 0, properties().mWidth, properties().mHeight, SkRegion::kIntersect_Op); handler(op, PROPERTY_SAVECOUNT, properties().mClipToBounds); } - if (CC_UNLIKELY(properties().mClipToOutline && !properties().mOutline.isEmpty())) { - ClipPathOp* op = new (handler.allocator()) ClipPathOp(&properties().mOutline, SkRegion::kIntersect_Op); + if (CC_UNLIKELY(properties().mOutline.willClip())) { + // TODO: optimize RR case + ClipPathOp* op = new (handler.allocator()) ClipPathOp(properties().mOutline.getPath(), + SkRegion::kIntersect_Op); handler(op, PROPERTY_SAVECOUNT, properties().mClipToBounds); } } @@ -443,7 +446,7 @@ void RenderNode::iterate3dChildren(const Vector& zTransl DisplayListOp* shadowOp = new (alloc) DrawShadowOp( shadowMatrixXY, shadowMatrixZ, - caster->properties().mAlpha, &(caster->properties().mOutline), + caster->properties().mAlpha, caster->properties().mOutline.getPath(), caster->properties().mWidth, caster->properties().mHeight); handler(shadowOp, PROPERTY_SAVECOUNT, properties().mClipToBounds); } diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index 233aace1a4f9..902a748a462e 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -26,7 +26,6 @@ RenderProperties::RenderProperties() : mClipToBounds(true) , mProjectBackwards(false) , mProjectionReceiver(false) - , mClipToOutline(false) , mAlpha(1) , mHasOverlappingRendering(true) , mTranslationX(0), mTranslationY(0), mTranslationZ(0) @@ -47,7 +46,6 @@ RenderProperties::RenderProperties() , mStaticMatrix(NULL) , mAnimationMatrix(NULL) , mCaching(false) { - mOutline.rewind(); } RenderProperties::~RenderProperties() { diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 6e3b8aebca3b..57fa4ba73b2b 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef RENDERNODEPROPERTIES_H_ -#define RENDERNODEPROPERTIES_H_ +#ifndef RENDERNODEPROPERTIES_H +#define RENDERNODEPROPERTIES_H #include #include @@ -22,7 +22,9 @@ #include #include -#include + +#include "Rect.h" +#include "Outline.h" #define TRANSLATION 0x0001 #define ROTATION 0x0002 @@ -64,18 +66,6 @@ public: return mProjectionReceiver; } - void setOutline(const SkPath* outline) { - if (!outline) { - mOutline.reset(); - } else { - mOutline = *outline; - } - } - - void setClipToOutline(bool clipToOutline) { - mClipToOutline = clipToOutline; - } - void setStaticMatrix(SkMatrix* matrix) { delete mStaticMatrix; mStaticMatrix = new SkMatrix(*matrix); @@ -375,14 +365,18 @@ public: mCaching = caching; } - int getWidth() { + int getWidth() const { return mWidth; } - int getHeight() { + int getHeight() const { return mHeight; } + Outline& outline() { + return mOutline; + } + private: void onTranslationUpdate() { mMatrixDirty = true; @@ -396,11 +390,10 @@ private: void updateMatrix(); // Rendering properties + Outline mOutline; bool mClipToBounds; bool mProjectBackwards; bool mProjectionReceiver; - SkPath mOutline; - bool mClipToOutline; float mAlpha; bool mHasOverlappingRendering; float mTranslationX, mTranslationY, mTranslationZ; @@ -436,4 +429,4 @@ private: } /* namespace uirenderer */ } /* namespace android */ -#endif /* RENDERNODEPROPERTIES_H_ */ +#endif /* RENDERNODEPROPERTIES_H */ -- 2.11.0