1 /* 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 package com.android.test.hwui; 18 19 import android.annotation.Nullable; 20 import android.app.Activity; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapShader; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.ColorFilter; 26 import android.graphics.RecordingCanvas; 27 import android.graphics.Rect; 28 import android.graphics.RenderEffect; 29 import android.graphics.RuntimeShader; 30 import android.graphics.Shader; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.ColorDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.os.Bundle; 35 import android.view.ViewGroup; 36 import android.view.ViewTreeObserver; 37 import android.widget.ImageView; 38 import android.widget.LinearLayout; 39 import android.widget.SeekBar; 40 import android.widget.TextView; 41 42 public class StretchShaderActivity extends Activity { 43 44 private static final float MAX_STRETCH_INTENSITY = 1.5f; 45 private static final float STRETCH_AFFECTED_DISTANCE = 1.0f; 46 47 private float mScrollX = 0f; 48 private float mScrollY = 0f; 49 50 private float mMaxStretchIntensity = MAX_STRETCH_INTENSITY; 51 private float mStretchAffectedDistance = STRETCH_AFFECTED_DISTANCE; 52 53 private float mOverscrollX = 25f; 54 private float mOverscrollY = 25f; 55 56 private RuntimeShader mRuntimeShader; 57 private ImageView mImageView; 58 private ImageView mTestImageView; 59 60 private Bitmap mBitmap; 61 62 private StretchDrawable mStretchDrawable = new StretchDrawable(); 63 64 @Override onCreate(@ullable Bundle savedInstanceState)65 protected void onCreate(@Nullable Bundle savedInstanceState) { 66 super.onCreate(savedInstanceState); 67 68 LinearLayout linearLayout = new LinearLayout(this); 69 linearLayout.setOrientation(LinearLayout.VERTICAL); 70 71 mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap(); 72 mRuntimeShader = new RuntimeShader(SKSL); 73 74 BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, 75 Shader.TileMode.CLAMP); 76 mRuntimeShader.setInputShader("uContentTexture", bitmapShader); 77 78 mImageView = new ImageView(this); 79 80 mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); 81 mImageView.setImageDrawable(new ColorDrawable(Color.CYAN)); 82 83 TextView overscrollXText = new TextView(this); 84 overscrollXText.setText("Overscroll X"); 85 86 SeekBar overscrollXBar = new SeekBar(this); 87 overscrollXBar.setProgress(0); 88 overscrollXBar.setMin(-50); 89 overscrollXBar.setMax(50); 90 overscrollXBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 91 @Override 92 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 93 mOverscrollX = progress; 94 overscrollXText.setText("Overscroll X: " + mOverscrollX); 95 updateShader(); 96 } 97 98 @Override 99 public void onStartTrackingTouch(SeekBar seekBar) { 100 101 } 102 103 @Override 104 public void onStopTrackingTouch(SeekBar seekBar) { 105 106 } 107 }); 108 109 TextView overscrollYText = new TextView(this); 110 overscrollYText.setText("Overscroll Y"); 111 112 SeekBar overscrollYBar = new SeekBar(this); 113 overscrollYBar.setProgress(0); 114 overscrollYBar.setMin(-50); 115 overscrollYBar.setMax(50); 116 overscrollYBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 117 @Override 118 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 119 mOverscrollY = progress; 120 overscrollYText.setText("Overscroll Y: " + mOverscrollY); 121 updateShader(); 122 } 123 124 @Override 125 public void onStartTrackingTouch(SeekBar seekBar) { 126 127 } 128 129 @Override 130 public void onStopTrackingTouch(SeekBar seekBar) { 131 132 } 133 }); 134 135 TextView scrollXText = new TextView(this); 136 scrollXText.setText("Scroll X"); 137 SeekBar scrollXSeekBar = new SeekBar(this); 138 scrollXSeekBar.setMin(0); 139 scrollXSeekBar.setMax(100); 140 scrollXSeekBar.setProgress(0); 141 scrollXSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 142 @Override 143 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 144 mScrollX = (progress / 100f); 145 scrollXText.setText("Scroll X: " + mScrollY); 146 updateShader(); 147 } 148 149 @Override 150 public void onStartTrackingTouch(SeekBar seekBar) { 151 152 } 153 154 @Override 155 public void onStopTrackingTouch(SeekBar seekBar) { 156 157 } 158 }); 159 160 TextView scrollYText = new TextView(this); 161 scrollYText.setText("Scroll Y"); 162 SeekBar scrollYSeekBar = new SeekBar(this); 163 scrollYSeekBar.setMin(0); 164 scrollYSeekBar.setMax(100); 165 scrollYSeekBar.setProgress(0); 166 scrollYSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 167 @Override 168 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 169 mScrollY = (progress / 100f); 170 scrollYText.setText("Scroll Y: " + mScrollY); 171 updateShader(); 172 } 173 174 @Override 175 public void onStartTrackingTouch(SeekBar seekBar) { 176 177 } 178 179 @Override 180 public void onStopTrackingTouch(SeekBar seekBar) { 181 182 } 183 }); 184 185 TextView stretchIntensityText = new TextView(this); 186 int stretchProgress = (int) (mMaxStretchIntensity * 100); 187 stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity); 188 SeekBar stretchIntensitySeekbar = new SeekBar(this); 189 stretchIntensitySeekbar.setProgress(stretchProgress); 190 stretchIntensitySeekbar.setMin(1); 191 stretchIntensitySeekbar.setMax((int) (MAX_STRETCH_INTENSITY * 100)); 192 stretchIntensitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 193 @Override 194 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 195 mMaxStretchIntensity = progress / 100f; 196 stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity); 197 updateShader(); 198 } 199 200 @Override 201 public void onStartTrackingTouch(SeekBar seekBar) { 202 203 } 204 205 @Override 206 public void onStopTrackingTouch(SeekBar seekBar) { 207 208 } 209 }); 210 211 TextView stretchDistanceText = new TextView(this); 212 stretchDistanceText.setText("StretchDistance"); 213 SeekBar stretchDistanceSeekbar = new SeekBar(this); 214 stretchDistanceSeekbar.setMin(0); 215 stretchDistanceSeekbar.setProgress((int) (mStretchAffectedDistance * 100)); 216 stretchDistanceSeekbar.setMax(100); 217 stretchDistanceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 218 @Override 219 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 220 mStretchAffectedDistance = progress / 100f; 221 stretchDistanceText.setText("StretchDistance: " + mStretchAffectedDistance); 222 updateShader(); 223 } 224 225 @Override 226 public void onStartTrackingTouch(SeekBar seekBar) { 227 228 } 229 230 @Override 231 public void onStopTrackingTouch(SeekBar seekBar) { 232 233 } 234 }); 235 236 237 linearLayout.addView(mImageView, 238 new LinearLayout.LayoutParams( 239 mBitmap.getWidth(), 240 mBitmap.getHeight()) 241 ); 242 243 linearLayout.addView(overscrollXText, 244 new LinearLayout.LayoutParams( 245 LinearLayout.LayoutParams.WRAP_CONTENT, 246 LinearLayout.LayoutParams.WRAP_CONTENT 247 )); 248 linearLayout.addView(overscrollXBar, 249 new LinearLayout.LayoutParams( 250 ViewGroup.LayoutParams.MATCH_PARENT, 251 ViewGroup.LayoutParams.WRAP_CONTENT 252 ) 253 ); 254 255 linearLayout.addView(overscrollYText, 256 new LinearLayout.LayoutParams( 257 LinearLayout.LayoutParams.WRAP_CONTENT, 258 LinearLayout.LayoutParams.WRAP_CONTENT 259 )); 260 linearLayout.addView(overscrollYBar, 261 new LinearLayout.LayoutParams( 262 ViewGroup.LayoutParams.MATCH_PARENT, 263 ViewGroup.LayoutParams.WRAP_CONTENT 264 ) 265 ); 266 267 linearLayout.addView(scrollXText, 268 new LinearLayout.LayoutParams( 269 LinearLayout.LayoutParams.WRAP_CONTENT, 270 LinearLayout.LayoutParams.WRAP_CONTENT 271 )); 272 273 linearLayout.addView(scrollXSeekBar, 274 new LinearLayout.LayoutParams( 275 ViewGroup.LayoutParams.MATCH_PARENT, 276 ViewGroup.LayoutParams.WRAP_CONTENT 277 )); 278 279 linearLayout.addView(scrollYText, 280 new LinearLayout.LayoutParams( 281 LinearLayout.LayoutParams.WRAP_CONTENT, 282 LinearLayout.LayoutParams.WRAP_CONTENT 283 )); 284 285 linearLayout.addView(scrollYSeekBar, 286 new LinearLayout.LayoutParams( 287 ViewGroup.LayoutParams.MATCH_PARENT, 288 ViewGroup.LayoutParams.WRAP_CONTENT 289 )); 290 291 linearLayout.addView(stretchIntensityText, 292 new LinearLayout.LayoutParams( 293 LinearLayout.LayoutParams.WRAP_CONTENT, 294 LinearLayout.LayoutParams.WRAP_CONTENT 295 ) 296 ); 297 298 linearLayout.addView(stretchIntensitySeekbar, 299 new LinearLayout.LayoutParams( 300 LinearLayout.LayoutParams.MATCH_PARENT, 301 LinearLayout.LayoutParams.WRAP_CONTENT 302 ) 303 ); 304 305 linearLayout.addView(stretchDistanceText, 306 new LinearLayout.LayoutParams( 307 LinearLayout.LayoutParams.WRAP_CONTENT, 308 LinearLayout.LayoutParams.WRAP_CONTENT 309 )); 310 311 linearLayout.addView(stretchDistanceSeekbar, 312 new LinearLayout.LayoutParams( 313 LinearLayout.LayoutParams.MATCH_PARENT, 314 LinearLayout.LayoutParams.WRAP_CONTENT 315 )); 316 317 ImageView test = new ImageView(this); 318 mStretchDrawable.setBitmap(mBitmap); 319 test.setImageDrawable(mStretchDrawable); 320 321 mTestImageView = test; 322 linearLayout.addView(test, 323 new LinearLayout.LayoutParams(mBitmap.getWidth(), mBitmap.getHeight())); 324 325 setContentView(linearLayout); 326 327 } 328 329 @Override onResume()330 protected void onResume() { 331 super.onResume(); 332 mImageView.getViewTreeObserver().addOnPreDrawListener( 333 new ViewTreeObserver.OnPreDrawListener() { 334 @Override 335 public boolean onPreDraw() { 336 updateShader(); 337 mImageView.getViewTreeObserver().removeOnPreDrawListener(this); 338 return false; 339 } 340 }); 341 } 342 updateShader()343 private void updateShader() { 344 final float width = mImageView.getWidth(); 345 final float height = mImageView.getHeight(); 346 final float distanceNotStretched = mStretchAffectedDistance; 347 final float normOverScrollDistX = mOverscrollX / width; 348 final float normOverScrollDistY = mOverscrollY / height; 349 final float distanceStretchedX = 350 mStretchAffectedDistance 351 / (1 + Math.abs(normOverScrollDistX) * mMaxStretchIntensity); 352 final float distanceStretchedY = 353 mStretchAffectedDistance 354 / (1 + Math.abs(normOverScrollDistY) * mMaxStretchIntensity); 355 final float diffX = distanceStretchedX - distanceNotStretched; 356 final float diffY = distanceStretchedY - distanceNotStretched; 357 float uScrollX = mScrollX; 358 float uScrollY = mScrollY; 359 360 mRuntimeShader.setFloatUniform("uMaxStretchIntensity", mMaxStretchIntensity); 361 mRuntimeShader.setFloatUniform("uStretchAffectedDist", mStretchAffectedDistance); 362 mRuntimeShader.setFloatUniform("uDistanceStretchedX", distanceStretchedX); 363 mRuntimeShader.setFloatUniform("uDistanceStretchedY", distanceStretchedY); 364 mRuntimeShader.setFloatUniform("uDistDiffX", diffX); 365 mRuntimeShader.setFloatUniform("uDistDiffY", diffY); 366 mRuntimeShader.setFloatUniform("uOverscrollX", normOverScrollDistX); 367 mRuntimeShader.setFloatUniform("uOverscrollY", normOverScrollDistY); 368 mRuntimeShader.setFloatUniform("uScrollX", uScrollX); 369 mRuntimeShader.setFloatUniform("uScrollY", uScrollY); 370 mRuntimeShader.setFloatUniform("viewportWidth", width); 371 mRuntimeShader.setFloatUniform("viewportHeight", height); 372 373 mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); 374 375 mStretchDrawable.setStretchDistance(mStretchAffectedDistance); 376 mStretchDrawable.setOverscrollX(normOverScrollDistX); 377 mStretchDrawable.setOverscrollY(normOverScrollDistY); 378 } 379 380 private static class StretchDrawable extends Drawable { 381 382 private float mStretchDistance = 0; 383 private float mOverScrollX = 0f; 384 private float mOverScrollY = 0f; 385 private Bitmap mBitmap = null; 386 setStretchDistance(float stretchDistance)387 public void setStretchDistance(float stretchDistance) { 388 mStretchDistance = stretchDistance; 389 invalidateSelf(); 390 } 391 setOverscrollX(float overscrollX)392 public void setOverscrollX(float overscrollX) { 393 mOverScrollX = overscrollX; 394 invalidateSelf(); 395 } 396 setOverscrollY(float overscrollY)397 public void setOverscrollY(float overscrollY) { 398 mOverScrollY = overscrollY; 399 invalidateSelf(); 400 } 401 setBitmap(Bitmap bitmap)402 public void setBitmap(Bitmap bitmap) { 403 mBitmap = bitmap; 404 invalidateSelf(); 405 } 406 407 @Override draw(Canvas canvas)408 public void draw(Canvas canvas) { 409 if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) { 410 Rect bounds = getBounds(); 411 ((RecordingCanvas) canvas).mNode.stretch( 412 mOverScrollX, 413 mOverScrollY, 414 mStretchDistance, 415 mStretchDistance 416 ); 417 } 418 if (mBitmap != null) { 419 canvas.drawBitmap(mBitmap, 0f, 0f, null); 420 } 421 } 422 423 @Override setAlpha(int alpha)424 public void setAlpha(int alpha) { 425 426 } 427 428 @Override setColorFilter(ColorFilter colorFilter)429 public void setColorFilter(ColorFilter colorFilter) { 430 431 } 432 433 @Override getOpacity()434 public int getOpacity() { 435 return 0; 436 } 437 } 438 439 private static final String SKSL = "uniform shader uContentTexture;\n" 440 + "uniform float uMaxStretchIntensity; // multiplier to apply to scale effect\n" 441 + "uniform float uStretchAffectedDist; // Maximum percentage to stretch beyond bounds" 442 + " of target\n" 443 + "\n" 444 + "// Distance stretched as a function of the normalized overscroll times scale " 445 + "intensity\n" 446 + "uniform float uDistanceStretchedX;\n" 447 + "uniform float uDistanceStretchedY;\n" 448 + "uniform float uDistDiffX;\n" 449 + "uniform float uDistDiffY; // Difference between the peak stretch amount and " 450 + "overscroll amount normalized\n" 451 + "uniform float uScrollX; // Horizontal offset represented as a ratio of pixels " 452 + "divided by the target width\n" 453 + "uniform float uScrollY; // Vertical offset represented as a ratio of pixels " 454 + "divided by the target height\n" 455 + "uniform float uOverscrollX; // Normalized overscroll amount in the horizontal " 456 + "direction\n" 457 + "uniform float uOverscrollY; // Normalized overscroll amount in the vertical " 458 + "direction\n" 459 + "\n" 460 + "uniform float viewportWidth; // target height in pixels\n" 461 + "uniform float viewportHeight; // target width in pixels\n" 462 + "\n" 463 + "vec4 main(vec2 coord) {\n" 464 + "\n" 465 + " // Normalize SKSL pixel coordinate into a unit vector\n" 466 + " vec2 uv = vec2(coord.x / viewportWidth, coord.y / viewportHeight);\n" 467 + " float inU = uv.x;\n" 468 + " float inV = uv.y;\n" 469 + " float outU;\n" 470 + " float outV;\n" 471 + " float stretchIntensity;\n" 472 + "\n" 473 + " // Add the normalized scroll position within scrolling list\n" 474 + " inU += uScrollX;\n" 475 + " inV += uScrollY;\n" 476 + "\n" 477 + " outU = inU;\n" 478 + " outV = inV;\n" 479 + " if (uOverscrollX > 0) {\n" 480 + " if (inU <= uStretchAffectedDist) {\n" 481 + " inU = uStretchAffectedDist - inU;\n" 482 + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inU);\n" 483 + " stretchIntensity = uMaxStretchIntensity * uOverscrollX * " 484 + "posBasedVariation;\n" 485 + " outU = uDistanceStretchedX - (inU / (1. + stretchIntensity));\n" 486 + " } else {\n" 487 + " outU = uDistDiffX + inU;\n" 488 + " }\n" 489 + " }\n" 490 + "\n" 491 + " if (uOverscrollX < 0) {\n" 492 + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n" 493 + " if (inU >= stretchAffectedDist) {\n" 494 + " inU = inU - stretchAffectedDist;\n" 495 + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, " 496 + "inU));\n" 497 + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollX) * " 498 + "posBasedVariation;\n" 499 + " outU = 1 - (uDistanceStretchedX - (inU / (1. + stretchIntensity)))" 500 + ";\n" 501 + " } else if (inU < stretchAffectedDist) {\n" 502 + " outU = -uDistDiffX + inU;\n" 503 + " }\n" 504 + " }\n" 505 + "\n" 506 + " if (uOverscrollY > 0) {\n" 507 + " if (inV <= uStretchAffectedDist) {\n" 508 + " inV = uStretchAffectedDist - inV;\n" 509 + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inV);\n" 510 + " stretchIntensity = uMaxStretchIntensity * uOverscrollY * " 511 + "posBasedVariation;\n" 512 + " outV = uDistanceStretchedY - (inV / (1. + stretchIntensity));\n" 513 + " } else if (inV >= uStretchAffectedDist) {\n" 514 + " outV = uDistDiffY + inV;\n" 515 + " }\n" 516 + " }\n" 517 + "\n" 518 + " if (uOverscrollY < 0) {\n" 519 + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n" 520 + " if (inV >= stretchAffectedDist) {\n" 521 + " inV = inV - stretchAffectedDist;\n" 522 + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, inV));\n" 523 + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollY) * " 524 + "posBasedVariation;\n" 525 + " outV = 1 - (uDistanceStretchedY - (inV / (1. + stretchIntensity)));\n" 526 + " } else if (inV < stretchAffectedDist) {\n" 527 + " outV = -uDistDiffY + inV;\n" 528 + " }\n" 529 + " }\n" 530 + "\n" 531 + " uv.x = outU;\n" 532 + " uv.y = outV;\n" 533 + " coord.x = uv.x * viewportWidth;\n" 534 + " coord.y = uv.y * viewportHeight;\n" 535 + " return uContentTexture.eval(coord);\n" 536 + "}"; 537 } 538