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

Commit 4a36d982 authored by Diego Perez's avatar Diego Perez Committed by Android Git Automerger
Browse files

am 081cebf5: Implement Path.approximate and add PathMeasure support

* commit '081cebf5':
  Implement Path.approximate and add PathMeasure support
parents bbf804ca 081cebf5
Loading
Loading
Loading
Loading
+0 −59
Original line number Diff line number Diff line
/*
 * 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.animation;

import com.android.tools.layoutlib.annotations.LayoutlibDelegate;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.util.AttributeSet;

/**
 * Delegate providing alternate implementation to static methods in {@link AnimatorInflater}.
 */
public class AnimatorInflater_Delegate {

    @LayoutlibDelegate
    /*package*/ static Animator loadAnimator(Context context, int id)
            throws NotFoundException {
        return loadAnimator(context.getResources(), context.getTheme(), id);
    }

    @LayoutlibDelegate
    /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id)
            throws NotFoundException {
        return loadAnimator(resources, theme, id, 1);
    }

    @LayoutlibDelegate
    /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id,
            float pathErrorScale) throws NotFoundException {
        // This is a temporary fix to http://b.android.com/77865. This skips loading the
        // animation altogether.
        // TODO: Remove this override when Path.approximate() is supported.
        return new FakeAnimator();
    }

    @LayoutlibDelegate
    /*package*/ static ValueAnimator loadAnimator(Resources res, Theme theme,
            AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
            throws NotFoundException {
        return AnimatorInflater.loadAnimator_Original(res, theme, attrs, anim, pathErrorScale);
    }
}
+210 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.graphics;

import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;

import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;

/**
 * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
 * <p/>
 * Through the layoutlib_create tool, the original native methods of PathMeasure have been
 * replaced by
 * calls to methods of the same name in this delegate class.
 * <p/>
 * This class behaves like the original native implementation, but in Java, keeping previously
 * native data into its own objects and mapping them to int that are sent back and forth between it
 * and the original PathMeasure class.
 *
 * @see DelegateManager
 */
public final class PathMeasure_Delegate {
    // ---- delegate manager ----
    private static final DelegateManager<PathMeasure_Delegate> sManager =
            new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);

    // ---- delegate data ----
    // This governs how accurate the approximation of the Path is.
    private static final float PRECISION = 0.002f;

    /**
     * Array containing the path points components. There are three components for each point:
     * <ul>
     *     <li>Fraction along the length of the path that the point resides</li>
     *     <li>The x coordinate of the point</li>
     *     <li>The y coordinate of the point</li>
     * </ul>
     */
    private float mPathPoints[];
    private long mNativePath;

    private PathMeasure_Delegate(long native_path, boolean forceClosed) {
        mNativePath = native_path;
        if (forceClosed && mNativePath != 0) {
            // Copy the path and call close
            mNativePath = Path_Delegate.init2(native_path);
            Path_Delegate.native_close(mNativePath);
        }

        mPathPoints =
                mNativePath != 0 ? Path_Delegate.native_approximate(mNativePath, PRECISION) : null;
    }

    @LayoutlibDelegate
    /*package*/ static long native_create(long native_path, boolean forceClosed) {
        return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
    }

    @LayoutlibDelegate
    /*package*/ static void native_destroy(long native_instance) {
        sManager.removeJavaReferenceFor(native_instance);
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
            float tan[]) {
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "PathMeasure.getPostTan is not supported.", null, null);
        return false;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
            native_matrix, int flags) {
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "PathMeasure.getMatrix is not supported.", null, null);
        return false;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_nextContour(long native_instance) {
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "PathMeasure.nextContour is not supported.", null, null);
        return false;
    }

    @LayoutlibDelegate
    /*package*/ static void native_setPath(long native_instance, long native_path, boolean
            forceClosed) {
        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        if (forceClosed && native_path != 0) {
            // Copy the path and call close
            native_path = Path_Delegate.init2(native_path);
            Path_Delegate.native_close(native_path);
        }
        pathMeasure.mNativePath = native_path;
        pathMeasure.mPathPoints = Path_Delegate.native_approximate(native_path, PRECISION);
    }

    @LayoutlibDelegate
    /*package*/ static float native_getLength(long native_instance) {
        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        if (pathMeasure.mPathPoints == null) {
            return 0;
        }

        float length = 0;
        int nPoints = pathMeasure.mPathPoints.length / 3;
        for (int i = 1; i < nPoints; i++) {
            length += Point2D.distance(
                    pathMeasure.mPathPoints[(i - 1) * 3 + 1],
                    pathMeasure.mPathPoints[(i - 1) * 3 + 2],
                    pathMeasure.mPathPoints[i*3 + 1],
                    pathMeasure.mPathPoints[i*3 + 2]);
        }

        return length;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_isClosed(long native_instance) {
        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
        if (path == null) {
            return false;
        }

        PathIterator pathIterator = path.getJavaShape().getPathIterator(null);

        int type = 0;
        float segment[] = new float[6];
        while (!pathIterator.isDone()) {
            type = pathIterator.currentSegment(segment);
            pathIterator.next();
        }

        // A path is a closed path if the last element is SEG_CLOSE
        return type == PathIterator.SEG_CLOSE;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
            long native_dst_path, boolean startWithMoveTo) {
        if (startD < 0) {
            startD = 0;
        }

        if (startD >= stopD) {
            return false;
        }

        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        if (pathMeasure.mPathPoints == null) {
            return false;
        }

        float accLength = 0;
        boolean isZeroLength = true; // Whether the output has zero length or not
        int nPoints = pathMeasure.mPathPoints.length / 3;
        for (int i = 0; i < nPoints; i++) {
            float x = pathMeasure.mPathPoints[i * 3 + 1];
            float y = pathMeasure.mPathPoints[i * 3 + 2];
            if (accLength >= startD && accLength <= stopD) {
                if (startWithMoveTo) {
                    startWithMoveTo = false;
                    Path_Delegate.native_moveTo(native_dst_path, x, y);
                } else {
                    isZeroLength = false;
                    Path_Delegate.native_lineTo(native_dst_path, x, y);
                }
            }

            if (i > 0) {
                accLength += Point2D.distance(
                        pathMeasure.mPathPoints[(i - 1) * 3 + 1],
                        pathMeasure.mPathPoints[(i - 1) * 3 + 2],
                        pathMeasure.mPathPoints[i * 3 + 1],
                        pathMeasure.mPathPoints[i * 3 + 2]);
            }
        }

        return !isZeroLength;
    }
}
+35 −44
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;

