Updated HWUI to calculate the stretch bounds for a surface

Added logic to StretchEffect to be able to calculate the
stretched position of a pixel on an input texture based
on the current stretch parameters.

Updated shader to leverage linear easing instead of
cubic in order to have a reversible method that
can be used to map input textures to the corresponding
stretch position.

Bug: 179047472
Test: manual

Change-Id: Id32afcf0df1cca03e68ac0c594d307a1090264f2
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index 94fe243..cf3ef40b 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -272,18 +272,21 @@
 
 DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const {
     DirtyStack* frame = mHead;
+    const auto& headProperties = mHead->renderNode->properties();
+    float startWidth = headProperties.getWidth();
+    float startHeight = headProperties.getHeight();
     while (frame->prev != frame) {
         if (frame->type == TransformRenderNode) {
             const auto& renderNode = frame->renderNode;
             const auto& frameRenderNodeProperties = renderNode->properties();
             const auto& effect =
                     frameRenderNodeProperties.layerProperties().getStretchEffect();
-            const float width = (float) renderNode->getWidth();
-            const float height = (float) renderNode->getHeight();
+            const float width = (float) frameRenderNodeProperties.getWidth();
+            const float height = (float) frameRenderNodeProperties.getHeight();
             if (!effect.isEmpty()) {
                 Matrix4 stretchMatrix;
                 computeTransformImpl(mHead, frame, &stretchMatrix);
-                Rect stretchRect = Rect(0.f, 0.f, width, height);
+                Rect stretchRect = Rect(0.f, 0.f, startWidth, startHeight);
                 stretchMatrix.mapRect(stretchRect);
 
                 return StretchResult{
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index 1519d69..0599bfa 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -68,9 +68,8 @@
     // and the other way around.
     uniform float uInterpolationStrength;
 
-    float easeInCubic(float t, float d) {
-        float tmp = t * d;
-        return tmp * tmp * tmp;
+    float easeIn(float t, float d) {
+        return t * d;
     }
 
     float computeOverscrollStart(
@@ -83,7 +82,7 @@
     ) {
         float offsetPos = uStretchAffectedDist - inPos;
         float posBasedVariation = mix(
-                1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
+                1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
         float stretchIntensity = overscroll * posBasedVariation;
         return distanceStretched - (offsetPos / (1. + stretchIntensity));
     }
@@ -99,7 +98,7 @@
     ) {
         float offsetPos = inPos - reverseStretchDist;
         float posBasedVariation = mix(
-                1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
+                1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
         float stretchIntensity = (-overscroll) * posBasedVariation;
         return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
     }
@@ -234,4 +233,80 @@
     return instance.effect;
 }
 
+/**
+ * Helper method that maps the input texture position to the stretch position
+ * based on the given overscroll value that represents an overscroll from
+ * either the top or left
+ * @param overscroll current overscroll value
+ * @param input normalized input position (can be x or y) on the input texture
+ * @return stretched position of the input normalized from 0 to 1
+ */
+float reverseMapStart(float overscroll, float input) {
+    float numerator = (-input * overscroll * overscroll) -
+        (2 * input * overscroll) - input;
+    float denominator = 1.f + (.3f * overscroll) +
+        (.7f * input * overscroll * overscroll) + (.7f * input * overscroll);
+    return -(numerator / denominator);
+}
+
+/**
+ * Helper method that maps the input texture position to the stretch position
+ * based on the given overscroll value that represents an overscroll from
+ * either the bottom or right
+ * @param overscroll current overscroll value
+ * @param input normalized input position (can be x or y) on the input texture
+ * @return stretched position of the input normalized from 0 to 1
+ */
+float reverseMapEnd(float overscroll, float input) {
+    float numerator = (.3f * overscroll * overscroll) -
+        (.3f * input * overscroll * overscroll) +
+        (1.3f * input * overscroll) - overscroll - input;
+    float denominator = (.7f * input * overscroll * overscroll) -
+        (.7f * input * overscroll) - (.7f * overscroll * overscroll) +
+        overscroll - 1.f;
+    return numerator / denominator;
+}
+
+/**
+  * Calculates the normalized stretch position given the normalized input
+  * position. This handles calculating the overscroll from either the
+  * top or left vs bottom or right depending on the sign of the given overscroll
+  * value
+  *
+  * @param overscroll unit vector of overscroll from -1 to 1 indicating overscroll
+  * from the bottom or right vs top or left respectively
+  * @param normalizedInput the
+  * @return
+  */
+float computeReverseOverscroll(float overscroll, float normalizedInput) {
+    float distanceStretched = 1.f / (1.f + abs(overscroll));
+    float distanceDiff = distanceStretched - 1.f;
+    if (overscroll > 0) {
+        float output = reverseMapStart(overscroll, normalizedInput);
+        if (output <= 1.0f) {
+            return output;
+        } else if (output >= distanceStretched){
+            return output - distanceDiff;
+        }
+    }
+
+    if (overscroll < 0) {
+        float output = reverseMapEnd(overscroll, normalizedInput);
+        if (output >= 0.f) {
+            return output;
+        } else if (output < 0.f){
+            return output + distanceDiff;
+        }
+    }
+    return normalizedInput;
+}
+
+float StretchEffect::computeStretchedPositionX(float normalizedX) const {
+  return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
+}
+
+float StretchEffect::computeStretchedPositionY(float normalizedY) const {
+  return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
+}
+
 } // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index 61537f0..6d517ac 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -75,6 +75,22 @@
         maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY);
     }
 
+    /**
+     * Return the stretched x position given the normalized x position with
+     * the current horizontal stretch direction
+     * @param normalizedX x position on the input texture from 0 to 1
+     * @return x position when the horizontal stretch direction applied
+     */
+    float computeStretchedPositionX(float normalizedX) const;
+
+    /**
+     * Return the stretched y position given the normalized y position with
+     * the current horizontal stretch direction
+     * @param normalizedX y position on the input texture from 0 to 1
+     * @return y position when the horizontal stretch direction applied
+     */
+    float computeStretchedPositionY(float normalizedY) const;
+
     sk_sp<SkShader> getShader(float width, float height,
                               const sk_sp<SkImage>& snapshotImage) const;
 
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 5131c64..f92c32c 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -572,11 +572,11 @@
             info.damageAccumulator->computeCurrentTransform(&transform);
             const RenderProperties& props = node.properties();
 
+            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
             if (info.stretchEffectCount) {
-                handleStretchEffect(info, transform);
+                handleStretchEffect(info, bounds);
             }
 
-            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
             transform.mapRect(bounds);
 
             if (CC_LIKELY(transform.isPureTranslate())) {
@@ -639,7 +639,33 @@
             return env;
         }
 
-        void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) {
+        void stretchTargetBounds(const StretchEffect& stretchEffect,
+                                 float width, float height,
+                                 const SkRect& childRelativeBounds,
+                                 uirenderer::Rect& bounds) {
+              float normalizedLeft = childRelativeBounds.left() / width;
+              float normalizedTop = childRelativeBounds.top() / height;
+              float normalizedRight = childRelativeBounds.right() / width;
+              float normalizedBottom = childRelativeBounds.bottom() / height;
+              float reverseLeft = width *
+                  (stretchEffect.computeStretchedPositionX(normalizedLeft) -
+                    normalizedLeft);
+              float reverseTop = height *
+                  (stretchEffect.computeStretchedPositionY(normalizedTop) -
+                    normalizedTop);
+              float reverseRight = width *
+                  (stretchEffect.computeStretchedPositionX(normalizedRight) -
+                    normalizedLeft);
+              float reverseBottom = height *
+                  (stretchEffect.computeStretchedPositionY(normalizedBottom) -
+                    normalizedTop);
+              bounds.left = reverseLeft;
+              bounds.top = reverseTop;
+              bounds.right = reverseRight;
+              bounds.bottom = reverseBottom;
+        }
+
+        void handleStretchEffect(const TreeInfo& info, uirenderer::Rect& targetBounds) {
             // Search up to find the nearest stretcheffect parent
             const DamageAccumulator::StretchResult result =
                 info.damageAccumulator->findNearestStretchEffect();
@@ -649,6 +675,8 @@
             }
 
             const auto& childRelativeBounds = result.childRelativeBounds;
+            stretchTargetBounds(*effect, result.width, result.height,
+                                childRelativeBounds,targetBounds);
 
             JNIEnv* env = jnienv();