Add stretch plumbing

Bug: 179047472
Test: builds & boots

Change-Id: I30b5d6683160af598f98555f61af1cf5a7639bbe
This commit is contained in:
John Reck
2021-02-01 13:47:31 -05:00
parent 1ee0214118
commit 5cb290bf54
8 changed files with 187 additions and 2 deletions

View File

@@ -21367,6 +21367,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int height = mBottom - mTop;
int layerType = getLayerType();
// Hacky hack: Reset any stretch effects as those are applied during the draw pass
// instead of being "stateful" like other RenderNode properties
renderNode.clearStretch();
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
@@ -22793,6 +22797,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
// Hacky hack: Reset any stretch effects as those are applied during the draw pass
// instead of being "stateful" like other RenderNode properties
renderNode.clearStretch();
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
// Reverse left/top translation done by drawable canvas, which will

View File

@@ -693,6 +693,32 @@ public final class RenderNode {
throw new IllegalArgumentException("Unrecognized outline?");
}
/** @hide */
public boolean clearStretch() {
return nClearStretch(mNativeRenderNode);
}
/** @hide */
public boolean stretch(float left, float top, float right, float bottom,
float vecX, float vecY, float maxStretchAmount) {
if (1.0 < vecX || vecX < -1.0) {
throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
}
if (1.0 < vecY || vecY < -1.0) {
throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
}
if (top <= bottom || right <= left) {
throw new IllegalArgumentException(
"Stretch region must not be empty, got "
+ new RectF(left, top, right, bottom).toString());
}
if (maxStretchAmount <= 0.0f) {
throw new IllegalArgumentException(
"The max stretch amount must be >0, got " + maxStretchAmount);
}
return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
}
/**
* Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()}
* and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with
@@ -1637,6 +1663,13 @@ public final class RenderNode {
@CriticalNative
private static native boolean nSetOutlineNone(long renderNode);
@CriticalNative
private static native boolean nClearStretch(long renderNode);
@CriticalNative
private static native boolean nStretch(long renderNode, float left, float top, float right,
float bottom, float vecX, float vecY, float maxStretch);
@CriticalNative
private static native boolean nHasShadow(long renderNode);

View File

@@ -435,6 +435,7 @@ cc_defaults {
"canvas/CanvasFrontend.cpp",
"canvas/CanvasOpBuffer.cpp",
"canvas/CanvasOpRasterizer.cpp",
"effects/StretchEffect.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",

View File

@@ -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 @@ public:
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 @@ private:
SkBlendMode mMode;
sk_sp<SkColorFilter> mColorFilter;
sk_sp<SkImageFilter> mImageFilter;
StretchEffect mStretchEffect;
};
/*

View File

@@ -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

View File

@@ -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

View File

@@ -166,6 +166,31 @@ static jboolean android_view_RenderNode_setOutlineNone(CRITICAL_JNI_PARAMS_COMMA
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 @@ static const JNINativeMethod gMethods[] = {
{ "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 },

View File

@@ -20,6 +20,8 @@
#include "SkiaDisplayList.h"
#include "utils/TraceUtils.h"
#include <include/effects/SkImageFilters.h>
#include <optional>
namespace android {
@@ -172,11 +174,25 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip
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;