├── .gitignore ├── README.md ├── bintray.gradle ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src └── main ├── java └── cz │ └── gradle │ └── android │ └── webp │ ├── WebPAndroidExtension.java │ ├── WebPAndroidPlugin.java │ ├── tasks │ ├── ConvertTask.java │ ├── DecompressTask.java │ └── DownloadLibTask.java │ └── util │ └── Logger.java └── resources └── META-INF └── gradle-plugins └── cz.webp.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea/ 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | !.idea/codeStyleSettings.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .vscode/ 12 | 13 | # Java class files 14 | *.class 15 | 16 | # Generated files 17 | bin/ 18 | gen/ 19 | out/ 20 | 21 | # Gradle files 22 | .gradle/ 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Intellij 32 | *.iml 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidWebP-GradlePlugin 2 | 一款支持自动将图片转换成WebP格式的Android构建插件 3 | 4 | ### 功能: 5 | 1. 支持将资源文件夹中的jpg、png图片转换成webp格式; 6 | 2. 构建过程中自动下载libwebp,无需再配置环境; 7 | 3. 支持手动与自动两种构建模式,祥见 `配置`; 8 | 4. 自动构建过程中,图片转换时机位于mergeResources之前,避免图片被重复处理多次; 9 | 10 | ### 接入: 11 | 1. 引用插件 12 | ``` 13 | buildscript { 14 | repositories { 15 | maven { 16 | url 'https://dl.bintray.com/zh8637688/maven/' 17 | } 18 | } 19 | dependencies { 20 | classpath 'cz.gradle.android:webp:0.0.1' 21 | } 22 | } 23 | ``` 24 | 2. 应用plugin 25 | ``` 26 | apply plugin: 'cz.webp' 27 | ``` 28 | 3. 配置 29 | ``` 30 | WebPAndroid { 31 | autoConvert true 32 | quality 75 33 | } 34 | ``` 35 | 36 | 4. 构建 37 | ``` 38 | autoConvert = true: ./gradlew build 39 | 40 | autoConvert = false: ./gradlew convert***WebP 41 | ``` 42 | 43 | ### 配置: 44 | 配置名称 | 类型 | 默认值 | 说明 45 | ------- | ------- | ------- | ------- 46 | quality | int | 75 | 指定转换过程中RGB通道的压缩因,取值范围0到100。值越小,压缩率越高,图片质量越低。 47 | autoConvert | boolean | false | 若设置为true,转换任务将自动加入构建过程,执行gradle build即可;若设置为false,需手动执行gradle convertWebP。 48 | 49 | ### 构建产物: 50 | - ```autoConvert = true```,sourSets.res指定资源文件夹中所有jpg、png(非.9)图片将被转换为webp格式,原有图片将移动到```projectDir/ori_res```目录中。 51 | - ```autoConvert = false```,原有图片路径不改变,转换后的webp图片位于```projectDir/webp```目录中。 52 | 53 | ### TODO: 54 | 自动模式下暂不支持增量构建(convertWebP任务的输出会改变输入属性。将输出路径设置在build目录下,并在其他任务中屏蔽原先的图片即可) -------------------------------------------------------------------------------- /bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | group = 'cz.gradle.android' 5 | version = '0.0.1' 6 | 7 | task sourcesJar(type: Jar) { 8 | from sourceSets.main.java.srcDirs 9 | classifier = 'sources' 10 | } 11 | 12 | javadoc.failOnError = false 13 | task javadocJar(type: Jar, dependsOn: javadoc) { 14 | classifier = 'javadoc' 15 | from javadoc.destinationDir 16 | } 17 | 18 | artifacts { 19 | archives sourcesJar 20 | archives javadocJar 21 | } 22 | 23 | def pomConfig = { 24 | licenses { 25 | license { 26 | name "The Apache Software License, Version 2.0" 27 | url "http://www.apache.org/licenses/LICENSE-2.0.txt" 28 | distribution "repo" 29 | } 30 | } 31 | developers { 32 | developer { 33 | id "haozhou" 34 | name "haozhou" 35 | email "joe.h0430@gmail.com" 36 | } 37 | } 38 | 39 | scm { 40 | url "https://github.com/zh8637688/AndroidWebP-GradlePlugin" 41 | } 42 | } 43 | 44 | publishing { 45 | publications { 46 | mavenPublication(MavenPublication) { 47 | from components.java 48 | artifact sourcesJar { 49 | classifier "sources" 50 | } 51 | artifact javadocJar { 52 | classifier "javadoc" 53 | } 54 | groupId 'cz.gradle.android' 55 | artifactId 'webp' 56 | version '0.0.1' 57 | pom.withXml { 58 | def root = asNode() 59 | root.appendNode('description', 'a gradle plugin that converting image to webp for android build') 60 | root.appendNode('name', 'webp') 61 | root.appendNode('url', 'https://github.com/zh8637688/AndroidWebP-GradlePlugin') 62 | root.children().last() + pomConfig 63 | } 64 | } 65 | } 66 | } 67 | 68 | Properties properties = new Properties() 69 | def file = project.rootProject.file('local.properties') 70 | if (file.exists()) { 71 | properties.load(file.newDataInputStream()) 72 | } 73 | 74 | bintray { 75 | user = properties.getProperty("BINTRAY_USER") 76 | key = properties.getProperty("BINTRAY_KEY") 77 | configurations = ['archives'] 78 | publish = true 79 | pkg { 80 | repo = 'maven' 81 | name = 'webp' 82 | vcsUrl = 'https://github.com/zh8637688/AndroidWebP-GradlePlugin.git' 83 | labels = ['gradle', 'plugin', 'android', 'webp'] 84 | licenses = ['Apache-2.0'] 85 | version { 86 | name = '0.0.1' 87 | released = new Date() 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 8 | } 9 | } 10 | 11 | apply plugin: 'groovy' 12 | apply from: 'bintray.gradle' 13 | 14 | repositories { 15 | jcenter() 16 | } 17 | 18 | dependencies { 19 | compile gradleApi() 20 | compile 'com.android.tools.build:gradle:2.3.3' 21 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zh8637688/AndroidWebP-GradlePlugin/34fd3e25ba1fb522dfc0e4f2cd8ef82df47dd0b4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 31 10:46:55 CST 2018 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-3.3-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/java/cz/gradle/android/webp/WebPAndroidExtension.java: -------------------------------------------------------------------------------- 1 | package cz.gradle.android.webp; 2 | 3 | /** 4 | * @author haozhou 5 | */ 6 | 7 | public class WebPAndroidExtension { 8 | private boolean autoConvert = false; 9 | private int quality = 75; 10 | 11 | public boolean isAutoConvert() { 12 | return autoConvert; 13 | } 14 | 15 | public void setAutoConvert(boolean autoConvert) { 16 | this.autoConvert = autoConvert; 17 | } 18 | 19 | public int getQuality() { 20 | return quality; 21 | } 22 | 23 | public void setQuality(int quality) { 24 | this.quality = quality; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cz/gradle/android/webp/WebPAndroidPlugin.java: -------------------------------------------------------------------------------- 1 | package cz.gradle.android.webp; 2 | 3 | import com.android.build.gradle.AppExtension; 4 | import com.android.build.gradle.LibraryExtension; 5 | import com.android.build.gradle.api.BaseVariant; 6 | import com.android.build.gradle.internal.variant.BaseVariantData; 7 | import com.android.utils.StringHelper; 8 | 9 | import org.gradle.api.Plugin; 10 | import org.gradle.api.Project; 11 | import org.gradle.api.Task; 12 | import org.gradle.api.invocation.Gradle; 13 | import org.gradle.internal.os.OperatingSystem; 14 | 15 | import java.io.File; 16 | import java.lang.reflect.Method; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import cz.gradle.android.webp.util.Logger; 21 | import cz.gradle.android.webp.tasks.ConvertTask; 22 | import cz.gradle.android.webp.tasks.DecompressTask; 23 | import cz.gradle.android.webp.tasks.DownloadLibTask; 24 | 25 | /** 26 | * @author haozhou 27 | */ 28 | 29 | public class WebPAndroidPlugin implements Plugin { 30 | public static final String APP_PLUGIN = "com.android.application"; 31 | public static final String LIB_PLUGIN = "com.android.library"; 32 | 33 | private static final String EXTENSIONS_NAME = "WebPAndroid"; 34 | private static final String HOST_URL = "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/"; 35 | 36 | @Override 37 | public void apply(Project project) { 38 | project.getExtensions().create(EXTENSIONS_NAME, WebPAndroidExtension.class); 39 | Logger.initialize(project.getLogger()); 40 | 41 | final String libFileName = getLibFileName(); 42 | 43 | if (libFileName != null) { 44 | project.afterEvaluate(p -> { 45 | WebPAndroidExtension extension = p.getExtensions().findByType(WebPAndroidExtension.class); 46 | String downloadUrl = HOST_URL + libFileName; 47 | String downloadDir = getDefaultDownloadDir(p.getGradle()); 48 | String downloadFilePath = downloadDir + "/" + libFileName; 49 | 50 | Task downloadTask = createDownloadTask(p, downloadUrl, downloadFilePath); 51 | Task decompressTask = createDecompressTask(p, downloadFilePath); 52 | 53 | List variantsName = getVariantsName(p); 54 | for (String variantName : variantsName) { 55 | Task convertTask = createConvertTask(p, 56 | variantName, getCWebPPath(downloadDir), 57 | extension.isAutoConvert(), extension.getQuality()); 58 | convertTask.dependsOn(decompressTask.dependsOn(downloadTask)); 59 | 60 | if (extension.isAutoConvert()) { 61 | attachCovertTaskToBuild(p, variantName, convertTask); 62 | } 63 | } 64 | }); 65 | } else { 66 | Logger.i("Can not support your operating system."); 67 | } 68 | } 69 | 70 | private Task createDownloadTask(Project project, String downloadUrl, String downloadFilePath) { 71 | DownloadLibTask task = project.getTasks().create("downloadLibWebP", DownloadLibTask.class, 72 | new DownloadLibTask.ConfigAction(downloadUrl, downloadFilePath)); 73 | task.setGroup("webp"); 74 | return task; 75 | } 76 | 77 | private Task createDecompressTask(Project project, String downloadFilePath) { 78 | DecompressTask task = project.getTasks().create("decompressDownloadFile", DecompressTask.class, 79 | new DecompressTask.ConfigAction(downloadFilePath)); 80 | task.setGroup("webp"); 81 | return task; 82 | } 83 | 84 | private Task createConvertTask(Project project, String variant, String cwebpPath, boolean autoConvert, int quality) { 85 | ConvertTask task = project.getTasks().create("convert" + StringHelper.capitalize(variant) + "WebP", 86 | ConvertTask.class, new ConvertTask.ConfigAction(project, cwebpPath, autoConvert, quality)); 87 | task.setGroup("webp"); 88 | return task; 89 | } 90 | 91 | private void attachCovertTaskToBuild(Project project, String variant, Task convertTask) { 92 | String nameOfMergeResources = "merge" + StringHelper.capitalize(variant) + "Resources"; 93 | Task mergeResources = project.getTasks().findByName(nameOfMergeResources); 94 | mergeResources.dependsOn(convertTask); 95 | } 96 | 97 | private String getDefaultDownloadDir(Gradle gradle) { 98 | File gradleUserHome = gradle.getGradleUserHomeDir(); 99 | String dependencyCacheDir = "/caches/modules-2/files-2.1/"; 100 | String packageName = "cz.webp"; 101 | return gradleUserHome.getAbsolutePath() + dependencyCacheDir + packageName; 102 | } 103 | 104 | private String getLibFileName() { 105 | OperatingSystem os = OperatingSystem.current(); 106 | if (os.isMacOsX()) { 107 | return "libwebp-1.0.0-rc3-mac-10.13.tar.gz"; 108 | } else if (os.isWindows()) { 109 | String arch = System.getProperty("os.arch"); 110 | if ("x86".equals(arch)) { 111 | return "libwebp-1.0.0-windows-x86-no-wic.zip"; 112 | } else if ("x86_64".equals(arch)) { 113 | return "libwebp-1.0.0-windows-x64-no-wic.zip"; 114 | } 115 | } 116 | return null; 117 | } 118 | 119 | private String getCWebPPath(String downloadDir) { 120 | String path = null; 121 | OperatingSystem os = OperatingSystem.current(); 122 | if (os.isMacOsX()) { 123 | path = "libwebp-1.0.0-rc3-mac-10.13/bin/cwebp"; 124 | } else if (os.isWindows()) { 125 | String arch = System.getProperty("os.arch"); 126 | if ("x86".equals(arch)) { 127 | path = "libwebp-1.0.0-windows-x86-no-wic/bin/cwebp.exe"; 128 | } else if ("x86_64".equals(arch)) { 129 | path = "libwebp-1.0.0-windows-x64-no-wic/bin/cwebp.exe"; 130 | } 131 | } 132 | return downloadDir + "/" + path; 133 | } 134 | 135 | private List getVariantsName(Project project) { 136 | List variantsName = new ArrayList<>(); 137 | Object androidExtension = project.getExtensions().findByName("android"); 138 | if (project.getPlugins().hasPlugin(APP_PLUGIN)) { 139 | AppExtension appExtension = (AppExtension) androidExtension; 140 | appExtension.getApplicationVariants().forEach(applicationVariant -> { 141 | BaseVariantData variantData = getVariantData(applicationVariant); 142 | if (variantData != null) { 143 | variantsName.add(variantData.getScope().getVariantConfiguration().getFullName()); 144 | } 145 | }); 146 | } else if (project.getPlugins().hasPlugin(LIB_PLUGIN)) { 147 | LibraryExtension libraryExtension = (LibraryExtension) androidExtension; 148 | libraryExtension.getLibraryVariants().forEach(libraryVariant -> { 149 | BaseVariantData variantData = getVariantData(libraryVariant); 150 | if (variantData != null) { 151 | variantsName.add(variantData.getVariantConfiguration().getFullName()); 152 | } 153 | }); 154 | } 155 | return variantsName; 156 | } 157 | 158 | private BaseVariantData getVariantData(BaseVariant variant) { 159 | try { 160 | Method methodGetVariantData = variant.getClass().getMethod("getVariantData"); 161 | methodGetVariantData.setAccessible(true); 162 | return (BaseVariantData) methodGetVariantData.invoke(variant); 163 | } catch (Exception e) { 164 | e.printStackTrace(); 165 | } 166 | return null; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/cz/gradle/android/webp/tasks/ConvertTask.java: -------------------------------------------------------------------------------- 1 | package cz.gradle.android.webp.tasks; 2 | 3 | import com.android.build.gradle.AppExtension; 4 | import com.android.build.gradle.LibraryExtension; 5 | import com.android.build.gradle.internal.api.DefaultAndroidSourceSet; 6 | 7 | import org.gradle.api.Action; 8 | import org.gradle.api.DefaultTask; 9 | import org.gradle.api.Project; 10 | import org.gradle.api.tasks.Input; 11 | import org.gradle.api.tasks.InputFiles; 12 | import org.gradle.api.tasks.TaskAction; 13 | import org.gradle.internal.os.OperatingSystem; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.List; 20 | import java.util.regex.Pattern; 21 | 22 | import cz.gradle.android.webp.WebPAndroidPlugin; 23 | import cz.gradle.android.webp.util.Logger; 24 | 25 | /** 26 | * @author haozhou 27 | */ 28 | 29 | public class ConvertTask extends DefaultTask { 30 | private int quality; 31 | private boolean autoConvert; 32 | private String cwebpPath; 33 | private String projectRootPath; 34 | private Collection drawableDirs; 35 | 36 | @Input 37 | public int getQuality() { 38 | return quality; 39 | } 40 | 41 | @Input 42 | public boolean isAutoConvert() { 43 | return autoConvert; 44 | } 45 | 46 | @InputFiles 47 | public Collection getDrawableDirs() { 48 | return drawableDirs; 49 | } 50 | 51 | /*@TaskAction 52 | public void convert(IncrementalTaskInputs inputs) { 53 | if (!inputs.isIncremental()) { 54 | cleanPreOutput(); 55 | } 56 | 57 | inputs.outOfDate(inputFileDetails -> { 58 | 59 | }); 60 | 61 | inputs.removed(inputFileDetails -> { 62 | 63 | }); 64 | }*/ 65 | 66 | @TaskAction 67 | public void convert() throws Exception { 68 | getPermissionForMac(); 69 | 70 | for (File drawableDir: drawableDirs) { 71 | File[] drawableFiles = drawableDir.listFiles(); 72 | if (drawableFiles != null) { 73 | for (File drawableFile : drawableFiles) { 74 | if (canConvert(drawableFile)) { 75 | String srcName = drawableFile.getName(); 76 | int dotIndex = srcName.lastIndexOf("."); 77 | String dstName = srcName.substring(0, dotIndex) + ".webp"; 78 | 79 | File parent = drawableFile.getParentFile(); 80 | 81 | String srcFilePath = drawableFile.getAbsolutePath(); 82 | String dstFilePath; 83 | if (isAutoConvert()) { 84 | dstFilePath = parent.getAbsolutePath() + "/" + dstName; 85 | } else { 86 | dstFilePath = projectRootPath + "/webp/" + parent.getName() + "/" + dstName; 87 | File dstFile = new File(dstFilePath); 88 | if (!dstFile.getParentFile().exists()) { 89 | dstFile.getParentFile().mkdirs(); 90 | } 91 | } 92 | 93 | try { 94 | String script = cwebpPath + " -q " + getQuality() + " " 95 | + srcFilePath + " -o " + dstFilePath; 96 | Process process = Runtime.getRuntime().exec(script); 97 | if (process.waitFor() != 0) { 98 | Logger.i("convert failed: " + srcFilePath); 99 | continue; 100 | } 101 | if (isAutoConvert()) { 102 | moveFileTo(srcFilePath, projectRootPath + "/ori_res/" + parent.getName() + "/" + srcName); 103 | } 104 | } catch (IOException e) { 105 | Logger.i("convert failed: " + srcFilePath); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | private boolean canConvert(File drawableFile) { 114 | String fileName = drawableFile.getName(); 115 | return (fileName.endsWith(".png") && !fileName.contains(".9")) 116 | || fileName.endsWith(".jpg"); 117 | } 118 | 119 | private void getPermissionForMac() throws Exception{ 120 | OperatingSystem os = OperatingSystem.current(); 121 | if (os.isMacOsX()) { 122 | String script = "chmod a+x " + cwebpPath; 123 | Process process = Runtime.getRuntime().exec(script); 124 | if (process.waitFor() != 0) { 125 | throw new Exception("Can not get executive permission, please execute 'chmod a+x " + cwebpPath + "' on terminal"); 126 | } 127 | } 128 | } 129 | 130 | private void moveFileTo(String src, String dst) { 131 | File dstFile = new File(dst); 132 | if (!dstFile.getParentFile().exists()) { 133 | dstFile.getParentFile().mkdirs(); 134 | } 135 | new File(src).renameTo(dstFile); 136 | } 137 | 138 | public static class ConfigAction implements Action { 139 | private Project project; 140 | private String cwebpPath; 141 | private boolean autoConvert; 142 | private int quality; 143 | 144 | public ConfigAction(Project project, String cwebpPath, boolean autoConvert, int quality) { 145 | this.project = project; 146 | this.cwebpPath = cwebpPath; 147 | this.autoConvert = autoConvert; 148 | this.quality = quality; 149 | } 150 | 151 | @Override 152 | public void execute(ConvertTask convertTask) { 153 | Collection androidResDirs = getAndroidResDirectories(project); 154 | convertTask.drawableDirs = getDrawableDirsFromRes(androidResDirs); 155 | convertTask.projectRootPath = project.getProjectDir().getAbsolutePath(); 156 | convertTask.cwebpPath = cwebpPath; 157 | convertTask.autoConvert = autoConvert; 158 | convertTask.quality = quality; 159 | } 160 | 161 | private Collection getDrawableDirsFromRes(Collection resDirs) { 162 | List drawableDirs = new ArrayList<>(); 163 | if (resDirs != null) { 164 | Pattern pattern = Pattern.compile("^drawable.*|^mipmap.*"); 165 | for (File resDir : resDirs) { 166 | File[] subResDirs = resDir.listFiles(); 167 | if (subResDirs != null) { 168 | for (File subResDir : subResDirs) { 169 | if (subResDir != null) { 170 | String dirName = subResDir.getName(); 171 | if (pattern.matcher(dirName).matches()) { 172 | drawableDirs.add(subResDir); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | return drawableDirs; 180 | } 181 | 182 | private Collection getAndroidResDirectories(Project project) { 183 | DefaultAndroidSourceSet sourceSet = null; 184 | Object androidExtension = project.getExtensions().findByName("android"); 185 | if (project.getPlugins().hasPlugin(WebPAndroidPlugin.APP_PLUGIN)) { 186 | AppExtension appExtension = (AppExtension) androidExtension; 187 | sourceSet = (DefaultAndroidSourceSet) appExtension.getSourceSets().getByName("main"); 188 | } else if (project.getPlugins().hasPlugin(WebPAndroidPlugin.LIB_PLUGIN)) { 189 | LibraryExtension libraryExtension = (LibraryExtension) androidExtension; 190 | sourceSet = (DefaultAndroidSourceSet) libraryExtension.getSourceSets().getByName("main"); 191 | } 192 | 193 | if (sourceSet != null) { 194 | return sourceSet.getResDirectories(); 195 | } 196 | return null; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/cz/gradle/android/webp/tasks/DecompressTask.java: -------------------------------------------------------------------------------- 1 | package cz.gradle.android.webp.tasks; 2 | 3 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 4 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 5 | import org.gradle.api.Action; 6 | import org.gradle.api.DefaultTask; 7 | import org.gradle.api.tasks.InputFile; 8 | import org.gradle.api.tasks.OutputDirectory; 9 | import org.gradle.api.tasks.TaskAction; 10 | 11 | import java.io.BufferedInputStream; 12 | import java.io.BufferedOutputStream; 13 | import java.io.File; 14 | import java.io.FileInputStream; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.OutputStream; 19 | import java.util.Enumeration; 20 | import java.util.zip.GZIPInputStream; 21 | import java.util.zip.ZipEntry; 22 | import java.util.zip.ZipFile; 23 | 24 | import cz.gradle.android.webp.util.Logger; 25 | 26 | /** 27 | * @author haozhou 28 | */ 29 | 30 | public class DecompressTask extends DefaultTask { 31 | private String srcFilePath; 32 | private String dstDirPath; 33 | private String succeedFlagPath; 34 | 35 | @InputFile 36 | public File getSrcFile() { 37 | return new File(srcFilePath); 38 | } 39 | 40 | @OutputDirectory 41 | public File getDstDir() { 42 | return new File(dstDirPath); 43 | } 44 | 45 | @TaskAction 46 | public void decompressFile() throws Exception { 47 | File succeedFlag = new File(succeedFlagPath); 48 | if (!succeedFlag.exists()) { 49 | Logger.i("Start decompress file " + srcFilePath); 50 | if (srcFilePath.endsWith("tar.gz")) { 51 | decompressGzip(srcFilePath); 52 | } else { 53 | decompressZip(srcFilePath); 54 | } 55 | succeedFlag.createNewFile(); 56 | Logger.i("Decompress finished."); 57 | } 58 | } 59 | 60 | private void decompressGzip(String gzipFilePath) throws Exception { 61 | TarArchiveInputStream tis = null; 62 | BufferedOutputStream bos = null; 63 | try { 64 | File gzipFile = new File(gzipFilePath); 65 | tis = new TarArchiveInputStream( 66 | new GZIPInputStream( 67 | new BufferedInputStream( 68 | new FileInputStream(gzipFile)))); 69 | TarArchiveEntry tae; 70 | String fileFolder = gzipFile.getParentFile().getAbsolutePath(); 71 | while ((tae = tis.getNextTarEntry()) != null) { 72 | File tmpFile = new File(fileFolder, tae.getName()); 73 | if (tae.isDirectory()) { 74 | tmpFile.mkdirs(); 75 | } else { 76 | if (!tmpFile.getParentFile().exists()) { 77 | tmpFile.getParentFile().mkdirs(); 78 | } 79 | try { 80 | bos = new BufferedOutputStream(new FileOutputStream(tmpFile)); 81 | int length; 82 | byte[] b = new byte[1024]; 83 | while ((length = tis.read(b)) != -1) { 84 | bos.write(b, 0, length); 85 | } 86 | } finally { 87 | try { 88 | if (bos != null) { 89 | bos.close(); 90 | } 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | } 96 | } 97 | } finally { 98 | if (tis != null) { 99 | try { 100 | tis.close(); 101 | } catch (Exception e) { 102 | e.printStackTrace(); 103 | } 104 | } 105 | } 106 | } 107 | 108 | private void decompressZip(String zipFilePath) throws Exception { 109 | ZipFile zipFile = null; 110 | try { 111 | zipFile = new ZipFile(zipFilePath); 112 | Enumeration enums = zipFile.entries(); 113 | String fileFolder = new File(zipFilePath).getParentFile().getAbsolutePath(); 114 | while (enums.hasMoreElements()) { 115 | ZipEntry entry = enums.nextElement(); 116 | File tmpFile = new File(fileFolder, entry.getName()); 117 | if (entry.isDirectory()) { 118 | tmpFile.mkdirs(); 119 | } else { 120 | if (!tmpFile.getParentFile().exists()) { 121 | tmpFile.getParentFile().mkdirs(); 122 | } 123 | 124 | InputStream in = null; 125 | OutputStream out = null; 126 | try { 127 | in = zipFile.getInputStream(entry); 128 | out = new FileOutputStream(tmpFile); 129 | int length; 130 | byte[] b = new byte[1024]; 131 | while ((length = in.read(b)) != -1) { 132 | out.write(b, 0, length); 133 | } 134 | 135 | } finally { 136 | if (in != null) { 137 | try { 138 | in.close(); 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | if (out != null) { 144 | try { 145 | out.close(); 146 | } catch (Exception e) { 147 | e.printStackTrace(); 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | } finally { 155 | try { 156 | if (zipFile != null) { 157 | zipFile.close(); 158 | } 159 | } catch (IOException e) { 160 | e.printStackTrace(); 161 | } 162 | } 163 | } 164 | 165 | public static class ConfigAction implements Action { 166 | private String downloadFilePath; 167 | 168 | public ConfigAction(String downloadFilePath) { 169 | this.downloadFilePath = downloadFilePath; 170 | } 171 | 172 | @Override 173 | public void execute(DecompressTask decompressTask) { 174 | decompressTask.srcFilePath = downloadFilePath; 175 | decompressTask.dstDirPath = new File(downloadFilePath).getParent(); 176 | decompressTask.succeedFlagPath = decompressTask.dstDirPath + "/succeed"; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/cz/gradle/android/webp/tasks/DownloadLibTask.java: -------------------------------------------------------------------------------- 1 | package cz.gradle.android.webp.tasks; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.api.DefaultTask; 5 | import org.gradle.api.tasks.Input; 6 | import org.gradle.api.tasks.OutputFile; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import java.io.BufferedInputStream; 10 | import java.io.BufferedOutputStream; 11 | import java.io.File; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.net.HttpURLConnection; 15 | import java.net.URL; 16 | 17 | import cz.gradle.android.webp.util.Logger; 18 | 19 | /** 20 | * @author haozhou 21 | */ 22 | 23 | public class DownloadLibTask extends DefaultTask { 24 | private String downloadUrl; 25 | private String downloadFilePath; 26 | 27 | @Input 28 | public String getDownloadFilePath() { 29 | return downloadFilePath; 30 | } 31 | 32 | @OutputFile 33 | public File getDownloadFile() { 34 | return new File(downloadFilePath); 35 | } 36 | 37 | @TaskAction 38 | public void downloadLib() throws Exception { 39 | if (downloadUrl != null && isFilePathValid(downloadFilePath) 40 | && !new File(downloadFilePath).exists()) { 41 | BufferedInputStream bis = null; 42 | BufferedOutputStream bos = null; 43 | try { 44 | Logger.i("Start download lib of WebP, " + downloadUrl); 45 | HttpURLConnection connection = (HttpURLConnection) new URL(downloadUrl).openConnection(); 46 | connection.setConnectTimeout(30000); 47 | connection.setReadTimeout(30000); 48 | connection.connect(); 49 | if (connection.getResponseCode() == 200) { 50 | if (createDownloadFileIfNeed()) { 51 | bis = new BufferedInputStream(connection.getInputStream()); 52 | bos = new BufferedOutputStream(new FileOutputStream(downloadFilePath)); 53 | int length; 54 | byte[] bytes = new byte[1024]; 55 | while ((length = bis.read(bytes)) != -1) { 56 | bos.write(bytes, 0, length); 57 | } 58 | bos.flush(); 59 | Logger.i("Download finished."); 60 | } else { 61 | throw new Exception("Download failed, can not create download file."); 62 | } 63 | } else { 64 | throw new Exception("Download failed, request has been refused."); 65 | } 66 | } finally { 67 | if (bis != null) { 68 | try { 69 | bis.close(); 70 | } catch (IOException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | if (bos != null) { 75 | try { 76 | bos.close(); 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | private boolean createDownloadFileIfNeed() { 86 | File file = new File(downloadFilePath); 87 | File parent = file.getParentFile(); 88 | if (parent != null && !parent.exists()) { 89 | if (!parent.mkdirs()) { 90 | return false; 91 | } 92 | } 93 | if (file.exists() && !file.delete()) { 94 | return false; 95 | } 96 | try { 97 | if (!file.createNewFile()) { 98 | return false; 99 | } 100 | } catch (IOException e) { 101 | e.printStackTrace(); 102 | return false; 103 | } 104 | return true; 105 | } 106 | 107 | private boolean isFilePathValid(String path) { 108 | if (path != null) { 109 | File dir = new File(path).getParentFile(); 110 | return dir.isDirectory() && dir.canWrite(); 111 | } else { 112 | return false; 113 | } 114 | } 115 | 116 | public static class ConfigAction implements Action { 117 | private String downloadUrl; 118 | private String downloadFilePath; 119 | 120 | public ConfigAction(String downloadUrl, String downloadFilePath) { 121 | this.downloadUrl = downloadUrl; 122 | this.downloadFilePath = downloadFilePath; 123 | } 124 | 125 | @Override 126 | public void execute(DownloadLibTask downloadLibTask) { 127 | downloadLibTask.downloadUrl = downloadUrl; 128 | downloadLibTask.downloadFilePath = downloadFilePath; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/cz/gradle/android/webp/util/Logger.java: -------------------------------------------------------------------------------- 1 | package cz.gradle.android.webp.util; 2 | 3 | /** 4 | * @author haozhou 5 | */ 6 | 7 | public class Logger { 8 | private static org.gradle.api.logging.Logger gradleLogger = null; 9 | 10 | public static void initialize(org.gradle.api.logging.Logger gradleLogger) { 11 | Logger.gradleLogger = gradleLogger; 12 | } 13 | 14 | public static void i(String s) { 15 | if (gradleLogger != null) { 16 | gradleLogger.lifecycle(String.format("WebPAndroidPlugin: %s", s)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/cz.webp.properties: -------------------------------------------------------------------------------- 1 | implementation-class=cz.gradle.android.webp.WebPAndroidPlugin --------------------------------------------------------------------------------