blob: d4fd1053b17fcd5406638630f63d85919d23404d [file] [log] [blame]
John Reck5cb290b2021-02-01 13:47:31 -05001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "StretchEffect.h"
Nader Jawad6701a602021-02-23 18:14:22 -080018#include <SkImageFilter.h>
19#include <SkRefCnt.h>
20#include <SkRuntimeEffect.h>
21#include <SkString.h>
22#include <SkSurface.h>
23#include <include/effects/SkImageFilters.h>
24
25#include <memory>
John Reck5cb290b2021-02-01 13:47:31 -050026
27namespace android::uirenderer {
28
Nader Jawad6701a602021-02-23 18:14:22 -080029static const SkString stretchShader = SkString(R"(
30 uniform shader uContentTexture;
31
32 // multiplier to apply to scale effect
33 uniform float uMaxStretchIntensity;
34
35 // Maximum percentage to stretch beyond bounds of target
36 uniform float uStretchAffectedDist;
37
38 // Distance stretched as a function of the normalized overscroll times
39 // scale intensity
40 uniform float uDistanceStretchedX;
41 uniform float uDistanceStretchedY;
42 uniform float uDistDiffX;
43
44 // Difference between the peak stretch amount and overscroll amount normalized
45 uniform float uDistDiffY;
46
47 // Horizontal offset represented as a ratio of pixels divided by the target width
48 uniform float uScrollX;
49 // Vertical offset represented as a ratio of pixels divided by the target height
50 uniform float uScrollY;
51
52 // Normalized overscroll amount in the horizontal direction
53 uniform float uOverscrollX;
54
55 // Normalized overscroll amount in the vertical direction
56 uniform float uOverscrollY;
57 uniform float viewportWidth; // target height in pixels
58 uniform float viewportHeight; // target width in pixels
59
60 void computeOverscrollStart(
61 out float outPos,
62 float inPos,
63 float overscroll,
64 float uStretchAffectedDist,
65 float distanceStretched
66 ) {
67 float offsetPos = uStretchAffectedDist - inPos;
68 float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos);
69 float stretchIntensity = overscroll * posBasedVariation;
70 outPos = distanceStretched - (offsetPos / (1. + stretchIntensity));
71 }
72
73 void computeOverscrollEnd(
74 out float outPos,
75 float inPos,
76 float overscroll,
77 float reverseStretchDist,
78 float uStretchAffectedDist,
79 float distanceStretched
80 ) {
81 float offsetPos = inPos - reverseStretchDist;
82 float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos));
83 float stretchIntensity = (-overscroll) * posBasedVariation;
84 outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
85 }
86
87 void computeOverscroll(
88 out float outPos,
89 float inPos,
90 float overscroll,
91 float uStretchAffectedDist,
92 float distanceStretched,
93 float distanceDiff
94 ) {
95 if (overscroll > 0) {
96 if (inPos <= uStretchAffectedDist) {
97 computeOverscrollStart(
98 outPos,
99 inPos,
100 overscroll,
101 uStretchAffectedDist,
102 distanceStretched
103 );
104 } else if (inPos >= distanceStretched) {
105 outPos = distanceDiff + inPos;
106 }
107 }
108 if (overscroll < 0) {
109 float stretchAffectedDist = 1. - uStretchAffectedDist;
110 if (inPos >= stretchAffectedDist) {
111 computeOverscrollEnd(
112 outPos,
113 inPos,
114 overscroll,
115 stretchAffectedDist,
116 uStretchAffectedDist,
117 distanceStretched
118 );
119 } else if (inPos < stretchAffectedDist) {
120 outPos = -distanceDiff + inPos;
121 }
122 }
123 }
124
125 vec4 main(vec2 coord) {
126 // Normalize SKSL pixel coordinate into a unit vector
127 float inU = coord.x / viewportWidth;
128 float inV = coord.y / viewportHeight;
129 float outU;
130 float outV;
131 float stretchIntensity;
132 // Add the normalized scroll position within scrolling list
133 inU += uScrollX;
134 inV += uScrollY;
135 outU = inU;
136 outV = inV;
137 computeOverscroll(
138 outU,
139 inU,
140 uOverscrollX,
141 uStretchAffectedDist,
142 uDistanceStretchedX,
143 uDistDiffX
144 );
145 computeOverscroll(
146 outV,
147 inV,
148 uOverscrollY,
149 uStretchAffectedDist,
150 uDistanceStretchedY,
151 uDistDiffY
152 );
153 coord.x = outU * viewportWidth;
154 coord.y = outV * viewportHeight;
155 return sample(uContentTexture, coord);
156 })");
157
158static const float ZERO = 0.f;
159
160sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapshotImage) const {
161 if (isEmpty()) {
162 return nullptr;
163 }
164
165 if (mStretchFilter != nullptr) {
166 return mStretchFilter;
167 }
168
169 float distanceNotStretchedX = maxStretchAmount / stretchArea.width();
170 float distanceNotStretchedY = maxStretchAmount / stretchArea.height();
171 float normOverScrollDistX = mStretchDirection.x();
172 float normOverScrollDistY = mStretchDirection.y();
173 float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX));
174 float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY));
175 float diffX = distanceStretchedX - distanceNotStretchedX;
176 float diffY = distanceStretchedY - distanceNotStretchedY;
177 float viewportWidth = stretchArea.width();
178 float viewportHeight = stretchArea.height();
179
180 if (mBuilder == nullptr) {
181 mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
182 }
183
184 mBuilder->child("uContentTexture") = snapshotImage->makeShader(
185 SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
186 mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1);
187 mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
188 mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
189 mBuilder->uniform("uDistDiffX").set(&diffX, 1);
190 mBuilder->uniform("uDistDiffY").set(&diffY, 1);
191 mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
192 mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
193 mBuilder->uniform("uScrollX").set(&ZERO, 1);
194 mBuilder->uniform("uScrollY").set(&ZERO, 1);
195 mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
196 mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
197
198 mStretchFilter = SkImageFilters::Shader(mBuilder->makeShader(nullptr, false),
199 SkRect{0, 0, viewportWidth, viewportHeight});
200
201 return mStretchFilter;
202}
203
204sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
205 const static SkRuntimeEffect::Result instance = SkRuntimeEffect::Make(stretchShader);
206 return instance.effect;
John Reck5cb290b2021-02-01 13:47:31 -0500207}
208
209} // namespace android::uirenderer