Add stretch plumbing

Bug: 179047472
Test: builds & boots

Change-Id: I30b5d6683160af598f98555f61af1cf5a7639bbe
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 615bf4d..ce1d96c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -435,6 +435,7 @@
         "canvas/CanvasFrontend.cpp",
         "canvas/CanvasOpBuffer.cpp",
         "canvas/CanvasOpRasterizer.cpp",
+        "effects/StretchEffect.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
         "pipeline/skia/SkiaRecordingCanvas.cpp",
         "pipeline/skia/RenderNodeDrawable.cpp",
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index aeb60e6..609706e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -23,6 +23,7 @@
 #include "Outline.h"
 #include "Rect.h"
 #include "RevealClip.h"
+#include "effects/StretchEffect.h"
 #include "utils/MathUtils.h"
 #include "utils/PaintUtils.h"
 
@@ -98,6 +99,10 @@
 
     SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
 
+    const StretchEffect& getStretchEffect() const { return mStretchEffect; }
+
+    StretchEffect& mutableStretchEffect() { return mStretchEffect; }
+
     // Sets alpha, xfermode, and colorfilter from an SkPaint
     // paint may be NULL, in which case defaults will be set
     bool setFromPaint(const SkPaint* paint);
@@ -124,6 +129,7 @@
     SkBlendMode mMode;
     sk_sp<SkColorFilter> mColorFilter;
     sk_sp<SkImageFilter> mImageFilter;
+    StretchEffect mStretchEffect;
 };
 
 /*
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
new file mode 100644
index 0000000..51cbc75
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "StretchEffect.h"
+
+namespace android::uirenderer {
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
+    // TODO: Implement & Cache
+    // Probably need to use mutable to achieve caching
+    return nullptr;
+}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
new file mode 100644
index 0000000..7dfd639
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include "utils/MathUtils.h"
+
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkImageFilter.h>
+
+namespace android::uirenderer {
+
+// TODO: Inherit from base RenderEffect type?
+class StretchEffect {
+public:
+    enum class StretchInterpolator {
+        SmoothStep,
+    };
+
+    bool isEmpty() const {
+        return MathUtils::isZero(stretchDirection.x())
+                && MathUtils::isZero(stretchDirection.y());
+    }
+
+    void setEmpty() {
+        *this = StretchEffect{};
+    }
+
+    void mergeWith(const StretchEffect& other) {
+        if (other.isEmpty()) {
+            return;
+        }
+        if (isEmpty()) {
+            *this = other;
+            return;
+        }
+        stretchDirection += other.stretchDirection;
+        if (isEmpty()) {
+            return setEmpty();
+        }
+        stretchArea.join(other.stretchArea);
+        maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
+    }
+
+    sk_sp<SkImageFilter> getImageFilter() const;
+
+    SkRect stretchArea {0, 0, 0, 0};
+    SkVector stretchDirection {0, 0};
+    float maxStretchAmount = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8b35d96..8023968 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -166,6 +166,31 @@
     return true;
 }
 
+static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    auto& stretch = renderNode->mutateStagingProperties()
+            .mutateLayerProperties().mutableStretchEffect();
+    if (stretch.isEmpty()) {
+        return false;
+    }
+    stretch.setEmpty();
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
+static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
+        jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
+            StretchEffect{
+        .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
+        .stretchDirection = {.fX = vX, .fY = vY},
+        .maxStretchAmount = max
+    });
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
 static jboolean android_view_RenderNode_hasShadow(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     return renderNode->stagingProperties().hasShadow();
@@ -678,6 +703,8 @@
     { "nSetOutlinePath",       "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
     { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
     { "nSetOutlineNone",       "(J)Z",   (void*) android_view_RenderNode_setOutlineNone },
+    { "nClearStretch",         "(J)Z",   (void*) android_view_RenderNode_clearStretch },
+    { "nStretch",              "(JFFFFFFF)Z",   (void*) android_view_RenderNode_stretch },
     { "nHasShadow",            "(J)Z",   (void*) android_view_RenderNode_hasShadow },
     { "nSetSpotShadowColor",   "(JI)Z",  (void*) android_view_RenderNode_setSpotShadowColor },
     { "nGetSpotShadowColor",   "(J)I",   (void*) android_view_RenderNode_getSpotShadowColor },
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 070a765..26c1674 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -20,6 +20,8 @@
 #include "SkiaDisplayList.h"
 #include "utils/TraceUtils.h"
 
+#include <include/effects/SkImageFilters.h>
+
 #include <optional>
 
 namespace android {
@@ -172,11 +174,25 @@
     paint->setFilterQuality(kLow_SkFilterQuality);
     if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
         properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
-        properties.getImageFilter() != nullptr) {
+        properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
         paint->setAlpha(properties.alpha() * alphaMultiplier);
         paint->setBlendMode(properties.xferMode());
         paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
-        paint->setImageFilter(sk_ref_sp(properties.getImageFilter()));
+
+        sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
+        sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+        sk_sp<SkImageFilter> filter;
+        if (imageFilter && stretchFilter) {
+            filter = SkImageFilters::Compose(
+                  std::move(stretchFilter),
+                  std::move(imageFilter)
+            );
+        } else if (stretchFilter) {
+            filter = std::move(stretchFilter);
+        } else {
+            filter = std::move(imageFilter);
+        }
+        paint->setImageFilter(std::move(filter));
         return true;
     }
     return false;