/* * 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 #include #include #include #include #include #include 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 StretchEffect::getShader(float width, float height, const sk_sp& 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(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 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) { 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