File: StretchEffect.cpp

package info (click to toggle)
android-platform-frameworks-base 1%3A14~beta1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 326,092 kB
  • sloc: java: 2,032,373; xml: 343,016; cpp: 304,181; python: 3,683; ansic: 2,090; sh: 1,871; makefile: 117; sed: 19
file content (316 lines) | stat: -rw-r--r-- 11,702 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/*
 * 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) {
        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