Repository: Rogero0o/MissView Branch: master Commit: 1486df5e811d Files: 54 Total size: 177.8 KB Directory structure: gitextract_d2mizj6n/ ├── .gitignore ├── .idea/ │ ├── .name │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── MissView.iml ├── README.md ├── build.gradle ├── demo/ │ ├── .gitignore │ ├── build.gradle │ ├── demo.iml │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── roger/ │ │ └── missview/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── roger/ │ │ └── missview/ │ │ └── MainActivity.java │ └── res/ │ ├── layout/ │ │ └── activity_main.xml │ ├── menu/ │ │ └── menu_main.xml │ ├── values/ │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── library/ │ ├── .gitignore │ ├── build.gradle │ ├── library.iml │ ├── libs/ │ │ └── picasso-2.1.1.jar │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── roger/ │ │ └── missview/ │ │ └── library/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── roger/ │ └── missview/ │ └── library/ │ ├── BlurRenderer.java │ ├── DemoRenderController.java │ ├── GLColorOverlay.java │ ├── GLPicture.java │ ├── GLTextureView.java │ ├── GLUtil.java │ ├── MissView.java │ ├── RenderController.java │ └── util/ │ ├── ArtDetailViewport.java │ ├── BitmapRegionLoader.java │ ├── ImageBlurrer.java │ ├── ImageUtil.java │ ├── MathUtil.java │ ├── TickingFloatAnimator.java │ └── UriUtil.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build ================================================ FILE: .idea/.name ================================================ MissView ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ 1.8 ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: MissView.iml ================================================ ================================================ FILE: README.md ================================================ # MissView This project is a part of Muzei,(https://github.com/romannurik/muzei) .
Thanks for Roman Nurik..
I like the animation in that project...so i studied his codes and make this..
![](https://github.com/Rogero0o/MissView/raw/master/missview.gif) ..as you see right now,wish you like it. ### Step Import this project into android studio..it's built with it. ### Usage #### Jcenter gradle ``` compile 'com.roger.missview.library:library:1.0.0' ``` ##### Config in xml ```xml ``` ##### and init in java code ```java mMissview = (MissView) findViewById(R.id.missview); mMissview.initPicture(path);//path is the String of background picture path ``` ## About me A small guy in mainland FUJIAN China. If you have any new idea about this project, feel free to tell me ,Tks. :smiley: ## License The MIT License (MIT) Copyright © 2015 Roger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.1.+' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' classpath 'com.github.dcendents:android-maven-plugin:1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } ================================================ FILE: demo/.gitignore ================================================ /build ================================================ FILE: demo/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 21 buildToolsVersion "21.1.2" defaultConfig { applicationId "com.roger.missview" minSdkVersion 19 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' compile project(':library') } ================================================ FILE: demo/demo.iml ================================================ ================================================ FILE: demo/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in F:\adt-bundle-windows-x86_64_with_Android_5.0\sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: demo/src/androidTest/java/com/roger/missview/ApplicationTest.java ================================================ package com.roger.missview; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: demo/src/main/AndroidManifest.xml ================================================ ================================================ FILE: demo/src/main/java/com/roger/missview/MainActivity.java ================================================ package com.roger.missview; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Environment; import com.roger.missview.library.MissView; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class MainActivity extends Activity { private final String IMAGE_NAME = "night.jpg"; MissView mMissview; String path; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initImage();//将图片从assets考到SD卡 mMissview = (MissView) findViewById(R.id.missview); Bitmap bit = BitmapFactory.decodeResource(this.getResources(),R.drawable.night); mMissview.initPicture(bit); } private void initImage() { try { path = getSDPath() + "/" + IMAGE_NAME; File image = new File(path); if (image.exists()) { return; } assetsDataToSD(path); } catch (IOException e) { e.printStackTrace(); } } private void assetsDataToSD(String fileName) throws IOException { InputStream myInput; OutputStream myOutput = new FileOutputStream(fileName); myInput = this.getAssets().open(IMAGE_NAME); byte[] buffer = new byte[1024]; int length = myInput.read(buffer); while (length > 0) { myOutput.write(buffer, 0, length); length = myInput.read(buffer); } myOutput.flush(); myInput.close(); myOutput.close(); } public String getSDPath() { File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState() .equals(android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在 if (sdCardExist) //如果SD卡存在,则获取跟目录 { sdDir = Environment.getExternalStorageDirectory();//获取跟目录 } return sdDir.toString(); } } ================================================ FILE: demo/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: demo/src/main/res/menu/menu_main.xml ================================================ ================================================ FILE: demo/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: demo/src/main/res/values/strings.xml ================================================ Missview Hello world! Settings ================================================ FILE: demo/src/main/res/values/styles.xml ================================================ ================================================ FILE: demo/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Apr 10 15:27:10 PDT 2013 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: library/.gitignore ================================================ /build ================================================ FILE: library/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' version = "1.0.0" android { compileSdkVersion 21 buildToolsVersion "21.1.2" resourcePrefix "missview__" defaultConfig { minSdkVersion 9 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile files('libs/picasso-2.1.1.jar') } def siteUrl = 'https://github.com/Rogero0o/MissView' def gitUrl = 'https://github.com/Rogero0o/MissView.git' group = "com.roger.missview.library" install { repositories.mavenInstaller { // This generates POM.xml with proper parameters pom { project { packaging 'aar' // Add your description here name 'Android MissView' //Ŀ url siteUrl // Set your license licenses { license { name 'The Apache Software License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id 'roger' //дһЩϢ name 'Jack' email 'flyrogermail@gmail.com' } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { archives javadocJar archives sourcesJar } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) bintray { user = properties.getProperty("bintray.user") key = properties.getProperty("bintray.apikey") configurations = ['archives'] pkg { repo = "maven" name = "MissView" websiteUrl = siteUrl vcsUrl = gitUrl licenses = ["Apache-2.0"] publish = true } } ================================================ FILE: library/library.iml ================================================ ================================================ FILE: library/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in F:/adt-bundle-windows-x86_64_with_Android_5.0/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: library/src/androidTest/java/com/roger/missview/library/ApplicationTest.java ================================================ package com.roger.missview.library; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: library/src/main/java/com/roger/missview/library/BlurRenderer.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.opengl.Matrix; import android.os.Build; import android.preference.PreferenceManager; import android.util.DisplayMetrics; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import com.roger.missview.library.util.ArtDetailViewport; import com.roger.missview.library.util.BitmapRegionLoader; import com.roger.missview.library.util.ImageBlurrer; import com.roger.missview.library.util.ImageUtil; import com.roger.missview.library.util.MathUtil; import com.roger.missview.library.util.TickingFloatAnimator; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; public class BlurRenderer implements GLSurfaceView.Renderer { private static final int CROSSFADE_ANIMATION_DURATION = 750; private static final int BLUR_ANIMATION_DURATION = 750; public static final int DEFAULT_BLUR = 250; // max 500 public static final int DEFAULT_GREY = 0; // max 500 public static final int DEMO_DIM = 64; public static final int DEFAULT_MAX_DIM = 128; // technical max 255 public static final float DIM_RANGE = 0.5f; // percent of max dim private boolean mDemoMode; private boolean mPreview; private int mMaxPrescaledBlurPixels; private int mBlurKeyframes = 3; private int mBlurredSampleSize; private int mMaxDim; private int mMaxGrey; // Model and view matrices. Projection and MVP stored in picture set private final float[] mMMatrix = new float[16]; private final float[] mVMatrix = new float[16]; private Callbacks mCallbacks; private float mAspectRatio; private int mHeight; private GLPictureSet mCurrentGLPictureSet; private GLPictureSet mNextGLPictureSet; private GLColorOverlay mColorOverlay; private BitmapRegionLoader mQueuedNextBitmapRegionLoader; private boolean mSurfaceCreated; private volatile float mNormalOffsetX; private volatile RectF mCurrentViewport = new RectF(); // [-1, -1] to [1, 1], flipped private Context mContext; private boolean mIsBlurred = true; private boolean mBlurRelatedToArtDetailMode = false; private Interpolator mBlurInterpolator = new AccelerateDecelerateInterpolator(); private TickingFloatAnimator mBlurAnimator; private TickingFloatAnimator mCrossfadeAnimator = TickingFloatAnimator.create().from(0); public BlurRenderer(Context context, Callbacks callbacks) { mContext = context; mCallbacks = callbacks; mBlurKeyframes = getNumberOfKeyframes(); mBlurAnimator = TickingFloatAnimator.create().from(mBlurKeyframes); mCurrentGLPictureSet = new GLPictureSet(0); mNextGLPictureSet = new GLPictureSet(1); // for transitioning to next pictures setNormalOffsetX(0); recomputeMaxPrescaledBlurPixels(); recomputeMaxDimAmount(); recomputeGreyAmount(); } @TargetApi(Build.VERSION_CODES.KITKAT) private int getNumberOfKeyframes() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); if (activityManager.isLowRamDevice()) { return 1; } } return 5; } public void recomputeMaxPrescaledBlurPixels() { // Compute blur sizes float maxBlurRadiusOverScreenHeight = PreferenceManager .getDefaultSharedPreferences(mContext).getInt("blur_amount", DEFAULT_BLUR) * 0.0001f; DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); int maxBlurPx = (int) (dm.heightPixels * maxBlurRadiusOverScreenHeight); mBlurredSampleSize = 4; while (maxBlurPx / mBlurredSampleSize > ImageBlurrer.MAX_SUPPORTED_BLUR_PIXELS) { mBlurredSampleSize <<= 1; } mMaxPrescaledBlurPixels = maxBlurPx / mBlurredSampleSize; } public void recomputeMaxDimAmount() { mMaxDim = PreferenceManager .getDefaultSharedPreferences(mContext).getInt("dim_amount", DEFAULT_MAX_DIM); } public void recomputeGreyAmount() { mMaxGrey = PreferenceManager .getDefaultSharedPreferences(mContext).getInt("grey_amount", DEFAULT_GREY); } public void onSurfaceCreated(GL10 unused, EGLConfig config) { mSurfaceCreated = false; GLES20.glEnable(GLES20.GL_BLEND); // GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); GLES20.glBlendFuncSeparate(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA, GLES20.GL_ONE, GLES20.GL_ONE); GLES20.glClearColor(0, 0, 0, 0); // Set the camera position (View matrix) Matrix.setLookAtM(mVMatrix, 0, 0, 0, 1, 0, 0, -1, 0, 1, 0); GLColorOverlay.initGl(); GLPicture.initGl(); mColorOverlay = new GLColorOverlay(0); mSurfaceCreated = true; if (mQueuedNextBitmapRegionLoader != null) { BitmapRegionLoader loader = mQueuedNextBitmapRegionLoader; mQueuedNextBitmapRegionLoader = null; setAndConsumeBitmapRegionLoader(loader); } } public void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); hintViewportSize(width, height); if (!mDemoMode && !mPreview) { // Reset art detail viewports ArtDetailViewport.getInstance().setViewport(0, 0, 0, 0, 0, false); ArtDetailViewport.getInstance().setViewport(1, 0, 0, 0, 0, false); } mCurrentGLPictureSet.recomputeTransformMatrices(); mNextGLPictureSet.recomputeTransformMatrices(); recomputeMaxPrescaledBlurPixels(); } public void hintViewportSize(int width, int height) { mHeight = height; mAspectRatio = width * 1f / height; } public void onDrawFrame(GL10 unused) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); Matrix.setIdentityM(mMMatrix, 0); boolean stillAnimating = mCrossfadeAnimator.tick(); stillAnimating |= mBlurAnimator.tick(); if (mBlurRelatedToArtDetailMode) { mCurrentGLPictureSet.recomputeTransformMatrices(); mNextGLPictureSet.recomputeTransformMatrices(); } float dimAmount = mCurrentGLPictureSet.mDimAmount; mCurrentGLPictureSet.drawFrame(1); if (mCrossfadeAnimator.isRunning()) { dimAmount = MathUtil.interpolate(dimAmount, mNextGLPictureSet.mDimAmount, mCrossfadeAnimator.currentValue()); mNextGLPictureSet.drawFrame(mCrossfadeAnimator.currentValue()); } mColorOverlay.setColor(Color.argb((int) (dimAmount * mBlurAnimator.currentValue() / mBlurKeyframes), 0, 0, 0)); mColorOverlay.draw(mMMatrix); // don't need any perspective or anything for color overlay if (stillAnimating) { mCallbacks.requestRender(); } } public void setNormalOffsetX(float x) { mNormalOffsetX = MathUtil.constrain(0, 1, x); onViewportChanged(); } private void onViewportChanged() { mCurrentGLPictureSet.recomputeTransformMatrices(); mNextGLPictureSet.recomputeTransformMatrices(); if (mSurfaceCreated) { mCallbacks.requestRender(); } } private float blurRadiusAtFrame(float f) { return mMaxPrescaledBlurPixels * mBlurInterpolator.getInterpolation(f / mBlurKeyframes); } public void setAndConsumeBitmapRegionLoader(final BitmapRegionLoader bitmapRegionLoader) { if (!mSurfaceCreated) { mQueuedNextBitmapRegionLoader = bitmapRegionLoader; return; } if (mCrossfadeAnimator.isRunning()) { if (mQueuedNextBitmapRegionLoader != null) { mQueuedNextBitmapRegionLoader.destroy(); } mQueuedNextBitmapRegionLoader = bitmapRegionLoader; return; } mNextGLPictureSet.load(bitmapRegionLoader); mCrossfadeAnimator .from(0).to(1) .withDuration(CROSSFADE_ANIMATION_DURATION) .withEndListener(new Runnable() { @Override public void run() { // swap current and next picturesets final GLPictureSet oldGLPictureSet = mCurrentGLPictureSet; mCurrentGLPictureSet = mNextGLPictureSet; mNextGLPictureSet = new GLPictureSet(oldGLPictureSet.mId); mCallbacks.requestRender(); oldGLPictureSet.destroyPictures(); System.gc(); if (mQueuedNextBitmapRegionLoader != null) { BitmapRegionLoader queuedNextBitmapRegionLoader = mQueuedNextBitmapRegionLoader; mQueuedNextBitmapRegionLoader = null; setAndConsumeBitmapRegionLoader(queuedNextBitmapRegionLoader); } } }) .start(); mCallbacks.requestRender(); } public void setDemoMode(boolean demoMode) { mDemoMode = demoMode; } public void setIsPreview(boolean preview) { mPreview = preview; } private class GLPictureSet { private int mId; private volatile float[] mPMatrix = new float[16]; private final float[] mMVPMatrix = new float[16]; private GLPicture[] mPictures = new GLPicture[mBlurKeyframes + 1]; private boolean mHasBitmap = false; private float mBitmapAspectRatio = 1f; private int mDimAmount = 0; public GLPictureSet(int id) { mId = id; } public void load(BitmapRegionLoader bitmapRegionLoader) { mHasBitmap = (bitmapRegionLoader != null); mBitmapAspectRatio = mHasBitmap ? bitmapRegionLoader.getWidth() * 1f / bitmapRegionLoader.getHeight() : 1f; mDimAmount = DEFAULT_MAX_DIM; destroyPictures(); if (bitmapRegionLoader != null) { BitmapFactory.Options options = new BitmapFactory.Options(); Rect rect = new Rect(); int originalWidth = bitmapRegionLoader.getWidth(); int originalHeight = bitmapRegionLoader.getHeight(); // Calculate image darkness to determine dim amount rect.set(0, 0, originalWidth, originalHeight); options.inSampleSize = ImageUtil.calculateSampleSize(originalHeight, 64); Bitmap tempBitmap = bitmapRegionLoader.decodeRegion(rect, options); float darkness = ImageUtil.calculateDarkness(tempBitmap); mDimAmount = mDemoMode ? DEMO_DIM : (int) (mMaxDim * ((1 - DIM_RANGE) + DIM_RANGE * Math.sqrt(darkness))); tempBitmap.recycle(); // Create the GLPicture objects mPictures[0] = new GLPicture(bitmapRegionLoader, mHeight); if (mMaxPrescaledBlurPixels == 0) { for (int f = 1; f <= mBlurKeyframes; f++) { mPictures[f] = mPictures[0]; } } else { ImageBlurrer blurrer = new ImageBlurrer(mContext); // To blur, first load the entire bitmap region, but at a very large // sample size that's appropriate for the final blurred image options.inSampleSize = ImageUtil.calculateSampleSize( originalHeight, mHeight / mBlurredSampleSize); rect.set(0, 0, originalWidth, originalHeight); tempBitmap = bitmapRegionLoader.decodeRegion(rect, options); // Next, create a scaled down version of the bitmap so that the blur radius // looks appropriate (tempBitmap will likely be bigger than the final blurred // bitmap, and thus the blur may look smaller if we just used tempBitmap as // the final blurred bitmap). // Note that image width should be a multiple of 4 to avoid // issues with RenderScript allocations. int scaledHeight = Math.max(2, MathUtil.floorEven( mHeight / mBlurredSampleSize)); int scaledWidth = Math.max(4, MathUtil.roundMult4( (int) (scaledHeight * mBitmapAspectRatio))); Bitmap scaledBitmap = Bitmap.createScaledBitmap( tempBitmap, scaledWidth, scaledHeight, true); tempBitmap.recycle(); // And finally, create a blurred copy for each keyframe. for (int f = 1; f <= mBlurKeyframes; f++) { float desaturateAmount = mMaxGrey / 500f * f / mBlurKeyframes; Bitmap blurredBitmap = blurrer.blurBitmap( scaledBitmap, blurRadiusAtFrame(f), desaturateAmount); mPictures[f] = new GLPicture(blurredBitmap); blurredBitmap.recycle(); } scaledBitmap.recycle(); blurrer.destroy(); } } recomputeTransformMatrices(); mCallbacks.requestRender(); } private void recomputeTransformMatrices() { float screenToBitmapAspectRatio = mAspectRatio / mBitmapAspectRatio; // Ensure the bitmap is wider than the screen relatively by applying zoom // if necessary. Vary width but keep height the same. float zoom = Math.max(1f, 1.15f * screenToBitmapAspectRatio); // Total scale factors in both zoom and scale due to aspect ratio. float totalScale = zoom / screenToBitmapAspectRatio; mCurrentViewport.left = MathUtil.interpolate( -1f * Math.min(1f, screenToBitmapAspectRatio), // remove screenToBitmapAspectRatio to unconstrain panning amount 1f * Math.min(1f, screenToBitmapAspectRatio), mNormalOffsetX * (totalScale - 1) / totalScale); mCurrentViewport.right = mCurrentViewport.left + 2f / totalScale; mCurrentViewport.bottom = -1f / zoom; mCurrentViewport.top = 1f / zoom; float focusAmount = (mBlurKeyframes - mBlurAnimator.currentValue()) / mBlurKeyframes; if (mBlurRelatedToArtDetailMode && focusAmount > 0) { RectF artDetailViewport = ArtDetailViewport.getInstance().getViewport(mId); if (artDetailViewport.width() == 0 || artDetailViewport.height() == 0) { if (!mDemoMode && !mPreview) { // reset art detail viewport ArtDetailViewport.getInstance().setViewport(mId, MathUtil.uninterpolate(-1, 1, mCurrentViewport.left), MathUtil.uninterpolate(1, -1, mCurrentViewport.top), MathUtil.uninterpolate(-1, 1, mCurrentViewport.right), MathUtil.uninterpolate(1, -1, mCurrentViewport.bottom), false); } } else { // interpolate mCurrentViewport.left = MathUtil.interpolate( mCurrentViewport.left, MathUtil.interpolate(-1, 1, artDetailViewport.left), focusAmount); mCurrentViewport.top = MathUtil.interpolate( mCurrentViewport.top, MathUtil.interpolate(1, -1, artDetailViewport.top), focusAmount); mCurrentViewport.right = MathUtil.interpolate( mCurrentViewport.right, MathUtil.interpolate(-1, 1, artDetailViewport.right), focusAmount); mCurrentViewport.bottom = MathUtil.interpolate( mCurrentViewport.bottom, MathUtil.interpolate(1, -1, artDetailViewport.bottom), focusAmount); } } Matrix.orthoM(mPMatrix, 0, mCurrentViewport.left, mCurrentViewport.right, mCurrentViewport.bottom, mCurrentViewport.top, 1, 10); } public void drawFrame(float globalAlpha) { if (!mHasBitmap) { return; } Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0); Matrix.multiplyMM(mMVPMatrix, 0, mPMatrix, 0, mMVPMatrix, 0); float blurFrame = mBlurAnimator.currentValue(); int lo = (int) Math.floor(blurFrame); int hi = (int) Math.ceil(blurFrame); float localHiAlpha = (blurFrame - lo); if (globalAlpha <= 0) { // Nothing to draw } else if (lo == hi) { // Just draw one mPictures[lo].draw(mMVPMatrix, globalAlpha); } else if (globalAlpha == 1) { // Simple drawing mPictures[lo].draw(mMVPMatrix, 1); mPictures[hi].draw(mMVPMatrix, localHiAlpha); } else { // If there's both a global and local alpha, re-compose alphas, to // effectively compose hi and lo before composing the result // with the background. // // The math, where a1,a2 are previous alphas and b1,b2 are new alphas: // b1 = a1 * (a2 - 1) / (a1 * a2 - 1) // b2 = a1 * a2 float newLocalLoAlpha = globalAlpha * (localHiAlpha - 1) / (globalAlpha * localHiAlpha - 1); float newLocalHiAlpha = globalAlpha * localHiAlpha; mPictures[lo].draw(mMVPMatrix, newLocalLoAlpha); mPictures[hi].draw(mMVPMatrix, newLocalHiAlpha); } } public void destroyPictures() { for (int i = 0; i < mPictures.length; i++) { if (mPictures[i] == null) { continue; } mPictures[i].destroy(); mPictures[i] = null; } } } public void destroy() { mCurrentGLPictureSet.destroyPictures(); mNextGLPictureSet.destroyPictures(); } public boolean isBlurred() { return mIsBlurred; } public void setIsBlurred(final boolean isBlurred, final boolean artDetailMode) { if (artDetailMode && !isBlurred && !mDemoMode && !mPreview) { // Reset art detail viewport ArtDetailViewport.getInstance().setViewport(0, 0, 0, 0, 0, false); ArtDetailViewport.getInstance().setViewport(1, 0, 0, 0, 0, false); } mBlurRelatedToArtDetailMode = artDetailMode; mIsBlurred = isBlurred; mBlurAnimator.cancel(); mBlurAnimator .to(isBlurred ? mBlurKeyframes : 0) .withDuration(BLUR_ANIMATION_DURATION * (mDemoMode ? 5 : 1)) .withEndListener(new Runnable() { @Override public void run() { if (isBlurred && artDetailMode) { System.gc(); } } }) .start(); mCallbacks.requestRender(); } public static interface Callbacks { void requestRender(); } } ================================================ FILE: library/src/main/java/com/roger/missview/library/DemoRenderController.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import com.roger.missview.library.util.BitmapRegionLoader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class DemoRenderController extends RenderController { private final Handler mHandler = new Handler(); private static final long ANIMATION_CYCLE_TIME_MILLIS = 35000; private static final long FOCUS_DELAY_TIME_MILLIS = 2000; private static final long FOCUS_TIME_MILLIS = 6000; private Animator mCurrentScrollAnimator; private boolean mReverseDirection = false; private boolean mAllowFocus = true; private String mPictureName; private Bitmap mBitmap; public DemoRenderController(Context context, BlurRenderer renderer, Callbacks callbacks, boolean allowFocus, String mPictureName) { super(context, renderer, callbacks); mAllowFocus = allowFocus; this.mPictureName = mPictureName; runAnimation(); } public DemoRenderController(Context context, BlurRenderer renderer, Callbacks callbacks, boolean allowFocus, Bitmap mBitmap) { super(context, renderer, callbacks); mAllowFocus = allowFocus; this.mBitmap = mBitmap; runAnimation(); } private void runAnimation() { if (mCurrentScrollAnimator != null) { mCurrentScrollAnimator.cancel(); } mCurrentScrollAnimator = ObjectAnimator.ofFloat(mRenderer, "normalOffsetX", mReverseDirection ? 1f : 0f, mReverseDirection ? 0f : 1f).setDuration(ANIMATION_CYCLE_TIME_MILLIS); mCurrentScrollAnimator.start(); mCurrentScrollAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mReverseDirection = !mReverseDirection; runAnimation(); } }); if (mAllowFocus) { mHandler.postDelayed(new Runnable() { @Override public void run() { mRenderer.setIsBlurred(false, false); mHandler.postDelayed(new Runnable() { @Override public void run() { mRenderer.setIsBlurred(true, false); } }, FOCUS_TIME_MILLIS); } }, FOCUS_DELAY_TIME_MILLIS); } } @Override public void destroy() { super.destroy(); if (mCurrentScrollAnimator != null) { mCurrentScrollAnimator.cancel(); } mHandler.removeCallbacksAndMessages(null); } @Override protected BitmapRegionLoader openDownloadedCurrentArtwork(boolean forceReload) { try { if (mBitmap != null) { return BitmapRegionLoader.newInstance(Bitmap2IS(mBitmap)); } else if (mPictureName != null) { InputStream m = new FileInputStream(mPictureName); return BitmapRegionLoader.newInstance(m); } } catch (IOException e) { e.printStackTrace(); return null; } return null; } private InputStream Bitmap2IS(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.JPEG, 100, baos); InputStream sbs = new ByteArrayInputStream(baos.toByteArray()); return sbs; } } ================================================ FILE: library/src/main/java/com/roger/missview/library/GLColorOverlay.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.graphics.Color; import android.opengl.GLES20; import java.nio.FloatBuffer; class GLColorOverlay { private static final String VERTEX_SHADER_CODE = "" + // This matrix member variable provides a hook to manipulate // the coordinates of the objects that use this vertex shader "uniform mat4 uMVPMatrix;" + "attribute vec4 aPosition;" + "void main(){" + " gl_Position = uMVPMatrix * aPosition;" + "}"; private static final String FRAGMENT_SHADER_CODE = "" + "precision mediump float;" + "uniform sampler2D uTexture;" + "uniform vec4 uColor;" + "void main(){" + " gl_FragColor = uColor;" + "}"; // number of coordinates per vertex in this array private static final int COORDS_PER_VERTEX = 3; private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * GLUtil.BYTES_PER_FLOAT; private float mVertices[] = { -1, 1, 0, // top left -1, -1, 0, // bottom left 1, -1, 0, // bottom right -1, 1, 0, // top left 1, -1, 0, // bottom right 1, 1, 0, // top right }; private int mColor; private FloatBuffer mVertexBuffer; private static int sProgramHandle; private static int sAttribPositionHandle; private static int sUniformColorHandle; private static int sUniformMVPMatrixHandle; public GLColorOverlay(int color) { mColor = color; mVertexBuffer = GLUtil.asFloatBuffer(mVertices); } public static void initGl() { // Initialize shaders and create/link program int vertexShaderHandle = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE); int fragShaderHandle = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE); sProgramHandle = GLUtil.createAndLinkProgram(vertexShaderHandle, fragShaderHandle, null); sAttribPositionHandle = GLES20.glGetAttribLocation(sProgramHandle, "aPosition"); sUniformMVPMatrixHandle = GLES20.glGetUniformLocation(sProgramHandle, "uMVPMatrix"); sUniformColorHandle = GLES20.glGetUniformLocation(sProgramHandle, "uColor"); } public void draw(float[] mvpMatrix) { // Add program to OpenGL ES environment GLES20.glUseProgram(sProgramHandle); // Pass in the vertex information GLES20.glEnableVertexAttribArray(sAttribPositionHandle); GLES20.glVertexAttribPointer(sAttribPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, VERTEX_STRIDE_BYTES, mVertexBuffer); // Apply the projection and view transformation GLES20.glUniformMatrix4fv(sUniformMVPMatrixHandle, 1, false, mvpMatrix, 0); GLUtil.checkGlError("glUniformMatrix4fv"); // Set the alpha float r = Color.red(mColor) * 1f / 255; float g = Color.green(mColor) * 1f / 255; float b = Color.blue(mColor) * 1f / 255; float a = Color.alpha(mColor) * 1f / 255; GLES20.glUniform4f(sUniformColorHandle, r, g, b, a); // Draw the triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertices.length / COORDS_PER_VERTEX); GLES20.glDisableVertexAttribArray(sAttribPositionHandle); } public void setColor(int color) { mColor = color; } public void destroy() { } } ================================================ FILE: library/src/main/java/com/roger/missview/library/GLPicture.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Rect; import android.opengl.GLES20; import com.roger.missview.library.util.BitmapRegionLoader; import java.nio.FloatBuffer; class GLPicture { private static final String VERTEX_SHADER_CODE = "" + // This matrix member variable provides a hook to manipulate // the coordinates of the objects that use this vertex shader "uniform mat4 uMVPMatrix;" + "attribute vec4 aPosition;" + "attribute vec2 aTexCoords;" + "varying vec2 vTexCoords;" + "void main(){" + " vTexCoords = aTexCoords;" + " gl_Position = uMVPMatrix * aPosition;" + "}"; private static final String FRAGMENT_SHADER_CODE = "" + "precision mediump float;" + "uniform sampler2D uTexture;" + "uniform float uAlpha;" + "varying vec2 vTexCoords;" + "void main(){" + " gl_FragColor = texture2D(uTexture, vTexCoords);" + " gl_FragColor.a = uAlpha;" + "}"; // number of coordinates per vertex in this array private static final int COORDS_PER_VERTEX = 3; private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * GLUtil.BYTES_PER_FLOAT; private static final int VERTICES = 6; // TL, BL, BR, TL, BR, TR // S, T (or X, Y) private static final int COORDS_PER_TEXTURE_VERTEX = 2; private static final int TEXTURE_VERTEX_STRIDE_BYTES = COORDS_PER_TEXTURE_VERTEX * GLUtil.BYTES_PER_FLOAT; private static final float[] SQUARE_TEXTURE_VERTICES = { 0, 0, // top left 0, 1, // bottom left 1, 1, // bottom right 0, 0, // top left 1, 1, // bottom right 1, 0, // top right }; private boolean mHasContent = false; private float[] mVertices = new float[COORDS_PER_VERTEX * VERTICES]; private FloatBuffer mVertexBuffer; private FloatBuffer mTextureCoordsBuffer; private static int sMaxTextureSize; private static int sProgramHandle; private static int sAttribPositionHandle; private static int sAttribTextureCoordsHandle; private static int sUniformAlphaHandle; private static int sUniformTextureHandle; private static int sUniformMVPMatrixHandle; private int mCols = 1; private int mRows = 1; private int mWidth = 0; private int mHeight = 0; private int mTileSize = sMaxTextureSize; private int[] mTextureHandles; public static void initGl() { // Initialize shaders and create/link program int vertexShaderHandle = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE); int fragShaderHandle = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE); sProgramHandle = GLUtil.createAndLinkProgram(vertexShaderHandle, fragShaderHandle, null); sAttribPositionHandle = GLES20.glGetAttribLocation(sProgramHandle, "aPosition"); sAttribTextureCoordsHandle = GLES20.glGetAttribLocation(sProgramHandle, "aTexCoords"); sUniformMVPMatrixHandle = GLES20.glGetUniformLocation(sProgramHandle, "uMVPMatrix"); sUniformTextureHandle = GLES20.glGetUniformLocation(sProgramHandle, "uTexture"); sUniformAlphaHandle = GLES20.glGetUniformLocation(sProgramHandle, "uAlpha"); // Compute max texture size int[] maxTextureSize = new int[1]; GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0); sMaxTextureSize = maxTextureSize[0]; } public GLPicture(BitmapRegionLoader bitmapRegionLoader, int maxHeight) { if (bitmapRegionLoader == null || maxHeight == 0) { return; } mHasContent = true; mVertexBuffer = GLUtil.newFloatBuffer(mVertices.length); mTextureCoordsBuffer = GLUtil.asFloatBuffer(SQUARE_TEXTURE_VERTICES); int originalWidth = bitmapRegionLoader.getWidth(); int originalHeight = bitmapRegionLoader.getHeight(); int sampleSize = 1; while (originalHeight / (sampleSize << 1) > maxHeight) { sampleSize <<= 1; } mWidth = originalWidth / sampleSize; mHeight = originalHeight / sampleSize; mTileSize = Math.min(512, sMaxTextureSize); int unsampledTileSize = mTileSize * sampleSize; int leftoverHeight = originalHeight % unsampledTileSize; // Load m x n textures mCols = mWidth / (mTileSize + 1) + 1; mRows = mHeight / (mTileSize + 1) + 1; mTextureHandles = new int[mCols * mRows]; Bitmap tileBitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888); Rect rect = new Rect(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = sampleSize; options.inBitmap = tileBitmap; for (int y = 0; y < mRows; y++) { for (int x = 0; x < mCols; x++) { rect.set(x * unsampledTileSize, (mRows - y - 1) * unsampledTileSize, (x + 1) * unsampledTileSize, (mRows - y) * unsampledTileSize); // The bottom tiles must be full tiles for drawing, so only allow edge tiles // at the top if (leftoverHeight > 0) { rect.offset(0, -unsampledTileSize + leftoverHeight); } rect.intersect(0, 0, originalWidth, originalHeight); Bitmap useBitmap = bitmapRegionLoader.decodeRegion(rect, options); mTextureHandles[y * mCols + x] = GLUtil.loadTexture(useBitmap); if (useBitmap != tileBitmap) { useBitmap.recycle(); } } } } public GLPicture(Bitmap bitmap) { if (bitmap == null) { return; } mTileSize = sMaxTextureSize; mHasContent = true; mVertexBuffer = GLUtil.newFloatBuffer(mVertices.length); mTextureCoordsBuffer = GLUtil.asFloatBuffer(SQUARE_TEXTURE_VERTICES); mWidth = bitmap.getWidth(); mHeight = bitmap.getHeight(); int leftoverHeight = mHeight % mTileSize; // Load m x n textures mCols = mWidth / (mTileSize + 1) + 1; mRows = mHeight / (mTileSize + 1) + 1; mTextureHandles = new int[mCols * mRows]; if (mCols == 1 && mRows == 1) { mTextureHandles[0] = GLUtil.loadTexture(bitmap); } else { Rect rect = new Rect(); for (int y = 0; y < mRows; y++) { for (int x = 0; x < mCols; x++) { rect.set(x * mTileSize, (mRows - y - 1) * mTileSize, (x + 1) * mTileSize, (mRows - y) * mTileSize); // The bottom tiles must be full tiles for drawing, so only allow edge tiles // at the top if (leftoverHeight > 0) { rect.offset(0, -mTileSize + leftoverHeight); } rect.intersect(0, 0, mWidth, mHeight); Bitmap subBitmap = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height()); mTextureHandles[y * mCols + x] = GLUtil.loadTexture(subBitmap); subBitmap.recycle(); } } } } public void draw(float[] mvpMatrix, float alpha) { if (!mHasContent) { return; } // Add program to OpenGL ES environment GLES20.glUseProgram(sProgramHandle); // Apply the projection and view transformation GLES20.glUniformMatrix4fv(sUniformMVPMatrixHandle, 1, false, mvpMatrix, 0); GLUtil.checkGlError("glUniformMatrix4fv"); // Set up vertex buffer GLES20.glEnableVertexAttribArray(sAttribPositionHandle); GLES20.glVertexAttribPointer(sAttribPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, VERTEX_STRIDE_BYTES, mVertexBuffer); // Set up texture stuff GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glUniform1i(sUniformTextureHandle, 0); GLES20.glVertexAttribPointer(sAttribTextureCoordsHandle, COORDS_PER_TEXTURE_VERTEX, GLES20.GL_FLOAT, false, TEXTURE_VERTEX_STRIDE_BYTES, mTextureCoordsBuffer); GLES20.glEnableVertexAttribArray(sAttribTextureCoordsHandle); // Set the alpha GLES20.glUniform1f(sUniformAlphaHandle, alpha); // Draw tiles for (int y = 0; y < mRows; y++) { for (int x = 0; x < mCols; x++) { // Pass in the vertex information mVertices[0] = mVertices[3] = mVertices[9] = Math.min(-1 + 2f * x * mTileSize / mWidth, 1); // left mVertices[1] = mVertices[10] = mVertices[16] = Math.min(-1 + 2f * (y + 1) * mTileSize / mHeight, 1); // top mVertices[6] = mVertices[12] = mVertices[15] = Math.min(-1 + 2f * (x + 1) * mTileSize / mWidth, 1); // right mVertices[4] = mVertices[7] = mVertices[13] = Math.min(-1 + 2f * y * mTileSize / mHeight, 1); // bottom mVertexBuffer.put(mVertices); mVertexBuffer.position(0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureHandles[y * mCols + x]); GLUtil.checkGlError("glBindTexture"); // Draw the two triangles GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertices.length / COORDS_PER_VERTEX); } } GLES20.glDisableVertexAttribArray(sAttribPositionHandle); GLES20.glDisableVertexAttribArray(sAttribTextureCoordsHandle); } public void destroy() { if (mTextureHandles != null) { GLES20.glDeleteTextures(mTextureHandles.length, mTextureHandles, 0); GLUtil.checkGlError("Destroy picture"); mTextureHandles = null; } } } ================================================ FILE: library/src/main/java/com/roger/missview/library/GLTextureView.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.content.Context; import android.graphics.SurfaceTexture; import android.opengl.GLDebugHelper; import android.opengl.GLSurfaceView; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.TextureView; import java.io.Writer; import java.lang.ref.WeakReference; import java.util.ArrayList; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; /** * See http://stackoverflow.com/questions/12061419/converting-from-glsurfaceview-to-textureview-via-gltextureview */ public class GLTextureView extends TextureView implements TextureView.SurfaceTextureListener { private final static String TAG = "GLTextureView"; private final static boolean LOG_ATTACH_DETACH = false; private final static boolean LOG_THREADS = false; private final static boolean LOG_PAUSE_RESUME = false; private final static boolean LOG_SURFACE = false; private final static boolean LOG_RENDERER = false; private final static boolean LOG_RENDERER_DRAW_FRAME = false; private final static boolean LOG_EGL = false; /** * The renderer only renders * when the surface is created, or when {@link #requestRender} is called. * * @see #getRenderMode() * @see #setRenderMode(int) * @see #requestRender() */ public final static int RENDERMODE_WHEN_DIRTY = 0; /** * The renderer is called * continuously to re-render the scene. * * @see #getRenderMode() * @see #setRenderMode(int) */ public final static int RENDERMODE_CONTINUOUSLY = 1; /** * Check glError() after every GL call and throw an exception if glError indicates * that an error has occurred. This can be used to help track down which OpenGL ES call * is causing an error. * * @see #getDebugFlags * @see #setDebugFlags */ public final static int DEBUG_CHECK_GL_ERROR = 1; /** * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView". * * @see #getDebugFlags * @see #setDebugFlags */ public final static int DEBUG_LOG_GL_CALLS = 2; /** * Standard View constructor. In order to render something, you * must call {@link #setRenderer} to register a renderer. */ public GLTextureView(Context context) { super(context); init(); } /** * Standard View constructor. In order to render something, you * must call {@link #setRenderer} to register a renderer. */ public GLTextureView(Context context, AttributeSet attrs) { super(context, attrs); init(); } @Override protected void finalize() throws Throwable { try { if (mGLThread != null) { // GLThread may still be running if this view was never // attached to a window. mGLThread.requestExitAndWait(); } } finally { super.finalize(); } } private void init() { setSurfaceTextureListener(this); } /** * Set the glWrapper. If the glWrapper is not null, its * {@link GLWrapper#wrap(javax.microedition.khronos.opengles.GL)} method is called * whenever a surface is created. A GLWrapper can be used to wrap * the GL object that's passed to the renderer. Wrapping a GL * object enables examining and modifying the behavior of the * GL calls made by the renderer. *

* Wrapping is typically used for debugging purposes. *

* The default value is null. * @param glWrapper the new GLWrapper */ public void setGLWrapper(GLWrapper glWrapper) { mGLWrapper = glWrapper; } /** * Set the debug flags to a new value. The value is * constructed by OR-together zero or more * of the DEBUG_CHECK_* constants. The debug flags take effect * whenever a surface is created. The default value is zero. * @param debugFlags the new debug flags * @see #DEBUG_CHECK_GL_ERROR * @see #DEBUG_LOG_GL_CALLS */ public void setDebugFlags(int debugFlags) { mDebugFlags = debugFlags; } /** * Get the current value of the debug flags. * @return the current value of the debug flags. */ public int getDebugFlags() { return mDebugFlags; } /** * Control whether the EGL context is preserved when the GLSurfaceView is paused and * resumed. *

* If set to true, then the EGL context may be preserved when the GLSurfaceView is paused. * Whether the EGL context is actually preserved or not depends upon whether the * Android device that the program is running on can support an arbitrary number of EGL * contexts or not. Devices that can only support a limited number of EGL contexts must * release the EGL context in order to allow multiple applications to share the GPU. *

* If set to false, the EGL context will be released when the GLSurfaceView is paused, * and recreated when the GLSurfaceView is resumed. *

* * The default is false. * * @param preserveOnPause preserve the EGL context when paused */ public void setPreserveEGLContextOnPause(boolean preserveOnPause) { mPreserveEGLContextOnPause = preserveOnPause; } /** * @return true if the EGL context will be preserved when paused */ public boolean getPreserveEGLContextOnPause() { return mPreserveEGLContextOnPause; } /** * Set the renderer associated with this view. Also starts the thread that * will call the renderer, which in turn causes the rendering to start. *

This method should be called once and only once in the life-cycle of * a GLSurfaceView. *

The following GLSurfaceView methods can only be called before * setRenderer is called: *

    *
  • {@link #setEGLConfigChooser(boolean)} *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} *
*

* The following GLSurfaceView methods can only be called after * setRenderer is called: *

    *
  • {@link #getRenderMode()} *
  • {@link #onPause()} *
  • {@link #onResume()} *
  • {@link #queueEvent(Runnable)} *
  • {@link #requestRender()} *
  • {@link #setRenderMode(int)} *
* * @param renderer the renderer to use to perform OpenGL drawing. */ public void setRenderer(GLSurfaceView.Renderer renderer) { checkRenderThreadState(); if (mEGLConfigChooser == null) { mEGLConfigChooser = new SimpleEGLConfigChooser(true); } if (mEGLContextFactory == null) { mEGLContextFactory = new DefaultContextFactory(); } if (mEGLWindowSurfaceFactory == null) { mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); } mRenderer = renderer; mGLThread = new GLThread(mThisWeakRef); mGLThread.start(); } /** * Install a custom EGLContextFactory. *

If this method is * called, it must be called before {@link #setRenderer(Renderer)} * is called. *

* If this method is not called, then by default * a context will be created with no shared context and * with a null attribute list. */ public void setEGLContextFactory(EGLContextFactory factory) { checkRenderThreadState(); mEGLContextFactory = factory; } /** * Install a custom EGLWindowSurfaceFactory. *

If this method is * called, it must be called before {@link #setRenderer(Renderer)} * is called. *

* If this method is not called, then by default * a window surface will be created with a null attribute list. */ public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { checkRenderThreadState(); mEGLWindowSurfaceFactory = factory; } /** * Install a custom EGLConfigChooser. *

If this method is * called, it must be called before {@link #setRenderer(Renderer)} * is called. *

* If no setEGLConfigChooser method is called, then by default the * view will choose an EGLConfig that is compatible with the current * android.view.Surface, with a depth buffer depth of * at least 16 bits. * @param configChooser */ public void setEGLConfigChooser(EGLConfigChooser configChooser) { checkRenderThreadState(); mEGLConfigChooser = configChooser; } /** * Install a config chooser which will choose a config * as close to 16-bit RGB as possible, with or without an optional depth * buffer as close to 16-bits as possible. *

If this method is * called, it must be called before {@link #setRenderer(Renderer)} * is called. *

* If no setEGLConfigChooser method is called, then by default the * view will choose an RGB_888 surface with a depth buffer depth of * at least 16 bits. * * @param needDepth */ public void setEGLConfigChooser(boolean needDepth) { setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); } /** * Install a config chooser which will choose a config * with at least the specified depthSize and stencilSize, * and exactly the specified redSize, greenSize, blueSize and alphaSize. *

If this method is * called, it must be called before {@link #setRenderer(Renderer)} * is called. *

* If no setEGLConfigChooser method is called, then by default the * view will choose an RGB_888 surface with a depth buffer depth of * at least 16 bits. * */ public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize)); } /** * Inform the default EGLContextFactory and default EGLConfigChooser * which EGLContext client version to pick. *

Use this method to create an OpenGL ES 2.0-compatible context. * Example: *

     *     public MyView(Context context) {
     *         super(context);
     *         setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
     *         setRenderer(new MyRenderer());
     *     }
     * 
*

Note: Activities which require OpenGL ES 2.0 should indicate this by * setting @lt;uses-feature android:glEsVersion="0x00020000" in the activity's * AndroidManifest.xml file. *

If this method is called, it must be called before {@link #setRenderer(Renderer)} * is called. *

This method only affects the behavior of the default EGLContexFactory and the * default EGLConfigChooser. If * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context. * If * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config. * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0 */ public void setEGLContextClientVersion(int version) { checkRenderThreadState(); mEGLContextClientVersion = version; } /** * Set the rendering mode. When renderMode is * RENDERMODE_CONTINUOUSLY, the renderer is called * repeatedly to re-render the scene. When renderMode * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. *

* Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance * by allowing the GPU and CPU to idle when the view does not need to be updated. *

* This method can only be called after {@link #setRenderer(Renderer)} * * @param renderMode one of the RENDERMODE_X constants * @see #RENDERMODE_CONTINUOUSLY * @see #RENDERMODE_WHEN_DIRTY */ public void setRenderMode(int renderMode) { mGLThread.setRenderMode(renderMode); } /** * Get the current rendering mode. May be called * from any thread. Must not be called before a renderer has been set. * @return the current rendering mode. * @see #RENDERMODE_CONTINUOUSLY * @see #RENDERMODE_WHEN_DIRTY */ public int getRenderMode() { return mGLThread.getRenderMode(); } /** * Request that the renderer render a frame. * This method is typically used when the render mode has been set to * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. * May be called * from any thread. Must not be called before a renderer has been set. */ public void requestRender() { mGLThread.requestRender(); } @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { mGLThread.surfaceCreated(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { mGLThread.onWindowResize(width, height); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mGLThread.onWindowResize(w, h); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { mGLThread.surfaceDestroyed(); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { requestRender(); } /** * This method is part of the SurfaceHolder.Callback interface, and is * not normally called or subclassed by clients of GLSurfaceView. */ public void on(SurfaceHolder holder) { mGLThread.surfaceCreated(); } /** * Inform the view that the activity is paused. The owner of this view must * call this method when the activity is paused. Calling this method will * pause the rendering thread. * Must not be called before a renderer has been set. */ public void onPause() { mGLThread.onPause(); } /** * Inform the view that the activity is resumed. The owner of this view must * call this method when the activity is resumed. Calling this method will * recreate the OpenGL display and resume the rendering * thread. * Must not be called before a renderer has been set. */ public void onResume() { mGLThread.onResume(); } /** * Queue a runnable to be run on the GL rendering thread. This can be used * to communicate with the Renderer on the rendering thread. * Must not be called before a renderer has been set. * @param r the runnable to be run on the GL rendering thread. */ public void queueEvent(Runnable r) { mGLThread.queueEvent(r); } /** * This method is used as part of the View class and is not normally * called or subclassed by clients of GLSurfaceView. */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (LOG_ATTACH_DETACH) { Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); } if (mDetached && (mRenderer != null)) { int renderMode = RENDERMODE_CONTINUOUSLY; if (mGLThread != null) { renderMode = mGLThread.getRenderMode(); } mGLThread = new GLThread(mThisWeakRef); if (renderMode != RENDERMODE_CONTINUOUSLY) { mGLThread.setRenderMode(renderMode); } mGLThread.start(); } mDetached = false; } /** * This method is used as part of the View class and is not normally * called or subclassed by clients of GLSurfaceView. * Must not be called before a renderer has been set. */ @Override protected void onDetachedFromWindow() { if (LOG_ATTACH_DETACH) { Log.d(TAG, "onDetachedFromWindow"); } if (mGLThread != null) { mGLThread.requestExitAndWait(); } mDetached = true; super.onDetachedFromWindow(); } // ---------------------------------------------------------------------- /** * An interface used to wrap a GL interface. *

Typically * used for implementing debugging and tracing on top of the default * GL interface. You would typically use this by creating your own class * that implemented all the GL methods by delegating to another GL instance. * Then you could add your own behavior before or after calling the * delegate. All the GLWrapper would do was instantiate and return the * wrapper GL instance: *

     * class MyGLWrapper implements GLWrapper {
     *     GL wrap(GL gl) {
     *         return new MyGLImplementation(gl);
     *     }
     *     static class MyGLImplementation implements GL,GL10,GL11,... {
     *         ...
     *     }
     * }
     * 
* @see #setGLWrapper(GLWrapper) */ public interface GLWrapper { /** * Wraps a gl interface in another gl interface. * @param gl a GL interface that is to be wrapped. * @return either the input argument or another GL object that wraps the input argument. */ GL wrap(GL gl); } /** * An interface for customizing the eglCreateContext and eglDestroyContext calls. *

* This interface must be implemented by clients wishing to call * */ public interface EGLContextFactory { EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); } private class DefaultContextFactory implements EGLContextFactory { private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, EGL10.EGL_NONE }; return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, mEGLContextClientVersion != 0 ? attrib_list : null); } public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { if (!egl.eglDestroyContext(display, context)) { Log.e("DefaultContextFactory", "display:" + display + " context: " + context); if (LOG_THREADS) { Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); } EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); } } } /** * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. *

* This interface must be implemented by clients wishing to call * */ public interface EGLWindowSurfaceFactory { /** * @return null if the surface cannot be constructed. */ EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow); void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); } private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) { EGLSurface result = null; try { result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); } catch (IllegalArgumentException e) { // This exception indicates that the surface flinger surface // is not valid. This can happen if the surface flinger surface has // been torn down, but the application has not yet been // notified via SurfaceHolder.Callback.surfaceDestroyed. // In theory the application should be notified first, // but in practice sometimes it is not. See b/4588890 Log.e(TAG, "eglCreateWindowSurface", e); } return result; } public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { egl.eglDestroySurface(display, surface); } } /** * An interface for choosing an EGLConfig configuration from a list of * potential configurations. *

* This interface must be implemented by clients wishing to call * */ public interface EGLConfigChooser { /** * Choose a configuration from the list. Implementors typically * implement this method by calling * {@link javax.microedition.khronos.egl.EGL10#eglChooseConfig} and iterating through the results. Please consult the * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. * @param egl the EGL10 for the current display. * @param display the current display. * @return the chosen configuration. */ EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); } private abstract class BaseConfigChooser implements EGLConfigChooser { public BaseConfigChooser(int[] configSpec) { mConfigSpec = filterConfigSpec(configSpec); } public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { int[] num_config = new int[1]; if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config)) { throw new IllegalArgumentException("eglChooseConfig failed"); } int numConfigs = num_config[0]; if (numConfigs <= 0) { throw new IllegalArgumentException( "No configs match configSpec"); } EGLConfig[] configs = new EGLConfig[numConfigs]; if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, num_config)) { throw new IllegalArgumentException("eglChooseConfig#2 failed"); } EGLConfig config = chooseConfig(egl, display, configs); if (config == null) { throw new IllegalArgumentException("No config chosen"); } return config; } abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs); protected int[] mConfigSpec; private int[] filterConfigSpec(int[] configSpec) { if (mEGLContextClientVersion != 2) { return configSpec; } /* We know none of the subclasses define EGL_RENDERABLE_TYPE. * And we know the configSpec is well formed. */ int len = configSpec.length; int[] newConfigSpec = new int[len + 2]; System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ newConfigSpec[len+1] = EGL10.EGL_NONE; return newConfigSpec; } } /** * Choose a configuration with exactly the specified r,g,b,a sizes, * and at least the specified depth and stencil sizes. */ private class ComponentSizeChooser extends BaseConfigChooser { public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { super(new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, EGL10.EGL_STENCIL_SIZE, stencilSize, EGL10.EGL_NONE}); mValue = new int[1]; mRedSize = redSize; mGreenSize = greenSize; mBlueSize = blueSize; mAlphaSize = alphaSize; mDepthSize = depthSize; mStencilSize = stencilSize; } @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { for (EGLConfig config : configs) { int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); if ((d >= mDepthSize) && (s >= mStencilSize)) { int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); if ((r == mRedSize) && (g == mGreenSize) && (b == mBlueSize) && (a == mAlphaSize)) { return config; } } } return null; } private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { return mValue[0]; } return defaultValue; } private int[] mValue; // Subclasses can adjust these values: protected int mRedSize; protected int mGreenSize; protected int mBlueSize; protected int mAlphaSize; protected int mDepthSize; protected int mStencilSize; } /** * This class will choose a RGB_888 surface with * or without a depth buffer. * */ private class SimpleEGLConfigChooser extends ComponentSizeChooser { public SimpleEGLConfigChooser(boolean withDepthBuffer) { super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); } } /** * An EGL helper class. */ private static class EglHelper { public EglHelper(WeakReference glTextureViewWeakRef) { mGLTextureViewWeakRef = glTextureViewWeakRef; } /** * Initialize EGL for a given configuration spec. * @param configSpec */ public void start() { if (LOG_EGL) { Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); } /* * Get an EGL instance */ mEgl = (EGL10) EGLContext.getEGL(); /* * Get to the default display. */ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { throw new RuntimeException("eglGetDisplay failed"); } /* * We can now initialize EGL for that display */ int[] version = new int[2]; if(!mEgl.eglInitialize(mEglDisplay, version)) { throw new RuntimeException("eglInitialize failed"); } GLTextureView view = mGLTextureViewWeakRef.get(); if (view == null) { mEglConfig = null; mEglContext = null; } else { mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); /* * Create an EGL context. We want to do this as rarely as we can, because an * EGL context is a somewhat heavy object. */ mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); } if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { mEglContext = null; throwEglException("createContext"); } if (LOG_EGL) { Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); } mEglSurface = null; } /** * Create an egl surface for the current SurfaceHolder surface. If a surface * already exists, destroy it before creating the new surface. * * @return true if the surface was created successfully. */ public boolean createSurface() { if (LOG_EGL) { Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); } /* * Check preconditions. */ if (mEgl == null) { throw new RuntimeException("egl not initialized"); } if (mEglDisplay == null) { throw new RuntimeException("eglDisplay not initialized"); } if (mEglConfig == null) { throw new RuntimeException("mEglConfig not initialized"); } /* * The window size has changed, so we need to create a new * surface. */ destroySurfaceImp(); /* * Create an EGL surface we can render into. */ GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay, mEglConfig, view.getSurfaceTexture()); } else { mEglSurface = null; } if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { int error = mEgl.eglGetError(); if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); } return false; } /* * Before we can issue GL commands, we need to make sure * the context is current and bound to a surface. */ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { /* * Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */ logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); return false; } return true; } /** * Create a GL object for the current EGL context. * @return */ GL createGL() { GL gl = mEglContext.getGL(); GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { if (view.mGLWrapper != null) { gl = view.mGLWrapper.wrap(gl); } if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) { int configFlags = 0; Writer log = null; if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; } if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { log = new LogWriter(); } gl = GLDebugHelper.wrap(gl, configFlags, log); } } return gl; } /** * Display the current render surface. * @return the EGL error code from eglSwapBuffers. */ public int swap() { if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { return mEgl.eglGetError(); } return EGL10.EGL_SUCCESS; } public void destroySurface() { if (LOG_EGL) { Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); } destroySurfaceImp(); } private void destroySurfaceImp() { if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); } mEglSurface = null; } } public void finish() { if (LOG_EGL) { Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); } if (mEglContext != null) { GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); } mEglContext = null; } if (mEglDisplay != null) { mEgl.eglTerminate(mEglDisplay); mEglDisplay = null; } } private void throwEglException(String function) { throwEglException(function, mEgl.eglGetError()); } public static void throwEglException(String function, int error) { String message = formatEglError(function, error); if (LOG_THREADS) { Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + message); } throw new RuntimeException(message); } public static void logEglErrorAsWarning(String tag, String function, int error) { Log.w(tag, formatEglError(function, error)); } public static String formatEglError(String function, int error) { return function + " failed"; // + EGLLogWrapper.getErrorString(error); } private WeakReference mGLTextureViewWeakRef; EGL10 mEgl; EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLConfig mEglConfig; EGLContext mEglContext; } /** * A generic GL Thread. Takes care of initializing EGL and GL. Delegates * to a Renderer instance to do the actual drawing. Can be configured to * render continuously or on request. * * All potentially blocking synchronization is done through the * sGLThreadManager object. This avoids multiple-lock ordering issues. * */ static class GLThread extends Thread { GLThread(WeakReference glTextureViewWeakRef) { super(); mWidth = 0; mHeight = 0; mRequestRender = true; mRenderMode = RENDERMODE_CONTINUOUSLY; mGLTextureViewWeakRef = glTextureViewWeakRef; } @Override public void run() { setName("GLThread " + getId()); if (LOG_THREADS) { Log.i("GLThread", "starting tid=" + getId()); } try { guardedRun(); } catch (InterruptedException e) { // fall thru and exit normally } finally { sGLThreadManager.threadExiting(this); } } /* * This private method should only be called inside a * synchronized(sGLThreadManager) block. */ private void stopEglSurfaceLocked() { if (mHaveEglSurface) { mHaveEglSurface = false; mEglHelper.destroySurface(); } } /* * This private method should only be called inside a * synchronized(sGLThreadManager) block. */ private void stopEglContextLocked() { if (mHaveEglContext) { mEglHelper.finish(); mHaveEglContext = false; sGLThreadManager.releaseEglContextLocked(this); } } private void guardedRun() throws InterruptedException { mEglHelper = new EglHelper(mGLTextureViewWeakRef); mHaveEglContext = false; mHaveEglSurface = false; try { GL10 gl = null; boolean createEglContext = false; boolean createEglSurface = false; boolean createGlInterface = false; boolean lostEglContext = false; boolean sizeChanged = false; boolean wantRenderNotification = false; boolean doRenderNotification = false; boolean askedToReleaseEglContext = false; int w = 0; int h = 0; Runnable event = null; while (true) { synchronized (sGLThreadManager) { while (true) { if (mShouldExit) { return; } if (! mEventQueue.isEmpty()) { event = mEventQueue.remove(0); break; } // Update the pause state. boolean pausing = false; if (mPaused != mRequestPaused) { pausing = mRequestPaused; mPaused = mRequestPaused; sGLThreadManager.notifyAll(); if (LOG_PAUSE_RESUME) { Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId()); } } // Do we need to give up the EGL context? if (mShouldReleaseEglContext) { if (LOG_SURFACE) { Log.i("GLThread", "releasing EGL context because asked to tid=" + getId()); } stopEglSurfaceLocked(); stopEglContextLocked(); mShouldReleaseEglContext = false; askedToReleaseEglContext = true; } // Have we lost the EGL context? if (lostEglContext) { stopEglSurfaceLocked(); stopEglContextLocked(); lostEglContext = false; } // When pausing, release the EGL surface: if (pausing && mHaveEglSurface) { if (LOG_SURFACE) { Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); } stopEglSurfaceLocked(); } // When pausing, optionally release the EGL Context: if (pausing && mHaveEglContext) { GLTextureView view = mGLTextureViewWeakRef.get(); boolean preserveEglContextOnPause = view == null ? false : view.mPreserveEGLContextOnPause; if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) { stopEglContextLocked(); if (LOG_SURFACE) { Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); } } } // When pausing, optionally terminate EGL: if (pausing) { if (sGLThreadManager.shouldTerminateEGLWhenPausing()) { mEglHelper.finish(); if (LOG_SURFACE) { Log.i("GLThread", "terminating EGL because paused tid=" + getId()); } } } // Have we lost the SurfaceView surface? if ((! mHasSurface) && (! mWaitingForSurface)) { if (LOG_SURFACE) { Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); } if (mHaveEglSurface) { stopEglSurfaceLocked(); } mWaitingForSurface = true; mSurfaceIsBad = false; sGLThreadManager.notifyAll(); } // Have we acquired the surface view surface? if (mHasSurface && mWaitingForSurface) { if (LOG_SURFACE) { Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId()); } mWaitingForSurface = false; sGLThreadManager.notifyAll(); } if (doRenderNotification) { if (LOG_SURFACE) { Log.i("GLThread", "sending render notification tid=" + getId()); } wantRenderNotification = false; doRenderNotification = false; mRenderComplete = true; sGLThreadManager.notifyAll(); } // Ready to draw? if (readyToDraw()) { // If we don't have an EGL context, try to acquire one. if (! mHaveEglContext) { if (askedToReleaseEglContext) { askedToReleaseEglContext = false; } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) { try { mEglHelper.start(); } catch (RuntimeException t) { sGLThreadManager.releaseEglContextLocked(this); throw t; } mHaveEglContext = true; createEglContext = true; sGLThreadManager.notifyAll(); } } if (mHaveEglContext && !mHaveEglSurface) { mHaveEglSurface = true; createEglSurface = true; createGlInterface = true; sizeChanged = true; } if (mHaveEglSurface) { if (mSizeChanged) { sizeChanged = true; w = mWidth; h = mHeight; wantRenderNotification = true; if (LOG_SURFACE) { Log.i("GLThread", "noticing that we want render notification tid=" + getId()); } // Destroy and recreate the EGL surface. createEglSurface = true; mSizeChanged = false; } mRequestRender = false; sGLThreadManager.notifyAll(); break; } } // By design, this is the only place in a GLThread thread where we wait(). if (LOG_THREADS) { Log.i("GLThread", "waiting tid=" + getId() + " mHaveEglContext: " + mHaveEglContext + " mHaveEglSurface: " + mHaveEglSurface + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface + " mPaused: " + mPaused + " mHasSurface: " + mHasSurface + " mSurfaceIsBad: " + mSurfaceIsBad + " mWaitingForSurface: " + mWaitingForSurface + " mWidth: " + mWidth + " mHeight: " + mHeight + " mRequestRender: " + mRequestRender + " mRenderMode: " + mRenderMode); } sGLThreadManager.wait(); } } // end of synchronized(sGLThreadManager) if (event != null) { event.run(); event = null; continue; } if (createEglSurface) { if (LOG_SURFACE) { Log.w("GLThread", "egl createSurface"); } if (mEglHelper.createSurface()) { synchronized(sGLThreadManager) { mFinishedCreatingEglSurface = true; sGLThreadManager.notifyAll(); } } else { synchronized(sGLThreadManager) { mFinishedCreatingEglSurface = true; mSurfaceIsBad = true; sGLThreadManager.notifyAll(); } continue; } createEglSurface = false; } if (createGlInterface) { gl = (GL10) mEglHelper.createGL(); sGLThreadManager.checkGLDriver(gl); createGlInterface = false; } if (createEglContext) { if (LOG_RENDERER) { Log.w("GLThread", "onSurfaceCreated"); } GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); } createEglContext = false; } if (sizeChanged) { if (LOG_RENDERER) { Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); } GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { view.mRenderer.onSurfaceChanged(gl, w, h); } sizeChanged = false; } if (LOG_RENDERER_DRAW_FRAME) { Log.w("GLThread", "onDrawFrame tid=" + getId()); } { GLTextureView view = mGLTextureViewWeakRef.get(); if (view != null) { view.mRenderer.onDrawFrame(gl); } } int swapError = mEglHelper.swap(); switch (swapError) { case EGL10.EGL_SUCCESS: break; case EGL11.EGL_CONTEXT_LOST: if (LOG_SURFACE) { Log.i("GLThread", "egl context lost tid=" + getId()); } lostEglContext = true; break; default: // Other errors typically mean that the current surface is bad, // probably because the SurfaceView surface has been destroyed, // but we haven't been notified yet. // Log the error to help developers understand why rendering stopped. EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); synchronized(sGLThreadManager) { mSurfaceIsBad = true; sGLThreadManager.notifyAll(); } break; } if (wantRenderNotification) { doRenderNotification = true; } } } finally { /* * clean-up everything... */ synchronized (sGLThreadManager) { stopEglSurfaceLocked(); stopEglContextLocked(); } } } public boolean ableToDraw() { return mHaveEglContext && mHaveEglSurface && readyToDraw(); } private boolean readyToDraw() { return (!mPaused) && mHasSurface && (!mSurfaceIsBad) && (mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); } public void setRenderMode(int renderMode) { if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { throw new IllegalArgumentException("renderMode"); } synchronized(sGLThreadManager) { mRenderMode = renderMode; sGLThreadManager.notifyAll(); } } public int getRenderMode() { synchronized(sGLThreadManager) { return mRenderMode; } } public void requestRender() { synchronized(sGLThreadManager) { mRequestRender = true; sGLThreadManager.notifyAll(); } } public void surfaceCreated() { synchronized(sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceCreated tid=" + getId()); } mHasSurface = true; mFinishedCreatingEglSurface = false; sGLThreadManager.notifyAll(); while (mWaitingForSurface && !mFinishedCreatingEglSurface && !mExited) { try { sGLThreadManager.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } public void surfaceDestroyed() { synchronized(sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceDestroyed tid=" + getId()); } mHasSurface = false; sGLThreadManager.notifyAll(); while((!mWaitingForSurface) && (!mExited)) { try { sGLThreadManager.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } public void onPause() { synchronized (sGLThreadManager) { if (LOG_PAUSE_RESUME) { Log.i("GLThread", "onPause tid=" + getId()); } mRequestPaused = true; sGLThreadManager.notifyAll(); while ((! mExited) && (! mPaused)) { if (LOG_PAUSE_RESUME) { Log.i("Main thread", "onPause waiting for mPaused."); } try { sGLThreadManager.wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } public void onResume() { synchronized (sGLThreadManager) { if (LOG_PAUSE_RESUME) { Log.i("GLThread", "onResume tid=" + getId()); } mRequestPaused = false; mRequestRender = true; mRenderComplete = false; sGLThreadManager.notifyAll(); while ((! mExited) && mPaused && (!mRenderComplete)) { if (LOG_PAUSE_RESUME) { Log.i("Main thread", "onResume waiting for !mPaused."); } try { sGLThreadManager.wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } public void onWindowResize(int w, int h) { synchronized (sGLThreadManager) { mWidth = w; mHeight = h; mSizeChanged = true; mRequestRender = true; mRenderComplete = false; sGLThreadManager.notifyAll(); // Wait for thread to react to resize and render a frame while (! mExited && !mPaused && !mRenderComplete && ableToDraw()) { if (LOG_SURFACE) { Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId()); } try { sGLThreadManager.wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } public void requestExitAndWait() { // don't call this from GLThread thread or it is a guaranteed // deadlock! synchronized(sGLThreadManager) { mShouldExit = true; sGLThreadManager.notifyAll(); while (! mExited) { try { sGLThreadManager.wait(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } public void requestReleaseEglContextLocked() { mShouldReleaseEglContext = true; sGLThreadManager.notifyAll(); } /** * Queue an "event" to be run on the GL rendering thread. * @param r the runnable to be run on the GL rendering thread. */ public void queueEvent(Runnable r) { if (r == null) { throw new IllegalArgumentException("r must not be null"); } synchronized(sGLThreadManager) { mEventQueue.add(r); sGLThreadManager.notifyAll(); } } // Once the thread is started, all accesses to the following member // variables are protected by the sGLThreadManager monitor private boolean mShouldExit; private boolean mExited; private boolean mRequestPaused; private boolean mPaused; private boolean mHasSurface; private boolean mSurfaceIsBad; private boolean mWaitingForSurface; private boolean mHaveEglContext; private boolean mHaveEglSurface; private boolean mFinishedCreatingEglSurface; private boolean mShouldReleaseEglContext; private int mWidth; private int mHeight; private int mRenderMode; private boolean mRequestRender; private boolean mRenderComplete; private ArrayList mEventQueue = new ArrayList(); private boolean mSizeChanged = true; // End of member variables protected by the sGLThreadManager monitor. private EglHelper mEglHelper; /** * Set once at thread construction time, nulled out when the parent view is garbage * called. This weak reference allows the GLSurfaceView to be garbage collected while * the GLThread is still alive. */ private WeakReference mGLTextureViewWeakRef; } static class LogWriter extends Writer { @Override public void close() { flushBuilder(); } @Override public void flush() { flushBuilder(); } @Override public void write(char[] buf, int offset, int count) { for(int i = 0; i < count; i++) { char c = buf[offset + i]; if ( c == '\n') { flushBuilder(); } else { mBuilder.append(c); } } } private void flushBuilder() { if (mBuilder.length() > 0) { Log.v("GLSurfaceView", mBuilder.toString()); mBuilder.delete(0, mBuilder.length()); } } private StringBuilder mBuilder = new StringBuilder(); } private void checkRenderThreadState() { if (mGLThread != null) { throw new IllegalStateException( "setRenderer has already been called for this instance."); } } private static class GLThreadManager { private static String TAG = "GLThreadManager"; public synchronized void threadExiting(GLThread thread) { if (LOG_THREADS) { Log.i("GLThread", "exiting tid=" + thread.getId()); } thread.mExited = true; if (mEglOwner == thread) { mEglOwner = null; } notifyAll(); } /* * Tries once to acquire the right to use an EGL * context. Does not block. Requires that we are already * in the sGLThreadManager monitor when this is called. * * @return true if the right to use an EGL context was acquired. */ public boolean tryAcquireEglContextLocked(GLThread thread) { if (mEglOwner == thread || mEglOwner == null) { mEglOwner = thread; notifyAll(); return true; } checkGLESVersion(); if (mMultipleGLESContextsAllowed) { return true; } // Notify the owning thread that it should release the context. // TODO: implement a fairness policy. Currently // if the owning thread is drawing continuously it will just // reacquire the EGL context. if (mEglOwner != null) { mEglOwner.requestReleaseEglContextLocked(); } return false; } /* * Releases the EGL context. Requires that we are already in the * sGLThreadManager monitor when this is called. */ public void releaseEglContextLocked(GLThread thread) { if (mEglOwner == thread) { mEglOwner = null; } notifyAll(); } public synchronized boolean shouldReleaseEGLContextWhenPausing() { // Release the EGL context when pausing even if // the hardware supports multiple EGL contexts. // Otherwise the device could run out of EGL contexts. return mLimitedGLESContexts; } public synchronized boolean shouldTerminateEGLWhenPausing() { checkGLESVersion(); return !mMultipleGLESContextsAllowed; } public synchronized void checkGLDriver(GL10 gl) { if (! mGLESDriverCheckComplete) { checkGLESVersion(); String renderer = gl.glGetString(GL10.GL_RENDERER); if (mGLESVersion < kGLES_20) { mMultipleGLESContextsAllowed = ! renderer.startsWith(kMSM7K_RENDERER_PREFIX); notifyAll(); } mLimitedGLESContexts = !mMultipleGLESContextsAllowed; if (LOG_SURFACE) { Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = " + mMultipleGLESContextsAllowed + " mLimitedGLESContexts = " + mLimitedGLESContexts); } mGLESDriverCheckComplete = true; } } private void checkGLESVersion() { if (! mGLESVersionCheckComplete) { // mGLESVersion = SystemProperties.getInt( // "ro.opengles.version", // ConfigurationInfo.GL_ES_VERSION_UNDEFINED); // if (mGLESVersion >= kGLES_20) { mMultipleGLESContextsAllowed = true; // } if (LOG_SURFACE) { Log.w(TAG, "checkGLESVersion mGLESVersion =" + " " + mGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed); } mGLESVersionCheckComplete = true; } } /** * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides * support for hardware-accelerated views, therefore multiple EGL contexts are * supported on all Android 3.0+ EGL drivers. */ private boolean mGLESVersionCheckComplete; private int mGLESVersion; private boolean mGLESDriverCheckComplete; private boolean mMultipleGLESContextsAllowed; private boolean mLimitedGLESContexts; private static final int kGLES_20 = 0x20000; private static final String kMSM7K_RENDERER_PREFIX = "Q3Dimension MSM7500 "; private GLThread mEglOwner; } private static final GLThreadManager sGLThreadManager = new GLThreadManager(); private final WeakReference mThisWeakRef = new WeakReference(this); private GLThread mGLThread; private GLSurfaceView.Renderer mRenderer; private boolean mDetached; private EGLConfigChooser mEGLConfigChooser; private EGLContextFactory mEGLContextFactory; private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; private GLWrapper mGLWrapper; private int mDebugFlags; private int mEGLContextClientVersion; private boolean mPreserveEGLContextOnPause; } ================================================ FILE: library/src/main/java/com/roger/missview/library/GLUtil.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.graphics.Bitmap; import android.opengl.GLES20; import android.opengl.GLUtils; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; public class GLUtil { public static final int BYTES_PER_FLOAT = 4; public static int loadShader(int type, String shaderCode) { // create a vertex shader type (GLES20.GL_VERTEX_SHADER) // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) int shaderHandle = GLES20.glCreateShader(type); // add the source code to the shader and compile it GLES20.glShaderSource(shaderHandle, shaderCode); GLES20.glCompileShader(shaderHandle); checkGlError("glCompileShader"); return shaderHandle; } public static int createAndLinkProgram(int vertexShaderHandle, int fragShaderHandle, String[] attributes) { int programHandle = GLES20.glCreateProgram(); GLUtil.checkGlError("glCreateProgram"); GLES20.glAttachShader(programHandle, vertexShaderHandle); GLES20.glAttachShader(programHandle, fragShaderHandle); if (attributes != null) { final int size = attributes.length; for (int i = 0; i < size; i++) { GLES20.glBindAttribLocation(programHandle, i, attributes[i]); } } GLES20.glLinkProgram(programHandle); GLUtil.checkGlError("glLinkProgram"); GLES20.glDeleteShader(vertexShaderHandle); GLES20.glDeleteShader(fragShaderHandle); return programHandle; } public static int loadTexture(Bitmap bitmap) { final int[] textureHandle = new int[1]; GLES20.glGenTextures(1, textureHandle, 0); GLUtil.checkGlError("glGenTextures"); if (textureHandle[0] != 0) { // Bind to the texture in OpenGL GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]); // Set filtering GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); // Load the bitmap into the bound texture. GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); GLUtil.checkGlError("texImage2D"); } if (textureHandle[0] == 0) { } return textureHandle[0]; } public static void checkGlError(String glOperation) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { } } public static FloatBuffer asFloatBuffer(float[] array) { FloatBuffer buffer = newFloatBuffer(array.length); buffer.put(array); buffer.position(0); return buffer; } public static FloatBuffer newFloatBuffer(int size) { FloatBuffer buffer = ByteBuffer.allocateDirect(size * BYTES_PER_FLOAT) .order(ByteOrder.nativeOrder()) .asFloatBuffer(); buffer.position(0); return buffer; } } ================================================ FILE: library/src/main/java/com/roger/missview/library/MissView.java ================================================ package com.roger.missview.library; import android.content.Context; import android.graphics.Bitmap; import android.opengl.GLSurfaceView; import android.util.AttributeSet; /** * Created by Administrator on 2015/4/30. */ public class MissView extends GLTextureView implements RenderController.Callbacks, BlurRenderer.Callbacks { private BlurRenderer mRenderer; private RenderController mRenderController; public MissView(Context context) { super(context); init(); } public MissView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mRenderer = new BlurRenderer(getContext(), this); setEGLContextClientVersion(2); setEGLConfigChooser(8, 8, 8, 8, 0, 0); setRenderer(mRenderer); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); } public void initPicture(String mPictureName) { mRenderController = new DemoRenderController(getContext(), mRenderer, this, true, mPictureName); mRenderer.setDemoMode(true); mRenderController.setVisible(true); } public void initPicture(Bitmap mBitmap) { mRenderController = new DemoRenderController(getContext(), mRenderer, this, true, mBitmap); mRenderer.setDemoMode(true); mRenderController.setVisible(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mRenderer.hintViewportSize(w, h); mRenderController.reloadCurrentArtwork(true); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (null != mRenderController) { mRenderController.destroy(); queueEventOnGlThread(new Runnable() { @Override public void run() { mRenderer.destroy(); } }); } } @Override public void queueEventOnGlThread(Runnable runnable) { if (this == null) { return; } super.queueEvent(runnable); } @Override public void requestRender() { if (this == null) { return; } super.requestRender(); } } ================================================ FILE: library/src/main/java/com/roger/missview/library/RenderController.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library; import android.content.Context; import android.os.AsyncTask; import com.roger.missview.library.util.BitmapRegionLoader; public abstract class RenderController { protected Context mContext; protected BlurRenderer mRenderer; protected Callbacks mCallbacks; protected boolean mVisible; private BitmapRegionLoader mQueuedBitmapRegionLoader; public RenderController(Context context, BlurRenderer renderer, Callbacks callbacks) { mRenderer = renderer; mContext = context; mCallbacks = callbacks; } public void destroy() { if (mQueuedBitmapRegionLoader != null) { mQueuedBitmapRegionLoader.destroy(); } } protected abstract BitmapRegionLoader openDownloadedCurrentArtwork(boolean forceReload); public void reloadCurrentArtwork(final boolean forceReload) { new AsyncTask() { @Override protected BitmapRegionLoader doInBackground(Void... voids) { // openDownloadedCurrentArtwork should be called on a background thread return openDownloadedCurrentArtwork(forceReload); } @Override protected void onPostExecute(final BitmapRegionLoader bitmapRegionLoader) { if (bitmapRegionLoader == null) { return; } mCallbacks.queueEventOnGlThread(new Runnable() { @Override public void run() { if (mVisible) { mRenderer.setAndConsumeBitmapRegionLoader(bitmapRegionLoader); } else { mQueuedBitmapRegionLoader = bitmapRegionLoader; } } }); } }.execute((Void) null); } public void setVisible(boolean visible) { mVisible = visible; if (visible) { mCallbacks.queueEventOnGlThread(new Runnable() { @Override public void run() { if (mQueuedBitmapRegionLoader != null) { mRenderer.setAndConsumeBitmapRegionLoader(mQueuedBitmapRegionLoader); mQueuedBitmapRegionLoader = null; } } }); mCallbacks.requestRender(); } } public static interface Callbacks { void queueEventOnGlThread(Runnable runnable); void requestRender(); } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/ArtDetailViewport.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library.util; import android.graphics.RectF; // Singleton that also behaves as an event public class ArtDetailViewport { private volatile RectF mViewport0 = new RectF(); private volatile RectF mViewport1 = new RectF(); private boolean mFromUser; private static ArtDetailViewport sInstance = new ArtDetailViewport(); public static ArtDetailViewport getInstance() { return sInstance; } private ArtDetailViewport() { } public RectF getViewport(int id) { return (id == 0) ? mViewport0 : mViewport1; } public ArtDetailViewport setViewport(int id, RectF viewport, boolean fromUser) { return setViewport(id, viewport.left, viewport.top, viewport.right, viewport.bottom, fromUser); } public ArtDetailViewport setViewport(int id, float left, float top, float right, float bottom, boolean fromUser) { mFromUser = fromUser; getViewport(id).set(left, top, right, bottom); return this; } public boolean isFromUser() { return mFromUser; } public ArtDetailViewport setDefaultViewport(int id, float bitmapAspectRatio, float screenAspectRatio) { mFromUser = false; if (bitmapAspectRatio > screenAspectRatio) { getViewport(id).set( 0.5f - screenAspectRatio / bitmapAspectRatio / 2, 0, 0.5f + screenAspectRatio / bitmapAspectRatio / 2, 1); } else { getViewport(id).set( 0, 0.5f - bitmapAspectRatio / screenAspectRatio / 2, 1, 0.5f + bitmapAspectRatio / screenAspectRatio / 2); } return this; } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/BitmapRegionLoader.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library.util; import android.graphics.Bitmap; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; import android.util.Log; import java.io.IOException; import java.io.InputStream; import static android.graphics.BitmapFactory.Options; /** * Wrapper for {@link android.graphics.BitmapRegionDecoder} with some extra functionality. */ public class BitmapRegionLoader { private boolean mValid = false; private int mRotation = 0; private int mOriginalWidth; private int mOriginalHeight; private Rect mTempRect = new Rect(); private InputStream mInputStream; private volatile BitmapRegionDecoder mBitmapRegionDecoder; private Matrix mRotateMatrix; public static BitmapRegionLoader newInstance(InputStream in) { return newInstance(in, 0); } public static BitmapRegionLoader newInstance(InputStream in, int rotation) { if (in == null) { Log.e("BitmapRegionLoader", "InputStream is null!"); return null; } BitmapRegionLoader loader = new BitmapRegionLoader(in); if (loader.mValid) { loader.mRotation = rotation; if (loader.mRotation != 0) { loader.mRotateMatrix = new Matrix(); loader.mRotateMatrix.postRotate(rotation); } return loader; } return null; } private BitmapRegionLoader(InputStream in) { mInputStream = in; try { mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(in, false); mOriginalWidth = mBitmapRegionDecoder.getWidth(); mOriginalHeight = mBitmapRegionDecoder.getHeight(); mValid = true; } catch (IOException e) { e.printStackTrace(); } } /** * Key difference, aside from support for ration, from * {@link android.graphics.BitmapRegionDecoder#decodeRegion(android.graphics.Rect, android.graphics.BitmapFactory.Options)} in this implementation is that even * if inBitmap is given, a sub-bitmap might be returned. */ public synchronized Bitmap decodeRegion(Rect rect, Options options) { int unsampledInBitmapWidth = -1; int unsampledInBitmapHeight = -1; int sampleSize = Math.max(1, options != null ? options.inSampleSize : 1); if (options != null && options.inBitmap != null) { unsampledInBitmapWidth = options.inBitmap.getWidth() * sampleSize; unsampledInBitmapHeight = options.inBitmap.getHeight() * sampleSize; } // Decode with rotation switch (mRotation) { case 90: mTempRect.set( rect.top, mOriginalHeight - rect.right, rect.bottom, mOriginalHeight - rect.left); break; case 180: mTempRect.set( mOriginalWidth - rect.right, mOriginalHeight - rect.bottom, mOriginalWidth - rect.left, mOriginalHeight - rect.top); break; case 270: mTempRect.set( mOriginalWidth - rect.bottom, rect.left, mOriginalWidth - rect.top, rect.right); break; default: mTempRect.set(rect); } Bitmap bitmap = mBitmapRegionDecoder.decodeRegion(mTempRect, options); if (options != null && options.inBitmap != null && ((mTempRect.width() != unsampledInBitmapWidth || mTempRect.height() != unsampledInBitmapHeight))) { // Need to extract the sub-bitmap Bitmap subBitmap = Bitmap.createBitmap( bitmap, 0, 0, mTempRect.width() / sampleSize, mTempRect.height() / sampleSize); if (bitmap != options.inBitmap) { bitmap.recycle(); } bitmap = subBitmap; } if (mRotateMatrix != null) { // Rotate decoded bitmap Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mRotateMatrix, true); if (options == null || bitmap != options.inBitmap) { bitmap.recycle(); } bitmap = rotatedBitmap; } return bitmap; } public synchronized int getWidth() { return (mRotation == 90 || mRotation == 270) ? mOriginalHeight : mOriginalWidth; } public synchronized int getHeight() { return (mRotation == 90 || mRotation == 270) ? mOriginalWidth : mOriginalHeight; } public synchronized void destroy() { mBitmapRegionDecoder.recycle(); mBitmapRegionDecoder = null; try { mInputStream.close(); } catch (IOException ignored) { ignored.printStackTrace(); } } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/ImageBlurrer.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library.util; import android.content.Context; import android.graphics.Bitmap; import android.renderscript.Allocation; import android.renderscript.Element; import android.renderscript.Matrix3f; import android.renderscript.RenderScript; import android.renderscript.ScriptIntrinsicBlur; import android.renderscript.ScriptIntrinsicColorMatrix; public class ImageBlurrer { public static final int MAX_SUPPORTED_BLUR_PIXELS = 25; private RenderScript mRS; private ScriptIntrinsicBlur mSIBlur; private ScriptIntrinsicColorMatrix mSIGrey; private Allocation mTmp1; private Allocation mTmp2; public ImageBlurrer(Context context) { mRS = RenderScript.create(context); mSIBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS)); mSIGrey = ScriptIntrinsicColorMatrix.create(mRS); } public Bitmap blurBitmap(Bitmap src, float radius, float desaturateAmount) { Bitmap dest = Bitmap.createBitmap(src); if ((int) radius == 0) { return dest; } if (mTmp1 != null) { mTmp1.destroy(); } if (mTmp2 != null) { mTmp2.destroy(); } mTmp1 = Allocation.createFromBitmap(mRS, src); mTmp2 = Allocation.createFromBitmap(mRS, dest); mSIBlur.setRadius((int) radius); mSIBlur.setInput(mTmp1); mSIBlur.forEach(mTmp2); if (desaturateAmount > 0) { desaturateAmount = MathUtil.constrain(0, 1, desaturateAmount); Matrix3f m = new Matrix3f(new float[]{ MathUtil.interpolate(1, 0.299f, desaturateAmount), MathUtil.interpolate(0, 0.299f, desaturateAmount), MathUtil.interpolate(0, 0.299f, desaturateAmount), MathUtil.interpolate(0, 0.587f, desaturateAmount), MathUtil.interpolate(1, 0.587f, desaturateAmount), MathUtil.interpolate(0, 0.587f, desaturateAmount), MathUtil.interpolate(0, 0.114f, desaturateAmount), MathUtil.interpolate(0, 0.114f, desaturateAmount), MathUtil.interpolate(1, 0.114f, desaturateAmount), }); mSIGrey.setColorMatrix(m); mSIGrey.forEach(mTmp2, mTmp1); mTmp1.copyTo(dest); } else { mTmp2.copyTo(dest); } return dest; } public void destroy() { mSIBlur.destroy(); if (mTmp1 != null) { mTmp1.destroy(); } if (mTmp2 != null) { mTmp2.destroy(); } mRS.destroy(); } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/ImageUtil.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library.util; import android.graphics.Bitmap; import android.graphics.Color; public class ImageUtil { // Make sure input images are very small! public static float calculateDarkness(Bitmap bitmap) { if (bitmap == null) { return 0; } int width = bitmap.getWidth(); int height = bitmap.getHeight(); int totalLum = 0; int n = 0; int x, y, color; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { ++n; color = bitmap.getPixel(x, y); totalLum += (0.21f * Color.red(color) + 0.71f * Color.green(color) + 0.07f * Color.blue(color)); } } return (totalLum / n) / 256f; } private ImageUtil() { } public static int calculateSampleSize(int rawHeight, int targetHeight) { int sampleSize = 1; while (rawHeight / (sampleSize << 1) > targetHeight) { sampleSize <<= 1; } return sampleSize; } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/MathUtil.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library.util; import android.util.FloatMath; public class MathUtil { public static float constrain(float min, float max, float v) { return Math.max(min, Math.min(max, v)); } public static float interpolate(float x1, float x2, float f) { return x1 + (x2 - x1) * f; } public static float uninterpolate(float x1, float x2, float v) { if (x2 - x1 == 0) { throw new IllegalArgumentException("Can't reverse interpolate with domain size of 0"); } return (v - x1) / (x2 - x1); } public static float dist(float x, float y) { return FloatMath.sqrt(x * x + y * y); } public static int floorEven(int num) { return num & ~0x01; } public static int roundMult4(int num) { return (num + 2) & ~0x03; } public static boolean isEven(int num) { return num % 2 == 0; } private MathUtil() { } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/TickingFloatAnimator.java ================================================ /* * Copyright 2014 Google Inc. * * 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.roger.missview.library.util; import android.animation.TimeInterpolator; import android.os.SystemClock; import android.view.animation.AccelerateDecelerateInterpolator; // Non thread-safe public class TickingFloatAnimator { private float mStartValue = 0; private float mCurrentValue; private float mEndValue; private boolean mRunning = false; private long mStartTime; private int mDuration = 1000; private Runnable mEndCallback; private TimeInterpolator mInterpolator = new AccelerateDecelerateInterpolator(); public static TickingFloatAnimator create() { return new TickingFloatAnimator(); } public TickingFloatAnimator from(float startValue) { cancel(); mStartValue = startValue; mCurrentValue = startValue; return this; } public TickingFloatAnimator to(float endValue) { mEndValue = endValue; return this; } public TickingFloatAnimator withDuration(int duration) { mDuration = Math.max(0, duration); return this; } public TickingFloatAnimator withInterpolator(TimeInterpolator interpolator) { mInterpolator = interpolator; return this; } public TickingFloatAnimator withEndListener(Runnable listener) { mEndCallback = listener; return this; } public void cancel() { mRunning = false; mEndValue = mCurrentValue; } public boolean tick() { if (!mRunning) { return false; } float t; if (mDuration <= 0) { t = 1; } else { t = (float) (SystemClock.elapsedRealtime() - mStartTime) * 1f / mDuration; if (t >= 1) { t = 1; } } if (t >= 1) { // Ended mRunning = false; mCurrentValue = mEndValue; if (mEndCallback != null) { mEndCallback.run(); } return false; } // Still running; compute value mCurrentValue = mStartValue + mInterpolator.getInterpolation(t) * (mEndValue - mStartValue); mRunning = true; return true; } public void start() { mRunning = true; mStartValue = mCurrentValue; mStartTime = SystemClock.elapsedRealtime(); tick(); } public boolean isRunning() { return mRunning; } public float currentValue() { return mCurrentValue; } private TickingFloatAnimator() { } } ================================================ FILE: library/src/main/java/com/roger/missview/library/util/UriUtil.java ================================================ package com.roger.missview.library.util; import android.annotation.TargetApi; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; /** * Created by Administrator on 2015/4/28. */ public class UriUtil { /** * 根据Uri获取图片绝对路径,解决Android4.4以上版本Uri转换 * @param context * @param imageUri */ @TargetApi(19) public static String getImageAbsolutePath(Context context, Uri imageUri) { if (context == null || imageUri == null) return null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, imageUri)) { if (isExternalStorageDocument(imageUri)) { String docId = DocumentsContract.getDocumentId(imageUri); String[] split = docId.split(":"); String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } } else if (isDownloadsDocument(imageUri)) { String id = DocumentsContract.getDocumentId(imageUri); Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } else if (isMediaDocument(imageUri)) { String docId = DocumentsContract.getDocumentId(imageUri); String[] split = docId.split(":"); String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } String selection = MediaStore.Images.Media._ID + "=?"; String[] selectionArgs = new String[] { split[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(imageUri.getScheme())) { // Return the remote address if (isGooglePhotosUri(imageUri)) return imageUri.getLastPathSegment(); return getDataColumn(context, imageUri, null, null); } // File else if ("file".equalsIgnoreCase(imageUri.getScheme())) { return imageUri.getPath(); } return null; } public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; String column = MediaStore.Images.Media.DATA; String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { int index = cursor.getColumnIndexOrThrow(column); return cursor.getString(index); } } finally { if (cursor != null) cursor.close(); } return null; } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ public static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ public static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ public static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } } ================================================ FILE: settings.gradle ================================================ include ':library', ':demo'