/**
 * Delegate implementing the native methods of android.graphics.Path
@@ -173,11 +174,8 @@ public final class Path_Delegate {
    @LayoutlibDelegate
    /*package*/ static boolean native_isEmpty(long nPath) {
        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
        if (pathDelegate == null) {
            return true;
        }
        return pathDelegate == null || pathDelegate.isEmpty();

        return pathDelegate.isEmpty();
    }

    @LayoutlibDelegate
@@ -488,54 +486,44 @@ public final class Path_Delegate {

    @LayoutlibDelegate
    /*package*/ static float[] native_approximate(long nPath, float error) {
        Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not fully supported",
                null);
        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
        if (pathDelegate == null) {
            return null;
        }
        PathIterator pathIterator = pathDelegate.mPath.getPathIterator(null);
        float[] tmp = new float[6];
        float[] coords = new float[6];
        boolean isFirstPoint = true;
        while (!pathIterator.isDone()) {
            int type = pathIterator.currentSegment(tmp);
            switch (type) {
                case PathIterator.SEG_MOVETO:
                case PathIterator.SEG_LINETO:
                    store(tmp, coords, 1, isFirstPoint);
                    break;
                case PathIterator.SEG_QUADTO:
                    store(tmp, coords, 2, isFirstPoint);
                    break;
                case PathIterator.SEG_CUBICTO:
                    store(tmp, coords, 3, isFirstPoint);
                    break;
                case PathIterator.SEG_CLOSE:
                    // No points returned.
            }
            isFirstPoint = false;
            pathIterator.next();
        }
        if (isFirstPoint) {
            // No points found
            return new float[0];
        } else {
            return coords;
        // Get a FlatteningIterator
        PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);

        float segment[] = new float[6];
        float totalLength = 0;
        ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
        Point2D.Float previousPoint = null;
        while (!iterator.isDone()) {
            int type = iterator.currentSegment(segment);
            Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
            // MoveTo shouldn't affect the length
            if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
                totalLength += currentPoint.distance(previousPoint);
            }
            previousPoint = currentPoint;
            points.add(currentPoint);
            iterator.next();
        }

    private static void store(float[] src, float[] dst, int count, boolean isFirst) {
        if (isFirst) {
            dst[0] = 0;       // fraction
            dst[1] = src[0];  // abscissa
            dst[2] = src[1];  // ordinate
        }
        if (count > 1 || !isFirst) {
            dst[3] = 1;
            dst[4] = src[2 * count - 2];
            dst[5] = src[2 * count - 1];
        int nPoints = points.size();
        float[] result = new float[nPoints * 3];
        previousPoint = null;
        for (int i = 0; i < nPoints; i++) {
            Point2D.Float point = points.get(i);
            float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
            result[i * 3] = distance / totalLength;
            result[i * 3 + 1] = point.x;
            result[i * 3 + 2] = point.y;

            totalLength += distance;
            previousPoint = point;
        }

        return result;
    }

    // ---- Private helper methods ----
@@ -735,6 +723,9 @@ public final class Path_Delegate {
     */
    private void cubicTo(float x1, float y1, float x2, float y2,
                        float x3, float y3) {
        if (isEmpty()) {
            mPath.moveTo(0, 0);
        }
        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
    }

+1 −1
Original line number Diff line number Diff line
@@ -157,7 +157,6 @@ public final class CreateInfo implements ICreateInfo {
     * The list of methods to rewrite as delegates.
     */
    public final static String[] DELEGATE_METHODS = new String[] {
        "android.animation.AnimatorInflater#loadAnimator",  // TODO: remove when Path.approximate() is supported.
        "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
        "android.content.res.Resources$Theme#obtainStyledAttributes",
        "android.content.res.Resources$Theme#resolveAttribute",
@@ -235,6 +234,7 @@ public final class CreateInfo implements ICreateInfo {
        "android.graphics.Path",
        "android.graphics.PathDashPathEffect",
        "android.graphics.PathEffect",
        "android.graphics.PathMeasure",
        "android.graphics.PixelXorXfermode",
        "android.graphics.PorterDuffColorFilter",
        "android.graphics.PorterDuffXfermode",