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 |
--------------------------------------------------------------------------------