blob: 3af54503d469a6d98c1abb20e314263a910f47ab [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.animation.ValueAnimator
import android.content.Context
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.ImageDecoder
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.RuntimeShader
import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
class BitmapTransitionView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val mPaint = Paint()
private val mImageA = ImageDecoder.decodeBitmap(
ImageDecoder.createSource(context.resources, R.drawable.large_photo))
private val mImageB = ImageDecoder.decodeBitmap(
ImageDecoder.createSource(context.resources, R.drawable.very_large_photo))
private val mShaderA = BitmapShader(mImageA, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
private val mShaderB = BitmapShader(mImageB, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
private val mShader = RuntimeShader(AGSL)
private var mCurrentProgress = -1f
private var mForwardProgress = true
private var mCurrentAnimator = ValueAnimator.ofFloat(-1f, 1f)
init {
isClickable = true
mCurrentAnimator.duration = 1500
mCurrentAnimator.addUpdateListener { animation ->
mCurrentProgress = animation.animatedValue as Float
postInvalidate()
}
}
override fun performClick(): Boolean {
if (super.performClick()) return true
if (mCurrentAnimator.isRunning) {
mCurrentAnimator.reverse()
return true
}
if (mForwardProgress) {
mCurrentAnimator.setFloatValues(-1f, 1f)
mForwardProgress = false
} else {
mCurrentAnimator.setFloatValues(1f, -1f)
mForwardProgress = true
}
mCurrentAnimator.start()
postInvalidate()
return true
}
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
val matrixA = Matrix()
val matrixB = Matrix()
matrixA.postScale(width.toFloat() / mImageA.width, height.toFloat() / mImageA.height)
matrixB.postScale(width.toFloat() / mImageB.width, height.toFloat() / mImageB.height)
mShaderA.setLocalMatrix(matrixA)
mShaderB.setLocalMatrix(matrixB)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mShader.setInputShader("imageA", mShaderA)
mShader.setInputShader("imageB", mShaderB)
mShader.setIntUniform("imageDimensions", width, height)
mShader.setFloatUniform("progress", mCurrentProgress)
mPaint.shader = mShader
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint)
}
private companion object {
const val AGSL = """
uniform shader imageA;
uniform shader imageB;
uniform ivec2 imageDimensions;
uniform float progress;
const vec2 iSize = vec2(48.0, 48.0);
const float iDir = 0.5;
const float iRand = 0.81;
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float ramp(float2 p) {
return mix(hash12(p),
dot(p/vec2(imageDimensions), float2(iDir, 1 - iDir)),
iRand);
}
half4 main(float2 p) {
float2 lowRes = p / iSize;
float2 cellCenter = (floor(lowRes) + 0.5) * iSize;
float2 posInCell = fract(lowRes) * 2 - 1;
float v = ramp(cellCenter) + progress;
float distToCenter = max(abs(posInCell.x), abs(posInCell.y));
return distToCenter > v ? imageA.eval(p).rgb1 : imageB.eval(p).rgb1;
}
"""
}
}