blob: 2990c9e59fec8e55bb24ee5727381359712996d2 [file] [log] [blame]
/*
* 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.
*/
package com.android.test.hwui;
import android.annotation.Nullable;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderEffect;
import android.graphics.RuntimeShader;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class StretchShaderActivity extends Activity {
private static final float MAX_STRETCH_INTENSITY = 1.5f;
private static final float STRETCH_AFFECTED_DISTANCE = 1.0f;
private float mScrollX = 0f;
private float mScrollY = 0f;
private float mMaxStretchIntensity = MAX_STRETCH_INTENSITY;
private float mStretchAffectedDistance = STRETCH_AFFECTED_DISTANCE;
private float mOverscrollX = 25f;
private float mOverscrollY = 25f;
private RuntimeShader mRuntimeShader;
private ImageView mImageView;
private ImageView mTestImageView;
private Bitmap mBitmap;
private StretchDrawable mStretchDrawable = new StretchDrawable();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap();
mRuntimeShader = new RuntimeShader(SKSL);
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
mRuntimeShader.setInputShader("uContentTexture", bitmapShader);
mImageView = new ImageView(this);
mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader));
mImageView.setImageDrawable(new ColorDrawable(Color.CYAN));
TextView overscrollXText = new TextView(this);
overscrollXText.setText("Overscroll X");
SeekBar overscrollXBar = new SeekBar(this);
overscrollXBar.setProgress(0);
overscrollXBar.setMin(-50);
overscrollXBar.setMax(50);
overscrollXBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mOverscrollX = progress;
overscrollXText.setText("Overscroll X: " + mOverscrollX);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView overscrollYText = new TextView(this);
overscrollYText.setText("Overscroll Y");
SeekBar overscrollYBar = new SeekBar(this);
overscrollYBar.setProgress(0);
overscrollYBar.setMin(-50);
overscrollYBar.setMax(50);
overscrollYBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mOverscrollY = progress;
overscrollYText.setText("Overscroll Y: " + mOverscrollY);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView scrollXText = new TextView(this);
scrollXText.setText("Scroll X");
SeekBar scrollXSeekBar = new SeekBar(this);
scrollXSeekBar.setMin(0);
scrollXSeekBar.setMax(100);
scrollXSeekBar.setProgress(0);
scrollXSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mScrollX = (progress / 100f);
scrollXText.setText("Scroll X: " + mScrollY);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView scrollYText = new TextView(this);
scrollYText.setText("Scroll Y");
SeekBar scrollYSeekBar = new SeekBar(this);
scrollYSeekBar.setMin(0);
scrollYSeekBar.setMax(100);
scrollYSeekBar.setProgress(0);
scrollYSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mScrollY = (progress / 100f);
scrollYText.setText("Scroll Y: " + mScrollY);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView stretchIntensityText = new TextView(this);
int stretchProgress = (int) (mMaxStretchIntensity * 100);
stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity);
SeekBar stretchIntensitySeekbar = new SeekBar(this);
stretchIntensitySeekbar.setProgress(stretchProgress);
stretchIntensitySeekbar.setMin(1);
stretchIntensitySeekbar.setMax((int) (MAX_STRETCH_INTENSITY * 100));
stretchIntensitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mMaxStretchIntensity = progress / 100f;
stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView stretchDistanceText = new TextView(this);
stretchDistanceText.setText("StretchDistance");
SeekBar stretchDistanceSeekbar = new SeekBar(this);
stretchDistanceSeekbar.setMin(0);
stretchDistanceSeekbar.setProgress((int) (mStretchAffectedDistance * 100));
stretchDistanceSeekbar.setMax(100);
stretchDistanceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mStretchAffectedDistance = progress / 100f;
stretchDistanceText.setText("StretchDistance: " + mStretchAffectedDistance);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
linearLayout.addView(mImageView,
new LinearLayout.LayoutParams(
mBitmap.getWidth(),
mBitmap.getHeight())
);
linearLayout.addView(overscrollXText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(overscrollXBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(overscrollYText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(overscrollYBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(scrollXText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(scrollXSeekBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(scrollYText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(scrollYSeekBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(stretchIntensityText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(stretchIntensitySeekbar,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(stretchDistanceText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(stretchDistanceSeekbar,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
ImageView test = new ImageView(this);
mStretchDrawable.setBitmap(mBitmap);
test.setImageDrawable(mStretchDrawable);
mTestImageView = test;
linearLayout.addView(test,
new LinearLayout.LayoutParams(mBitmap.getWidth(), mBitmap.getHeight()));
setContentView(linearLayout);
}
@Override
protected void onResume() {
super.onResume();
mImageView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
updateShader();
mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
private void updateShader() {
final float width = mImageView.getWidth();
final float height = mImageView.getHeight();
final float distanceNotStretched = mStretchAffectedDistance;
final float normOverScrollDistX = mOverscrollX / width;
final float normOverScrollDistY = mOverscrollY / height;
final float distanceStretchedX =
mStretchAffectedDistance
/ (1 + Math.abs(normOverScrollDistX) * mMaxStretchIntensity);
final float distanceStretchedY =
mStretchAffectedDistance
/ (1 + Math.abs(normOverScrollDistY) * mMaxStretchIntensity);
final float diffX = distanceStretchedX - distanceNotStretched;
final float diffY = distanceStretchedY - distanceNotStretched;
float uScrollX = mScrollX;
float uScrollY = mScrollY;
mRuntimeShader.setFloatUniform("uMaxStretchIntensity", mMaxStretchIntensity);
mRuntimeShader.setFloatUniform("uStretchAffectedDist", mStretchAffectedDistance);
mRuntimeShader.setFloatUniform("uDistanceStretchedX", distanceStretchedX);
mRuntimeShader.setFloatUniform("uDistanceStretchedY", distanceStretchedY);
mRuntimeShader.setFloatUniform("uDistDiffX", diffX);
mRuntimeShader.setFloatUniform("uDistDiffY", diffY);
mRuntimeShader.setFloatUniform("uOverscrollX", normOverScrollDistX);
mRuntimeShader.setFloatUniform("uOverscrollY", normOverScrollDistY);
mRuntimeShader.setFloatUniform("uScrollX", uScrollX);
mRuntimeShader.setFloatUniform("uScrollY", uScrollY);
mRuntimeShader.setFloatUniform("viewportWidth", width);
mRuntimeShader.setFloatUniform("viewportHeight", height);
mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader));
mStretchDrawable.setStretchDistance(mStretchAffectedDistance);
mStretchDrawable.setOverscrollX(normOverScrollDistX);
mStretchDrawable.setOverscrollY(normOverScrollDistY);
}
private static class StretchDrawable extends Drawable {
private float mStretchDistance = 0;
private float mOverScrollX = 0f;
private float mOverScrollY = 0f;
private Bitmap mBitmap = null;
public void setStretchDistance(float stretchDistance) {
mStretchDistance = stretchDistance;
invalidateSelf();
}
public void setOverscrollX(float overscrollX) {
mOverScrollX = overscrollX;
invalidateSelf();
}
public void setOverscrollY(float overscrollY) {
mOverScrollY = overscrollY;
invalidateSelf();
}
public void setBitmap(Bitmap bitmap) {
mBitmap = bitmap;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) {
Rect bounds = getBounds();
((RecordingCanvas) canvas).mNode.stretch(
mOverScrollX,
mOverScrollY,
mStretchDistance,
mStretchDistance
);
}
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0f, 0f, null);
}
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
}
private static final String SKSL = "uniform shader uContentTexture;\n"
+ "uniform float uMaxStretchIntensity; // multiplier to apply to scale effect\n"
+ "uniform float uStretchAffectedDist; // Maximum percentage to stretch beyond bounds"
+ " of target\n"
+ "\n"
+ "// Distance stretched as a function of the normalized overscroll times scale "
+ "intensity\n"
+ "uniform float uDistanceStretchedX;\n"
+ "uniform float uDistanceStretchedY;\n"
+ "uniform float uDistDiffX;\n"
+ "uniform float uDistDiffY; // Difference between the peak stretch amount and "
+ "overscroll amount normalized\n"
+ "uniform float uScrollX; // Horizontal offset represented as a ratio of pixels "
+ "divided by the target width\n"
+ "uniform float uScrollY; // Vertical offset represented as a ratio of pixels "
+ "divided by the target height\n"
+ "uniform float uOverscrollX; // Normalized overscroll amount in the horizontal "
+ "direction\n"
+ "uniform float uOverscrollY; // Normalized overscroll amount in the vertical "
+ "direction\n"
+ "\n"
+ "uniform float viewportWidth; // target height in pixels\n"
+ "uniform float viewportHeight; // target width in pixels\n"
+ "\n"
+ "vec4 main(vec2 coord) {\n"
+ "\n"
+ " // Normalize SKSL pixel coordinate into a unit vector\n"
+ " vec2 uv = vec2(coord.x / viewportWidth, coord.y / viewportHeight);\n"
+ " float inU = uv.x;\n"
+ " float inV = uv.y;\n"
+ " float outU;\n"
+ " float outV;\n"
+ " float stretchIntensity;\n"
+ "\n"
+ " // Add the normalized scroll position within scrolling list\n"
+ " inU += uScrollX;\n"
+ " inV += uScrollY;\n"
+ "\n"
+ " outU = inU;\n"
+ " outV = inV;\n"
+ " if (uOverscrollX > 0) {\n"
+ " if (inU <= uStretchAffectedDist) {\n"
+ " inU = uStretchAffectedDist - inU;\n"
+ " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inU);\n"
+ " stretchIntensity = uMaxStretchIntensity * uOverscrollX * "
+ "posBasedVariation;\n"
+ " outU = uDistanceStretchedX - (inU / (1. + stretchIntensity));\n"
+ " } else {\n"
+ " outU = uDistDiffX + inU;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (uOverscrollX < 0) {\n"
+ " float stretchAffectedDist = 1. - uStretchAffectedDist;\n"
+ " if (inU >= stretchAffectedDist) {\n"
+ " inU = inU - stretchAffectedDist;\n"
+ " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, "
+ "inU));\n"
+ " stretchIntensity = uMaxStretchIntensity * (-uOverscrollX) * "
+ "posBasedVariation;\n"
+ " outU = 1 - (uDistanceStretchedX - (inU / (1. + stretchIntensity)))"
+ ";\n"
+ " } else if (inU < stretchAffectedDist) {\n"
+ " outU = -uDistDiffX + inU;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (uOverscrollY > 0) {\n"
+ " if (inV <= uStretchAffectedDist) {\n"
+ " inV = uStretchAffectedDist - inV;\n"
+ " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inV);\n"
+ " stretchIntensity = uMaxStretchIntensity * uOverscrollY * "
+ "posBasedVariation;\n"
+ " outV = uDistanceStretchedY - (inV / (1. + stretchIntensity));\n"
+ " } else if (inV >= uStretchAffectedDist) {\n"
+ " outV = uDistDiffY + inV;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (uOverscrollY < 0) {\n"
+ " float stretchAffectedDist = 1. - uStretchAffectedDist;\n"
+ " if (inV >= stretchAffectedDist) {\n"
+ " inV = inV - stretchAffectedDist;\n"
+ " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, inV));\n"
+ " stretchIntensity = uMaxStretchIntensity * (-uOverscrollY) * "
+ "posBasedVariation;\n"
+ " outV = 1 - (uDistanceStretchedY - (inV / (1. + stretchIntensity)));\n"
+ " } else if (inV < stretchAffectedDist) {\n"
+ " outV = -uDistDiffY + inV;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " uv.x = outU;\n"
+ " uv.y = outV;\n"
+ " coord.x = uv.x * viewportWidth;\n"
+ " coord.y = uv.y * viewportHeight;\n"
+ " return uContentTexture.eval(coord);\n"
+ "}";
}