├── settings.gradle ├── app ├── release │ └── app-release.apk ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── menu │ │ │ ├── menu_main.xml │ │ │ └── menu_real_time_blur.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ └── activity_real_time_blur.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── per │ │ └── goweii │ │ └── android │ │ └── blurred │ │ ├── MainActivity.java │ │ ├── PictureSelectorHelper.java │ │ └── RealTimeBlurActivity.java ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── burred ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── per │ │ └── goweii │ │ └── burred │ │ ├── Utils.java │ │ ├── DefaultSnapshotInterceptor.java │ │ ├── IBlur.java │ │ ├── GaussianBlur.java │ │ ├── BitmapProcessor.java │ │ ├── FastBlur.java │ │ └── Blurred.java ├── build.gradle └── proguard-rules.pro ├── README.md ├── gradle.properties ├── .gitignore ├── gradlew.bat ├── gradlew └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':burred' 2 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/release/app-release.apk -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Blurred 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /burred/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | burred 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /burred/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/Blurred/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 10 12:01:22 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_real_time_blur.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/Utils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | /** 4 | * @author CuiZhen 5 | * @date 2019/8/10 6 | * QQ: 302833254 7 | * E-mail: goweii@163.com 8 | * GitHub: https://github.com/goweii 9 | */ 10 | class Utils { 11 | 12 | static T requireNonNull(T obj, String message) { 13 | if (obj == null) { 14 | throw new NullPointerException(message); 15 | } 16 | return obj; 17 | } 18 | 19 | static T requireNonNull(T obj) { 20 | return requireNonNull(obj, ""); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /burred/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | } 25 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/DefaultSnapshotInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | import android.graphics.Bitmap; 4 | import android.view.View; 5 | 6 | /** 7 | * @author CuiZhen 8 | * @date 2019/8/11 9 | * QQ: 302833254 10 | * E-mail: goweii@163.com 11 | * GitHub: https://github.com/goweii 12 | */ 13 | public class DefaultSnapshotInterceptor implements Blurred.SnapshotInterceptor { 14 | @Override 15 | public Bitmap snapshot(View from, int backgroundColor, int foregroundColor, float scale, boolean antiAlias) { 16 | return BitmapProcessor.get().snapshot(from, backgroundColor, foregroundColor, scale, antiAlias); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blurred 2 | 3 | 4 | Android高斯模糊库,调教参数后可实现实时高斯模糊(<16ms,可达2~3ms) 5 | 6 | [GitHub主页](https://github.com/goweii/Blurred) 7 | 8 | [Demo下载](https://github.com/goweii/Blurred/raw/master/app/release/app-release.apk) 9 | 10 | 11 | # How to use 12 | 13 | 14 | To get a Git project into your build: 15 | 16 | Step 1. Add the JitPack repository to your build file 17 | 18 | gradle 19 | maven 20 | sbt 21 | leiningen 22 | Add it in your root build.gradle at the end of repositories: 23 | 24 | allprojects { 25 | repositories { 26 | ... 27 | maven { url 'https://www.jitpack.io' } 28 | } 29 | } 30 | Step 2. Add the dependency 31 | 32 | dependencies { 33 | implementation 'com.github.goweii:Blurred:1.2.0' 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/IBlur.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * 描述: 7 | * 8 | * @author Cuizhen 9 | * @date 2019/2/13 10 | */ 11 | public interface IBlur { 12 | /** 13 | * 高斯模糊图片 14 | * 15 | * @param originalBitmap 原图 16 | * @param radius 模糊半径 17 | * @param scale 缩小因子 18 | * @param keepSize 缩小后是否再次放大为原图尺寸 19 | * @param recycleOriginal 回收原图 20 | * @return 模糊图 21 | */ 22 | Bitmap process(final Bitmap originalBitmap, 23 | final float radius, 24 | final float scale, 25 | final boolean keepSize, 26 | final boolean recycleOriginal); 27 | 28 | /** 29 | * 回收资源 30 | */ 31 | void recycle(); 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /burred/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "per.goweii.android.blurred" 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | // 图片加载框架 25 | api 'com.github.bumptech.glide:glide:4.8.0' 26 | annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 27 | implementation('com.github.bumptech.glide:okhttp3-integration:4.8.0') { 28 | exclude group: "com.android.support" 29 | } 30 | implementation 'com.github.chrisbanes:PhotoView:2.0.0' 31 | // 照片,视频,音频的选择和拍摄 32 | implementation 'com.github.goweii:PictureSelector:v2.3.1' 33 | implementation project(':burred') 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | app/release/output.json 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # IntelliJ 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/assetWizardSettings.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | .idea/caches 46 | 47 | # Keystore files 48 | # Uncomment the following line if you do not want to check your keystore files in. 49 | #*.jks 50 | 51 | # External native build folder generated in Android Studio 2.2 and later 52 | .externalNativeBuild 53 | 54 | # Google Services (e.g. APIs or Firebase) 55 | google-services.json 56 | 57 | # Freeline 58 | freeline.py 59 | freeline/ 60 | freeline_project_description.json 61 | 62 | # fastlane 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | fastlane/readme.md 68 | 69 | .gitignore 70 | .idea/ 71 | Blurred.iml 72 | app/app.iml 73 | app/build/ 74 | burred/build/ 75 | burred/burred.iml 76 | burred/src/main/res/drawable/ 77 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 29 | 30 | 31 | 32 | 36 | 37 | 45 | 46 | 54 | 55 | 64 | 65 | 66 | 67 | 71 | 72 | 80 | 81 | 89 | 90 | 99 | 100 | 101 | 102 | 105 | 106 | 112 | 113 | 119 | 120 | 121 | 122 | 127 | 128 | 133 | 134 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_real_time_blur.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 31 | 32 | 37 | 38 | 43 | 44 | 49 | 50 | 55 | 56 | 57 | 58 | 65 | 66 | 73 | 74 | 81 | 82 | 83 | 84 | 89 | 90 | 94 | 95 | 103 | 104 | 112 | 113 | 122 | 123 | 124 | 125 | 129 | 130 | 138 | 139 | 147 | 148 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/GaussianBlur.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.os.Build; 6 | import android.renderscript.Allocation; 7 | import android.renderscript.Element; 8 | import android.renderscript.RSIllegalArgumentException; 9 | import android.renderscript.RSInvalidStateException; 10 | import android.renderscript.RenderScript; 11 | import android.renderscript.ScriptIntrinsicBlur; 12 | 13 | /** 14 | * @author Cuizhen 15 | * @date 2018/4/4 16 | * QQ: 302833254 17 | * E-mail: goweii@163.com 18 | * GitHub: https://github.com/goweii 19 | */ 20 | public final class GaussianBlur implements IBlur { 21 | private static GaussianBlur INSTANCE = null; 22 | 23 | private boolean mRealTimeMode = false; 24 | 25 | private final RenderScript renderScript; 26 | private final ScriptIntrinsicBlur gaussianBlur; 27 | 28 | private Allocation mInput = null; 29 | private Allocation mOutput = null; 30 | 31 | private GaussianBlur(Context context) { 32 | Utils.requireNonNull(context); 33 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { 34 | throw new RuntimeException("Call requires API level " + Build.VERSION_CODES.JELLY_BEAN_MR1 + " (current min is " + Build.VERSION.SDK_INT + ")"); 35 | } 36 | renderScript = RenderScript.create(context.getApplicationContext()); 37 | gaussianBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)); 38 | } 39 | 40 | public static GaussianBlur get(Context context) { 41 | if (INSTANCE == null) { 42 | synchronized (GaussianBlur.class) { 43 | if (INSTANCE == null) { 44 | INSTANCE = new GaussianBlur(context); 45 | } 46 | } 47 | } 48 | return INSTANCE; 49 | } 50 | 51 | public GaussianBlur realTimeMode(boolean realTimeMode) { 52 | mRealTimeMode = realTimeMode; 53 | if (!mRealTimeMode) { 54 | destroyAllocations(); 55 | } 56 | BitmapProcessor.get().realTimeMode(realTimeMode); 57 | return this; 58 | } 59 | 60 | /** 61 | * RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) 62 | * 模糊 63 | * 采用系统自带的RenderScript 64 | * 输出图与原图参数相同 65 | * 66 | * @param originalBitmap 原图 67 | * @param scale 缩放因子(>=1) 68 | * @param radius 模糊半径 69 | * @return 模糊Bitmap 70 | */ 71 | @Override 72 | public Bitmap process(final Bitmap originalBitmap, 73 | final float radius, 74 | final float scale, 75 | final boolean keepSize, 76 | final boolean recycleOriginal) { 77 | Utils.requireNonNull(originalBitmap, "待模糊Bitmap不能为空"); 78 | float newRadius = radius < 0 ? 0 : radius; 79 | float newScale = scale <= 0 ? 1 : scale; 80 | if (newRadius == 0) { 81 | if (newScale == 1) { 82 | return originalBitmap; 83 | } 84 | Bitmap scaleBitmap = BitmapProcessor.get().scaleBitmap(originalBitmap, newScale); 85 | if (recycleOriginal) { 86 | originalBitmap.recycle(); 87 | } 88 | return scaleBitmap; 89 | } 90 | if (newRadius > 25) { 91 | newScale = newScale / (newRadius / 25); 92 | newRadius = 25; 93 | } 94 | if (newScale == 1) { 95 | Bitmap output = blurIn25(originalBitmap, newRadius); 96 | if (recycleOriginal) { 97 | originalBitmap.recycle(); 98 | } 99 | return output; 100 | } 101 | final int width = originalBitmap.getWidth(); 102 | final int height = originalBitmap.getHeight(); 103 | Bitmap input = BitmapProcessor.get().scaleBitmap(originalBitmap, newScale); 104 | if (recycleOriginal) { 105 | originalBitmap.recycle(); 106 | } 107 | Bitmap output = blurIn25(input, newRadius); 108 | input.recycle(); 109 | if (!keepSize) { 110 | return output; 111 | } 112 | Bitmap outputScaled = BitmapProcessor.get().scaleBitmap(output, width, height); 113 | output.recycle(); 114 | return outputScaled; 115 | } 116 | 117 | @Override 118 | public void recycle() { 119 | gaussianBlur.destroy(); 120 | renderScript.destroy(); 121 | destroyAllocations(); 122 | INSTANCE = null; 123 | } 124 | 125 | /** 126 | * RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) 127 | *

128 | * 高斯模糊 129 | * 采用系统自带的RenderScript 130 | * 图像越大耗时越长,测试时1280*680的图片耗时在30~60毫秒 131 | * 建议在子线程模糊通过Handler回调获取 132 | * 133 | * @param input 原图 134 | * @param radius 模糊半径 135 | */ 136 | private Bitmap blurIn25(final Bitmap input, final float radius) { 137 | Utils.requireNonNull(input, "待模糊Bitmap不能为空"); 138 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { 139 | throw new RuntimeException("Call requires API level " + Build.VERSION_CODES.JELLY_BEAN_MR1 + " (current min is " + Build.VERSION.SDK_INT + ")"); 140 | } 141 | final float newRadius; 142 | if (radius < 0) { 143 | newRadius = 0; 144 | } else if (radius > 25) { 145 | newRadius = 25; 146 | } else { 147 | newRadius = radius; 148 | } 149 | if (mRealTimeMode) { 150 | tryReuseAllocation(input); 151 | } else { 152 | createAllocation(input); 153 | } 154 | try { 155 | gaussianBlur.setRadius(newRadius); 156 | gaussianBlur.setInput(mInput); 157 | gaussianBlur.forEach(mOutput); 158 | Bitmap output = Bitmap.createBitmap(input.getWidth(), input.getHeight(), input.getConfig()); 159 | mOutput.copyTo(output); 160 | return output; 161 | } finally { 162 | if (!mRealTimeMode) { 163 | destroyAllocations(); 164 | } 165 | } 166 | } 167 | 168 | private void destroyAllocations() { 169 | if (mInput != null) { 170 | try { 171 | mInput.destroy(); 172 | mInput = null; 173 | } catch (RSInvalidStateException ignore) { 174 | } 175 | } 176 | if (mOutput != null) { 177 | try { 178 | mOutput.destroy(); 179 | mOutput = null; 180 | } catch (RSInvalidStateException ignore) { 181 | } 182 | } 183 | } 184 | 185 | private void tryReuseAllocation(Bitmap bitmap) { 186 | if (mInput == null) { 187 | createAllocation(bitmap); 188 | } 189 | if (mInput.getType().getX() != bitmap.getWidth() || mInput.getType().getY() != bitmap.getHeight()) { 190 | createAllocation(bitmap); 191 | } 192 | try { 193 | mInput.copyFrom(bitmap); 194 | } catch (RSIllegalArgumentException ignore) { 195 | destroyAllocations(); 196 | createAllocation(bitmap); 197 | } 198 | } 199 | 200 | private void createAllocation(Bitmap bitmap) { 201 | destroyAllocations(); 202 | mInput = Allocation.createFromBitmap(renderScript, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); 203 | mOutput = Allocation.createTyped(renderScript, mInput.getType()); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/blurred/MainActivity.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.blurred; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Color; 7 | import android.graphics.drawable.ColorDrawable; 8 | import android.os.Bundle; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.CheckBox; 16 | import android.widget.CompoundButton; 17 | import android.widget.ImageView; 18 | import android.widget.SeekBar; 19 | import android.widget.TextView; 20 | 21 | import java.util.List; 22 | 23 | import per.goweii.burred.Blurred; 24 | 25 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 26 | 27 | public static final String TAG = MainActivity.class.getSimpleName(); 28 | 29 | private ImageView iv_original; 30 | private TextView tv_original; 31 | private ImageView iv_blurred; 32 | private TextView tv_blurred; 33 | private SeekBar sb_radius; 34 | private TextView tv_radius; 35 | private SeekBar sb_scale; 36 | private TextView tv_scale; 37 | private CheckBox cb_keep_size; 38 | private CheckBox cb_real_time; 39 | private PictureSelectorHelper mHelper; 40 | private Bitmap mBitmapOriginal; 41 | private Bitmap mBitmapBlurred; 42 | 43 | private int color1 = 0; 44 | private int color2 = 0; 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_main); 50 | 51 | Blurred.init(MainActivity.this); 52 | 53 | color1 = Color.parseColor("#33000000"); 54 | color2 = Color.parseColor("#33ff0000"); 55 | 56 | iv_original = findViewById(R.id.iv_original); 57 | tv_original = findViewById(R.id.tv_original); 58 | iv_blurred = findViewById(R.id.iv_blurred); 59 | tv_blurred = findViewById(R.id.tv_blurred); 60 | sb_radius = findViewById(R.id.sb_radius); 61 | tv_radius = findViewById(R.id.tv_radius); 62 | sb_scale = findViewById(R.id.sb_scale); 63 | tv_scale = findViewById(R.id.tv_scale); 64 | cb_keep_size = findViewById(R.id.cb_keep_size); 65 | cb_real_time = findViewById(R.id.cb_real_time); 66 | cb_real_time.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 67 | @Override 68 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 69 | Blurred.realTimeMode(isChecked); 70 | } 71 | }); 72 | sb_radius.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 73 | @Override 74 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 75 | tv_radius.setText("" + progress); 76 | if (cb_real_time.isChecked()) { 77 | blurAndUpdateView(); 78 | } 79 | } 80 | 81 | @Override 82 | public void onStartTrackingTouch(SeekBar seekBar) { 83 | } 84 | 85 | @Override 86 | public void onStopTrackingTouch(SeekBar seekBar) { 87 | } 88 | }); 89 | sb_scale.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 90 | @Override 91 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 92 | tv_scale.setText("" + progress); 93 | if (cb_real_time.isChecked()) { 94 | blurAndUpdateView(); 95 | } 96 | } 97 | 98 | @Override 99 | public void onStartTrackingTouch(SeekBar seekBar) { 100 | } 101 | 102 | @Override 103 | public void onStopTrackingTouch(SeekBar seekBar) { 104 | } 105 | }); 106 | 107 | iv_original.setOnClickListener(this); 108 | iv_blurred.setOnClickListener(this); 109 | } 110 | 111 | @Override 112 | public boolean onCreateOptionsMenu(Menu menu) { 113 | getMenuInflater().inflate(R.menu.menu_main, menu); 114 | return super.onCreateOptionsMenu(menu); 115 | } 116 | 117 | @Override 118 | public boolean onOptionsItemSelected(MenuItem item) { 119 | if (item.getItemId() == R.id.action_real_time_blur) { 120 | startActivity(new Intent(this, RealTimeBlurActivity.class)); 121 | } 122 | return true; 123 | } 124 | 125 | @Override 126 | public void onClick(View v) { 127 | switch (v.getId()) { 128 | default: 129 | break; 130 | case R.id.iv_original: 131 | mHelper = PictureSelectorHelper.with(MainActivity.this, 1) 132 | .singleMode(true) 133 | .selectPhoto(); 134 | break; 135 | case R.id.iv_blurred: 136 | blurAndUpdateView(); 137 | break; 138 | } 139 | } 140 | 141 | private void blurAndUpdateView() { 142 | if (mBitmapOriginal == null) return; 143 | long start = System.currentTimeMillis(); 144 | blur(); 145 | long end = System.currentTimeMillis(); 146 | long off = end - start; 147 | Log.i(TAG, "process:" + off); 148 | setInfo(true, end - start); 149 | if (off <= 16) { 150 | tv_blurred.setBackgroundColor(color1); 151 | } else { 152 | tv_blurred.setBackgroundColor(color2); 153 | } 154 | if (mBitmapBlurred != null) { 155 | iv_blurred.setImageBitmap(mBitmapBlurred); 156 | } 157 | } 158 | 159 | private Blurred mBlurred = null; 160 | 161 | private void blur() { 162 | if (mBlurred == null) { 163 | mBlurred = new Blurred(); 164 | } 165 | mBitmapBlurred = mBlurred.bitmap(mBitmapOriginal) 166 | .keepSize(cb_keep_size.isChecked()) 167 | .recycleOriginal(false) 168 | .scale(1F / sb_scale.getProgress()) 169 | .radius(sb_radius.getProgress()) 170 | .blur(); 171 | } 172 | 173 | @Override 174 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 175 | super.onActivityResult(requestCode, resultCode, data); 176 | if (mHelper != null) { 177 | List imgs = mHelper.selectResult(requestCode, resultCode, data); 178 | if (imgs != null && imgs.size() > 0) { 179 | mBitmapOriginal = BitmapFactory.decodeFile(imgs.get(0)); 180 | iv_original.setImageBitmap(mBitmapOriginal); 181 | iv_blurred.setImageDrawable(new ColorDrawable(Color.TRANSPARENT)); 182 | setInfo(false, 0); 183 | } 184 | } 185 | } 186 | 187 | private void setInfo(boolean isBlurred, long time) { 188 | Bitmap bitmap; 189 | TextView textView; 190 | if (isBlurred) { 191 | bitmap = mBitmapBlurred; 192 | textView = tv_blurred; 193 | } else { 194 | bitmap = mBitmapOriginal; 195 | textView = tv_original; 196 | } 197 | String info = ""; 198 | if (bitmap != null) { 199 | int w = bitmap.getWidth(); 200 | int h = bitmap.getHeight(); 201 | info = w + "*" + h; 202 | if (isBlurred) { 203 | info = info + ", time:" + time + "ms"; 204 | } 205 | } 206 | textView.setText(info); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/BitmapProcessor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.PaintFlagsDrawFilter; 7 | import android.graphics.Rect; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | 11 | /** 12 | * @author CuiZhen 13 | * @date 2019/8/10 14 | * QQ: 302833254 15 | * E-mail: goweii@163.com 16 | * GitHub: https://github.com/goweii 17 | */ 18 | class BitmapProcessor { 19 | private static BitmapProcessor INSTANCE = null; 20 | 21 | private boolean mRealTimeMode = false; 22 | 23 | private PaintFlagsDrawFilter mFilter = null; 24 | private Paint mPaint = null; 25 | private Canvas mCanvas = null; 26 | 27 | static BitmapProcessor get() { 28 | if (INSTANCE == null) { 29 | synchronized (BitmapProcessor.class) { 30 | if (INSTANCE == null) { 31 | INSTANCE = new BitmapProcessor(); 32 | } 33 | } 34 | } 35 | return INSTANCE; 36 | } 37 | 38 | private BitmapProcessor() { 39 | } 40 | 41 | void realTimeMode(boolean realTimeMode) { 42 | mRealTimeMode = realTimeMode; 43 | if (mRealTimeMode) { 44 | prepare(); 45 | } else { 46 | recycle(); 47 | } 48 | } 49 | 50 | private void prepare() { 51 | if (mFilter == null) { 52 | mFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 53 | } 54 | if (mPaint == null) { 55 | mPaint = new Paint(); 56 | } 57 | if (mCanvas == null) { 58 | mCanvas = new Canvas(); 59 | } 60 | } 61 | 62 | private void recycle() { 63 | mFilter = null; 64 | mPaint = null; 65 | mCanvas = null; 66 | } 67 | 68 | private Canvas reuseOrCreateCanvas() { 69 | final Canvas canvas; 70 | if (mRealTimeMode) { 71 | prepare(); 72 | canvas = mCanvas; 73 | } else { 74 | canvas = new Canvas(); 75 | } 76 | return canvas; 77 | } 78 | 79 | private PaintFlagsDrawFilter reuseOrCreateFilter() { 80 | final PaintFlagsDrawFilter filter; 81 | if (mRealTimeMode) { 82 | prepare(); 83 | filter = mFilter; 84 | } else { 85 | filter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 86 | } 87 | return filter; 88 | } 89 | 90 | private Paint reuseOrCreatePaint() { 91 | final Paint paint; 92 | if (mRealTimeMode) { 93 | prepare(); 94 | paint = mPaint; 95 | } else { 96 | paint = new Paint(); 97 | } 98 | return paint; 99 | } 100 | 101 | Bitmap snapshot(View from, int bgColor, int fgColor, float scale, boolean antiAlias) { 102 | final float newScale = scale > 0 ? scale : 1; 103 | final int w = (int) (from.getWidth() * newScale); 104 | final int h = (int) (from.getHeight() * newScale); 105 | Bitmap output = Bitmap.createBitmap(w <= 0 ? 1 : w, h <= 0 ? 1 : h, Bitmap.Config.ARGB_8888); 106 | final Canvas canvas = reuseOrCreateCanvas(); 107 | canvas.setBitmap(output); 108 | if (antiAlias) { 109 | canvas.setDrawFilter(reuseOrCreateFilter()); 110 | } else { 111 | canvas.setDrawFilter(null); 112 | } 113 | canvas.save(); 114 | canvas.scale(newScale, newScale); 115 | if (bgColor != 0) { 116 | canvas.drawColor(bgColor); 117 | } 118 | from.draw(canvas); 119 | if (fgColor != 0) { 120 | canvas.drawColor(fgColor); 121 | } 122 | canvas.restore(); 123 | return output; 124 | } 125 | 126 | Bitmap clip(Bitmap bitmap, View from, ImageView into, boolean fitXY, boolean antiAlias) { 127 | int[] lf = new int[2]; 128 | from.getLocationOnScreen(lf); 129 | int[] lt = new int[2]; 130 | into.getLocationOnScreen(lt); 131 | int bw = bitmap.getWidth(); 132 | int bh = bitmap.getHeight(); 133 | float sx = (float) bw / (float) from.getWidth(); 134 | float sh = (float) bh / (float) from.getHeight(); 135 | Rect rf = new Rect( 136 | (int) ((lt[0] - lf[0]) * sx), 137 | (int) ((lt[1] - lf[1]) * sh), 138 | (int) ((lt[0] - lf[0]) * sx + into.getWidth() * sx), 139 | (int) ((lt[1] - lf[1]) * sh + into.getHeight() * sh) 140 | ); 141 | Rect rt = new Rect(0, 0, into.getWidth(), into.getHeight()); 142 | if (!fitXY) { 143 | float s = Math.max( 144 | (float) into.getWidth() / (float) rf.width(), 145 | (float) into.getHeight() / (float) rf.height() 146 | ); 147 | if (s > 1) { 148 | rt.right = rf.width(); 149 | rt.bottom = rf.height(); 150 | } 151 | } 152 | Bitmap output = Bitmap.createBitmap(rt.width(), rt.height(), Bitmap.Config.ARGB_8888); 153 | final Canvas canvas = reuseOrCreateCanvas(); 154 | canvas.setBitmap(output); 155 | if (antiAlias) { 156 | canvas.setDrawFilter(reuseOrCreateFilter()); 157 | } else { 158 | canvas.setDrawFilter(null); 159 | } 160 | Paint paint = null; 161 | if (antiAlias) { 162 | paint = reuseOrCreatePaint(); 163 | paint.setXfermode(null); 164 | paint.setAntiAlias(true); 165 | } 166 | canvas.drawBitmap(bitmap, rf, rt, paint); 167 | return output; 168 | } 169 | 170 | private static class StopDrawException extends Exception { 171 | } 172 | 173 | Bitmap scaleBitmap(Bitmap bitmap, float scale) { 174 | return scaleBitmap(bitmap, scale, true); 175 | } 176 | 177 | Bitmap scaleBitmap(Bitmap bitmap, float scale, boolean antiAlias) { 178 | final int iw = bitmap.getWidth(); 179 | final int ih = bitmap.getHeight(); 180 | final int ow = (int) (iw * scale); 181 | final int oh = (int) (ih * scale); 182 | return scaleBitmap(bitmap, ow, oh, antiAlias); 183 | } 184 | 185 | Bitmap scaleBitmap(Bitmap bitmap, int w, int h) { 186 | return scaleBitmap(bitmap, w, h, true); 187 | } 188 | 189 | Bitmap scaleBitmap(Bitmap bitmap, int w, int h, boolean antiAlias) { 190 | final Bitmap.Config config = bitmap.getConfig(); 191 | final Bitmap.Config newConfig; 192 | switch (config) { 193 | case RGB_565: 194 | newConfig = Bitmap.Config.RGB_565; 195 | break; 196 | case ALPHA_8: 197 | newConfig = Bitmap.Config.ALPHA_8; 198 | break; 199 | case ARGB_4444: 200 | case ARGB_8888: 201 | default: 202 | newConfig = Bitmap.Config.ARGB_8888; 203 | break; 204 | } 205 | Bitmap output = Bitmap.createBitmap(w, h, newConfig); 206 | final Canvas canvas = reuseOrCreateCanvas(); 207 | canvas.setBitmap(output); 208 | if (antiAlias) { 209 | canvas.setDrawFilter(reuseOrCreateFilter()); 210 | } else { 211 | canvas.setDrawFilter(null); 212 | } 213 | Paint paint = null; 214 | if (antiAlias) { 215 | paint = reuseOrCreatePaint(); 216 | paint.setXfermode(null); 217 | paint.setAntiAlias(true); 218 | } 219 | canvas.drawBitmap(bitmap, 220 | new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), 221 | new Rect(0, 0, w, h), 222 | paint); 223 | return output; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/blurred/PictureSelectorHelper.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.blurred; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.support.annotation.NonNull; 6 | import android.support.v4.app.Fragment; 7 | 8 | import com.luck.picture.lib.PictureSelector; 9 | import com.luck.picture.lib.config.PictureConfig; 10 | import com.luck.picture.lib.config.PictureMimeType; 11 | import com.luck.picture.lib.entity.LocalMedia; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * 照片选择和视频选择辅助类 18 | * 用类名调用对用的方法 设置回调监听 在最下面有回调监听的代码复制过去直接使用 19 | *

20 | * .openGallery()//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio() 21 | * .theme()//主题样式(不设置为默认样式) 也可参考demo values/styles下 例如:R.style.picture.white.style 22 | * .maxSelectNum()// 最大图片选择数量 int 23 | * .minSelectNum()// 最小选择数量 int 24 | * .imageSpanCount(4)// 每行显示个数 int 25 | * .selectionMode()// 多选 or 单选 PictureConfig.MULTIPLE or PictureConfig.SINGLE 26 | * .previewImage()// 是否可预览图片 true or false 27 | * .previewVideo()// 是否可预览视频 true or false 28 | * .enablePreviewAudio() // 是否可播放音频 true or false 29 | * .isCamera()// 是否显示拍照按钮 true or false 30 | * .imageFormat(PictureMimeType.PNG)// 拍照保存图片格式后缀,默认jpeg 31 | * .isZoomAnim(true)// 图片列表点击 缩放效果 默认true 32 | * .sizeMultiplier(0.5f)// glide 加载图片大小 0~1之间 如设置 .glideOverride()无效 33 | * .setOutputCameraPath("/CustomPath")// 自定义拍照保存路径,可不填 34 | * .enableCrop()// 是否裁剪 true or false 35 | * .compress()// 是否压缩 true or false 36 | * .glideOverride()// int glide 加载宽高,越小图片列表越流畅,但会影响列表图片浏览的清晰度 37 | * .withAspectRatio()// int 裁剪比例 如16:9 3:2 3:4 1:1 可自定义 38 | * .hideBottomControls()// 是否显示uCrop工具栏,默认不显示 true or false 39 | * .isGif()// 是否显示gif图片 true or false 40 | * .compressSavePath(getPath())//压缩图片保存地址 41 | * .freeStyleCropEnabled()// 裁剪框是否可拖拽 true or false 42 | * .circleDimmedLayer()// 是否圆形裁剪 true or false 43 | * .showCropFrame()// 是否显示裁剪矩形边框 圆形裁剪时建议设为false true or false 44 | * .showCropGrid()// 是否显示裁剪矩形网格 圆形裁剪时建议设为false true or false 45 | * .openClickSound()// 是否开启点击声音 true or false 46 | * .selectionMedia()// 是否传入已选图片 List list 47 | * .previewEggs()// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false 48 | * .cropCompressQuality()// 裁剪压缩质量 默认90 int 49 | * .minimumCompressSize(100)// 小于100kb的图片不压缩 50 | * .synOrAsy(true)//同步true或异步false 压缩 默认同步 51 | * .cropWH()// 裁剪宽高比,设置如果大于图片本身宽高则无效 int 52 | * .rotateEnabled() // 裁剪是否可旋转图片 true or false 53 | * .scaleEnabled()// 裁剪是否可放大缩小图片 true or false 54 | * .videoQuality()// 视频录制质量 0 or 1 int 55 | * .videoMaxSecond(15)// 显示多少秒以内的视频or音频也可适用 int 56 | * .videoMinSecond(10)// 显示多少秒以内的视频or音频也可适用 int 57 | * .recordVideoSecond()//视频秒数录制 默认60s int 58 | * .isDragFrame(false)// 是否可拖动裁剪框(固定) 59 | * .forResult(PictureConfig.CHOOSE_REQUEST);//结果回调onActivityResult code 60 | * 61 | * @author Cuizhen 62 | * @date 2018/9/3 63 | */ 64 | public class PictureSelectorHelper { 65 | 66 | private final PictureSelector mSelector; 67 | private final int mRequestCode; 68 | private boolean mSingleMode; 69 | private boolean mEnableCrop; 70 | private int mMaxSelectNum; 71 | 72 | private PictureSelectorHelper(Activity activity, int requestCode) { 73 | mSelector = PictureSelector.create(activity); 74 | mRequestCode = requestCode; 75 | } 76 | 77 | @NonNull 78 | public static PictureSelectorHelper with(Activity activity, int requestCode) { 79 | return new PictureSelectorHelper(activity, requestCode); 80 | } 81 | 82 | private PictureSelectorHelper(Fragment fragment, int requestCode) { 83 | mSelector = PictureSelector.create(fragment); 84 | mRequestCode = requestCode; 85 | } 86 | 87 | public static PictureSelectorHelper with(Fragment fragment, int requestCode) { 88 | return new PictureSelectorHelper(fragment, requestCode); 89 | } 90 | 91 | public PictureSelectorHelper singleMode(boolean singleMode) { 92 | mSingleMode = singleMode; 93 | return this; 94 | } 95 | 96 | public PictureSelectorHelper enableCrop(boolean enableCrop) { 97 | mEnableCrop = enableCrop; 98 | return this; 99 | } 100 | 101 | public PictureSelectorHelper maxSelectNum(int maxSelectNum) { 102 | mMaxSelectNum = maxSelectNum; 103 | return this; 104 | } 105 | 106 | private boolean isSingleMode(){ 107 | return mSingleMode || mMaxSelectNum == 1; 108 | } 109 | 110 | public PictureSelectorHelper selectPhoto() { 111 | mSelector.openGallery(PictureMimeType.ofImage())// 全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio() 112 | // .theme(R.style.PictureSelectorNumStyle)// 主题样式设置 具体参考 values/styles 用法:R.style.picture.white.style 113 | .maxSelectNum(isSingleMode() ? 2 : mMaxSelectNum)// 最大图片选择数量 114 | .minSelectNum(1)// 最小选择数量 115 | .imageSpanCount(4)// 每行显示个数 116 | .selectionMode(isSingleMode() ? PictureConfig.SINGLE : PictureConfig.MULTIPLE)// 多选 or 单选 117 | .previewImage(true)// 是否可预览图片 118 | .enablePreviewAudio(true) // 是否可播放音频 119 | .isCamera(true)// 是否显示拍照按钮 120 | .isZoomAnim(true)// 图片列表点击 缩放效果 默认true 121 | // .setOutputCameraPath(Config.DOWNLOAD_FILE_PATH)// 自定义拍照保存路径 122 | .enableCrop(mSingleMode ? mEnableCrop : false)// 是否裁剪 123 | .compress(false)// 是否压缩 124 | .glideOverride(160, 160)// glide 加载宽高,越小图片列表越流畅,但会影响列表图片浏览的清晰度 125 | .withAspectRatio(1, 1)// 裁剪比例 如16:9 3:2 3:4 1:1 可自定义 126 | .hideBottomControls(true)// 是否显示uCrop工具栏,默认不显示 127 | .isGif(true)// 是否显示gif图片 128 | .freeStyleCropEnabled(false)// 裁剪框是否可拖拽 129 | .circleDimmedLayer(false)// 是否圆形裁剪 130 | .showCropFrame(true)// 是否显示裁剪矩形边框 圆形裁剪时建议设为false 131 | .showCropGrid(true)// 是否显示裁剪矩形网格 圆形裁剪时建议设为false 132 | .openClickSound(false)// 是否开启点击声音 133 | .forResult(mRequestCode);//结果回调onActivityResult code 134 | return this; 135 | } 136 | 137 | public PictureSelectorHelper selectVideo() { 138 | // 进入相册 以下是例子:不需要的api可以不写 139 | mSelector.openGallery(PictureMimeType.ofVideo())// 全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio() 140 | // .theme(R.style.PictureSelectorNumStyle)// 主题样式设置 具体参考 values/styles 用法:R.style.picture.white.style 141 | .maxSelectNum(1)// 最大图片选择数量 142 | .minSelectNum(1)// 最小选择数量 143 | .imageSpanCount(4)// 每行显示个数 144 | .selectionMode(PictureConfig.SINGLE)// 多选 or 单选 145 | .previewVideo(true)// 是否可预览视频 146 | .enablePreviewAudio(true) // 是否可播放音频 147 | .isCamera(true)// 是否显示拍照按钮 148 | // .setOutputCameraPath(Config.DOWNLOAD_FILE_PATH)// 自定义拍照保存路径 149 | .compress(false)// 是否压缩 150 | .synOrAsy(true)//同步true或异步false 压缩 默认同步 151 | .hideBottomControls(true)// 是否显示uCrop工具栏,默认不显示 152 | .videoMaxSecond(60) 153 | .videoMinSecond(10) 154 | .videoQuality(1)// 视频录制质量 0 or 1 155 | // .recordVideoSecond(60)//录制视频秒数 默认60s 156 | .forResult(mRequestCode);//结果回调onActivityResult code 157 | return this; 158 | } 159 | 160 | public List selectResult(int requestCode, int resultCode, Intent data) { 161 | List selectPath = new ArrayList<>(0); 162 | if (resultCode == Activity.RESULT_OK && requestCode == mRequestCode) { 163 | List selectList = PictureSelector.obtainMultipleResult(data); 164 | for (LocalMedia media : selectList) { 165 | if (media.isCut()) { 166 | selectPath.add(media.getCutPath()); 167 | } else { 168 | selectPath.add(media.getPath()); 169 | } 170 | } 171 | } 172 | return selectPath; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/blurred/RealTimeBlurActivity.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.blurred; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.CheckBox; 15 | import android.widget.CompoundButton; 16 | import android.widget.FrameLayout; 17 | import android.widget.ImageView; 18 | import android.widget.SeekBar; 19 | import android.widget.TextView; 20 | 21 | import com.bumptech.glide.Glide; 22 | import com.github.chrisbanes.photoview.PhotoView; 23 | 24 | import java.util.List; 25 | 26 | import per.goweii.burred.Blurred; 27 | 28 | public class RealTimeBlurActivity extends AppCompatActivity implements View.OnClickListener { 29 | 30 | private FrameLayout fl_blurred; 31 | private TextView tv_fps; 32 | private TextView tv_mspf; 33 | private CheckBox cb_anti_alias; 34 | private CheckBox cb_fit_xy; 35 | private PhotoView iv_original; 36 | private ImageView iv_blurred; 37 | private SeekBar sb_radius; 38 | private TextView tv_radius; 39 | private SeekBar sb_scale; 40 | private TextView tv_scale; 41 | 42 | private float lastX = 0; 43 | private float lastY = 0; 44 | private float downX = 0; 45 | private float downY = 0; 46 | 47 | private PictureSelectorHelper mHelper; 48 | private Blurred mBlurred = null; 49 | 50 | @SuppressLint("ClickableViewAccessibility") 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | setContentView(R.layout.activity_real_time_blur); 55 | 56 | Blurred.init(RealTimeBlurActivity.this); 57 | 58 | fl_blurred = findViewById(R.id.fl_blurred); 59 | tv_fps = findViewById(R.id.tv_fps); 60 | tv_mspf = findViewById(R.id.tv_mspf); 61 | cb_anti_alias = findViewById(R.id.cb_anti_alias); 62 | cb_fit_xy = findViewById(R.id.cb_fit_xy); 63 | iv_original = findViewById(R.id.iv_original); 64 | iv_blurred = findViewById(R.id.iv_blurred); 65 | sb_radius = findViewById(R.id.sb_radius); 66 | tv_radius = findViewById(R.id.tv_radius); 67 | sb_scale = findViewById(R.id.sb_scale); 68 | tv_scale = findViewById(R.id.tv_scale); 69 | tv_scale = findViewById(R.id.tv_scale); 70 | 71 | cb_anti_alias.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 72 | @Override 73 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 74 | if (mBlurred != null) { 75 | mBlurred.antiAlias(isChecked); 76 | } 77 | } 78 | }); 79 | cb_fit_xy.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 80 | @Override 81 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 82 | if (mBlurred != null) { 83 | mBlurred.fitIntoViewXY(isChecked); 84 | } 85 | } 86 | }); 87 | sb_radius.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 88 | @Override 89 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 90 | if (mBlurred != null) { 91 | mBlurred.radius(progress); 92 | } 93 | tv_radius.setText("" + progress); 94 | } 95 | 96 | @Override 97 | public void onStartTrackingTouch(SeekBar seekBar) { 98 | } 99 | 100 | @Override 101 | public void onStopTrackingTouch(SeekBar seekBar) { 102 | } 103 | }); 104 | sb_scale.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 105 | @Override 106 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 107 | if (mBlurred != null) { 108 | mBlurred.scale(1F / (progress <= 0 ? 1 : progress)); 109 | } 110 | tv_scale.setText("" + progress); 111 | } 112 | 113 | @Override 114 | public void onStartTrackingTouch(SeekBar seekBar) { 115 | } 116 | 117 | @Override 118 | public void onStopTrackingTouch(SeekBar seekBar) { 119 | } 120 | }); 121 | iv_blurred.setOnTouchListener(new View.OnTouchListener() { 122 | @Override 123 | public boolean onTouch(View v, MotionEvent event) { 124 | switch (event.getAction()) { 125 | case MotionEvent.ACTION_DOWN: 126 | downX = event.getRawX(); 127 | downY = event.getRawY(); 128 | lastX = fl_blurred.getTranslationX(); 129 | lastY = fl_blurred.getTranslationY(); 130 | break; 131 | case MotionEvent.ACTION_MOVE: 132 | float currX = event.getRawX(); 133 | float currY = event.getRawY(); 134 | fl_blurred.setTranslationX(lastX + (currX - downX)); 135 | fl_blurred.setTranslationY(lastY + (currY - downY)); 136 | break; 137 | } 138 | return true; 139 | } 140 | }); 141 | 142 | mBlurred = Blurred.with(findViewById(R.id.sv)) 143 | .fitIntoViewXY(cb_fit_xy.isChecked()) 144 | .antiAlias(cb_anti_alias.isChecked()) 145 | .backgroundColor(Color.WHITE) 146 | .scale(1F / (sb_scale.getProgress() <= 0 ? 1 : sb_scale.getProgress())) 147 | .radius(sb_radius.getProgress()) 148 | .fpsListener(new Blurred.FpsListener() { 149 | @Override 150 | public void currFps(float fps) { 151 | tv_fps.setText(String.format("fps%.1f", fps)); 152 | } 153 | }) 154 | .listener(new Blurred.Listener() { 155 | @Override 156 | public void begin() { 157 | start = System.currentTimeMillis(); 158 | } 159 | 160 | @Override 161 | public void end() { 162 | long end = System.currentTimeMillis(); 163 | long off = end - start; 164 | tv_mspf.setText(String.format("mspf%d", off)); 165 | } 166 | }); 167 | mBlurred.blur(iv_blurred); 168 | } 169 | 170 | @Override 171 | public boolean onCreateOptionsMenu(Menu menu) { 172 | getMenuInflater().inflate(R.menu.menu_real_time_blur, menu); 173 | return super.onCreateOptionsMenu(menu); 174 | } 175 | 176 | @Override 177 | public boolean onOptionsItemSelected(MenuItem item) { 178 | if (item.getItemId() == R.id.action_choose) { 179 | mHelper = PictureSelectorHelper.with(RealTimeBlurActivity.this, 1) 180 | .singleMode(true) 181 | .selectPhoto(); 182 | } 183 | return true; 184 | } 185 | 186 | private long start = 0; 187 | 188 | @Override 189 | protected void onDestroy() { 190 | mBlurred.reset(); 191 | super.onDestroy(); 192 | } 193 | 194 | @Override 195 | public void onClick(View v) { 196 | } 197 | 198 | @Override 199 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 200 | super.onActivityResult(requestCode, resultCode, data); 201 | if (mHelper != null) { 202 | List imgs = mHelper.selectResult(requestCode, resultCode, data); 203 | if (imgs != null && imgs.size() > 0) { 204 | Glide.with(RealTimeBlurActivity.this) 205 | .load(imgs.get(0)) 206 | .into(iv_original); 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/FastBlur.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * 毛玻璃处理 7 | * 8 | * @author Cuizhen 9 | * @date 2018/03/30 10 | * QQ: 302833254 11 | * E-mail: goweii@163.com 12 | * GitHub: https://github.com/goweii 13 | */ 14 | public final class FastBlur implements IBlur { 15 | 16 | private static FastBlur INSTANCE = null; 17 | 18 | private FastBlur() { 19 | } 20 | 21 | public static FastBlur get() { 22 | if (INSTANCE == null) { 23 | synchronized (FastBlur.class) { 24 | if (INSTANCE == null) { 25 | INSTANCE = new FastBlur(); 26 | } 27 | } 28 | } 29 | return INSTANCE; 30 | } 31 | 32 | /** 33 | * 模糊 34 | * 采用FastBlur算法 35 | * 缩小-->模糊 36 | * 37 | * @param originalBitmap 原图 38 | * @param radius 模糊半径 39 | * @param scale 缩放因子(>=1) 40 | * @return 模糊Bitmap 41 | */ 42 | @Override 43 | public Bitmap process(final Bitmap originalBitmap, 44 | final float radius, 45 | final float scale, 46 | final boolean keepSize, 47 | final boolean recycleOriginal) { 48 | Utils.requireNonNull(originalBitmap, "待模糊Bitmap不能为空"); 49 | final int newRadius = radius < 0 ? 0 : (int) radius; 50 | final float newScale = scale <= 0 ? 1 : scale; 51 | if (newRadius == 0) { 52 | if (newScale == 1) { 53 | return originalBitmap; 54 | } 55 | Bitmap scaleBitmap = BitmapProcessor.get().scaleBitmap(originalBitmap, newScale); 56 | if (recycleOriginal) { 57 | originalBitmap.recycle(); 58 | } 59 | return scaleBitmap; 60 | } 61 | if (newScale == 1) { 62 | Bitmap output = blur(originalBitmap, newRadius); 63 | if (recycleOriginal) { 64 | originalBitmap.recycle(); 65 | } 66 | return output; 67 | } 68 | final int width = originalBitmap.getWidth(); 69 | final int height = originalBitmap.getHeight(); 70 | Bitmap input = BitmapProcessor.get().scaleBitmap(originalBitmap, newScale); 71 | if (recycleOriginal) { 72 | originalBitmap.recycle(); 73 | } 74 | Bitmap output = blur(input, newRadius); 75 | input.recycle(); 76 | if (!keepSize) { 77 | return output; 78 | } 79 | Bitmap outputScaled = BitmapProcessor.get().scaleBitmap(output, width, height); 80 | output.recycle(); 81 | return outputScaled; 82 | } 83 | 84 | @Override 85 | public void recycle() { 86 | INSTANCE = null; 87 | } 88 | 89 | /** 90 | * 模糊 91 | * 直接模糊原图 92 | * 93 | * @param originalBitmap 原图 94 | * @param radius 模糊半径 95 | * @return 模糊Bitmap 96 | */ 97 | private static Bitmap blur(Bitmap originalBitmap, int radius) { 98 | 99 | if (radius <= 0) { 100 | return originalBitmap; 101 | } 102 | 103 | Bitmap bitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true); 104 | 105 | int w = bitmap.getWidth(); 106 | int h = bitmap.getHeight(); 107 | 108 | int[] pix = new int[w * h]; 109 | bitmap.getPixels(pix, 0, w, 0, 0, w, h); 110 | 111 | int wm = w - 1; 112 | int hm = h - 1; 113 | int wh = w * h; 114 | int div = radius + radius + 1; 115 | 116 | int r[] = new int[wh]; 117 | int g[] = new int[wh]; 118 | int b[] = new int[wh]; 119 | int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; 120 | int vmin[] = new int[Math.max(w, h)]; 121 | 122 | int divsum = (div + 1) >> 1; 123 | divsum *= divsum; 124 | int dv[] = new int[256 * divsum]; 125 | for (i = 0; i < 256 * divsum; i++) { 126 | dv[i] = (i / divsum); 127 | } 128 | 129 | yw = yi = 0; 130 | 131 | int[][] stack = new int[div][3]; 132 | int stackpointer; 133 | int stackstart; 134 | int[] sir; 135 | int rbs; 136 | int r1 = radius + 1; 137 | int routsum, goutsum, boutsum; 138 | int rinsum, ginsum, binsum; 139 | 140 | for (y = 0; y < h; y++) { 141 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; 142 | for (i = -radius; i <= radius; i++) { 143 | p = pix[yi + Math.min(wm, Math.max(i, 0))]; 144 | sir = stack[i + radius]; 145 | sir[0] = (p & 0xff0000) >> 16; 146 | sir[1] = (p & 0x00ff00) >> 8; 147 | sir[2] = (p & 0x0000ff); 148 | rbs = r1 - Math.abs(i); 149 | rsum += sir[0] * rbs; 150 | gsum += sir[1] * rbs; 151 | bsum += sir[2] * rbs; 152 | if (i > 0) { 153 | rinsum += sir[0]; 154 | ginsum += sir[1]; 155 | binsum += sir[2]; 156 | } else { 157 | routsum += sir[0]; 158 | goutsum += sir[1]; 159 | boutsum += sir[2]; 160 | } 161 | } 162 | stackpointer = radius; 163 | 164 | for (x = 0; x < w; x++) { 165 | 166 | r[yi] = dv[rsum]; 167 | g[yi] = dv[gsum]; 168 | b[yi] = dv[bsum]; 169 | 170 | rsum -= routsum; 171 | gsum -= goutsum; 172 | bsum -= boutsum; 173 | 174 | stackstart = stackpointer - radius + div; 175 | sir = stack[stackstart % div]; 176 | 177 | routsum -= sir[0]; 178 | goutsum -= sir[1]; 179 | boutsum -= sir[2]; 180 | 181 | if (y == 0) { 182 | vmin[x] = Math.min(x + radius + 1, wm); 183 | } 184 | p = pix[yw + vmin[x]]; 185 | 186 | sir[0] = (p & 0xff0000) >> 16; 187 | sir[1] = (p & 0x00ff00) >> 8; 188 | sir[2] = (p & 0x0000ff); 189 | 190 | rinsum += sir[0]; 191 | ginsum += sir[1]; 192 | binsum += sir[2]; 193 | 194 | rsum += rinsum; 195 | gsum += ginsum; 196 | bsum += binsum; 197 | 198 | stackpointer = (stackpointer + 1) % div; 199 | sir = stack[(stackpointer) % div]; 200 | 201 | routsum += sir[0]; 202 | goutsum += sir[1]; 203 | boutsum += sir[2]; 204 | 205 | rinsum -= sir[0]; 206 | ginsum -= sir[1]; 207 | binsum -= sir[2]; 208 | 209 | yi++; 210 | } 211 | yw += w; 212 | } 213 | for (x = 0; x < w; x++) { 214 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; 215 | yp = -radius * w; 216 | for (i = -radius; i <= radius; i++) { 217 | yi = Math.max(0, yp) + x; 218 | 219 | sir = stack[i + radius]; 220 | 221 | sir[0] = r[yi]; 222 | sir[1] = g[yi]; 223 | sir[2] = b[yi]; 224 | 225 | rbs = r1 - Math.abs(i); 226 | 227 | rsum += r[yi] * rbs; 228 | gsum += g[yi] * rbs; 229 | bsum += b[yi] * rbs; 230 | 231 | if (i > 0) { 232 | rinsum += sir[0]; 233 | ginsum += sir[1]; 234 | binsum += sir[2]; 235 | } else { 236 | routsum += sir[0]; 237 | goutsum += sir[1]; 238 | boutsum += sir[2]; 239 | } 240 | 241 | if (i < hm) { 242 | yp += w; 243 | } 244 | } 245 | yi = x; 246 | stackpointer = radius; 247 | for (y = 0; y < h; y++) { 248 | // Preserve alpha channel: ( 0xff000000 & pix[yi] ) 249 | pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; 250 | 251 | rsum -= routsum; 252 | gsum -= goutsum; 253 | bsum -= boutsum; 254 | 255 | stackstart = stackpointer - radius + div; 256 | sir = stack[stackstart % div]; 257 | 258 | routsum -= sir[0]; 259 | goutsum -= sir[1]; 260 | boutsum -= sir[2]; 261 | 262 | if (x == 0) { 263 | vmin[y] = Math.min(y + r1, hm) * w; 264 | } 265 | p = x + vmin[y]; 266 | 267 | sir[0] = r[p]; 268 | sir[1] = g[p]; 269 | sir[2] = b[p]; 270 | 271 | rinsum += sir[0]; 272 | ginsum += sir[1]; 273 | binsum += sir[2]; 274 | 275 | rsum += rinsum; 276 | gsum += ginsum; 277 | bsum += binsum; 278 | 279 | stackpointer = (stackpointer + 1) % div; 280 | sir = stack[stackpointer]; 281 | 282 | routsum += sir[0]; 283 | goutsum += sir[1]; 284 | boutsum += sir[2]; 285 | 286 | rinsum -= sir[0]; 287 | ginsum -= sir[1]; 288 | binsum -= sir[2]; 289 | 290 | yi += w; 291 | } 292 | } 293 | 294 | bitmap.setPixels(pix, 0, w, 0, 0, w, h); 295 | 296 | return (bitmap); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /burred/src/main/java/per/goweii/burred/Blurred.java: -------------------------------------------------------------------------------- 1 | package per.goweii.burred; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.os.Message; 10 | import android.view.View; 11 | import android.view.ViewTreeObserver; 12 | import android.widget.ImageView; 13 | 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | 17 | /** 18 | * @author Cuizhen 19 | * @date 2018/4/27 20 | * QQ: 302833254 21 | * E-mail: goweii@163.com 22 | * GitHub: https://github.com/goweii 23 | */ 24 | public final class Blurred { 25 | 26 | private static final Float MAX_FPS = 60F; 27 | 28 | private static IBlur sBlur; 29 | private static ExecutorService sExecutor; 30 | 31 | private long mLastFrameTime = 0L; 32 | 33 | private float mPercent = 0; 34 | private float mRadius = 0; 35 | private float mScale = 1; 36 | private boolean mAntiAlias = false; 37 | private boolean mKeepSize = false; 38 | private boolean mFitIntoViewXY = false; 39 | private boolean mRecycleOriginal = false; 40 | private float mMaxFps = MAX_FPS; 41 | 42 | private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener = null; 43 | private ViewTreeObserver.OnDrawListener mOnDrawListener = null; 44 | 45 | private int mBackgroundColor = Color.TRANSPARENT; 46 | private int mForegroundColor = Color.TRANSPARENT; 47 | private Bitmap mOriginalBitmap = null; 48 | private View mViewFrom = null; 49 | private ImageView mViewInto = null; 50 | 51 | private SnapshotInterceptor mSnapshotInterceptor = null; 52 | private FpsListener mFpsListener = null; 53 | private Listener mListener = null; 54 | private Callback mCallback = null; 55 | private Handler mCallbackHandler = null; 56 | 57 | public static void init(Context context) { 58 | if (sBlur == null) { 59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 60 | sBlur = GaussianBlur.get(context); 61 | } else { 62 | sBlur = FastBlur.get(); 63 | } 64 | } 65 | } 66 | 67 | public static void recycle() { 68 | if (sBlur != null) { 69 | sBlur.recycle(); 70 | sBlur = null; 71 | } 72 | BitmapProcessor.get().realTimeMode(false); 73 | if (sExecutor != null) { 74 | if (!sExecutor.isShutdown()) { 75 | sExecutor.shutdown(); 76 | } 77 | sExecutor = null; 78 | } 79 | } 80 | 81 | public static void realTimeMode(boolean realTimeMode) { 82 | final IBlur iBlur = requireBlur(); 83 | if (iBlur instanceof GaussianBlur) { 84 | GaussianBlur gaussianBlur = (GaussianBlur) sBlur; 85 | gaussianBlur.realTimeMode(realTimeMode); 86 | } 87 | BitmapProcessor.get().realTimeMode(realTimeMode); 88 | } 89 | 90 | private static IBlur requireBlur() { 91 | return Utils.requireNonNull(sBlur, "Blurred未初始化"); 92 | } 93 | 94 | private static ExecutorService requireExecutor() { 95 | if (sExecutor == null || sExecutor.isShutdown()) { 96 | sExecutor = Executors.newSingleThreadExecutor(); 97 | } 98 | return sExecutor; 99 | } 100 | 101 | public static Blurred with(Bitmap original) { 102 | return new Blurred().bitmap(original); 103 | } 104 | 105 | public static Blurred with(View view) { 106 | return new Blurred().view(view); 107 | } 108 | 109 | public void reset() { 110 | mMaxFps = MAX_FPS; 111 | mPercent = 0; 112 | mRadius = 0; 113 | mScale = 1; 114 | mKeepSize = false; 115 | mAntiAlias = false; 116 | mFitIntoViewXY = false; 117 | mRecycleOriginal = false; 118 | mOriginalBitmap = null; 119 | if (mViewFrom != null) { 120 | if (mOnPreDrawListener != null) { 121 | mViewFrom.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener); 122 | mOnPreDrawListener = null; 123 | } 124 | mViewFrom = null; 125 | } 126 | mViewInto = null; 127 | mBackgroundColor = Color.TRANSPARENT; 128 | mForegroundColor = Color.TRANSPARENT; 129 | } 130 | 131 | public Blurred view(View view) { 132 | reset(); 133 | mViewFrom = view; 134 | return this; 135 | } 136 | 137 | public Blurred bitmap(Bitmap original) { 138 | reset(); 139 | mOriginalBitmap = original; 140 | return this; 141 | } 142 | 143 | public Blurred suggestConfig() { 144 | mMaxFps = 60F; 145 | mPercent = 0; 146 | mRadius = 10; 147 | mScale = 8; 148 | mKeepSize = false; 149 | mAntiAlias = false; 150 | mFitIntoViewXY = false; 151 | mRecycleOriginal = false; 152 | return this; 153 | } 154 | 155 | public Blurred backgroundColor(int color) { 156 | mBackgroundColor = color; 157 | return this; 158 | } 159 | 160 | public Blurred foregroundColor(int color) { 161 | mForegroundColor = color; 162 | return this; 163 | } 164 | 165 | public Blurred percent(float percent) { 166 | this.mPercent = percent; 167 | return this; 168 | } 169 | 170 | public Blurred radius(float radius) { 171 | this.mRadius = radius; 172 | return this; 173 | } 174 | 175 | public Blurred scale(float scale) { 176 | this.mScale = scale; 177 | return this; 178 | } 179 | 180 | public Blurred maxFps(float maxFps) { 181 | this.mMaxFps = maxFps; 182 | return this; 183 | } 184 | 185 | public Blurred keepSize(boolean keepSize) { 186 | this.mKeepSize = keepSize; 187 | return this; 188 | } 189 | 190 | public Blurred fitIntoViewXY(boolean fit) { 191 | this.mFitIntoViewXY = fit; 192 | return this; 193 | } 194 | 195 | public Blurred antiAlias(boolean antiAlias) { 196 | this.mAntiAlias = antiAlias; 197 | return this; 198 | } 199 | 200 | public Blurred recycleOriginal(boolean recycleOriginal) { 201 | this.mRecycleOriginal = recycleOriginal; 202 | return this; 203 | } 204 | 205 | public Blurred snapshotInterceptor(SnapshotInterceptor interceptor) { 206 | this.mSnapshotInterceptor = interceptor; 207 | return this; 208 | } 209 | 210 | public Blurred fpsListener(FpsListener listener) { 211 | this.mFpsListener = listener; 212 | return this; 213 | } 214 | 215 | public Blurred listener(Listener listener) { 216 | this.mListener = listener; 217 | return this; 218 | } 219 | 220 | public Bitmap blur() { 221 | if (mViewFrom == null && mOriginalBitmap == null) { 222 | throw new NullPointerException("待模糊View和Bitmap不能同时为空"); 223 | } 224 | if (mListener != null) mListener.begin(); 225 | float scale = mScale <= 0 ? 1 : mScale; 226 | float radius = mPercent <= 0 ? mRadius : Math.min( 227 | mViewFrom != null ? mViewFrom.getWidth() : mOriginalBitmap.getWidth(), 228 | mViewFrom != null ? mViewFrom.getHeight() : mOriginalBitmap.getHeight() 229 | ) * mPercent; 230 | final Bitmap blurredBitmap; 231 | if (mViewFrom == null) { 232 | blurredBitmap = requireBlur().process(mOriginalBitmap, radius, scale, mKeepSize, mRecycleOriginal); 233 | } else { 234 | if (radius > 25) { 235 | scale = scale / (radius / 25); 236 | radius = 25; 237 | } 238 | final SnapshotInterceptor snapshotInterceptor = checkSnapshotInterceptor(); 239 | Bitmap bitmap = snapshotInterceptor.snapshot(mViewFrom, mBackgroundColor, mForegroundColor, scale, mAntiAlias); 240 | blurredBitmap = requireBlur().process(bitmap, radius, 1, mKeepSize, mRecycleOriginal); 241 | } 242 | if (mListener != null) mListener.end(); 243 | return blurredBitmap; 244 | } 245 | 246 | public void blur(final Callback callback) { 247 | Utils.requireNonNull(callback, "Callback不能为空"); 248 | mCallback = callback; 249 | mCallbackHandler = new Handler(Looper.getMainLooper()) { 250 | @Override 251 | public void handleMessage(Message msg) { 252 | mCallbackHandler = null; 253 | mCallback.down((Bitmap) msg.obj); 254 | } 255 | }; 256 | requireExecutor().submit(new Runnable() { 257 | @Override 258 | public void run() { 259 | Bitmap bitmap = blur(); 260 | Message msg = mCallbackHandler.obtainMessage(); 261 | msg.obj = bitmap; 262 | mCallbackHandler.sendMessage(msg); 263 | } 264 | }); 265 | } 266 | 267 | /** 268 | * 用于实现实时高斯模糊 269 | * viewFrom : 通过with()/view()传入的view 270 | * viewInto : 该处的ImageView 271 | * 将对viewFrom进行截图模糊处理,并对遮盖区域裁剪后设置到viewInto 272 | * viewFrom和viewInto不能有包含关系,及viewInto不能是viewFrom的子控件 273 | */ 274 | public void blur(final ImageView into) { 275 | Utils.requireNonNull(mViewFrom, "实时高斯模糊时待模糊View不能为空"); 276 | Utils.requireNonNull(into, "ImageView不能为空"); 277 | mViewInto = into; 278 | if (mOnPreDrawListener == null) { 279 | mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { 280 | @Override 281 | public boolean onPreDraw() { 282 | if (mViewInto == null) return true; 283 | long currFrameTime = System.currentTimeMillis(); 284 | final float fps = 1000F / (currFrameTime - mLastFrameTime); 285 | if (fps > mMaxFps) return true; 286 | mLastFrameTime = currFrameTime; 287 | if (mFpsListener != null) mFpsListener.currFps(fps); 288 | realTimeMode(true); 289 | keepSize(false); 290 | recycleOriginal(true); 291 | Bitmap blur = blur(); 292 | Bitmap clip = BitmapProcessor.get().clip(blur, mViewFrom, mViewInto, mFitIntoViewXY, mAntiAlias); 293 | blur.recycle(); 294 | mViewInto.setImageBitmap(clip); 295 | return true; 296 | } 297 | }; 298 | mViewFrom.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); 299 | } 300 | } 301 | 302 | private SnapshotInterceptor checkSnapshotInterceptor() { 303 | if (mSnapshotInterceptor == null) { 304 | mSnapshotInterceptor = new DefaultSnapshotInterceptor(); 305 | } 306 | return mSnapshotInterceptor; 307 | } 308 | 309 | public interface SnapshotInterceptor { 310 | Bitmap snapshot(View from, 311 | int backgroundColor, 312 | int foregroundColor, 313 | float scale, 314 | boolean antiAlias); 315 | } 316 | 317 | public interface Callback { 318 | void down(Bitmap bitmap); 319 | } 320 | 321 | public interface Listener { 322 | void begin(); 323 | 324 | void end(); 325 | } 326 | 327 | public interface FpsListener { 328 | void currFps(float fps); 329 | } 330 | } 331 | --------------------------------------------------------------------------------