commit 5c899a0220c752da428be04ded83ccda9d383dfa Author: liushaofeng Date: Tue Jun 23 15:24:55 2020 +0800 初始提交,滑块验证码功能已实现 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bf7654 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +target +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.mvn +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..d7cb849 --- /dev/null +++ b/HELP.md @@ -0,0 +1,17 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/maven-plugin/reference/html/#build-image) +* [Spring Web](https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications) + +### Guides +The following guides illustrate how to use some features concretely: + +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) + diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..2f3bf54 --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + cloud.tianai.captcha + tianai-captcha + tianai-captcha + 0.0.1-SNAPSHOT + Demo project for Spring Boot + + + + maven-compiler-plugin + + 8 + 8 + + + + maven-shade-plugin + 3.2.2 + + + package + + shade + + + true + false + + + com.github.nintha:webp-imageio-core + + + + + + + + + + + org.projectlombok + lombok + 1.18.12 + compile + true + + + com.github.nintha + webp-imageio-core + 0.1.3 + system + ${project.basedir}/src/main/resources/libs/webp-imageio-core-0.1.3.jar + + + + 1.8 + + diff --git a/image/1.png b/image/1.png new file mode 100644 index 0000000..4549900 Binary files /dev/null and b/image/1.png differ diff --git a/image/2.png b/image/2.png new file mode 100644 index 0000000..4c592bc Binary files /dev/null and b/image/2.png differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f3edbfe --- /dev/null +++ b/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + cloud.tianai.captcha + tianai-captcha + 0.0.1-SNAPSHOT + tianai-captcha + Demo project for Spring Boot + + + 1.8 + + + + + + org.projectlombok + lombok + 1.18.12 + true + + + + + + + + + + + + org.scijava + native-lib-loader + 2.3.4 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d21c6dc --- /dev/null +++ b/readme.md @@ -0,0 +1,42 @@ +# 这是一个滑块验证码的实现 +## 不说废话,直接上成品 +![](image/1.png) +![](image/2.png) + +- 该滑块验证码实现了 普通图片和 **webp**图片两种格式 +- java获取滑块验证码例子 +```java +public static void main(String[] args) { + SliderCaptchaTemplate sliderCaptchaTemplate = new SliderCaptchaTemplate(); + // 生成滑块图片 + SliderCaptchaInfo slideImageInfo = sliderCaptchaTemplate.getSlideImageInfo(); + // 获取背景图片的base64 + String backgroundImage = slideImageInfo.getBackgroundImage(); + // 获取滑块图片 + slideImageInfo.getSliderImage(); + // 获取滑块被背景图片的百分比, (校验图片使用) + Float xPercent = slideImageInfo.getXPercent(); +} +``` +- 添加自定义背景图片例子 +```java +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/2.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/3.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/4.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/5.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/6.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/7.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/8.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/9.jpg"))); +addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/10.jpg"))); +``` +- 添加自定义模板(滑块的颜色和形状) +```java +Map template1 = new HashMap<>(4); +template1.put(ACTIVE_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png"))); +template1.put(CUT_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/cut.png"))); +template1.put(FIXED_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png"))); +template1.put(MATRIX_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/matrix.png"))); +addTemplate(template1); +``` \ No newline at end of file diff --git a/src/main/java/cloud/tianai/captcha/tianaicaptcha/template/slider/SliderCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/tianaicaptcha/template/slider/SliderCaptchaInfo.java new file mode 100644 index 0000000..fe3c8ce --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/tianaicaptcha/template/slider/SliderCaptchaInfo.java @@ -0,0 +1,39 @@ +package cloud.tianai.captcha.tianaicaptcha.template.slider; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @Author: 天爱有情 + * @Date 2020/5/29 8:04 + * @Description 滑块验证码 + */ +@Data +@AllArgsConstructor +public class SliderCaptchaInfo { + + /** + * x轴 + */ + private Integer x; + /** + * x轴百分比 + */ + private Float xPercent; + /** + * y轴 + */ + private Integer y; + /** + * 背景图 + */ + private String backgroundImage; + /** + * 移动图 + */ + private String sliderImage; + + public static SliderCaptchaInfo of(Integer x, Float xPercent, Integer y, String backgroundImage, String sliderImage) { + return new SliderCaptchaInfo(x, xPercent, y, backgroundImage, sliderImage); + } +} diff --git a/src/main/java/cloud/tianai/captcha/tianaicaptcha/template/slider/SliderCaptchaTemplate.java b/src/main/java/cloud/tianai/captcha/tianaicaptcha/template/slider/SliderCaptchaTemplate.java new file mode 100644 index 0000000..548edbc --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/tianaicaptcha/template/slider/SliderCaptchaTemplate.java @@ -0,0 +1,384 @@ +package cloud.tianai.captcha.tianaicaptcha.template.slider; + +import lombok.SneakyThrows; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.geom.Area; +import java.awt.image.BufferedImage; +import java.awt.image.PixelGrabber; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.math.BigDecimal; +import java.net.URL; +import java.util.List; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @Author: 天爱有情 + * @Date 2020/5/29 8:06 + * @Description 滑块验证码模板 + */ +public class SliderCaptchaTemplate { + + /** 默认的resource资源文件路径.*/ + public static final String DEFAULT_SLIDER_IMAGE_RESOURCE_PATH = "META-INF/cut-image/resource"; + /** 默认的template资源文件路径.*/ + public static final String DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH = "META-INF/cut-image/template"; + + public static final String ACTIVE_IMAGE_NAME = "active.png"; + public static final String CUT_IMAGE_NAME = "cut.png"; + public static final String FIXED_IMAGE_NAME = "fixed.png"; + public static final String MATRIX_IMAGE_NAME = "matrix.png"; + + /** resource图片.*/ + private static List resourceImageFiles = new ArrayList<>(20); + /** 模板图片.*/ + private static List> templateImageFiles = new ArrayList<>(2); + + static { + // 添加一些系统的资源文件 + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/2.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/3.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/4.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/5.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/6.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/7.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/8.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/9.jpg"))); + addResource(getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/10.jpg"))); + + // 添加一些系统的 模板文件 + Map template1 = new HashMap<>(4); + template1.put(ACTIVE_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png"))); + template1.put(CUT_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/cut.png"))); + template1.put(FIXED_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png"))); + template1.put(MATRIX_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/matrix.png"))); + addTemplate(template1); + + + Map template2 = new HashMap<>(4); + template2.put(ACTIVE_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png"))); + template2.put(CUT_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/cut.png"))); + template2.put(FIXED_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png"))); + template2.put(MATRIX_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/matrix.png"))); + addTemplate(template2); + + Map template3 = new HashMap<>(4); + template3.put(ACTIVE_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/active.png"))); + template3.put(CUT_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/cut.png"))); + template3.put(FIXED_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/fixed.png"))); + template3.put(MATRIX_IMAGE_NAME, getClassLoader().getResource(DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/matrix.png"))); + addTemplate(template3); + + } + + + + private final AtomicBoolean loadResources = new AtomicBoolean(false); + + private String sliderImageResourcePath = DEFAULT_SLIDER_IMAGE_RESOURCE_PATH; + private String sliderImageTemplatePath = DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH; + + public SliderCaptchaTemplate() { + // 加载系统资源文件 + } + + + public SliderCaptchaTemplate(String sliderImageResourcePath, String sliderImageTemplatePath) { + this.sliderImageResourcePath = sliderImageResourcePath; + this.sliderImageTemplatePath = sliderImageTemplatePath; + // 加载系统资源文件 + } + + public SliderCaptchaTemplate(List r, List> t) { + resourceImageFiles = r; + templateImageFiles = t; + } + + public static void addResource(URL url) { + resourceImageFiles.remove(url); + resourceImageFiles.add(url); + } + + public static void setResource(List resources) { + resourceImageFiles = resources; + } + + public static void setTemplates(List> imageTemplates) { + templateImageFiles = imageTemplates; + } + + public static void deleteResource(URL resource) { + resourceImageFiles.remove(resource); + } + + public static void deleteTemplate(Map template) { + templateImageFiles.remove(template); + } + + public static void addTemplate(Map template) { + templateImageFiles.remove(template); + templateImageFiles.add(template); + } + + private static ClassLoader getClassLoader() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = SliderCaptchaTemplate.getClassLoader(); + } + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + return classLoader; + } + + public SliderCaptchaInfo getSlideImageInfo(){ + return getSlideImageInfo("jpg", "png"); + } + + public SliderCaptchaInfo getSlideImageInfoForWebp(){ + return getSlideImageInfo("webp", "webp"); + } + + + @SneakyThrows + public SliderCaptchaInfo getSlideImageInfo(String targetFormatName, String matrixFormatName) { + URL resourceImage = getRandomResourceImage(); + Map templateImages = getRandomTemplateImages(); + + BufferedImage cutBackground = warpFile2BufferedImage(resourceImage); + BufferedImage targetBackground = warpFile2BufferedImage(resourceImage); + + BufferedImage fixedTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, FIXED_IMAGE_NAME)); + BufferedImage activeTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, ACTIVE_IMAGE_NAME)); + BufferedImage matrixTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, MATRIX_IMAGE_NAME)); + BufferedImage cutTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, CUT_IMAGE_NAME)); + + // 获取随机的 x 和 y 轴 + Random random = new Random(); + int randomX = random.nextInt(targetBackground.getWidth() - fixedTemplate.getWidth() * 2) + fixedTemplate.getWidth(); + int randomY = random.nextInt(targetBackground.getHeight() - fixedTemplate.getHeight()); + + coverImage(targetBackground, fixedTemplate, randomX, randomY); + BufferedImage cutImage = cutImage(cutBackground, cutTemplate, randomX, randomY); + coverImage(cutImage, activeTemplate, 0, 0); + coverImage(matrixTemplate, cutImage, 0, randomY); + // 计算滑块百分比 + Float xPercent = (float) randomX / targetBackground.getWidth(); + + String backGroundImageBase64 = transformBase64(targetBackground, targetFormatName); + String sliderImageBase64 = transformBase64(matrixTemplate, matrixFormatName); + + return SliderCaptchaInfo.of(randomX, xPercent, randomY, backGroundImageBase64, sliderImageBase64); + } + + /** + * 百分比对比 + * + * @param newPercentage 用户百分比 + * @param oriPercentage 原百分比 + * @return true 成功 false 失败 + */ + public boolean percentageContrast(Float newPercentage, Float oriPercentage) { + boolean falg = false; + BigDecimal num = BigDecimal.valueOf(0.05d).setScale(2, BigDecimal.ROUND_HALF_UP); + BigDecimal newPercentageBig = new BigDecimal(newPercentage).setScale(2, BigDecimal.ROUND_HALF_UP); + BigDecimal oriPercentageBig = new BigDecimal(oriPercentage).setScale(2, BigDecimal.ROUND_HALF_UP); + //最小百分比 + BigDecimal minOriPercentage = oriPercentageBig.subtract(num).setScale(2, BigDecimal.ROUND_HALF_UP); + //最大百分比 + BigDecimal maxOriPercentage = oriPercentageBig.add(num).setScale(2, BigDecimal.ROUND_HALF_UP); + if (newPercentageBig.compareTo(minOriPercentage) > 0 && maxOriPercentage.compareTo(newPercentageBig) > 0) { + falg = true; + } + return falg; + } + + + private String transformBase64(BufferedImage bufferedImage, String formatName) { + byte[] data = null; + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + ImageIO.write(bufferedImage, formatName, byteArrayOutputStream); + //转换成字节码 + data = byteArrayOutputStream.toByteArray(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + String base64 = Base64.getEncoder().encodeToString(data); + return "data:image/"+formatName+";base64,".concat(base64); + } + + + /** + * 通过模板图片抠图(不透明部分) + * + * @param origin 源图片 + * @param template 模板图片 + * @param x 坐标轴x + * @param y 坐标轴y + * @return BufferedImage + */ + @SneakyThrows + private static BufferedImage cutImage(BufferedImage origin, BufferedImage template, int x, int y) { + int bw = template.getWidth(null); + int bh = template.getHeight(null); + int lw = origin.getWidth(null); + int lh = origin.getHeight(null); + //得到透明的区域(人物轮廓) + Shape imageShape = getImageShape(template, false); + //合成后的图片 + BufferedImage image = new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = image.createGraphics(); + //设置画布为透明 + image = graphics.getDeviceConfiguration().createCompatibleImage(bw, bh, Transparency.TRANSLUCENT); + graphics.dispose(); + Graphics2D graphics2 = image.createGraphics(); + //取交集(限制可以画的范围为shape的范围) + graphics2.clip(imageShape); + //抗锯齿 + graphics2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics2.setStroke(new BasicStroke(5, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + graphics2.drawImage(origin, -x, -y, lw, lh, null); + graphics2.dispose(); + return image; + } + + /** + * 将Image图像中的透明/不透明部分转换为Shape图形 + * + * @param img 图片信息 + * @param transparent 是否透明 + * @return Shape + * @throws InterruptedException 异常 + */ + public static Shape getImageShape(Image img, boolean transparent) throws InterruptedException { + ArrayList x = new ArrayList<>(); + ArrayList y = new ArrayList<>(); + int width = img.getWidth(null); + int height = img.getHeight(null); + + // 首先获取图像所有的像素信息 + PixelGrabber pgr = new PixelGrabber(img, 0, 0, -1, -1, true); + pgr.grabPixels(); + int[] pixels = (int[]) pgr.getPixels(); + + // 循环像素 + for (int i = 0; i < pixels.length; i++) { + // 筛选,将不透明的像素的坐标加入到坐标ArrayList x和y中 + int alpha = (pixels[i] >> 24) & 0xff; + if (alpha != 0) { + x.add(i % width > 0 ? i % width - 1 : 0); + y.add(i % width == 0 ? (i == 0 ? 0 : i / width - 1) : i / width); + } + } + + // 建立图像矩阵并初始化(0为透明,1为不透明) + int[][] matrix = new int[height][width]; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + matrix[i][j] = 0; + } + } + + // 导入坐标ArrayList中的不透明坐标信息 + for (int c = 0; c < x.size(); c++) { + matrix[y.get(c)][x.get(c)] = 1; + } + + /* + * 逐一水平"扫描"图像矩阵的每一行,将透明(这里也可以取不透明的)的像素生成为Rectangle, + * 再将每一行的Rectangle通过Area类的rec对象进行合并, 最后形成一个完整的Shape图形 + */ + Area rec = new Area(); + int temp = 0; + //生成Shape时是1取透明区域还是取非透明区域的flag + int flag = transparent ? 0 : 1; + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (matrix[i][j] == flag) { + if (temp == 0) { + temp = j; + } + if (j == width) { + if (temp == 0) { + rec.add(new Area(new Rectangle(j, i, 1, 1))); + } else { + rec.add(new Area(new Rectangle(temp, i, j - temp, 1))); + temp = 0; + } + } + } else { + if (temp != 0) { + rec.add(new Area(new Rectangle(temp, i, j - temp, 1))); + temp = 0; + } + } + } + temp = 0; + } + return rec; + } + + /** + * 图片覆盖(覆盖图压缩到width*height大小,覆盖到底图上) + * + * @param baseBufferedImage 底图 + * @param coverBufferedImage 覆盖图 + * @param x 起始x轴 + * @param y 起始y轴 + */ + private static void coverImage(BufferedImage baseBufferedImage, BufferedImage coverBufferedImage, + int x, int y) { + // 创建Graphics2D对象,用在底图对象上绘图 + Graphics2D g2d = baseBufferedImage.createGraphics(); + // 绘制 + g2d.drawImage(coverBufferedImage, x, y, coverBufferedImage.getWidth(), coverBufferedImage.getHeight(), null); + // 释放图形上下文使用的系统资源 + g2d.dispose(); + } + + private URL getTemplateFile(Map templateImages, String imageName) { + URL url = templateImages.get(imageName); + if (url == null) { + throw new IllegalArgumentException("查找模板异常, 该模板下未找到 "); + } + return url; + } + + private Map getRandomTemplateImages() { + if (templateImageFiles.size() == 1) { + return templateImageFiles.get(0); + } + int templateNo = new Random().nextInt(templateImageFiles.size()); + return templateImageFiles.get(templateNo); + } + + @SneakyThrows + private static BufferedImage warpFile2BufferedImage(URL resourceImage) { + if (resourceImage == null) { + throw new IllegalArgumentException("包装文件到 BufferedImage 失败, file不能为空"); + } + return ImageIO.read(resourceImage); + } + + private URL getRandomResourceImage() { + int targetNo = new Random().nextInt(resourceImageFiles.size()); + return resourceImageFiles.get(targetNo); + } + + public static void main(String[] args) { + SliderCaptchaTemplate sliderCaptchaTemplate = new SliderCaptchaTemplate(); + // 生成滑块图片 + SliderCaptchaInfo slideImageInfo = sliderCaptchaTemplate.getSlideImageInfo(); + // 获取背景图片的base64 + String backgroundImage = slideImageInfo.getBackgroundImage(); + // 获取滑块图片 + slideImageInfo.getSliderImage(); + // 获取滑块被背景图片的百分比, (校验图片使用) + Float xPercent = slideImageInfo.getXPercent(); + } +} diff --git a/src/main/java/com/luciad/imageio/webp/VP8StatusCode.java b/src/main/java/com/luciad/imageio/webp/VP8StatusCode.java new file mode 100644 index 0000000..0d1047e --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/VP8StatusCode.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +enum VP8StatusCode { + VP8_STATUS_OK, + VP8_STATUS_OUT_OF_MEMORY, + VP8_STATUS_INVALID_PARAM, + VP8_STATUS_BITSTREAM_ERROR, + VP8_STATUS_UNSUPPORTED_FEATURE, + VP8_STATUS_SUSPENDED, + VP8_STATUS_USER_ABORT, + VP8_STATUS_NOT_ENOUGH_DATA,; + + private static VP8StatusCode[] VALUES = values(); + + public static VP8StatusCode getStatusCode( int aValue ) { + if ( aValue >= 0 && aValue < VALUES.length ) { + return VALUES[ aValue ]; + } + else { + return null; + } + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebP.java b/src/main/java/com/luciad/imageio/webp/WebP.java new file mode 100644 index 0000000..a8c78cb --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebP.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import lombok.extern.slf4j.Slf4j; +import org.scijava.nativelib.DefaultJniExtractor; +import org.scijava.nativelib.NativeLibraryUtil; + +import java.io.IOException; +import java.nio.ByteOrder; + +@Slf4j +final class WebP { + private static boolean NATIVE_LIBRARY_LOADED = false; + + static synchronized void loadNativeLibrary() { + if (!NATIVE_LIBRARY_LOADED) { + NATIVE_LIBRARY_LOADED = true; + try { + NativeLibraryUtil.loadNativeLibrary(new DefaultJniExtractor(WebP.class), "webp-imageio"); + } catch (IOException e) { + log.debug("IOException creating DefaultJniExtractor", e); + } + } + } + + static { + loadNativeLibrary(); + } + + private WebP() { + } + + public static int[] decode(WebPDecoderOptions aOptions, byte[] aData, int aOffset, int aLength, int[] aOut) throws IOException { + if (aOptions == null) { + throw new NullPointerException("Decoder options may not be null"); + } + + if (aData == null) { + throw new NullPointerException("Input data may not be null"); + } + + if (aOffset + aLength > aData.length) { + throw new IllegalArgumentException("Offset/length exceeds array size"); + } + + int[] pixels = decode(aOptions.fPointer, aData, aOffset, aLength, aOut, ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)); + VP8StatusCode status = VP8StatusCode.getStatusCode(aOut[0]); + switch (status) { + case VP8_STATUS_OK: + break; + case VP8_STATUS_OUT_OF_MEMORY: + throw new OutOfMemoryError(); + default: + throw new IOException("Decode returned code " + status); + } + + return pixels; + } + + private static native int[] decode(long aDecoderOptionsPointer, byte[] aData, int aOffset, int aLength, int[] aFlags, boolean aBigEndian); + + public static int[] getInfo(byte[] aData, int aOffset, int aLength) throws IOException { + int[] out = new int[2]; + int result = getInfo(aData, aOffset, aLength, out); + if (result == 0) { + throw new IOException("Invalid WebP data"); + } + + return out; + } + + private static native int getInfo(byte[] aData, int aOffset, int aLength, int[] aOut); + + public static byte[] encodeRGBA(WebPEncoderOptions aOptions, byte[] aRgbaData, int aWidth, int aHeight, int aStride) { + return encodeRGBA(aOptions.fPointer, aRgbaData, aWidth, aHeight, aStride); + } + + private static native byte[] encodeRGBA(long aConfig, byte[] aRgbaData, int aWidth, int aHeight, int aStride); + + public static byte[] encodeRGB(WebPEncoderOptions aOptions, byte[] aRgbaData, int aWidth, int aHeight, int aStride) { + return encodeRGB(aOptions.fPointer, aRgbaData, aWidth, aHeight, aStride); + } + + private static native byte[] encodeRGB(long aConfig, byte[] aRgbaData, int aWidth, int aHeight, int aStride); +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPDecoderOptions.java b/src/main/java/com/luciad/imageio/webp/WebPDecoderOptions.java new file mode 100644 index 0000000..93153bb --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPDecoderOptions.java @@ -0,0 +1,182 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +public final class WebPDecoderOptions { + static { + WebP.loadNativeLibrary(); + } + + long fPointer; + + public WebPDecoderOptions() { + fPointer = createDecoderOptions(); + if ( fPointer == 0 ) { + throw new OutOfMemoryError(); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + deleteDecoderOptions( fPointer ); + fPointer = 0L; + } + + public int getCropHeight() { + return getCropHeight( fPointer ); + } + + public void setCropHeight( int aCropHeight ) { + setCropHeight( fPointer, aCropHeight ); + } + + public int getCropLeft() { + return getCropLeft( fPointer ); + } + + public void setCropLeft( int aCropLeft ) { + setCropLeft( fPointer, aCropLeft ); + } + + public int getCropTop() { + return getCropTop( fPointer ); + } + + public void setCropTop( int aCropTop ) { + setCropTop( fPointer, aCropTop ); + } + + public int getCropWidth() { + return getCropWidth( fPointer ); + } + + public void setCropWidth( int aCropWidth ) { + setCropWidth( fPointer, aCropWidth ); + } + + public boolean isFancyUpsampling() { + return !isNoFancyUpsampling( fPointer ); + } + + public void setFancyUpsampling( boolean aFancyUpsampling ) { + setNoFancyUpsampling( fPointer, !aFancyUpsampling ); + } + + public int getScaledHeight() { + return getScaledHeight( fPointer ); + } + + public void setScaledHeight( int aScaledHeight ) { + setScaledHeight( fPointer, aScaledHeight ); + } + + public int getScaledWidth() { + return getScaledWidth( fPointer ); + } + + public void setScaledWidth( int aScaledWidth ) { + setScaledWidth( fPointer, aScaledWidth ); + } + + public boolean isUseCropping() { + return isUseCropping( fPointer ); + } + + public void setUseCropping( boolean aUseCropping ) { + setUseCropping( fPointer, aUseCropping ); + } + + public boolean isUseScaling() { + return isUseScaling( fPointer ); + } + + public void setUseScaling( boolean aUseScaling ) { + setUseScaling( fPointer, aUseScaling ); + } + + public boolean isUseThreads() { + return isUseThreads( fPointer ); + } + + public void setUseThreads( boolean aUseThreads ) { + setUseThreads( fPointer, aUseThreads ); + } + + public boolean isBypassFiltering() { + return isBypassFiltering( fPointer ); + } + + public void setBypassFiltering( boolean aBypassFiltering ) { + setBypassFiltering( fPointer, aBypassFiltering ); + } + + private static native long createDecoderOptions(); + + private static native void deleteDecoderOptions( long aPointer ); + + private static native int getCropHeight( long aPointer ); + + private static native void setCropHeight( long aPointer, int aCropHeight ); + + private static native int getCropLeft( long aPointer ); + + private static native void setCropLeft( long aPointer, int aCropLeft ); + + private static native int getCropTop( long aPointer ); + + private static native void setCropTop( long aPointer, int aCropTop ); + + private static native int getCropWidth( long aPointer ); + + private static native void setCropWidth( long aPointer, int aCropWidth ); + + private static native boolean isForceRotation( long aPointer ); + + private static native void setForceRotation( long aPointer, boolean aForceRotation ); + + private static native boolean isNoEnhancement( long aPointer ); + + private static native void setNoEnhancement( long aPointer, boolean aNoEnhancement ); + + private static native boolean isNoFancyUpsampling( long aPointer ); + + private static native void setNoFancyUpsampling( long aPointer, boolean aFancyUpsampling ); + + private static native int getScaledHeight( long aPointer ); + + private static native void setScaledHeight( long aPointer, int aScaledHeight ); + + private static native int getScaledWidth( long aPointer ); + + private static native void setScaledWidth( long aPointer, int aScaledWidth ); + + private static native boolean isUseCropping( long aPointer ); + + private static native void setUseCropping( long aPointer, boolean aUseCropping ); + + private static native boolean isUseScaling( long aPointer ); + + private static native void setUseScaling( long aPointer, boolean aUseScaling ); + + private static native boolean isUseThreads( long aPointer ); + + private static native void setUseThreads( long aPointer, boolean aUseThreads ); + + private static native boolean isBypassFiltering( long aPointer ); + + private static native void setBypassFiltering( long aPointer, boolean aBypassFiltering ); +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPEncoderOptions.java b/src/main/java/com/luciad/imageio/webp/WebPEncoderOptions.java new file mode 100644 index 0000000..9d83f63 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPEncoderOptions.java @@ -0,0 +1,310 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +public class WebPEncoderOptions { + static { + WebP.loadNativeLibrary(); + } + + long fPointer; + + public WebPEncoderOptions() { + fPointer = createConfig(); + if ( fPointer == 0 ) { + throw new OutOfMemoryError(); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + deleteConfig( fPointer ); + fPointer = 0L; + } + + private static native long createConfig(); + + private static native void deleteConfig( long aPointer ); + + long getPointer() { + return fPointer; + } + + public float getCompressionQuality() { + return getQuality(fPointer); + } + + public void setCompressionQuality( float quality ) { + setQuality( fPointer, quality ); + } + + public boolean isLossless() { + return getLossless( fPointer ) != 0; + } + + public void setLossless( boolean aLossless ) { + setLossless(fPointer, aLossless ? 1 : 0); + } + + public int getTargetSize() { + return getTargetSize( fPointer ); + } + + public void setTargetSize( int aTargetSize ) { + setTargetSize( fPointer, aTargetSize ); + } + + public float getTargetPSNR() { + return getTargetPSNR( fPointer ); + } + + public void setTargetPSNR( float aTargetPSNR ) { + setTargetPSNR( fPointer, aTargetPSNR ); + } + + public int getMethod() { + return getMethod( fPointer ); + } + + public void setMethod( int aMethod ) { + setMethod( fPointer, aMethod ); + } + + public int getSegments() { + return getSegments( fPointer ); + } + + public void setSegments( int aSegments ) { + setSegments( fPointer, aSegments ); + } + + public int getSnsStrength() { + return getSnsStrength( fPointer ); + } + + public void setSnsStrength( int aSnsStrength ) { + setSnsStrength( fPointer, aSnsStrength ); + } + + public int getFilterStrength() { + return getFilterStrength( fPointer ); + } + + public void setFilterStrength( int aFilterStrength ) { + setFilterStrength( fPointer, aFilterStrength ); + } + + public int getFilterSharpness() { + return getFilterSharpness( fPointer ); + } + + public void setFilterSharpness( int aFilterSharpness ) { + setFilterSharpness( fPointer, aFilterSharpness ); + } + + public int getFilterType() { + return getFilterType( fPointer ); + } + + public void setFilterType( int aFilterType ) { + setFilterType( fPointer, aFilterType ); + } + + public boolean isAutoAdjustFilterStrength() { + return getAutofilter( fPointer ) != 0; + } + + public void setAutoAdjustFilterStrength( boolean aAutofilter ) { + setAutofilter( fPointer, aAutofilter ? 1 : 0 ); + } + + public int getEntropyAnalysisPassCount() { + return getPass( fPointer ); + } + + public void setEntropyAnalysisPassCount( int aPass ) { + setPass( fPointer, aPass ); + } + + public boolean isShowCompressed() { + return getShowCompressed( fPointer ) != 0; + } + + public void setShowCompressed( boolean aShowCompressed ) { + setShowCompressed( fPointer, aShowCompressed ? 1 : 0 ); + } + + public int getPreprocessing() { + return getPreprocessing( fPointer ); + } + + public void setPreprocessing( int aPreprocessing ) { + setPreprocessing( fPointer, aPreprocessing ); + } + + public int getPartitions() { + return getPartitions( fPointer ); + } + + public void setPartitions( int aPartitions ) { + setPartitions( fPointer, aPartitions ); + } + + public int getPartitionLimit() { + return getPartitionLimit( fPointer ); + } + + public void setPartitionLimit( int aPartitionLimit ) { + setPartitionLimit( fPointer, aPartitionLimit ); + } + + public int getAlphaCompression() { + return getAlphaCompression( fPointer ); + } + + public void setAlphaCompression( int aAlphaCompression ) { + setAlphaCompression( fPointer, aAlphaCompression ); + } + + public int getAlphaFiltering() { + return getAlphaFiltering( fPointer ); + } + + public void setAlphaFiltering( int aAlphaFiltering ) { + setAlphaFiltering( fPointer, aAlphaFiltering ); + } + + public int getAlphaQuality() { + return getAlphaQuality( fPointer ); + } + + public void setAlphaQuality( int aAlphaQuality ) { + setAlphaQuality( fPointer, aAlphaQuality ); + } + + public boolean isEmulateJpegSize() { + return getEmulateJpegSize( fPointer ) != 0; + } + + public void setEmulateJpegSize( boolean aEmulateJpegSize ) { + setEmulateJpegSize( fPointer, aEmulateJpegSize ? 1 : 0 ); + } + + public int getThreadLevel() { + return getThreadLevel( fPointer ); + } + + public void setThreadLevel( int aThreadLevel ) { + setThreadLevel( fPointer, aThreadLevel ); + } + + public boolean isReduceMemoryUsage() { + return getLowMemory( fPointer ) != 0; + } + + public void setReduceMemoryUsage( boolean aLowMemory ) { + setLowMemory( fPointer, aLowMemory ? 1 : 0 ); + } + + private static native float getQuality( long aPointer ); + + private static native void setQuality( long aPointer, float aQuality ); + + private static native int getTargetSize( long aPointer ); + + private static native void setTargetSize( long aPointer, int aTargetSize ); + + private static native float getTargetPSNR( long aPointer ); + + private static native void setTargetPSNR( long aPointer, float aTargetPSNR ); + + private static native int getMethod( long aPointer ); + + private static native void setMethod( long aPointer, int aMethod ); + + private static native int getSegments( long aPointer ); + + private static native void setSegments( long aPointer, int aSegments ); + + private static native int getSnsStrength( long aPointer ); + + private static native void setSnsStrength( long aPointer, int aSnsStrength ); + + private static native int getFilterStrength( long aPointer ); + + private static native void setFilterStrength( long aPointer, int aFilterStrength ); + + private static native int getFilterSharpness( long aPointer ); + + private static native void setFilterSharpness( long aPointer, int aFilterSharpness ); + + private static native int getFilterType( long aPointer ); + + private static native void setFilterType( long aPointer, int aFilterType ); + + private static native int getAutofilter( long aPointer ); + + private static native void setAutofilter( long aPointer, int aAutofilter ); + + private static native int getPass( long aPointer ); + + private static native void setPass( long aPointer, int aPass ); + + private static native int getShowCompressed( long aPointer ); + + private static native void setShowCompressed( long aPointer, int aShowCompressed ); + + private static native int getPreprocessing( long aPointer ); + + private static native void setPreprocessing( long aPointer, int aPreprocessing ); + + private static native int getPartitions( long aPointer ); + + private static native void setPartitions( long aPointer, int aPartitions ); + + private static native int getPartitionLimit( long aPointer ); + + private static native void setPartitionLimit( long aPointer, int aPartitionLimit ); + + private static native int getAlphaCompression( long aPointer ); + + private static native void setAlphaCompression( long aPointer, int aAlphaCompression ); + + private static native int getAlphaFiltering( long aPointer ); + + private static native void setAlphaFiltering( long aPointer, int aAlphaFiltering ); + + private static native int getAlphaQuality( long aPointer ); + + private static native void setAlphaQuality( long aPointer, int aAlphaQuality ); + + private static native int getLossless( long aPointer ); + + private static native void setLossless( long aPointer, int aLossless ); + + private static native int getEmulateJpegSize( long aPointer ); + + private static native void setEmulateJpegSize( long aPointer, int aEmulateJpegSize ); + + private static native int getThreadLevel( long aPointer ); + + private static native void setThreadLevel( long aPointer, int aThreadLevel ); + + private static native int getLowMemory( long aPointer ); + + private static native void setLowMemory( long aPointer, int aLowMemory ); +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPImageReaderSpi.java b/src/main/java/com/luciad/imageio/webp/WebPImageReaderSpi.java new file mode 100644 index 0000000..bf4a37b --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPImageReaderSpi.java @@ -0,0 +1,107 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import javax.imageio.ImageReader; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Locale; + +/** + * + */ +public class WebPImageReaderSpi extends ImageReaderSpi { + private static final byte[] RIFF = new byte[]{ 'R', 'I', 'F', 'F' }; + private static final byte[] WEBP = new byte[]{ 'W', 'E', 'B', 'P' }; + private static final byte[] VP8_ = new byte[]{ 'V', 'P', '8', ' ' }; + private static final byte[] VP8L = new byte[]{ 'V', 'P', '8', 'L' }; + private static final byte[] VP8X = new byte[]{ 'V', 'P', '8', 'X' }; + + public WebPImageReaderSpi() { + super( + "Luciad", + "1.0", + new String[]{ "WebP", "webp" }, + new String[]{ "webp" }, + new String[]{ "image/webp" }, + WebPReader.class.getName(), + new Class[] { ImageInputStream.class }, + new String[]{ WebPImageWriterSpi.class.getName() }, + false, + null, + null, + null, + null, + false, + null, + null, + null, + null + ); + } + + @Override + public ImageReader createReaderInstance( Object extension ) throws IOException { + return new WebPReader( this ); + } + + @Override + public boolean canDecodeInput( Object source ) throws IOException { + if ( !( source instanceof ImageInputStream ) ) { + return false; + } + + ImageInputStream stream = ( ImageInputStream ) source; + byte[] b = new byte[ 4 ]; + ByteOrder oldByteOrder = stream.getByteOrder(); + stream.mark(); + stream.setByteOrder( ByteOrder.LITTLE_ENDIAN ); + + try { + stream.readFully( b ); + if ( !Arrays.equals( b, RIFF ) ) { + return false; + } + long chunkLength = stream.readUnsignedInt(); + long streamLength = stream.length(); + if ( streamLength != -1 && streamLength != chunkLength + 8 ) { + return false; + } + stream.readFully( b ); + if ( !Arrays.equals( b, WEBP ) ) { + return false; + } + + stream.readFully( b ); + if ( !Arrays.equals( b, VP8_ ) && !Arrays.equals( b, VP8L ) && !Arrays.equals( b, VP8X ) ) { + return false; + } + } finally { + stream.setByteOrder( oldByteOrder ); + stream.reset(); + } + + return true; + } + + @Override + public String getDescription( Locale locale ) { + return "WebP Reader"; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPImageWriterSpi.java b/src/main/java/com/luciad/imageio/webp/WebPImageWriterSpi.java new file mode 100644 index 0000000..78e92f4 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPImageWriterSpi.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.io.IOException; +import java.util.Locale; + +/** + * + */ +public class WebPImageWriterSpi extends ImageWriterSpi { + public WebPImageWriterSpi() { + super( + "Luciad", + "1.0", + new String[]{ "WebP", "webp" }, + new String[]{ "webp" }, + new String[]{ "image/webp" }, + WebPReader.class.getName(), + new Class[]{ ImageOutputStream.class }, + new String[]{ WebPImageReaderSpi.class.getName() }, + false, + null, + null, + null, + null, + false, + null, + null, + null, + null + ); + } + + @Override + public boolean canEncodeImage( ImageTypeSpecifier type ) { + ColorModel colorModel = type.getColorModel(); + SampleModel sampleModel = type.getSampleModel(); + int transferType = sampleModel.getTransferType(); + + if ( colorModel instanceof ComponentColorModel ) { + if ( !( sampleModel instanceof ComponentSampleModel ) ) { + return false; + } + + if ( transferType != DataBuffer.TYPE_BYTE && transferType != DataBuffer.TYPE_INT ) { + return false; + } + } + else if ( colorModel instanceof DirectColorModel ) { + if ( !( sampleModel instanceof SinglePixelPackedSampleModel ) ) { + return false; + } + + if ( transferType != DataBuffer.TYPE_INT ) { + return false; + } + } + + ColorSpace colorSpace = colorModel.getColorSpace(); + if ( !( colorSpace.isCS_sRGB() ) ) { + return false; + } + + int[] sampleSize = sampleModel.getSampleSize(); + for ( int i = 0; i < sampleSize.length; i++ ) { + if ( sampleSize[ i ] > 8 ) { + return false; + } + } + + + return true; + } + + @Override + public ImageWriter createWriterInstance( Object extension ) throws IOException { + return new WebPWriter( this ); + } + + @Override + public String getDescription( Locale locale ) { + return "WebP Writer"; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPReadParam.java b/src/main/java/com/luciad/imageio/webp/WebPReadParam.java new file mode 100644 index 0000000..52bf241 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPReadParam.java @@ -0,0 +1,118 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import javax.imageio.ImageReadParam; + +public final class WebPReadParam extends ImageReadParam { + private WebPDecoderOptions fOptions; + + public WebPReadParam() { + fOptions = new WebPDecoderOptions(); + } + + public void setScaledHeight(int aScaledHeight) { + fOptions.setScaledHeight(aScaledHeight); + } + + public void setUseScaling(boolean aUseScaling) { + fOptions.setUseScaling(aUseScaling); + } + + public void setUseThreads(boolean aUseThreads) { + fOptions.setUseThreads(aUseThreads); + } + + public int getCropHeight() { + return fOptions.getCropHeight(); + } + + public int getScaledWidth() { + return fOptions.getScaledWidth(); + } + + public boolean isUseCropping() { + return fOptions.isUseCropping(); + } + + public void setCropWidth(int aCropWidth) { + fOptions.setCropWidth(aCropWidth); + } + + public boolean isBypassFiltering() { + return fOptions.isBypassFiltering(); + } + + public int getCropLeft() { + return fOptions.getCropLeft(); + } + + public int getCropWidth() { + return fOptions.getCropWidth(); + } + + public int getScaledHeight() { + return fOptions.getScaledHeight(); + } + + public void setBypassFiltering(boolean aBypassFiltering) { + fOptions.setBypassFiltering(aBypassFiltering); + } + + public void setUseCropping(boolean aUseCropping) { + fOptions.setUseCropping(aUseCropping); + } + + public void setCropHeight(int aCropHeight) { + fOptions.setCropHeight(aCropHeight); + } + + public void setFancyUpsampling(boolean aFancyUpsampling) { + fOptions.setFancyUpsampling(aFancyUpsampling); + } + + public boolean isUseThreads() { + return fOptions.isUseThreads(); + } + + public boolean isFancyUpsampling() { + return fOptions.isFancyUpsampling(); + } + + public boolean isUseScaling() { + return fOptions.isUseScaling(); + } + + public void setCropLeft(int aCropLeft) { + fOptions.setCropLeft(aCropLeft); + } + + public int getCropTop() { + return fOptions.getCropTop(); + } + + public void setScaledWidth(int aScaledWidth) { + fOptions.setScaledWidth(aScaledWidth); + } + + public void setCropTop(int aCropTop) { + fOptions.setCropTop(aCropTop); + } + + WebPDecoderOptions getDecoderOptions() { + return fOptions; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPReader.java b/src/main/java/com/luciad/imageio/webp/WebPReader.java new file mode 100644 index 0000000..ed3809d --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPReader.java @@ -0,0 +1,177 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBufferInt; +import java.awt.image.DirectColorModel; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Iterator; + +class WebPReader extends ImageReader { + private byte[] fData; + private int fWidth; + private int fHeight; + + WebPReader( ImageReaderSpi originatingProvider ) { + super( originatingProvider ); + } + + @Override + public void setInput( Object input, boolean seekForwardOnly, boolean ignoreMetadata ) { + super.setInput( input, seekForwardOnly, ignoreMetadata ); + fData = null; + fWidth = -1; + fHeight = -1; + } + + @Override + public int getNumImages( boolean allowSearch ) throws IOException { + return 1; + } + + private void readHeader() throws IOException { + if ( fWidth != -1 && fHeight != -1 ) { + return; + } + + readData(); + int[] info = WebP.getInfo( fData, 0, fData.length ); + fWidth = info[ 0 ]; + fHeight = info[ 1 ]; + } + + private void readData() throws IOException { + if ( fData != null ) { + return; + } + + ImageInputStream input = ( ImageInputStream ) getInput(); + long length = input.length(); + if ( length > Integer.MAX_VALUE ) { + throw new IOException( "Cannot read image of size " + length ); + } + + if ( input.getStreamPosition() != 0L ) { + if ( isSeekForwardOnly() ) { + throw new IOException(); + } + else { + input.seek( 0 ); + } + } + + byte[] data; + if ( length > 0 ) { + data = new byte[ ( int ) length ]; + input.readFully( data ); + } + else { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[ 4096 ]; + int bytesRead; + while ( ( bytesRead = input.read( buffer ) ) != -1 ) { + out.write( buffer, 0, bytesRead ); + } + out.close(); + data = out.toByteArray(); + } + fData = data; + } + + private void checkIndex( int imageIndex ) { + if ( imageIndex != 0 ) { + throw new IndexOutOfBoundsException( "Invalid image index: " + imageIndex ); + } + } + + @Override + public int getWidth( int imageIndex ) throws IOException { + checkIndex( imageIndex ); + readHeader(); + return fWidth; + } + + @Override + public int getHeight( int imageIndex ) throws IOException { + checkIndex( imageIndex ); + readHeader(); + return fHeight; + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + return null; + } + + @Override + public IIOMetadata getImageMetadata( int imageIndex ) throws IOException { + return null; + } + + @Override + public Iterator getImageTypes( int imageIndex ) throws IOException { + return Collections.singletonList( + ImageTypeSpecifier.createFromBufferedImageType( BufferedImage.TYPE_INT_ARGB ) + ).iterator(); + } + + @Override + public ImageReadParam getDefaultReadParam() { + return new WebPReadParam(); + } + + @Override + public BufferedImage read( int imageIndex, ImageReadParam param ) throws IOException { + checkIndex( imageIndex ); + readData(); + readHeader(); + WebPReadParam readParam = param != null ? (WebPReadParam) param : new WebPReadParam(); + + int[] outParams = new int[4]; + int[] pixels = WebP.decode(readParam.getDecoderOptions(), fData, 0, fData.length, outParams); + + int width = outParams[1]; + int height = outParams[2]; + boolean alpha = outParams[3] != 0; + + ColorModel colorModel; + if ( alpha ) { + colorModel = new DirectColorModel( 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 ); + } else { + colorModel = new DirectColorModel( 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 ); + } + + SampleModel sampleModel = colorModel.createCompatibleSampleModel( width, height ); + DataBufferInt db = new DataBufferInt( pixels, width * height ); + WritableRaster raster = WritableRaster.createWritableRaster(sampleModel, db, null); + + return new BufferedImage( colorModel, raster, false, new Hashtable() ); + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPWriteParam.java b/src/main/java/com/luciad/imageio/webp/WebPWriteParam.java new file mode 100644 index 0000000..b0ce813 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPWriteParam.java @@ -0,0 +1,234 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import javax.imageio.ImageWriteParam; +import java.util.Locale; + +public class WebPWriteParam extends ImageWriteParam { + public static final int LOSSY_COMPRESSION = 0; + public static final int LOSSLESS_COMPRESSION = 1; + + private final boolean fDefaultLossless; + private WebPEncoderOptions fOptions; + + public WebPWriteParam( Locale aLocale ) { + super( aLocale ); + fOptions = new WebPEncoderOptions(); + fDefaultLossless = fOptions.isLossless(); + canWriteCompressed = true; + compressionTypes = new String[]{ + "Lossy", + "Lossless" + }; + compressionType = compressionTypes[fDefaultLossless ? LOSSLESS_COMPRESSION : LOSSY_COMPRESSION]; + compressionQuality = fOptions.getCompressionQuality() / 100f; + compressionMode = MODE_EXPLICIT; + } + + @Override + public float getCompressionQuality() { + return super.getCompressionQuality(); + } + + @Override + public void setCompressionQuality( float quality ) { + super.setCompressionQuality( quality ); + fOptions.setCompressionQuality( quality * 100f ); + } + + @Override + public void setCompressionType( String compressionType ) { + super.setCompressionType( compressionType ); + for ( int i = 0; i < compressionTypes.length; i++ ) { + if ( compressionTypes[i].equals( compressionType ) ) { + fOptions.setLossless( i == LOSSLESS_COMPRESSION ); + break; + } + } + + } + + @Override + public void unsetCompression() { + super.unsetCompression(); + fOptions.setLossless( fDefaultLossless ); + } + + public void setSnsStrength(int aSnsStrength) { + fOptions.setSnsStrength(aSnsStrength); + } + + public void setAlphaQuality(int aAlphaQuality) { + fOptions.setAlphaQuality(aAlphaQuality); + } + + public int getSegments() { + return fOptions.getSegments(); + } + + public int getPreprocessing() { + return fOptions.getPreprocessing(); + } + + public int getFilterStrength() { + return fOptions.getFilterStrength(); + } + + public void setEmulateJpegSize(boolean aEmulateJpegSize) { + fOptions.setEmulateJpegSize(aEmulateJpegSize); + } + + public int getPartitions() { + return fOptions.getPartitions(); + } + + public void setTargetPSNR(float aTargetPSNR) { + fOptions.setTargetPSNR(aTargetPSNR); + } + + public int getEntropyAnalysisPassCount() { + return fOptions.getEntropyAnalysisPassCount(); + } + + public int getPartitionLimit() { + return fOptions.getPartitionLimit(); + } + + public int getFilterType() { + return fOptions.getFilterType(); + } + + public int getFilterSharpness() { + return fOptions.getFilterSharpness(); + } + + public int getAlphaQuality() { + return fOptions.getAlphaQuality(); + } + + public boolean isShowCompressed() { + return fOptions.isShowCompressed(); + } + + public boolean isReduceMemoryUsage() { + return fOptions.isReduceMemoryUsage(); + } + + public void setThreadLevel(int aThreadLevel) { + fOptions.setThreadLevel(aThreadLevel); + } + + public boolean isAutoAdjustFilterStrength() { + return fOptions.isAutoAdjustFilterStrength(); + } + + public void setReduceMemoryUsage(boolean aLowMemory) { + fOptions.setReduceMemoryUsage(aLowMemory); + } + + public void setFilterStrength(int aFilterStrength) { + fOptions.setFilterStrength(aFilterStrength); + } + + public int getTargetSize() { + return fOptions.getTargetSize(); + } + + public void setEntropyAnalysisPassCount(int aPass) { + fOptions.setEntropyAnalysisPassCount(aPass); + } + + public void setFilterSharpness(int aFilterSharpness) { + fOptions.setFilterSharpness(aFilterSharpness); + } + + public int getAlphaFiltering() { + return fOptions.getAlphaFiltering(); + } + + public int getSnsStrength() { + return fOptions.getSnsStrength(); + } + + public void setPartitionLimit(int aPartitionLimit) { + fOptions.setPartitionLimit(aPartitionLimit); + } + + public void setMethod(int aMethod) { + fOptions.setMethod(aMethod); + } + + public void setAlphaFiltering(int aAlphaFiltering) { + fOptions.setAlphaFiltering(aAlphaFiltering); + } + + public int getMethod() { + return fOptions.getMethod(); + } + + public void setFilterType(int aFilterType) { + fOptions.setFilterType(aFilterType); + } + + public void setPartitions(int aPartitions) { + fOptions.setPartitions(aPartitions); + } + + public void setAutoAdjustFilterStrength(boolean aAutofilter) { + fOptions.setAutoAdjustFilterStrength(aAutofilter); + } + + public boolean isEmulateJpegSize() { + return fOptions.isEmulateJpegSize(); + } + + public int getAlphaCompression() { + return fOptions.getAlphaCompression(); + } + + public void setShowCompressed(boolean aShowCompressed) { + fOptions.setShowCompressed(aShowCompressed); + } + + public void setSegments(int aSegments) { + fOptions.setSegments(aSegments); + } + + public float getTargetPSNR() { + return fOptions.getTargetPSNR(); + } + + public int getThreadLevel() { + return fOptions.getThreadLevel(); + } + + public void setTargetSize(int aTargetSize) { + fOptions.setTargetSize(aTargetSize); + } + + public void setAlphaCompression(int aAlphaCompression) { + fOptions.setAlphaCompression(aAlphaCompression); + } + + public void setPreprocessing(int aPreprocessing) { + fOptions.setPreprocessing(aPreprocessing); + } + + WebPEncoderOptions getEncoderOptions() { + return fOptions; + } +} diff --git a/src/main/java/com/luciad/imageio/webp/WebPWriter.java b/src/main/java/com/luciad/imageio/webp/WebPWriter.java new file mode 100644 index 0000000..9e7e837 --- /dev/null +++ b/src/main/java/com/luciad/imageio/webp/WebPWriter.java @@ -0,0 +1,372 @@ +/* + * Copyright 2013 Luciad (http://www.luciad.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.luciad.imageio.webp; + +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.*; +import java.io.IOException; + +class WebPWriter extends ImageWriter { + WebPWriter(ImageWriterSpi originatingProvider) { + super(originatingProvider); + } + + @Override + public ImageWriteParam getDefaultWriteParam() { + return new WebPWriteParam(getLocale()); + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + return null; + } + + @Override + public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException { + if (param == null) { + param = getDefaultWriteParam(); + } + + WebPWriteParam writeParam = (WebPWriteParam) param; + + ImageOutputStream output = (ImageOutputStream) getOutput(); + RenderedImage ri = image.getRenderedImage(); + + byte[] encodedData = encode(writeParam.getEncoderOptions(), ri); + output.write(encodedData); + } + + private static byte[] encode(WebPEncoderOptions aOptions, RenderedImage aImage) throws IOException + { + if (aOptions == null) { + throw new NullPointerException("Encoder options may not be null"); + } + + if (aImage == null) { + throw new NullPointerException("Image may not be null"); + } + + boolean encodeAlpha = hasTranslucency(aImage); + if (encodeAlpha) { + byte[] rgbaData = getRGBA(aImage); + return WebP.encodeRGBA(aOptions, rgbaData, aImage.getWidth(), aImage.getHeight(), aImage.getWidth() * 4); + } else { + byte[] rgbData = getRGB(aImage); + return WebP.encodeRGB(aOptions, rgbData, aImage.getWidth(), aImage.getHeight(), aImage.getWidth() * 3); + } + } + + private static boolean hasTranslucency(RenderedImage aRi) { + return aRi.getColorModel().hasAlpha(); + } + + private static int getShift(int aMask) { + int shift = 0; + while (((aMask >> shift) & 0x1) == 0) { + shift++; + } + return shift; + } + + private static byte[] getRGB(RenderedImage aRi) throws IOException { + int width = aRi.getWidth(); + int height = aRi.getHeight(); + + ColorModel colorModel = aRi.getColorModel(); + if (colorModel instanceof ComponentColorModel) { + ComponentSampleModel sampleModel = (ComponentSampleModel) aRi.getSampleModel(); + int type = sampleModel.getTransferType(); + if (type == DataBuffer.TYPE_BYTE) { + return extractComponentRGBByte(width, height, sampleModel, ((DataBufferByte) aRi.getData().getDataBuffer())); + } else if (type == DataBuffer.TYPE_INT) { + return extractComponentRGBInt(width, height, sampleModel, ((DataBufferInt) aRi.getData().getDataBuffer())); + } else { + throw new IOException("Incompatible image: " + aRi); + } + } else if (colorModel instanceof DirectColorModel) { + SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) aRi.getSampleModel(); + int type = sampleModel.getTransferType(); + if (type == DataBuffer.TYPE_INT) { + return extractDirectRGBInt(width, height, (DirectColorModel) colorModel, sampleModel, ((DataBufferInt) aRi.getData().getDataBuffer())); + } else { + throw new IOException("Incompatible image: " + aRi); + } + } else { + BufferedImage i = new BufferedImage(aRi.getWidth(), aRi.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g = i.createGraphics(); + g.drawRenderedImage(aRi, new AffineTransform()); + g.dispose(); + return getRGB(i); + } + } + + private static byte[] extractDirectRGBInt(int aWidth, int aHeight, DirectColorModel aColorModel, SinglePixelPackedSampleModel aSampleModel, DataBufferInt aDataBuffer) { + byte[] out = new byte[aWidth * aHeight * 3]; + + int rMask = aColorModel.getRedMask(); + int gMask = aColorModel.getGreenMask(); + int bMask = aColorModel.getBlueMask(); + int rShift = getShift(rMask); + int gShift = getShift(gMask); + int bShift = getShift(bMask); + int[] bank = aDataBuffer.getBankData()[0]; + int scanlineStride = aSampleModel.getScanlineStride(); + int scanIx = 0; + for (int b = 0, y = 0; y < aHeight; y++) { + int pixIx = scanIx; + for (int x = 0; x < aWidth; x++, b += 3) { + int pixel = bank[pixIx++]; + out[b] = (byte) ((pixel & rMask) >>> rShift); + out[b + 1] = (byte) ((pixel & gMask) >>> gShift); + out[b + 2] = (byte) ((pixel & bMask) >>> bShift); + } + scanIx += scanlineStride; + } + return out; + } + + private static byte[] extractComponentRGBInt(int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferInt aDataBuffer) { + byte[] out = new byte[aWidth * aHeight * 3]; + + int[] bankIndices = aSampleModel.getBankIndices(); + int[] rBank = aDataBuffer.getBankData()[bankIndices[0]]; + int[] gBank = aDataBuffer.getBankData()[bankIndices[1]]; + int[] bBank = aDataBuffer.getBankData()[bankIndices[2]]; + + int[] bankOffsets = aSampleModel.getBandOffsets(); + int rScanIx = bankOffsets[0]; + int gScanIx = bankOffsets[1]; + int bScanIx = bankOffsets[2]; + + int pixelStride = aSampleModel.getPixelStride(); + int scanlineStride = aSampleModel.getScanlineStride(); + for (int b = 0, y = 0; y < aHeight; y++) { + int rPixIx = rScanIx; + int gPixIx = gScanIx; + int bPixIx = bScanIx; + for (int x = 0; x < aWidth; x++, b += 3) { + out[b] = (byte) rBank[rPixIx]; + rPixIx += pixelStride; + out[b + 1] = (byte) gBank[gPixIx]; + gPixIx += pixelStride; + out[b + 2] = (byte) bBank[bPixIx]; + bPixIx += pixelStride; + } + rScanIx += scanlineStride; + gScanIx += scanlineStride; + bScanIx += scanlineStride; + } + return out; + } + + private static byte[] extractComponentRGBByte(int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferByte aDataBuffer) { + byte[] out = new byte[aWidth * aHeight * 3]; + + int[] bankIndices = aSampleModel.getBankIndices(); + byte[] rBank = aDataBuffer.getBankData()[bankIndices[0]]; + byte[] gBank = aDataBuffer.getBankData()[bankIndices[1]]; + byte[] bBank = aDataBuffer.getBankData()[bankIndices[2]]; + + int[] bankOffsets = aSampleModel.getBandOffsets(); + int rScanIx = bankOffsets[0]; + int gScanIx = bankOffsets[1]; + int bScanIx = bankOffsets[2]; + + int pixelStride = aSampleModel.getPixelStride(); + int scanlineStride = aSampleModel.getScanlineStride(); + for (int b = 0, y = 0; y < aHeight; y++) { + int rPixIx = rScanIx; + int gPixIx = gScanIx; + int bPixIx = bScanIx; + for (int x = 0; x < aWidth; x++, b += 3) { + out[b] = rBank[rPixIx]; + rPixIx += pixelStride; + out[b + 1] = gBank[gPixIx]; + gPixIx += pixelStride; + out[b + 2] = bBank[bPixIx]; + bPixIx += pixelStride; + } + rScanIx += scanlineStride; + gScanIx += scanlineStride; + bScanIx += scanlineStride; + } + return out; + } + + private static byte[] getRGBA(RenderedImage aRi) throws IOException { + int width = aRi.getWidth(); + int height = aRi.getHeight(); + + ColorModel colorModel = aRi.getColorModel(); + if (colorModel instanceof ComponentColorModel) { + ComponentSampleModel sampleModel = (ComponentSampleModel) aRi.getSampleModel(); + int type = sampleModel.getTransferType(); + if (type == DataBuffer.TYPE_BYTE) { + return extractComponentRGBAByte(width, height, sampleModel, ((DataBufferByte) aRi.getData().getDataBuffer())); + } else if (type == DataBuffer.TYPE_INT) { + return extractComponentRGBAInt(width, height, sampleModel, ((DataBufferInt) aRi.getData().getDataBuffer())); + } else { + throw new IOException("Incompatible image: " + aRi); + } + } else if (colorModel instanceof DirectColorModel) { + SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) aRi.getSampleModel(); + int type = sampleModel.getTransferType(); + if (type == DataBuffer.TYPE_INT) { + return extractDirectRGBAInt(width, height, (DirectColorModel) colorModel, sampleModel, ((DataBufferInt) aRi.getData().getDataBuffer())); + } else { + throw new IOException("Incompatible image: " + aRi); + } + } else { + BufferedImage i = new BufferedImage(aRi.getWidth(), aRi.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = i.createGraphics(); + g.drawRenderedImage(aRi, new AffineTransform()); + g.dispose(); + return getRGBA(i); + } + } + + private static byte[] extractDirectRGBAInt(int aWidth, int aHeight, DirectColorModel aColorModel, SinglePixelPackedSampleModel aSampleModel, DataBufferInt aDataBuffer) { + byte[] out = new byte[aWidth * aHeight * 4]; + + int rMask = aColorModel.getRedMask(); + int gMask = aColorModel.getGreenMask(); + int bMask = aColorModel.getBlueMask(); + int aMask = aColorModel.getAlphaMask(); + int rShift = getShift(rMask); + int gShift = getShift(gMask); + int bShift = getShift(bMask); + int aShift = getShift(aMask); + int[] bank = aDataBuffer.getBankData()[0]; + int scanlineStride = aSampleModel.getScanlineStride(); + int scanIx = 0; + for (int b = 0, y = 0; y < aHeight; y++) { + int pixIx = scanIx; + for (int x = 0; x < aWidth; x++, b += 4) { + int pixel = bank[pixIx++]; + out[b] = (byte) ((pixel & rMask) >>> rShift); + out[b + 1] = (byte) ((pixel & gMask) >>> gShift); + out[b + 2] = (byte) ((pixel & bMask) >>> bShift); + out[b + 3] = (byte) ((pixel & aMask) >>> aShift); + } + scanIx += scanlineStride; + } + return out; + } + + private static byte[] extractComponentRGBAInt(int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferInt aDataBuffer) { + byte[] out = new byte[aWidth * aHeight * 4]; + + int[] bankIndices = aSampleModel.getBankIndices(); + int[] rBank = aDataBuffer.getBankData()[bankIndices[0]]; + int[] gBank = aDataBuffer.getBankData()[bankIndices[1]]; + int[] bBank = aDataBuffer.getBankData()[bankIndices[2]]; + int[] aBank = aDataBuffer.getBankData()[bankIndices[3]]; + + int[] bankOffsets = aSampleModel.getBandOffsets(); + int rScanIx = bankOffsets[0]; + int gScanIx = bankOffsets[1]; + int bScanIx = bankOffsets[2]; + int aScanIx = bankOffsets[3]; + + int pixelStride = aSampleModel.getPixelStride(); + int scanlineStride = aSampleModel.getScanlineStride(); + for (int b = 0, y = 0; y < aHeight; y++) { + int rPixIx = rScanIx; + int gPixIx = gScanIx; + int bPixIx = bScanIx; + int aPixIx = aScanIx; + for (int x = 0; x < aWidth; x++, b += 4) { + out[b] = (byte) rBank[rPixIx]; + rPixIx += pixelStride; + out[b + 1] = (byte) gBank[gPixIx]; + gPixIx += pixelStride; + out[b + 2] = (byte) bBank[bPixIx]; + bPixIx += pixelStride; + out[b + 3] = (byte) aBank[aPixIx]; + aPixIx += pixelStride; + } + rScanIx += scanlineStride; + gScanIx += scanlineStride; + bScanIx += scanlineStride; + aScanIx += scanlineStride; + } + return out; + } + + private static byte[] extractComponentRGBAByte(int aWidth, int aHeight, ComponentSampleModel aSampleModel, DataBufferByte aDataBuffer) { + byte[] out = new byte[aWidth * aHeight * 4]; + + int[] bankIndices = aSampleModel.getBankIndices(); + byte[] rBank = aDataBuffer.getBankData()[bankIndices[0]]; + byte[] gBank = aDataBuffer.getBankData()[bankIndices[1]]; + byte[] bBank = aDataBuffer.getBankData()[bankIndices[2]]; + byte[] aBank = aDataBuffer.getBankData()[bankIndices[3]]; + + int[] bankOffsets = aSampleModel.getBandOffsets(); + int rScanIx = bankOffsets[0]; + int gScanIx = bankOffsets[1]; + int bScanIx = bankOffsets[2]; + int aScanIx = bankOffsets[3]; + + int pixelStride = aSampleModel.getPixelStride(); + int scanlineStride = aSampleModel.getScanlineStride(); + for (int b = 0, y = 0; y < aHeight; y++) { + int rPixIx = rScanIx; + int gPixIx = gScanIx; + int bPixIx = bScanIx; + int aPixIx = aScanIx; + for (int x = 0; x < aWidth; x++, b += 4) { + out[b] = rBank[rPixIx]; + rPixIx += pixelStride; + out[b + 1] = gBank[gPixIx]; + gPixIx += pixelStride; + out[b + 2] = bBank[bPixIx]; + bPixIx += pixelStride; + out[b + 3] = aBank[aPixIx]; + aPixIx += pixelStride; + } + rScanIx += scanlineStride; + gScanIx += scanlineStride; + bScanIx += scanlineStride; + aScanIx += scanlineStride; + } + return out; + } +} diff --git a/src/main/java/example/DecodeTest.java b/src/main/java/example/DecodeTest.java new file mode 100644 index 0000000..54050a2 --- /dev/null +++ b/src/main/java/example/DecodeTest.java @@ -0,0 +1,37 @@ +package example; + +import com.luciad.imageio.webp.WebPReadParam; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.FileImageInputStream; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class DecodeTest { + public static void main(String args[]) throws IOException { + String inputWebpPath = "test_pic/test.webp"; + String outputJpgPath = "test_pic/test_.jpg"; + String outputJpegPath = "test_pic/test_.jpeg"; + String outputPngPath = "test_pic/test_.png"; + + // Obtain a WebP ImageReader instance + ImageReader reader = ImageIO.getImageReadersByMIMEType("image/webp").next(); + + // Configure decoding parameters + WebPReadParam readParam = new WebPReadParam(); + readParam.setBypassFiltering(true); + + // Configure the input on the ImageReader + reader.setInput(new FileImageInputStream(new File(inputWebpPath))); + + // Decode the image + BufferedImage image = reader.read(0, readParam); + + ImageIO.write(image, "png", new File(outputPngPath)); + ImageIO.write(image, "jpg", new File(outputJpgPath)); + ImageIO.write(image, "jpeg", new File(outputJpegPath)); + + } +} diff --git a/src/main/java/example/EncodeTest.java b/src/main/java/example/EncodeTest.java new file mode 100644 index 0000000..93134dd --- /dev/null +++ b/src/main/java/example/EncodeTest.java @@ -0,0 +1,37 @@ +package example; + +import com.luciad.imageio.webp.WebPWriteParam; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.stream.FileImageOutputStream; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class EncodeTest { + public static void main(String args[]) throws IOException { + String inputPngPath = "test_pic/test.png"; + String inputJpgPath = "test_pic/test.jpg"; + String outputWebpPath = "test_pic/test_.webp"; + + // Obtain an image to encode from somewhere + BufferedImage image = ImageIO.read(new File(inputJpgPath)); + + // Obtain a WebP ImageWriter instance + ImageWriter writer = ImageIO.getImageWritersByMIMEType("image/webp").next(); + + // Configure encoding parameters + WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale()); + writeParam.setCompressionMode(WebPWriteParam.MODE_DEFAULT); + + // Configure the output on the ImageWriter + writer.setOutput(new FileImageOutputStream(new File(outputWebpPath))); + + // Encode + long st = System.currentTimeMillis(); + writer.write(null, new IIOImage(image, null, null), writeParam); + System.out.println("cost: " + (System.currentTimeMillis() - st)); + } +} diff --git a/src/main/resources/META-INF/cut-image/resource/1.jpg b/src/main/resources/META-INF/cut-image/resource/1.jpg new file mode 100644 index 0000000..cd5b21d Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/1.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/10.jpg b/src/main/resources/META-INF/cut-image/resource/10.jpg new file mode 100644 index 0000000..7e58078 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/10.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/2.jpg b/src/main/resources/META-INF/cut-image/resource/2.jpg new file mode 100644 index 0000000..238ccb1 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/2.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/3.jpg b/src/main/resources/META-INF/cut-image/resource/3.jpg new file mode 100644 index 0000000..b57a46e Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/3.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/4.jpg b/src/main/resources/META-INF/cut-image/resource/4.jpg new file mode 100644 index 0000000..b809dd9 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/4.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/5.jpg b/src/main/resources/META-INF/cut-image/resource/5.jpg new file mode 100644 index 0000000..3fc6fa5 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/5.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/6.jpg b/src/main/resources/META-INF/cut-image/resource/6.jpg new file mode 100644 index 0000000..9cf4e13 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/6.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/7.jpg b/src/main/resources/META-INF/cut-image/resource/7.jpg new file mode 100644 index 0000000..40e4f56 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/7.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/8.jpg b/src/main/resources/META-INF/cut-image/resource/8.jpg new file mode 100644 index 0000000..64b17ef Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/8.jpg differ diff --git a/src/main/resources/META-INF/cut-image/resource/9.jpg b/src/main/resources/META-INF/cut-image/resource/9.jpg new file mode 100644 index 0000000..e51db4b Binary files /dev/null and b/src/main/resources/META-INF/cut-image/resource/9.jpg differ diff --git a/src/main/resources/META-INF/cut-image/template/1/active.png b/src/main/resources/META-INF/cut-image/template/1/active.png new file mode 100644 index 0000000..4384609 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/1/active.png differ diff --git a/src/main/resources/META-INF/cut-image/template/1/cut.png b/src/main/resources/META-INF/cut-image/template/1/cut.png new file mode 100644 index 0000000..8c5e443 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/1/cut.png differ diff --git a/src/main/resources/META-INF/cut-image/template/1/fixed.png b/src/main/resources/META-INF/cut-image/template/1/fixed.png new file mode 100644 index 0000000..b3ce56b Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/1/fixed.png differ diff --git a/src/main/resources/META-INF/cut-image/template/1/matrix.png b/src/main/resources/META-INF/cut-image/template/1/matrix.png new file mode 100644 index 0000000..5f17f0d Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/1/matrix.png differ diff --git a/src/main/resources/META-INF/cut-image/template/2/active.png b/src/main/resources/META-INF/cut-image/template/2/active.png new file mode 100644 index 0000000..3ed2a04 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/2/active.png differ diff --git a/src/main/resources/META-INF/cut-image/template/2/cut.png b/src/main/resources/META-INF/cut-image/template/2/cut.png new file mode 100644 index 0000000..8c5e443 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/2/cut.png differ diff --git a/src/main/resources/META-INF/cut-image/template/2/fixed.png b/src/main/resources/META-INF/cut-image/template/2/fixed.png new file mode 100644 index 0000000..f7959d8 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/2/fixed.png differ diff --git a/src/main/resources/META-INF/cut-image/template/2/matrix.png b/src/main/resources/META-INF/cut-image/template/2/matrix.png new file mode 100644 index 0000000..5f17f0d Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/2/matrix.png differ diff --git a/src/main/resources/META-INF/cut-image/template/3/active.png b/src/main/resources/META-INF/cut-image/template/3/active.png new file mode 100644 index 0000000..3f3b9b2 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/3/active.png differ diff --git a/src/main/resources/META-INF/cut-image/template/3/cut.png b/src/main/resources/META-INF/cut-image/template/3/cut.png new file mode 100644 index 0000000..b5775ad Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/3/cut.png differ diff --git a/src/main/resources/META-INF/cut-image/template/3/fixed.png b/src/main/resources/META-INF/cut-image/template/3/fixed.png new file mode 100644 index 0000000..5ab0814 Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/3/fixed.png differ diff --git a/src/main/resources/META-INF/cut-image/template/3/matrix.png b/src/main/resources/META-INF/cut-image/template/3/matrix.png new file mode 100644 index 0000000..5f17f0d Binary files /dev/null and b/src/main/resources/META-INF/cut-image/template/3/matrix.png differ diff --git a/src/main/resources/META-INF/lib/linux_32/libwebp-imageio.so b/src/main/resources/META-INF/lib/linux_32/libwebp-imageio.so new file mode 100644 index 0000000..6b196f5 Binary files /dev/null and b/src/main/resources/META-INF/lib/linux_32/libwebp-imageio.so differ diff --git a/src/main/resources/META-INF/lib/linux_64/libwebp-imageio.so b/src/main/resources/META-INF/lib/linux_64/libwebp-imageio.so new file mode 100644 index 0000000..93f7e8d Binary files /dev/null and b/src/main/resources/META-INF/lib/linux_64/libwebp-imageio.so differ diff --git a/src/main/resources/META-INF/lib/osx_32/libwebp-imageio.dylib b/src/main/resources/META-INF/lib/osx_32/libwebp-imageio.dylib new file mode 100644 index 0000000..75b913d Binary files /dev/null and b/src/main/resources/META-INF/lib/osx_32/libwebp-imageio.dylib differ diff --git a/src/main/resources/META-INF/lib/osx_64/libwebp-imageio.dylib b/src/main/resources/META-INF/lib/osx_64/libwebp-imageio.dylib new file mode 100644 index 0000000..1921daf Binary files /dev/null and b/src/main/resources/META-INF/lib/osx_64/libwebp-imageio.dylib differ diff --git a/src/main/resources/META-INF/lib/windows_32/webp-imageio.dll b/src/main/resources/META-INF/lib/windows_32/webp-imageio.dll new file mode 100644 index 0000000..3811223 Binary files /dev/null and b/src/main/resources/META-INF/lib/windows_32/webp-imageio.dll differ diff --git a/src/main/resources/META-INF/lib/windows_64/webp-imageio.dll b/src/main/resources/META-INF/lib/windows_64/webp-imageio.dll new file mode 100644 index 0000000..4ef18e5 Binary files /dev/null and b/src/main/resources/META-INF/lib/windows_64/webp-imageio.dll differ diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi new file mode 100644 index 0000000..ae274b7 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -0,0 +1 @@ +com.luciad.imageio.webp.WebPImageReaderSpi \ No newline at end of file diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 0000000..274d4d4 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +com.luciad.imageio.webp.WebPImageWriterSpi \ No newline at end of file