Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e6a39b12 authored by Chris Craik's avatar Chris Craik
Browse files

Refactor Drawable outline production, flesh out Outline methods

Change-Id: I1b8c25384b5f123e86cf5e0b2270eb741bc3159b
parent 717f39c7
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -10021,11 +10021,16 @@ package android.graphics {
    method public void setPaint(android.graphics.Paint);
  }
  public class Outline {
  public final class Outline {
    ctor public Outline();
    method public final boolean isValid();
    ctor public Outline(android.graphics.Outline);
    method public boolean isValid();
    method public void set(android.graphics.Outline);
    method public void setConvexPath(android.graphics.Path);
    method public void setRect(int, int, int, int);
    method public void setRect(android.graphics.Rect);
    method public void setRoundRect(int, int, int, int, float);
    method public void setRoundRect(android.graphics.Rect, float);
  }
  public class Paint {
@@ -10748,7 +10753,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 getOutline(android.graphics.Outline);
    method public boolean getPadding(android.graphics.Rect);
    method public int[] getState();
    method public android.graphics.Region getTransparentRegion();
+30 −55
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Interpolator;
@@ -2375,25 +2374,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    static final int PFLAG3_CALLED_SUPER = 0x10;
    /**
     * Flag indicating that an view will be clipped to its outline.
     */
    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;
    static final int PFLAG3_OUTLINE_DEFINED = 0x20;
    /**
     * Flag indicating that we're in the process of applying window insets.
     */
    static final int PFLAG3_APPLYING_INSETS = 0x80;
    static final int PFLAG3_APPLYING_INSETS = 0x40;
    /**
     * Flag indicating that we're in the process of fitting system windows using the old method.
     */
    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
    static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
    /* End of masks for mPrivateFlags3 */
@@ -3237,9 +3231,7 @@ 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.
     * defining shadow shape.
     */
    private Outline mOutline;
@@ -10519,16 +10511,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    /**
     * Sets the outline of the view, which defines the shape of the shadow it
     * casts, and can used for clipping.
     * casts.
     * <p>
     * If the outline is not set or is null, shadows will be cast from the
     * bounds of the View, and clipToOutline will be ignored.
     * bounds of the View.
     *
     * @param outline The new outline of the view.
     *         Must be {@link android.graphics.Outline#isValid() valid.}
     *
     * @see #getClipToOutline()
     * @see #setClipToOutline(boolean)
     */
    public void setOutline(@Nullable Outline outline) {
        if (outline != null && !outline.isValid()) {
@@ -10542,46 +10531,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        } else {
            // always copy the path since caller may reuse
            if (mOutline == null) {
                mOutline = new Outline();
                mOutline = new Outline(outline);
            }
            mOutline.set(outline);
        }
        mRenderNode.setOutline(mOutline);
    }
    /**
     * Returns whether the outline of the View will be used for clipping.
     *
     * @see #setOutline(Outline)
     */
    public final boolean getClipToOutline() {
        return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0);
    }
    // TODO: remove
    public final boolean getClipToOutline() { return false; }
    public void setClipToOutline(boolean clipToOutline) {}
    /**
     * Sets whether the outline of the View will be used for clipping.
     * <p>
     * The current implementation of outline clipping uses
     * {@link Canvas#clipPath(Path) path clipping},
     * and thus does not support anti-aliasing, and is expensive in terms of
     * graphics performance. Therefore, it is strongly recommended that this
     * property only be set temporarily, as in an animation. For the same
     * reasons, there is no parallel XML attribute for this property.
     * <p>
     * If the outline of the view is not set or is empty, no clipping will be
     * performed.
     *
     * @see #setOutline(Outline)
     */
    public void setClipToOutline(boolean clipToOutline) {
        // TODO : Add a fast invalidation here.
        if (getClipToOutline() != clipToOutline) {
            if (clipToOutline) {
                mPrivateFlags3 |= PFLAG3_CLIP_TO_OUTLINE;
    private void queryOutlineFromBackgroundIfUndefined() {
        if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
            // Outline not currently defined, query from background
            if (mOutline == null) {
                mOutline = new Outline();
            } else {
                mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE;
                mOutline.markInvalid();
            }
            if (mBackground.getOutline(mOutline)) {
                if (!mOutline.isValid()) {
                    throw new IllegalStateException("Background drawable failed to build outline");
                }
                mRenderNode.setOutline(mOutline);
            } else {
                mRenderNode.setOutline(null);
            }
            mRenderNode.setClipToOutline(clipToOutline);
        }
    }
@@ -14839,11 +14814,7 @@ 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();
                mRenderNode.setOutline(mOutline);
            }
            queryOutlineFromBackgroundIfUndefined();
        }
        // Attempt to use a display list if requested.
@@ -15245,7 +15216,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * @param drawable the drawable to invalidate
     */
    @Override
    public void invalidateDrawable(Drawable drawable) {
    public void invalidateDrawable(@NonNull Drawable drawable) {
        if (verifyDrawable(drawable)) {
            final Rect dirty = drawable.getDirtyBounds();
            final int scrollX = mScrollX;
@@ -15253,6 +15224,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            invalidate(dirty.left + scrollX, dirty.top + scrollY,
                    dirty.right + scrollX, dirty.bottom + scrollY);
            if (drawable == mBackground) {
                queryOutlineFromBackgroundIfUndefined();
            }
        }
    }
+42 −16
Original line number Diff line number Diff line
@@ -16,18 +16,19 @@

package android.graphics;

import android.graphics.drawable.Drawable;
import android.view.View;

/**
 * Defines an area of content.
 *
 * Can be used with a View or Drawable to drive the shape of shadows cast by a
 * Can be used with a View, or computed by a 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)
 * @see Drawable#getOutline(Outline)
 */
public class Outline {
public final class Outline {
    /** @hide */
    public Rect mRect;

@@ -44,19 +45,26 @@ public class Outline {
    public Outline() {}

    /**
     * Returns whether the Outline is valid for use with a View.
     * <p>
     * Outlines are invalid when constructed until a setter method is called.
     * Constructs an Outline with a copy of the data in src.
     */
    public final boolean isValid() {
        return mRect != null || mPath != null;
    public Outline(Outline src) {
        set(src);
    }

    /** @hide */
    public void markInvalid() {
        mRadius = 0;
        mRect = null;
        mPath = null;
    }

    /**
     * @hide
     * Returns whether the Outline is valid for use with a View.
     * <p>
     * Outlines are invalid when constructed until a setter method is called.
     */
    public final boolean canClip() {
        return mPath == null;
    public boolean isValid() {
        return mRect != null || mPath != null;
    }

    /**
@@ -81,9 +89,20 @@ public class Outline {

    /**
     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
     * <p>
     * Outlines produced by this method support
     * {@link View#setClipToOutline(boolean) View clipping.}
     */
    public void setRect(int left, int top, int right, int bottom) {
        setRoundRect(left, top, right, bottom, 0.0f);
    }

    /**
     * Convenience for {@link #setRect(int, int, int, int)}
     */
    public void setRect(Rect rect) {
        setRect(rect.left, rect.top, rect.right, rect.bottom);
    }

    /**
     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
     */
    public void setRoundRect(int left, int top, int right, int bottom, float radius) {
        if (mRect == null) mRect = new Rect();
@@ -92,10 +111,17 @@ public class Outline {
        mPath = null;
    }

    /**
     * Convenience for {@link #setRoundRect(int, int, int, int, float)}
     * @param rect
     * @param radius
     */
    public void setRoundRect(Rect rect, float radius) {
        setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
    }

    /**
     * Sets the Constructs an Outline from a {@link android.graphics.Path#isConvex() convex path}.
     *
     * @hide
     */
    public void setConvexPath(Path convexPath) {
        if (!convexPath.isConvex()) {
+12 −13
Original line number Diff line number Diff line
@@ -864,22 +864,21 @@ public abstract class Drawable {
    }

    /**
     * Returns the outline for this drawable if defined, null if not.
     * Called to get the drawable to populate the Outline.
     * <p>
     * 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.
     * <p>
     * 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.
     * This method will be called by a View on its background Drawable after bounds change, or its
     * Drawable is invalidated, if the View's Outline isn't set explicitly. This allows the
     * background Drawable to define the shape of the shadow cast by the View.
     *
     * The default behavior defines the outline to be the bounding rectangle. Subclasses that wish
     * to convey a different shape must override this method.
     *
     * @see View#setOutline(android.view.Outline)
     * @see View#setClipToOutline(boolean)
     * @see View#setOutline(android.graphics.Outline)
     */
    public Outline getOutline() { return null; }
    public boolean getOutline(Outline outline) {
        outline.setRect(getBounds());
        return true;
    }

    /**
     * Make this drawable mutable. This operation cannot be reversed. A mutable
+23 −22
Original line number Diff line number Diff line
@@ -139,7 +139,6 @@ 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
@@ -577,11 +576,7 @@ public class GradientDrawable extends Drawable {
        switch (st.mShape) {
            case RECTANGLE:
                if (st.mRadiusArray != null) {
                    if (mPathIsDirty || mRectIsDirty) {
                        mPath.reset();
                        mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
                        mPathIsDirty = mRectIsDirty = false;
                    }
                    buildPathIfDirty();
                    canvas.drawPath(mPath, mFillPaint);
                    if (haveStroke) {
                        canvas.drawPath(mPath, mStrokePaint);
@@ -639,6 +634,15 @@ public class GradientDrawable extends Drawable {
        }
    }

    private void buildPathIfDirty() {
        final GradientState st = mGradientState;
        if (mPathIsDirty || mRectIsDirty) {
            mPath.reset();
            mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
            mPathIsDirty = mRectIsDirty = false;
        }
    }

    private Path buildRing(GradientState st) {
        if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
        mPathIsDirty = false;
@@ -1428,42 +1432,39 @@ public class GradientDrawable extends Drawable {
    }

    @Override
    public Outline getOutline() {
    public boolean getOutline(Outline outline) {
        final GradientState st = mGradientState;
        final Rect bounds = getBounds();

        switch (st.mShape) {
            case RECTANGLE:
                if (st.mRadiusArray != null) {
                    return null;
                    buildPathIfDirty();
                    outline.setConvexPath(mPath);
                    return true;
                }

                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,
                outline.setRoundRect(bounds.left, bounds.top,
                        bounds.right, bounds.bottom, rad);
                return mOutline;
                return true;
            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;
                outline.setRect(bounds.left, top, bounds.right, bottom);
                return true;
            }
            default:
                // TODO: investigate
                return null;
                return false;
        }
    }