| /* | |
| * 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" | |
| #include <SkImageFilter.h> | |
| #include <SkRefCnt.h> | |
| #include <SkRuntimeEffect.h> | |
| #include <SkString.h> | |
| #include <SkSurface.h> | |
| #include <include/effects/SkImageFilters.h> | |
| #include <memory> | |
| namespace android::uirenderer { | |
| static const SkString stretchShader = SkString(R"( | |
| uniform shader uContentTexture; | |
| // multiplier to apply to scale effect | |
| uniform float uMaxStretchIntensity; | |
| // Maximum percentage to stretch beyond bounds of target | |
| uniform float uStretchAffectedDistX; | |
| uniform float uStretchAffectedDistY; | |
| // Distance stretched as a function of the normalized overscroll times | |
| // scale intensity | |
| uniform float uDistanceStretchedX; | |
| uniform float uDistanceStretchedY; | |
| uniform float uInverseDistanceStretchedX; | |
| uniform float uInverseDistanceStretchedY; | |
| uniform float uDistDiffX; | |
| // Difference between the peak stretch amount and overscroll amount normalized | |
| uniform float uDistDiffY; | |
| // Horizontal offset represented as a ratio of pixels divided by the target width | |
| uniform float uScrollX; | |
| // Vertical offset represented as a ratio of pixels divided by the target height | |
| uniform float uScrollY; | |
| // Normalized overscroll amount in the horizontal direction | |
| uniform float uOverscrollX; | |
| // Normalized overscroll amount in the vertical direction | |
| uniform float uOverscrollY; | |
| uniform float viewportWidth; // target height in pixels | |
| uniform float viewportHeight; // target width in pixels | |
| // uInterpolationStrength is the intensity of the interpolation. | |
| // if uInterpolationStrength is 0, then the stretch is constant for all the | |
| // uStretchAffectedDist. if uInterpolationStrength is 1, then stretch intensity | |
| // is interpolated based on the pixel position in the uStretchAffectedDist area; | |
| // The closer we are from the scroll anchor point, the more it stretches, | |
| // and the other way around. | |
| uniform float uInterpolationStrength; | |
| float easeIn(float t, float d) { | |
| return t * d; | |
| } | |
| float computeOverscrollStart( | |
| float inPos, | |
| float overscroll, | |
| float uStretchAffectedDist, | |
| float uInverseStretchAffectedDist, | |
| float distanceStretched, | |
| float interpolationStrength | |
| ) { | |
| float offsetPos = uStretchAffectedDist - inPos; | |
| float posBasedVariation = mix( | |
| 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength); | |
| float stretchIntensity = overscroll * posBasedVariation; | |
| return distanceStretched - (offsetPos / (1. + stretchIntensity)); | |
| } | |
| float computeOverscrollEnd( | |
| float inPos, | |
| float overscroll, | |
| float reverseStretchDist, | |
| float uStretchAffectedDist, | |
| float uInverseStretchAffectedDist, | |
| float distanceStretched, | |
| float interpolationStrength, | |
| float viewportDimension | |
| ) { | |
| float offsetPos = inPos - reverseStretchDist; | |
| float posBasedVariation = mix( | |
| 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength); | |
| float stretchIntensity = (-overscroll) * posBasedVariation; | |
| return viewportDimension - (distanceStretched - (offsetPos / (1. + stretchIntensity))); | |
| } | |
| // Prefer usage of return values over out parameters as it enables | |
| // SKSL to properly inline method calls and works around potential GPU | |
| // driver issues on Wembly. See b/182566543 for details | |
| float computeOverscroll( | |
| float inPos, | |
| float overscroll, | |
| float uStretchAffectedDist, | |
| float uInverseStretchAffectedDist, | |
| float distanceStretched, | |
| float distanceDiff, | |
| float interpolationStrength, | |
| float viewportDimension | |
| ) { | |
| if (overscroll > 0) { | |
| if (inPos <= uStretchAffectedDist) { | |
| return computeOverscrollStart( | |
| inPos, | |
| overscroll, | |
| uStretchAffectedDist, | |
| uInverseStretchAffectedDist, | |
| distanceStretched, | |
| interpolationStrength | |
| ); | |
| } else { | |
| return distanceDiff + inPos; | |
| } | |
| } else if (overscroll < 0) { | |
| float stretchAffectedDist = viewportDimension - uStretchAffectedDist; | |
| if (inPos >= stretchAffectedDist) { | |
| return computeOverscrollEnd( | |
| inPos, | |
| overscroll, | |
| stretchAffectedDist, | |
| uStretchAffectedDist, | |
| uInverseStretchAffectedDist, | |
| distanceStretched, | |
| interpolationStrength, | |
| viewportDimension | |
| ); | |
| } else { | |
| return -distanceDiff + inPos; | |
| } | |
| } else { | |
| return inPos; | |
| } | |
| } | |
| vec4 main(vec2 coord) { | |
| float inU = coord.x; | |
| float inV = coord.y; | |
| float outU; | |
| float outV; | |
| inU += uScrollX; | |
| inV += uScrollY; | |
| outU = computeOverscroll( | |
| inU, | |
| uOverscrollX, | |
| uStretchAffectedDistX, | |
| uInverseDistanceStretchedX, | |
| uDistanceStretchedX, | |
| uDistDiffX, | |
| uInterpolationStrength, | |
| viewportWidth | |
| ); | |
| outV = computeOverscroll( | |
| inV, | |
| uOverscrollY, | |
| uStretchAffectedDistY, | |
| uInverseDistanceStretchedY, | |
| uDistanceStretchedY, | |
| uDistDiffY, | |
| uInterpolationStrength, | |
| viewportHeight | |
| ); | |
| coord.x = outU; | |
| coord.y = outV; | |
| return uContentTexture.eval(coord); | |
| })"); | |
| static const float ZERO = 0.f; | |
| static const float INTERPOLATION_STRENGTH_VALUE = 0.7f; | |
| static const char CONTENT_TEXTURE[] = "uContentTexture"; | |
| sk_sp<SkShader> StretchEffect::getShader(float width, float height, | |
| const sk_sp<SkImage>& snapshotImage, | |
| const SkMatrix* matrix) const { | |
| if (isEmpty()) { | |
| return nullptr; | |
| } | |
| float normOverScrollDistX = mStretchDirection.x(); | |
| float normOverScrollDistY = mStretchDirection.y(); | |
| float distanceStretchedX = width / (1 + abs(normOverScrollDistX)); | |
| float distanceStretchedY = height / (1 + abs(normOverScrollDistY)); | |
| float inverseDistanceStretchedX = 1.f / width; | |
| float inverseDistanceStretchedY = 1.f / height; | |
| float diffX = distanceStretchedX - width; | |
| float diffY = distanceStretchedY - height; | |
| if (mBuilder == nullptr) { | |
| mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect()); | |
| } | |
| mBuilder->child(CONTENT_TEXTURE) = | |
| snapshotImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, | |
| SkSamplingOptions(SkFilterMode::kLinear), matrix); | |
| mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1); | |
| mBuilder->uniform("uStretchAffectedDistX").set(&width, 1); | |
| mBuilder->uniform("uStretchAffectedDistY").set(&height, 1); | |
| mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1); | |
| mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1); | |
| mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1); | |
| mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1); | |
| mBuilder->uniform("uDistDiffX").set(&diffX, 1); | |
| mBuilder->uniform("uDistDiffY").set(&diffY, 1); | |
| mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1); | |
| mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1); | |
| mBuilder->uniform("uScrollX").set(&ZERO, 1); | |
| mBuilder->uniform("uScrollY").set(&ZERO, 1); | |
| mBuilder->uniform("viewportWidth").set(&width, 1); | |
| mBuilder->uniform("viewportHeight").set(&height, 1); | |
| auto result = mBuilder->makeShader(); | |
| mBuilder->child(CONTENT_TEXTURE) = nullptr; | |
| return result; | |
| } | |
| sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() { | |
| const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader); | |
| 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) { | |
| if (normalizedInput >= distanceStretched) { | |
| return normalizedInput - distanceDiff; | |
| } | |
| return reverseMapStart(overscroll, normalizedInput); | |
| } | |
| if (overscroll < 0) { | |
| distanceStretched = 1.f - distanceStretched; | |
| if (normalizedInput <= distanceStretched) { | |
| return normalizedInput + distanceDiff; | |
| } | |
| return reverseMapEnd(overscroll, normalizedInput); | |
| } | |
| 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 |