stjdydayou před 4 roky
rodič
revize
ee623f6078
46 změnil soubory, kde provedl 2945 přidání a 3 odebrání
  1. 129 0
      src/main/java/com/zhiqiyun/open/config/OauthInterceptor.java
  2. 135 0
      src/main/java/com/zhiqiyun/open/config/WebMvcConfig.java
  3. 81 0
      src/main/java/com/zhiqiyun/open/manager/FundaErrorController.java
  4. 3 3
      src/main/java/com/zhiqiyun/open/manager/controller/OauthController.java
  5. 49 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/Captcha.java
  6. 136 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/CaptchaServiceImpl.java
  7. 27 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/background/BackgroundFactory.java
  8. 52 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/background/SingleColorBackgroundFactory.java
  9. 26 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/color/ColorFactory.java
  10. 48 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/color/GradientColorFactory.java
  11. 54 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/color/RandomColorFactory.java
  12. 44 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/color/SingleColorFactory.java
  13. 42 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/AbstractFilterFactory.java
  14. 37 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/ConfigurableFilterFactory.java
  15. 27 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/FilterFactory.java
  16. 65 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/AbstractConvolveImageOp.java
  17. 172 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/AbstractImageOp.java
  18. 55 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/AbstractTransformImageOp.java
  19. 30 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/BlurImageOp.java
  20. 116 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/CurvesImageOp.java
  21. 60 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/DiffuseImageOp.java
  22. 31 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/DoubleRippleImageOp.java
  23. 91 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/MarbleImageOp.java
  24. 95 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/PerlinNoise.java
  25. 80 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/RippleImageOp.java
  26. 30 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/SoftenImageOp.java
  27. 101 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/WobbleImageOp.java
  28. 59 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/CurvesRippleFilterFactory.java
  29. 39 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/DiffuseRippleFilterFactory.java
  30. 48 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/DoubleRippleFilterFactory.java
  31. 41 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/MarbleRippleFilterFactory.java
  32. 60 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/RippleFilterFactory.java
  33. 43 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/WobbleRippleFilterFactory.java
  34. 25 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/font/FontFactory.java
  35. 90 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/font/RandomFontFactory.java
  36. 104 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/AbstractTextRenderer.java
  37. 36 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/BestFitTextRenderer.java
  38. 41 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/RandomYBestFitTextRenderer.java
  39. 34 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/SimpleTextRenderer.java
  40. 117 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/TextCharacter.java
  41. 38 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/TextRenderer.java
  42. 83 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/TextString.java
  43. 58 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/word/AdaptiveRandomWordFactory.java
  44. 60 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/word/RandomWordFactory.java
  45. 24 0
      src/main/java/com/zhiqiyun/open/service/impl/captcha/word/WordFactory.java
  46. 129 0
      src/main/java/com/zhiqiyun/open/utils/ClassScaner.java

+ 129 - 0
src/main/java/com/zhiqiyun/open/config/OauthInterceptor.java

@@ -0,0 +1,129 @@
+package com.zhiqiyun.open.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zhiqiyun.open.annotation.Permission;
+import com.zhiqiyun.open.exception.NoLoginException;
+import com.zhiqiyun.open.exception.PermissionDeniedException;
+import com.zhiqiyun.open.models.OauthInfo;
+import com.zhiqiyun.open.models.UserOperateLog;
+import com.zhiqiyun.open.service.OauthService;
+import com.zhiqiyun.open.service.UserBaseInfoService;
+import com.zhiqiyun.open.utils.DateUtil;
+import com.zhiqiyun.open.utils.ServletContext;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.lang.NonNull;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * @author jtoms
+ */
+@Slf4j
+@Component
+public class OauthInterceptor implements HandlerInterceptor {
+
+    @Autowired
+    private OauthService oauthService;
+
+    @Autowired
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    @Autowired
+    private UserBaseInfoService userBaseInfoService;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler)
+            throws PermissionDeniedException, NoLoginException {
+        UserOperateLog userOperateLog = new UserOperateLog();
+        String requestId = ServletContext.getRequestId();
+        String requestIp = ServletContext.getRemoteIPAddress();
+        String fromSource = ServletContext.getFromSource();
+        String requestUrl = request.getRequestURI();
+        Map<String, String> requestParam = ServletContext.getRequestParamMap();
+        try {
+            if (handler instanceof HandlerMethod) {
+                HandlerMethod handlerMethod = (HandlerMethod) handler;
+                Permission permission = handlerMethod.getMethodAnnotation(Permission.class);
+                if (permission != null) {
+                    userOperateLog.setTags(permission.tags());
+
+                    String accessToken = ServletContext.getAccessToken();
+                    if (StringUtils.isBlank(accessToken)) {
+                        throw new NoLoginException();
+                    }
+
+                    OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+                    if (oauthInfo == null || oauthInfo.getId() == null
+                            || StringUtils.isBlank(oauthInfo.getAccessToken())) {
+                        throw new NoLoginException();
+                    }
+                    userOperateLog.setUid(oauthInfo.getId());
+
+                    String[] arrayAuthorities = permission.value();
+                    if (arrayAuthorities.length > 0) {
+                        if (oauthInfo.getAuthorities() == null || oauthInfo.getAuthorities().isEmpty()) {
+                            throw new PermissionDeniedException();
+                        }
+
+                        boolean hasAuthority = false;
+                        for (String authority : arrayAuthorities) {
+                            if (oauthInfo.getAuthorities().contains(authority.trim())) {
+                                hasAuthority = true;
+                                break;
+                            }
+                        }
+                        if (!hasAuthority) {
+                            throw new PermissionDeniedException();
+                        }
+                    }
+                }
+            }
+            return true;
+        } finally {
+            if (handler instanceof HandlerMethod) {
+                HandlerMethod handlerMethod = (HandlerMethod) handler;
+                Permission permission = handlerMethod.getMethodAnnotation(Permission.class);
+                if (permission != null && permission.writeLog()) {
+                    this.threadPoolTaskExecutor.execute(() -> {
+                        try {
+                            userOperateLog.setId(requestId);
+                            userOperateLog.setFromSource(fromSource);
+                            userOperateLog.setRequestIp(requestIp);
+                            userOperateLog.setRequestUrl(requestUrl);
+                            userOperateLog.setRequestParams(objectMapper.writeValueAsString(requestParam));
+                            userOperateLog.setRequestData("");
+                            userOperateLog.setRequestTime(DateUtil.current());
+                            userBaseInfoService.insertOperateLog(userOperateLog);
+                        } catch (Exception e) {
+                            log.error("", e);
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    @Override
+    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
+        if (handler instanceof HandlerMethod) {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Permission permission = handlerMethod.getMethodAnnotation(Permission.class);
+            if (permission != null && permission.writeLog()) {
+                UserOperateLog userOperateLog = new UserOperateLog();
+                userOperateLog.setResponseTime(DateUtil.current());
+                userOperateLog.setId(ServletContext.getRequestId());
+                this.threadPoolTaskExecutor.execute(() -> userBaseInfoService.updateOperateLogById(userOperateLog));
+            }
+        }
+    }
+}

+ 135 - 0
src/main/java/com/zhiqiyun/open/config/WebMvcConfig.java

@@ -0,0 +1,135 @@
+package com.zhiqiyun.open.config;
+
+
+import com.alibaba.fastjson.serializer.SerializeConfig;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alibaba.fastjson.support.config.FastJsonConfig;
+import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zhiqiyun.open.utils.ClassScaner;
+import com.zhiqiyun.open.utils.ServletContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.lang.NonNull;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CharacterEncodingFilter;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 系统启动配置
+ *
+ * @author jtoms
+ */
+@Slf4j
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+    @Autowired
+    private OauthInterceptor oauthInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new HandlerInterceptor() {
+            @Override
+            public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
+                ServletContext.init(request, response);
+                return true;
+            }
+
+            @Override
+            public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
+                ServletContext.clean();
+            }
+        }).addPathPatterns("/**");
+        registry.addInterceptor(this.oauthInterceptor).addPathPatterns("/**");
+    }
+
+    @Bean
+    public FilterRegistrationBean<CharacterEncodingFilter> encodingFilter() {
+        FilterRegistrationBean<CharacterEncodingFilter> filterRegistration = new FilterRegistrationBean<>();
+        filterRegistration.setFilter(new CharacterEncodingFilter());
+        filterRegistration.addUrlPatterns("/*");
+        Map<String, String> initParameters = new HashMap<>();
+        initParameters.put("encoding", "UTF-8");
+        initParameters.put("forceEncoding", "true");
+        filterRegistration.setInitParameters(initParameters);
+        return filterRegistration;
+    }
+
+    @Bean
+    @Primary
+    @ConditionalOnMissingBean(ObjectMapper.class)
+    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
+        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+
+        return objectMapper;
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
+        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
+        //自定义配置...
+        FastJsonConfig config = new FastJsonConfig();
+
+        SerializeConfig serializeConfig = new SerializeConfig();
+
+        Set<Class<?>> classes = ClassScaner.scan("com.zhiqiyun.open.enmus");
+
+        for (Class<?> clazz : classes) {
+            try {
+                Class<? extends Enum<?>> eClazz = (Class<? extends Enum<?>>) clazz;
+                serializeConfig.configEnumAsJavaBean(eClazz);
+            } catch (Exception e) {
+                log.warn("configEnumAsJavaBean Error {}", clazz.toString(), e);
+            }
+        }
+
+        config.setSerializeConfig(serializeConfig);
+        config.setSerializerFeatures(
+                SerializerFeature.QuoteFieldNames,
+                SerializerFeature.WriteEnumUsingToString,
+                /*SerializerFeature.WriteMapNullValue,*/
+                SerializerFeature.WriteDateUseDateFormat,
+                SerializerFeature.DisableCircularReferenceDetect
+        );
+        fastJsonHttpMessageConverter.setFastJsonConfig(config);
+        converters.add(fastJsonHttpMessageConverter);
+    }
+
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOriginPatterns("*")
+                .allowCredentials(true)
+                .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH")
+                .maxAge(3600);
+    }
+}
+

+ 81 - 0
src/main/java/com/zhiqiyun/open/manager/FundaErrorController.java

@@ -0,0 +1,81 @@
+package com.zhiqiyun.open.manager;
+
+import com.zhiqiyun.open.exception.NoFoundException;
+import com.zhiqiyun.open.exception.NoLoginException;
+import com.zhiqiyun.open.exception.NotSufficientFundsException;
+import com.zhiqiyun.open.exception.PermissionDeniedException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import java.util.Objects;
+import java.util.Set;
+
+@Slf4j
+@RestController
+@ControllerAdvice
+@RequestMapping("${server.error.path:${error.path:/error}}")
+public class FundaErrorController implements ErrorController {
+
+
+    @RequestMapping
+    public Result doHandleError() {
+        return Result.instance(Result.Code.NO_FOUND, "对不起,您访问的页面不存在");
+    }
+
+    /**
+     * 校验错误拦截处理
+     *
+     * @param exception 错误信息集合
+     * @return 错误信息
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Result methodArgumentNotValidException(MethodArgumentNotValidException exception) {
+
+        BindingResult result = exception.getBindingResult();
+        String message = "请填写正确信息";
+        if (result.hasErrors()) {
+            message = Objects.requireNonNull(result.getFieldError()).getDefaultMessage();
+        }
+        return Result.instance(message);
+    }
+
+    @ExceptionHandler(ConstraintViolationException.class)
+    public Result constraintViolationException(ConstraintViolationException ex) {
+        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
+        return Result.instance(Result.Code.SERVER_ERROR, constraintViolations.iterator().next().getMessageTemplate());
+    }
+
+    @ExceptionHandler(NoFoundException.class)
+    public Result noFoundException(Exception ex) {
+        return Result.instance(Result.Code.NO_FOUND, "您访问的内容不存在");
+    }
+
+    @ExceptionHandler(PermissionDeniedException.class)
+    public Result permissionDeniedException(Exception ex) {
+        return Result.instance(Result.Code.FORBIDDEN, "您没有权限访问此内容");
+    }
+
+    @ExceptionHandler(NoLoginException.class)
+    public Result noLoginException(Exception ex) {
+        return Result.instance(Result.Code.NO_LOGIN, "您还没有登录,请先登录");
+    }
+
+    @ExceptionHandler(NotSufficientFundsException.class)
+    public Result notSufficientFundsException(Exception ex) {
+        return Result.instance(Result.Code.NOT_SUFFICIENT_FUNDS, "您账户全额不足,请先充值");
+    }
+
+    @ExceptionHandler(Exception.class)
+    public Result handleException(Exception ex) {
+        log.error("system error", ex);
+        return Result.instance(Result.Code.SERVER_ERROR, "系统忙,请稍后再试");
+    }
+}

+ 3 - 3
src/main/java/com/zhiqiyun/open/manager/controller/OauthController.java

@@ -45,7 +45,7 @@ public class OauthController {
 	@Autowired
 	private ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
-	@GetMapping("/getCaptcha")
+	@PostMapping("/getCaptcha")
 	public Result captcha() {
 		String token = ServletContext.getAccessToken();
 		if (StringUtils.isBlank(token) || !Token.checkToken(token)) {
@@ -132,7 +132,7 @@ public class OauthController {
 
 
 	@Permission(tags = "获取用户登录信息", writeLog = false)
-	@GetMapping("/getOauthInfo")
+	@PostMapping("/getOauthInfo")
 	public Result getOauthInfo() {
 		String accessToken = ServletContext.getAccessToken();
 		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
@@ -151,7 +151,7 @@ public class OauthController {
 		return Result.instance(Result.Code.SUCCESS).setData(oauthInfo);
 	}
 
-	@GetMapping("/loginOut")
+	@PostMapping("/loginOut")
 	public Result loginOut() {
 		String accessToken = ServletContext.getAccessToken();
 		if (StringUtils.isNotBlank(accessToken)) {

+ 49 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/Captcha.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha;
+
+import java.awt.image.BufferedImage;
+
+public class Captcha {
+
+	private String challenge;
+	private BufferedImage image;
+
+	public Captcha(String challenge, BufferedImage image) {
+		this.challenge = challenge;
+		this.image = image;
+	}
+
+	public String getChallenge() {
+		return challenge;
+	}
+
+	public void setChallenge(String challenge) {
+		this.challenge = challenge;
+	}
+
+	public BufferedImage getImage() {
+		return image;
+	}
+
+	public void setImage(BufferedImage image) {
+		this.image = image;
+	}
+
+}

+ 136 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/CaptchaServiceImpl.java

@@ -0,0 +1,136 @@
+package com.zhiqiyun.open.service.impl.captcha;
+
+
+import com.zhiqiyun.open.service.CaptchaService;
+import com.zhiqiyun.open.service.impl.captcha.background.BackgroundFactory;
+import com.zhiqiyun.open.service.impl.captcha.background.SingleColorBackgroundFactory;
+import com.zhiqiyun.open.service.impl.captcha.color.ColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.color.SingleColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.filter.FilterFactory;
+import com.zhiqiyun.open.service.impl.captcha.filter.predefined.*;
+import com.zhiqiyun.open.service.impl.captcha.font.FontFactory;
+import com.zhiqiyun.open.service.impl.captcha.font.RandomFontFactory;
+import com.zhiqiyun.open.service.impl.captcha.text.renderer.BestFitTextRenderer;
+import com.zhiqiyun.open.service.impl.captcha.text.renderer.TextRenderer;
+import com.zhiqiyun.open.service.impl.captcha.word.AdaptiveRandomWordFactory;
+import com.zhiqiyun.open.service.impl.captcha.word.WordFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.BoundValueOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class CaptchaServiceImpl implements CaptchaService {
+    private final String CAPTCHA_VALUE = "CAPTCHA_VALUE:%s";
+
+    private final FontFactory fontFactory = new RandomFontFactory();
+    private final WordFactory wordFactory = new AdaptiveRandomWordFactory();
+    private final BackgroundFactory backgroundFactory = new SingleColorBackgroundFactory();
+    private final TextRenderer textRenderer = new BestFitTextRenderer();
+    private final ColorFactory colorFactory = new SingleColorFactory(new Color(25, 60, 170));
+
+    @Resource
+    private RedisTemplate<String, String> redisTemplate;
+
+    /**
+     * 产生一个验证码图片二进制流
+     */
+    @Override
+    public String generateCaptchaBase64(String token) {
+        return this.generateCaptchaBase64(token, 120, 60);
+    }
+
+    @Override
+    public String generateCaptchaBase64(String token, int width, int height) {
+        try {
+            BufferedImage image = generateCaptcha(token, width, height);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(image, "png", baos);
+            String data = Base64.encodeBase64String(baos.toByteArray());
+            data = "data:image/png;base64," + data;
+            return data;
+        } catch (Exception e) {
+            log.error("convert image to byte error: ", e);
+        }
+        return null;
+    }
+
+    /**
+     * 比较验证码
+     *
+     * @param captcha
+     * @param token
+     * @param destroySession
+     */
+    @Override
+    public boolean compareCaptcha(String captcha, String token, boolean destroySession) {
+        String redisKey = String.format(CAPTCHA_VALUE, token);
+        log.debug(redisKey);
+        BoundValueOperations<String, String> boundValueOperations = this.redisTemplate.boundValueOps(redisKey);
+
+        String extistCaptcha = boundValueOperations.get();
+        if (destroySession) {
+            this.redisTemplate.delete(redisKey);
+        }
+        log.debug("SESSION captcha " + extistCaptcha);
+        log.debug("INPUT captcha " + captcha);
+        return StringUtils.equalsIgnoreCase(extistCaptcha, captcha);
+    }
+
+
+    private BufferedImage generateCaptcha(String token, int width, int height) {
+        try {
+
+            Random random = new Random();
+            FilterFactory filterFactory = new CurvesRippleFilterFactory(colorFactory);
+            switch (random.nextInt(4)) {
+                case 0:
+                    filterFactory = new CurvesRippleFilterFactory(colorFactory);
+                    break;
+                case 1:
+                    filterFactory = new MarbleRippleFilterFactory();
+                    break;
+                case 2:
+                    filterFactory = new DoubleRippleFilterFactory();
+                    break;
+                case 3:
+                    filterFactory = new WobbleRippleFilterFactory();
+                    break;
+                case 4:
+                    filterFactory = new DiffuseRippleFilterFactory();
+                    break;
+                default:
+                    break;
+
+            }
+
+            BufferedImage bufImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            backgroundFactory.fillBackground(bufImage);
+            String word = wordFactory.getNextWord();
+            textRenderer.draw(word, bufImage, fontFactory, colorFactory);
+            bufImage = filterFactory.applyFilters(bufImage);
+
+            Captcha captcha = new Captcha(word, bufImage);
+            String redisKey = String.format(CAPTCHA_VALUE, token);
+            BoundValueOperations<String, String> boundValueOperations = this.redisTemplate.boundValueOps(redisKey);
+            boundValueOperations.set(captcha.getChallenge());
+            boundValueOperations.expire(5, TimeUnit.MINUTES);
+
+            return captcha.getImage();
+        } catch (Exception e) {
+            log.error("error", e);
+            return null;
+        }
+    }
+}

+ 27 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/background/BackgroundFactory.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.zhiqiyun.open.service.impl.captcha.background;
+
+import java.awt.image.BufferedImage;
+
+public interface BackgroundFactory {
+
+	void fillBackground(BufferedImage dest);
+}

+ 52 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/background/SingleColorBackgroundFactory.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.background;
+
+
+import com.zhiqiyun.open.service.impl.captcha.color.ColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.color.SingleColorFactory;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+
+public class SingleColorBackgroundFactory implements BackgroundFactory {
+
+	private ColorFactory colorFactory;
+	
+	public SingleColorBackgroundFactory() {
+		colorFactory = new SingleColorFactory(Color.WHITE);
+	}
+
+	public SingleColorBackgroundFactory(Color color) {
+		colorFactory = new SingleColorFactory(color);
+	}
+	
+	public void setColorFactory(ColorFactory colorFactory) {
+		this.colorFactory = colorFactory;
+	}
+
+	@Override
+	public void fillBackground(BufferedImage dest) {
+		Graphics g = dest.getGraphics();
+		g.setColor(colorFactory.getColor(0));
+		g.fillRect(0, 0, dest.getWidth(), dest.getHeight());
+	}
+
+}

+ 26 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/color/ColorFactory.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.color;
+
+import java.awt.*;
+
+public interface ColorFactory {
+
+	Color getColor(int index);
+}

+ 48 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/color/GradientColorFactory.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.color;
+
+import java.awt.*;
+
+public class GradientColorFactory implements ColorFactory {
+
+	private Color start;
+	private Color step;
+	
+	public GradientColorFactory() {
+		start = new Color(192, 192, 0);
+		step = new Color(192, 128, 128);
+	}
+	
+	@Override
+	public Color getColor(int index) {
+		return new Color((start.getRed() + step.getRed() * index) % 256, 
+				(start.getGreen() + step.getGreen() * index) % 256,  
+				(start.getBlue() + step.getBlue() * index) % 256);
+	}
+
+	public void setStart(Color start) {
+		this.start = start;
+	}
+
+	public void setStep(Color step) {
+		this.step = step;
+	}
+
+}

+ 54 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/color/RandomColorFactory.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.color;
+
+import java.awt.*;
+import java.util.Random;
+
+public class RandomColorFactory implements ColorFactory {
+
+	private Color min;
+	private Color max;
+	private Color color;
+
+	public RandomColorFactory() {
+		min = new Color(20,40,80);
+		max = new Color(21,50,140);
+	}
+	
+	public void setMin(Color min) {
+		this.min = min;
+	}
+
+	public void setMax(Color max) {
+		this.max = max;
+	}
+
+	@Override
+	public Color getColor(int index) {
+		if (color == null) {
+			Random r = new Random();
+			color = new Color( min.getRed() + r.nextInt((max.getRed() - min.getRed())),
+					min.getGreen() + r.nextInt((max.getGreen() - min.getGreen())),
+					min.getBlue() + r.nextInt((max.getBlue() - min.getBlue())));
+		}
+		return color;
+	}
+
+}

+ 44 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/color/SingleColorFactory.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.color;
+
+import java.awt.*;
+
+public class SingleColorFactory implements ColorFactory {
+
+	private Color color;
+	
+	public SingleColorFactory() {
+		color = Color.BLACK;
+	}
+
+	public SingleColorFactory(Color color) {
+		this.color = color;
+	}
+	
+	@Override
+	public Color getColor(int index) {
+		return color;
+	}
+
+	public void setColor(Color color) {
+		this.color = color;
+	}
+	
+}

+ 42 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/AbstractFilterFactory.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.util.List;
+
+public abstract class AbstractFilterFactory implements FilterFactory {
+
+	protected abstract List<BufferedImageOp> getFilters();
+
+	@Override
+	public BufferedImage applyFilters(BufferedImage source) {
+		BufferedImage dest = source;
+		for (BufferedImageOp filter : getFilters()) {
+			dest = filter.filter(dest, null);
+		}
+		int x = (source.getWidth() - dest.getWidth()) / 2;
+		int y = (source.getHeight() - dest.getHeight()) / 2;
+		source = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
+		source.getGraphics().drawImage(dest, x, y, null);
+		return source;
+	}
+
+}

+ 37 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/ConfigurableFilterFactory.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter;
+
+import java.awt.image.BufferedImageOp;
+import java.util.List;
+
+public class ConfigurableFilterFactory extends AbstractFilterFactory {
+
+	private List<BufferedImageOp> filters;
+
+	@Override
+	public List<BufferedImageOp> getFilters() {
+		return filters;
+	}
+
+	public void setFilters(List<BufferedImageOp> filters) {
+		this.filters = filters;
+	}
+
+}

+ 27 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/FilterFactory.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter;
+
+import java.awt.image.BufferedImage;
+
+public interface FilterFactory {
+
+	BufferedImage applyFilters(BufferedImage source);
+
+}

+ 65 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/AbstractConvolveImageOp.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+
+public abstract class AbstractConvolveImageOp extends AbstractImageOp {
+
+	private float[][] matrix; 
+	
+	protected AbstractConvolveImageOp(float[][] matrix) {
+		this.matrix = matrix;
+	}
+
+	@Override
+	protected void filter(int[] inPixels, int[] outPixels, int width, int height) {
+//		long time1 = System.currentTimeMillis();
+		int matrixWidth = matrix[0].length;
+		int matrixHeight = matrix.length;
+		int mattrixLeft = - matrixWidth / 2; 
+		int matrixTop = - matrixHeight / 2;
+		for (int y = 0; y < height; y++) {
+			int ytop = y + matrixTop;
+			int ybottom = y + matrixTop + matrixHeight; 
+			for (int x = 0; x < width; x++) {
+				float[] sum = {0.5f, 0.5f, 0.5f, 0.5f};
+				int xleft = x + mattrixLeft;
+				int xright = x + mattrixLeft + matrixWidth;
+				int matrixY = 0;
+				for (int my = ytop; my < ybottom; my ++, matrixY++) {
+					int matrixX = 0;
+					for (int mx = xleft; mx < xright; mx ++, matrixX ++) {
+						int pixel = getPixel(inPixels, mx, my, width, height, EDGE_ZERO);
+						float m = matrix[matrixY][matrixX];
+						sum[0] += m * ((pixel >> 24) & 0xff);
+						sum[1] += m * ((pixel >> 16) & 0xff);
+						sum[2] += m * ((pixel >> 8) & 0xff);
+						sum[3] += m * (pixel & 0xff);
+					}
+				}
+				outPixels[x + y * width] = (limitByte((int)sum[0]) << 24) | (limitByte((int)sum[1]) << 16) | (limitByte((int)sum[2]) << 8) | (limitByte((int)sum[3]));				
+			}
+		}
+//		long time2 = System.currentTimeMillis() - time1;
+		//System.out.println("AbstractConvolveImageOp " + time2);
+		
+	}
+
+	
+}

+ 172 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/AbstractImageOp.java

@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+import java.awt.*;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+
+public abstract class AbstractImageOp implements BufferedImageOp {
+
+	public static final int EDGE_ZERO = 0;
+	public static final int EDGE_MIRROR = 1;
+	public static final int EDGE_CLAMP = 2;
+
+	protected int edgeMode;
+	protected RenderingHints hints;
+
+	public int getEdgeMode() {
+		return edgeMode;
+	}
+
+	public void setEdgeMode(int edgeMode) {
+		this.edgeMode = edgeMode;
+	}
+
+	protected int getPixel(int pixels[], int x, int y, int width, int height, int edgeMode) {
+		if (x >= 0 && x < width && y >= 0 && y < height) {
+			return pixels[x + y * width];
+		} else if (edgeMode == EDGE_ZERO) {
+			return 0;
+		} else if (edgeMode == EDGE_CLAMP) {
+			if (x < 0) {
+				x = 0;
+			} else if (x >= width) {
+				x = width - 1;
+			}
+			if (y < 0) {
+				y = 0;
+			} else if (y >= height) {
+				y = height - 1;
+			}
+			return pixels[x + y * width];
+		} else {
+			if (x < 0) {
+				x = -x;
+			} else if (x >= width) {
+				x = width + width - x - 1;
+			}
+			if (y < 0) {
+				y = -y;
+			} else if (y > height) {
+				y = height = height - y - 1;
+			}
+			try {
+				return pixels[x + y * width];
+			} catch (Exception e) {
+				return 0;
+			}
+		}
+	}
+
+	private int linear(int from, int to, int shift, double d) {
+		return ((int) Math.floor(((from >> shift) & 0xff) + d * (((to >> shift) & 0xff) - ((from >> shift) & 0xff)))) << shift;
+	}
+
+	private int linear(int from, int to, double d) {
+		int c = 0;
+		for (int i = 0; i < 4; i++) {
+			c += linear(from, to, i * 8, d);
+		}
+		return c;
+	}
+
+	protected int bilinear(int nw, int ne, int sw, int se, double xd, double yd) {
+		return linear(linear(nw, ne, xd), linear(sw, se, xd), yd);
+	}
+
+	protected int getPixelBilinear(int pixels[], double x, double y, int width, int height, int edgeMode) {
+		int xi = (int) Math.floor(x);
+		int yi = (int) Math.floor(y);
+		double xd = x - xi;
+		double yd = y - yi;
+		int nw = getPixel(pixels, xi, yi, width, height, edgeMode);
+		int ne = getPixel(pixels, xi + 1, yi, width, height, edgeMode);
+		int sw = getPixel(pixels, xi, yi + 1, width, height, edgeMode);
+		int se = getPixel(pixels, xi + 1, yi + 1, width, height, edgeMode);
+		return bilinear(nw, ne, sw, se, xd, yd);
+	}
+
+	protected int limit(int v, int min, int max) {
+		if (v < min) {
+			v = min;
+		} else if (v > max) {
+			v = max;
+		}
+		return v;
+	}
+
+	protected int limitByte(int v) {
+		return limit(v, 0, 255);
+	}
+
+	protected void filter(int[] inPixels, int[] outPixels, int width, int height) {
+
+	}
+
+	@Override
+	public BufferedImage filter(BufferedImage src, BufferedImage dest) {
+		if (dest == null) {
+			dest = createCompatibleDestImage(src, null);
+		}
+		int width = src.getWidth();
+		int height = src.getHeight();
+		int[] inPixels = new int[width * height];
+		int[] outPixels = new int[width * height];
+		src.getRaster().getDataElements(0, 0, width, height, inPixels);
+		filter(inPixels, outPixels, width, height);
+		dest.getRaster().setDataElements(0, 0, width, height, outPixels);
+		return dest;
+	}
+
+	@Override
+	public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
+		if (destCM == null) {
+			destCM = src.getColorModel();
+			if (destCM instanceof IndexColorModel) {
+				destCM = ColorModel.getRGBdefault();
+			}
+		}
+		return new BufferedImage(destCM, destCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), destCM.isAlphaPremultiplied(), null);
+	}
+
+	@Override
+	public Rectangle2D getBounds2D(BufferedImage src) {
+		return src.getRaster().getBounds();
+	}
+
+	@Override
+	public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
+		if (dstPt == null) {
+			dstPt = new Point2D.Float();
+		}
+		dstPt.setLocation(srcPt.getX(), srcPt.getY());
+		return dstPt;
+	}
+
+	@Override
+	public RenderingHints getRenderingHints() {
+		return hints;
+	}
+
+}

+ 55 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/AbstractTransformImageOp.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+
+public abstract class AbstractTransformImageOp extends AbstractImageOp {
+
+	protected abstract void transform(int x, int y, double[] t);
+	
+	protected void init() {
+	}
+
+	private boolean initialized;
+	
+	public AbstractTransformImageOp() {
+		setEdgeMode(EDGE_CLAMP);
+	}
+	
+	@Override
+	protected void filter(int[] inPixels, int[] outPixels, int width, int height) {
+		if (!initialized) {
+			init();
+			initialized = true;
+		}
+//		long time1 = System.currentTimeMillis();
+		double[]t = new double[2];
+		for (int y = 0; y < height; y++) {
+			for (int x = 0; x < width; x++) {
+				transform(x, y, t);
+				int pixel = getPixelBilinear(inPixels, t[0], t[1], width, height, getEdgeMode());
+				outPixels[x + y * width] = pixel;
+			}
+		}
+//		long time2 = System.currentTimeMillis() - time1;
+		//System.out.println("AbstractTransformImageOp " + time2);
+	}
+	
+}
+

+ 30 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/BlurImageOp.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+public class BlurImageOp extends AbstractConvolveImageOp {
+
+	private static final float[][] matrix = { { 1 / 16f, 2 / 16f, 1 / 16f },
+			{ 2 / 16f, 4 / 16f, 2 / 16f }, { 1 / 16f, 2 / 16f, 1 / 16f } };
+
+	public BlurImageOp() {
+		super(matrix);
+	}
+
+}

+ 116 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/CurvesImageOp.java

@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+
+import com.zhiqiyun.open.service.impl.captcha.color.ColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.color.SingleColorFactory;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Random;
+
+
+public class CurvesImageOp extends AbstractImageOp {
+
+	private float strokeMin;
+	private float strokeMax;
+	private ColorFactory colorFactory = new SingleColorFactory();
+
+	public float getStrokeMin() {
+		return strokeMin;
+	}
+
+	public void setStrokeMin(float strokeMin) {
+		this.strokeMin = strokeMin;
+	}
+
+	public float getStrokeMax() {
+		return strokeMax;
+	}
+
+
+	public void setStrokeMax(float strokeMax) {
+		this.strokeMax = strokeMax;
+	}
+	public ColorFactory getColorFactory() {
+		return colorFactory;
+	}
+
+	public void setColorFactory(ColorFactory colorFactory) {
+		this.colorFactory = colorFactory;
+	}
+
+	private double hermiteSpline(double x1, double a1, double x2, double a2, double t) {
+		double t2=t*t;
+	    double t3=t2*t;
+	    double b=-a2-2.0*a1-3.0*x1+3.0*x2;
+	    double a=a2+a1+2.0*x1-2.0*x2;
+	    return a*t3+b*t2+a1*t+x1;	    
+	}
+	
+	private double catmullRomSpline(double x0, double x1, double x2, double x3, double t) {
+		double a1 = (x2 - x0) / 2;
+		double a2 = (x3 - x1) / 2;
+		return hermiteSpline(x1, a1, x2, a2, t);
+	}
+
+	@Override
+	public BufferedImage filter(BufferedImage src, BufferedImage dest) {
+		if (dest == null) {
+			dest = createCompatibleDestImage(src, null);
+		}
+		double width = dest.getWidth();
+		double height = dest.getHeight();
+		Graphics2D g2 = (Graphics2D) src.getGraphics();
+		g2.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
+		Random r = new Random();
+		int cp = 4 + r.nextInt(3);
+		int[] xPoints = new int[cp];
+		int[] yPoints = new int[cp];
+		width -= 10;
+		for (int i = 0; i < cp; i++) {
+			xPoints[i] = (int) ((int) 5 + (i * width) / (cp - 1));
+			yPoints[i] = (int) (height * (r.nextDouble() * 0.5 + 0.2));
+		}
+		int subsections = 6;
+		int[] xPointsSpline = new int[(cp - 1)*subsections];
+		int[] yPointsSpline = new int[(cp - 1)*subsections];
+		for (int i = 0; i < cp - 1; i++) {
+			double x0 = i > 0 ? xPoints[i - 1] : 2 * xPoints[i] - xPoints[i + 1];
+			double x1 = xPoints[i];
+			double x2 = xPoints[i + 1];
+			double x3 = (i + 2 < cp) ? xPoints[i + 2] : 2 * xPoints[i + 1] - xPoints[i];
+			double y0 = i > 0 ? yPoints[i - 1] : 2 * yPoints[i] - yPoints[i + 1];
+			double y1 = yPoints[i];
+			double y2 = yPoints[i + 1];
+			double y3 = (i + 2 < cp) ? yPoints[i + 2] : 2 * yPoints[i + 1] - yPoints[i];
+			for (int j = 0; j < subsections; j++) {
+				xPointsSpline[i * subsections + j] = (int) catmullRomSpline(x0, x1, x2, x3, 1.0 / subsections * j); 
+				yPointsSpline[i * subsections + j] = (int) catmullRomSpline(y0, y1, y2, y3, 1.0 / subsections * j); 
+			}
+		}
+		for (int i = 0; i < xPointsSpline.length - 1; i++) {
+			g2.setColor(colorFactory.getColor(i));
+			g2.setStroke(new BasicStroke(2 + 2 * r.nextFloat()));
+			g2.drawLine(xPointsSpline[i], yPointsSpline[i], xPointsSpline[i + 1], yPointsSpline[i + 1]);
+		}
+		return src;
+	}
+}

+ 60 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/DiffuseImageOp.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+import java.util.Random;
+
+public class DiffuseImageOp extends AbstractTransformImageOp {
+
+	double[] tx;
+	double[] ty;
+	double amount;
+
+	public DiffuseImageOp() {
+		amount = 1.6;
+	}
+
+	public double getAmount() {
+		return amount;
+	}
+
+	public void setAmount(double amount) {
+		this.amount = amount;
+	}
+
+	@Override
+	protected synchronized void init() {
+		tx = new double[256];
+		ty = new double[256];
+		for (int i = 0; i < 256; i++) {
+			double angle = 2 * Math.PI * i / 256;
+			tx[i] = amount * Math.sin(angle);
+			ty[i] = amount * Math.cos(angle);
+		}
+	}
+
+	@Override
+	protected void transform(int x, int y, double[] t) {
+		Random r = new Random();
+		int angle = (int) (r.nextFloat() * 255);
+		t[0] = x + tx[angle];
+		t[1] = y + ty[angle];
+	}
+
+}

+ 31 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/DoubleRippleImageOp.java

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+public class DoubleRippleImageOp extends RippleImageOp {
+
+	@Override
+	protected void transform(int x, int y, double[] t) {
+		double tx = Math.sin((double) y / yWavelength + yRandom) + 1.3 * Math.sin((double) 0.6 * y / yWavelength  + yRandom);
+		double ty = Math.cos((double) x / xWavelength + xRandom) + 1.3 * Math.cos((double) 0.6 * x / xWavelength + xRandom);
+		t[0] = x + xAmplitude * tx;
+		t[1] = y + yAmplitude * ty;
+	}
+
+}

+ 91 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/MarbleImageOp.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+public class MarbleImageOp extends AbstractTransformImageOp {
+
+	double scale;
+	double amount;
+	double turbulence;
+	double[] tx;
+	double[] ty;
+	double randomX;
+	double randomY;
+	
+	public MarbleImageOp() {
+		scale = 15;
+		amount = 1.1;
+		turbulence = 6.2;
+		randomX = 256 * Math.random();
+		randomY = 256 * Math.random();
+	}
+	
+	public double getScale() {
+		return scale;
+	}
+
+	public void setScale(double scale) {
+		this.scale = scale;
+	}
+
+	public double getAmount() {
+		return amount;
+	}
+
+	public void setAmount(double amount) {
+		this.amount = amount;
+	}
+
+	public double getTurbulence() {
+		return turbulence;
+	}
+
+	public void setTurbulence(double turbulence) {
+		this.turbulence = turbulence;
+	}
+
+	@Override
+	protected synchronized void init() {
+		tx = new double[256];
+		ty = new double[256];
+		for (int i = 0; i < 256; i++) {
+			double angle = 2 * Math.PI * i * turbulence / 256;
+			tx[i] = amount * Math.sin(angle);
+			ty[i] = amount * Math.cos(angle);
+		}
+	}
+	
+	@Override
+	protected void transform(int x, int y, double[] t) {
+		int d = limitByte((int) (127 * (1 + PerlinNoise.noise2D(((double)x) / scale + randomX, ((double)y) / scale + randomY))));
+		t[0] = x + tx[d];
+		t[1] = y + ty[d];
+	}
+	
+	protected void filter2(int[] inPixels, int[] outPixels, int width, int height) {
+		for (int y = 0; y < height; y++) {
+			for (int x = 0; x < width; x++) {
+				int pixel = limitByte((int) (127 * (1 + PerlinNoise.noise2D(((double)x) / scale + randomX, ((double)y) / scale + randomY))));
+				outPixels[x + y * width] = (limitByte((int)255) << 24) | (limitByte((int)pixel) << 16) | (limitByte((int)pixel) << 8) | (limitByte((int)pixel));				
+			}
+		}
+	}
+	
+
+}

+ 95 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/PerlinNoise.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+public class PerlinNoise {
+
+	static int p[] = { 151, 160, 137, 91, 90, 15,
+			131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
+			142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0,
+			26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,
+			237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165,
+			71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
+			60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102,
+			143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
+			89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109,
+			198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147,
+			118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182,
+			189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163,
+			70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98,
+			108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
+			251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51,
+			145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
+			184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236,
+			205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215,
+			61, 156, 180, 151, 160, 137, 91, 90, 15,
+			131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
+			142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0,
+			26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,
+			237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165,
+			71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
+			60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102,
+			143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
+			89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109,
+			198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147,
+			118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182,
+			189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163,
+			70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98,
+			108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
+			251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51,
+			145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
+			184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236,
+			205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215,
+			61, 156, 180 };
+
+	public static double noise2D(double x, double y) {
+		double fx = Math.floor(x);
+		double fy = Math.floor(y);
+		double u, v;
+		int ix = ((int) fx) & 0xFF;
+		int iy = ((int) fy) & 0xFF;
+		x -= fx;
+		y -= fy;
+		int i = p[ix];
+		int j = p[ix + 1];
+		u = fade(x);
+		v = fade(y);
+		double grad1 = grad(p[i + iy], x, y);
+		double grad2 = grad(p[j + iy], x - 1, y);
+		double grad3 = grad(p[i + iy + 1], x, y - 1);
+		double grad4 = grad(p[j + iy + 1], x - 1, y - 1);
+		return lerp(v, lerp(u, grad1, grad2), lerp(u, grad3, grad4));
+	}
+
+	static double fade(double t) {
+		return t * t * t * (t * (t * 6 - 15) + 10);
+	}
+
+	static double grad(int hash, double x, double y) {
+		int h = hash & 3;
+		double u = (h & 1) == 0 ? x : -x;
+		double v = (h & 2) == 0 ? y : -y;
+		return u + v;
+	}
+
+	public static double lerp(double t, double a, double b) {
+		return a + t * (b - a);
+	}
+
+}

+ 80 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/RippleImageOp.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+
+public class RippleImageOp extends AbstractTransformImageOp {
+
+	protected double xWavelength;
+	protected double yWavelength;
+	protected double xAmplitude;
+	protected double yAmplitude;
+	protected double xRandom;
+	protected double yRandom;
+
+	public RippleImageOp() {
+		xWavelength = 20;
+		yWavelength = 10;
+		xAmplitude = 5;
+		yAmplitude = 5;
+		xRandom = 5 * Math.random();
+		yRandom = 5 * Math.random();
+	}
+
+	public double getxWavelength() {
+		return xWavelength;
+	}
+
+	public void setxWavelength(double xWavelength) {
+		this.xWavelength = xWavelength;
+	}
+
+	public double getyWavelength() {
+		return yWavelength;
+	}
+
+	public void setyWavelength(double yWavelength) {
+		this.yWavelength = yWavelength;
+	}
+
+	public double getxAmplitude() {
+		return xAmplitude;
+	}
+
+	public void setxAmplitude(double xAmplitude) {
+		this.xAmplitude = xAmplitude;
+	}
+
+	public double getyAmplitude() {
+		return yAmplitude;
+	}
+
+	public void setyAmplitude(double yAmplitude) {
+		this.yAmplitude = yAmplitude;
+	}
+
+	@Override
+	protected void transform(int x, int y, double[] t) {
+		double tx = Math.sin((double) y / yWavelength + yRandom);
+		double ty = Math.cos((double) x / xWavelength + xRandom);
+		t[0] = x + xAmplitude * tx;
+		t[1] = y + yAmplitude * ty;
+	}
+
+}

+ 30 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/SoftenImageOp.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+public class SoftenImageOp extends AbstractConvolveImageOp {
+
+	private static final float[][] matrix = { { 0 / 16f, 1 / 16f, 0 / 16f },
+			{ 1 / 16f, 12 / 16f, 1 / 16f }, { 0 / 16f, 1 / 16f, 0 / 16f } };
+
+	public SoftenImageOp() {
+		super(matrix);
+	}
+
+}

+ 101 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/library/WobbleImageOp.java

@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.library;
+
+
+public class WobbleImageOp extends AbstractTransformImageOp {
+
+	private double xWavelength;
+	private double yWavelength;
+	private double xAmplitude;
+	private double yAmplitude;
+	private double xRandom;
+	private double yRandom;
+	private double xScale;
+	private double yScale;
+
+	public WobbleImageOp() {
+		xWavelength = 15;
+		yWavelength = 15;
+		xAmplitude = 4.0;
+		yAmplitude = 3.0;
+		xScale = 1.0;
+		yScale = 1.0;
+		xRandom = 3 * Math.random();
+		yRandom = 10 * Math.random();
+	}
+
+	public double getxWavelength() {
+		return xWavelength;
+	}
+
+	public void setxWavelength(double xWavelength) {
+		this.xWavelength = xWavelength;
+	}
+
+	public double getyWavelength() {
+		return yWavelength;
+	}
+
+	public void setyWavelength(double yWavelength) {
+		this.yWavelength = yWavelength;
+	}
+
+	public double getxAmplitude() {
+		return xAmplitude;
+	}
+
+	public void setxAmplitude(double xAmplitude) {
+		this.xAmplitude = xAmplitude;
+	}
+
+	public double getyAmplitude() {
+		return yAmplitude;
+	}
+
+	public void setyAmplitude(double yAmplitude) {
+		this.yAmplitude = yAmplitude;
+	}
+
+	public double getxScale() {
+		return xScale;
+	}
+
+	public void setxScale(double xScale) {
+		this.xScale = xScale;
+	}
+
+	public double getyScale() {
+		return yScale;
+	}
+
+	public void setyScale(double yScale) {
+		this.yScale = yScale;
+	}
+
+	@Override
+	protected void transform(int x, int y, double[] t) {
+		double tx = Math.cos((double) (xScale * x + y) / xWavelength + xRandom);
+		double ty = Math.sin((double) (yScale * y + x) / yWavelength + yRandom);
+		t[0] = x + xAmplitude * tx;
+		t[1] = y + yAmplitude * ty;
+
+	}
+
+}

+ 59 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/CurvesRippleFilterFactory.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.predefined;
+
+
+import com.zhiqiyun.open.service.impl.captcha.color.ColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.filter.library.CurvesImageOp;
+
+import java.awt.image.BufferedImageOp;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CurvesRippleFilterFactory extends RippleFilterFactory {
+
+	protected CurvesImageOp curves = new CurvesImageOp();
+
+	public CurvesRippleFilterFactory() {
+	}
+
+	public CurvesRippleFilterFactory(ColorFactory colorFactory) {
+		setColorFactory(colorFactory);
+	}
+
+	@Override
+	protected List<BufferedImageOp> getPreRippleFilters() {
+		List<BufferedImageOp> list = new ArrayList<BufferedImageOp>();
+		list.add(curves);
+		return list;
+	}
+
+	public void setStrokeMin(float strokeMin) {
+		curves.setStrokeMin(strokeMin);
+	}
+
+	public void setStrokeMax(float strokeMax) {
+		curves.setStrokeMax(strokeMax);
+	}
+
+	public void setColorFactory(ColorFactory colorFactory) {
+		curves.setColorFactory(colorFactory);
+	}
+
+}

+ 39 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/DiffuseRippleFilterFactory.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ *
+ * This file is part of Patchca CAPTCHA library.
+ *
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.predefined;
+
+
+import com.zhiqiyun.open.service.impl.captcha.filter.library.DiffuseImageOp;
+
+import java.awt.image.BufferedImageOp;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class DiffuseRippleFilterFactory extends RippleFilterFactory {
+
+    protected DiffuseImageOp diffuse = new DiffuseImageOp();
+
+    @Override
+    protected List<BufferedImageOp> getPreRippleFilters() {
+        List<BufferedImageOp> list = new ArrayList<BufferedImageOp>();
+        list.add(diffuse);
+        return list;
+    }
+}

+ 48 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/DoubleRippleFilterFactory.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ *
+ * This file is part of Patchca CAPTCHA library.
+ *
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.predefined;
+
+
+import com.zhiqiyun.open.service.impl.captcha.filter.AbstractFilterFactory;
+import com.zhiqiyun.open.service.impl.captcha.filter.library.DoubleRippleImageOp;
+
+import java.awt.image.BufferedImageOp;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class DoubleRippleFilterFactory extends AbstractFilterFactory {
+
+    protected List<BufferedImageOp> filters;
+    protected DoubleRippleImageOp ripple;
+
+    public DoubleRippleFilterFactory() {
+        ripple = new DoubleRippleImageOp();
+    }
+
+    @Override
+    public List<BufferedImageOp> getFilters() {
+        if (filters == null) {
+            filters = new ArrayList<BufferedImageOp>();
+            filters.add(ripple);
+        }
+        return filters;
+    }
+
+}

+ 41 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/MarbleRippleFilterFactory.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.predefined;
+
+
+import com.zhiqiyun.open.service.impl.captcha.filter.library.MarbleImageOp;
+
+import java.awt.image.BufferedImageOp;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class MarbleRippleFilterFactory extends RippleFilterFactory {
+
+	protected MarbleImageOp marble = new MarbleImageOp();
+
+	@Override
+	protected List<BufferedImageOp> getPreRippleFilters() {
+		List<BufferedImageOp> list = new ArrayList<BufferedImageOp>();
+		list.add(marble);
+		return list;
+	}
+	
+}
+

+ 60 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/RippleFilterFactory.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ *
+ * This file is part of Patchca CAPTCHA library.
+ *
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.predefined;
+
+
+import com.zhiqiyun.open.service.impl.captcha.filter.AbstractFilterFactory;
+import com.zhiqiyun.open.service.impl.captcha.filter.library.RippleImageOp;
+
+import java.awt.image.BufferedImageOp;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class RippleFilterFactory extends AbstractFilterFactory {
+
+    protected List<BufferedImageOp> filters;
+    protected RippleImageOp ripple;
+
+    public RippleFilterFactory() {
+        ripple = new RippleImageOp();
+    }
+
+    protected List<BufferedImageOp> getPreRippleFilters() {
+        return new ArrayList<BufferedImageOp>();
+    }
+
+    protected List<BufferedImageOp> getPostRippleFilters() {
+        return new ArrayList<BufferedImageOp>();
+
+    }
+
+    @Override
+    public List<BufferedImageOp> getFilters() {
+        if (filters == null) {
+            filters = new ArrayList<BufferedImageOp>();
+            filters.addAll(getPreRippleFilters());
+            filters.add(ripple);
+            filters.addAll(getPostRippleFilters());
+        }
+        return filters;
+    }
+
+
+}

+ 43 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/filter/predefined/WobbleRippleFilterFactory.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.filter.predefined;
+
+
+import com.zhiqiyun.open.service.impl.captcha.filter.library.WobbleImageOp;
+
+import java.awt.image.BufferedImageOp;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class WobbleRippleFilterFactory extends RippleFilterFactory {
+
+	protected WobbleImageOp wobble;
+	
+	public WobbleRippleFilterFactory() {
+		wobble = new WobbleImageOp();
+	}
+	@Override
+	protected List<BufferedImageOp> getPreRippleFilters() {
+		List<BufferedImageOp> list = new ArrayList<BufferedImageOp>();
+		list.add(wobble);
+		return list;
+	}
+
+}

+ 25 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/font/FontFactory.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.font;
+
+import java.awt.*;
+
+public interface FontFactory {
+	Font getFont(int index);
+}

+ 90 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/font/RandomFontFactory.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.font;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+public class RandomFontFactory implements FontFactory {
+
+	protected List<String> families;
+	protected int minSize;
+	protected int maxSize;
+	protected boolean randomStyle;
+
+	public RandomFontFactory() {
+		families = new ArrayList<String>();
+		families.add("Verdana");
+		families.add("Tahoma");
+		minSize = 45;
+		maxSize = 45;
+	}
+
+	public RandomFontFactory(List<String> families) {
+		this();
+		this.families = families;
+	}
+
+	public RandomFontFactory(String[] families) {
+		this();
+		this.families = Arrays.asList(families);
+	}
+	
+	public RandomFontFactory(int size, List<String> families) {
+		this(families);
+		minSize = maxSize = size;
+	}
+
+	public RandomFontFactory(int size, String[] families) {
+		this(families);
+		minSize = maxSize = size;
+	}
+	
+	public void setFamilies(List<String> families) {
+		this.families = families;
+	}
+
+	public void setMinSize(int minSize) {
+		this.minSize = minSize;
+	}
+
+	public void setMaxSize(int maxSize) {
+		this.maxSize = maxSize;
+	}
+
+	public void setRandomStyle(boolean randomStyle) {
+		this.randomStyle = randomStyle;
+	}
+
+	@Override
+	public Font getFont(int index) {
+		Random r = new Random();
+		String family = families.get(r.nextInt(families.size()));
+		boolean bold = r.nextBoolean() && randomStyle;
+		int size = minSize;
+		if (maxSize - minSize > 0) {
+			size += r.nextInt(maxSize - minSize);
+		}
+		return new Font(family, bold ? Font.BOLD : Font.PLAIN, size);
+	}
+
+}

+ 104 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/AbstractTextRenderer.java

@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+
+import com.zhiqiyun.open.service.impl.captcha.color.ColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.font.FontFactory;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+public abstract class AbstractTextRenderer implements TextRenderer {
+
+	protected int leftMargin;
+	protected int rightMargin;
+	protected int topMargin;
+	protected int bottomMargin;
+
+	protected abstract void arrangeCharacters(int width, int height, TextString ts);
+
+	public AbstractTextRenderer() {
+		leftMargin = rightMargin = 5;
+		topMargin = bottomMargin = 5;
+	}
+
+	@Override
+	public void setLeftMargin(int leftMargin) {
+		this.leftMargin = leftMargin;
+	}
+
+	@Override
+	public void setRightMargin(int rightMargin) {
+		this.rightMargin = rightMargin;
+	}
+
+	@Override
+	public void setTopMargin(int topMargin) {
+		this.topMargin = topMargin;
+	}
+
+	@Override
+	public void setBottomMargin(int bottomMargin) {
+		this.bottomMargin = bottomMargin;
+	}
+
+	@Override
+	public void draw(String text, BufferedImage canvas, FontFactory fontFactory, ColorFactory colorFactory) {
+		Graphics2D g = (Graphics2D) canvas.getGraphics();
+		TextString ts = convertToCharacters(text, g, fontFactory, colorFactory);
+		arrangeCharacters(canvas.getWidth(), canvas.getHeight(), ts);
+		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+		g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+		for (TextCharacter tc : ts.getCharacters()) {
+			g.setColor(tc.getColor());
+			g.drawString(tc.iterator(), (float) tc.getX(), (float) tc.getY());
+		}
+	}
+
+	protected TextString convertToCharacters(String text, Graphics2D g, FontFactory fontFactory, ColorFactory colorFactory) {
+		TextString characters = new TextString();
+		FontRenderContext frc = g.getFontRenderContext();
+		double lastx = 0;
+		for (int i = 0; i < text.length(); i++) {
+			Font font = fontFactory.getFont(i);
+			char c = text.charAt(i);
+			FontMetrics fm = g.getFontMetrics(font);
+			Rectangle2D bounds = font.getStringBounds(String.valueOf(c), frc);
+			TextCharacter tc = new TextCharacter();
+			tc.setCharacter(c);
+			tc.setFont(font);
+			tc.setWidth(fm.charWidth(c));
+			tc.setHeight(fm.getAscent() + fm.getDescent());
+			tc.setAscent(fm.getAscent());
+			tc.setDescent(fm.getDescent());
+			tc.setX(lastx);
+			tc.setY(0);
+			tc.setFont(font);
+			tc.setColor(colorFactory.getColor(i));
+			lastx += bounds.getWidth();
+			characters.addCharacter(tc);
+		}
+		return characters;
+	}
+
+}

+ 36 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/BestFitTextRenderer.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+public class BestFitTextRenderer extends AbstractTextRenderer {
+
+	@Override
+	protected void arrangeCharacters(int width, int height, TextString ts) {
+		double widthRemaining = (width - ts.getWidth() - leftMargin - rightMargin) / ts.getCharacters().size();
+		double x = leftMargin + widthRemaining / 2;
+		height -= topMargin + bottomMargin;
+		for (TextCharacter tc : ts.getCharacters()) {
+			double y = topMargin + (height + tc.getAscent() * 0.7) / 2;
+			tc.setX(x);
+			tc.setY(y);
+			x += tc.getWidth() + widthRemaining;
+		}
+	}
+
+}

+ 41 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/RandomYBestFitTextRenderer.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+import java.util.Random;
+
+public class RandomYBestFitTextRenderer extends AbstractTextRenderer {
+
+	@Override
+	protected void arrangeCharacters(int width, int height, TextString ts) {
+		double widthRemaining = (width - ts.getWidth() - leftMargin - rightMargin) / ts.getCharacters().size();
+		double vmiddle = height / 2;
+		double x = leftMargin + widthRemaining / 2;
+		Random r = new Random();
+		height -= topMargin + bottomMargin;
+		for (TextCharacter tc : ts.getCharacters()) {
+			double heightRemaining = height - tc.getHeight();
+			double y = vmiddle + 0.35 * tc.getAscent() + (1 - 2 * r.nextDouble()) * heightRemaining;
+			tc.setX(x);
+			tc.setY(y);
+			x += tc.getWidth() + widthRemaining;
+		}
+	}
+
+}

+ 34 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/SimpleTextRenderer.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+public class SimpleTextRenderer extends AbstractTextRenderer {
+
+	@Override
+	protected void arrangeCharacters(int width, int height, TextString ts) {
+		double x = leftMargin;
+		for (TextCharacter tc : ts.getCharacters()) {
+			double y = topMargin + (height + tc.getAscent() * 0.7) / 2;
+			tc.setX(x);
+			tc.setY(y);
+			x += tc.getWidth();
+		}
+	}
+
+}

+ 117 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/TextCharacter.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+import java.awt.*;
+import java.awt.font.TextAttribute;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+
+public class TextCharacter {
+
+	private double x;
+	private double y;
+	private double width;
+	private double height;
+	private double ascent;
+	private double descent;
+	private char character;
+	private Font font;
+	private Color color;
+
+	public double getX() {
+		return x;
+	}
+
+	public void setX(double x) {
+		this.x = x;
+	}
+
+	public double getY() {
+		return y;
+	}
+
+	public void setY(double y) {
+		this.y = y;
+	}
+
+	public double getWidth() {
+		return width;
+	}
+
+	public void setWidth(double width) {
+		this.width = width;
+	}
+
+	public double getHeight() {
+		return height;
+	}
+
+	public void setHeight(double height) {
+		this.height = height;
+	}
+
+	public char getCharacter() {
+		return character;
+	}
+
+	public void setCharacter(char character) {
+		this.character = character;
+	}
+
+	public Font getFont() {
+		return font;
+	}
+
+	public void setFont(Font font) {
+		this.font = font;
+	}
+
+	public Color getColor() {
+		return color;
+	}
+
+	public void setColor(Color color) {
+		this.color = color;
+	}
+
+	public double getAscent() {
+		return ascent;
+	}
+
+	public void setAscent(double ascent) {
+		this.ascent = ascent;
+	}
+
+	public double getDescent() {
+		return descent;
+	}
+
+	public void setDescent(double descent) {
+		this.descent = descent;
+	}
+
+	public AttributedCharacterIterator iterator() {
+		AttributedString aString = new AttributedString(String
+				.valueOf(character));
+		aString.addAttribute(TextAttribute.FONT, font, 0, 1);
+		return aString.getIterator();
+	}
+
+}

+ 38 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/TextRenderer.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+import com.zhiqiyun.open.service.impl.captcha.color.ColorFactory;
+import com.zhiqiyun.open.service.impl.captcha.font.FontFactory;
+
+import java.awt.image.BufferedImage;
+
+public interface TextRenderer {
+
+	void setLeftMargin(int leftMargin);
+
+	void setRightMargin(int rightMargin);
+
+	void setTopMargin(int topMargin);
+
+	void setBottomMargin(int bottomMargin);
+
+	void draw(String text, BufferedImage canvas, FontFactory fontFactory, ColorFactory colorFactory);
+
+}

+ 83 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/text/renderer/TextString.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.text.renderer;
+
+import java.util.ArrayList;
+
+public class TextString {
+
+	private ArrayList<TextCharacter> characters = new ArrayList<TextCharacter>();
+	
+	public void clear() {
+		characters.clear();
+	}
+	
+	public void addCharacter(TextCharacter tc) {
+		characters.add(tc);
+	}
+	
+	public ArrayList<TextCharacter> getCharacters() {
+		return characters;
+	}
+	
+	public double getWidth() {
+		double minx = 0;
+		double maxx = 0;
+		boolean first = true;
+		for (TextCharacter tc : characters) {
+			if (first) {
+				minx = tc.getX();
+				maxx = tc.getX() + tc.getWidth();
+				first = false;
+			} else {
+				if (minx > tc.getX()) {
+					minx = tc.getX(); 
+				}
+				if (maxx < tc.getX() + tc.getWidth()) {
+					maxx = tc.getX() + tc.getWidth();
+				}
+			}
+			
+		}
+		return maxx - minx;
+	}
+	
+	public double getHeight() {
+		double miny = 0;
+		double maxy = 0;
+		boolean first = true;
+		for (TextCharacter tc : characters) {
+			if (first) {
+				miny = tc.getY();
+				maxy = tc.getY() + tc.getHeight();
+				first = false;
+			} else {
+				if (miny > tc.getY()) {
+					miny = tc.getY(); 
+				}
+				if (maxy < tc.getY() + tc.getHeight()) {
+					maxy = tc.getY() + tc.getHeight();
+				}
+			}
+			
+		}
+		return maxy - miny;
+	}
+
+}

+ 58 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/word/AdaptiveRandomWordFactory.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.word;
+
+import java.util.Random;
+
+public class AdaptiveRandomWordFactory extends RandomWordFactory {
+
+	protected String wideCharacters;
+
+	public void setWideCharacters(String wideCharacters) {
+		this.wideCharacters = wideCharacters;
+	}
+
+	public AdaptiveRandomWordFactory() {
+		characters = "absdegkmnpwxABSDEGKMNPWX23456789";
+		wideCharacters = "mw";
+	}
+	
+	@Override
+	public String getNextWord() {
+		Random rnd = new Random();
+		StringBuffer sb = new StringBuffer();
+		StringBuffer chars = new StringBuffer(characters);
+		int l = minLength + (maxLength > minLength ? rnd.nextInt(maxLength - minLength) : 0);
+		for (int i = 0; i < l; i++) {
+			int j = rnd.nextInt(chars.length());
+			char c = chars.charAt(j);
+			if (wideCharacters.indexOf(c) != -1) {
+				for (int k = 0; k < wideCharacters.length(); k++) {
+					int idx = chars.indexOf(String.valueOf(wideCharacters.charAt(k)));
+					if (idx != -1) {
+						chars.deleteCharAt(idx);
+					}
+				}
+			}
+			sb.append(c);
+		}
+		return sb.toString();
+	}
+
+}

+ 60 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/word/RandomWordFactory.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.word;
+
+import java.util.Random;
+
+public class RandomWordFactory implements WordFactory {
+
+	protected String characters;
+	protected int minLength;
+	protected int maxLength;
+	
+	public void setCharacters(String characters) {
+		this.characters = characters;
+	}
+
+	public void setMinLength(int minLength) {
+		this.minLength = minLength;
+	}
+
+	public void setMaxLength(int maxLength) {
+		this.maxLength = maxLength;
+	}
+
+	public RandomWordFactory() {
+		characters = "absdegkmnopwx23456789";
+		minLength = 4;
+		maxLength = 5;
+	}
+	
+	@Override
+	public String getNextWord() {
+		Random rnd = new Random();
+		StringBuffer sb = new StringBuffer();
+		int l = minLength + (maxLength > minLength ? rnd.nextInt(maxLength - minLength) : 0);
+		for (int i = 0; i < l; i++) {
+			int j = rnd.nextInt(characters.length());
+			sb.append(characters.charAt(j));
+		}
+		return sb.toString();
+	}
+
+	
+}

+ 24 - 0
src/main/java/com/zhiqiyun/open/service/impl/captcha/word/WordFactory.java

@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2009 Piotr Piastucki
+ * 
+ * This file is part of Patchca CAPTCHA library.
+ * 
+ *  Patchca is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  Patchca is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with Patchca. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.zhiqiyun.open.service.impl.captcha.word;
+
+public interface WordFactory {
+
+	String getNextWord();
+}

+ 129 - 0
src/main/java/com/zhiqiyun/open/utils/ClassScaner.java

@@ -0,0 +1,129 @@
+package com.zhiqiyun.open.utils;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.util.StringUtils;
+import org.springframework.util.SystemPropertyUtils;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author jtoms
+ */
+public class ClassScaner implements ResourceLoaderAware {
+
+    /**
+     * 保存过滤规则要排除的注解
+     */
+    private final List<TypeFilter> includeFilters = new LinkedList<>();
+
+    private final List<TypeFilter> excludeFilters = new LinkedList<>();
+
+    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
+
+    public static Set<Class<?>> scan(String[] basePackages, Class<? extends Annotation>... annotations) {
+        ClassScaner cs = new ClassScaner();
+
+        if (ArrayUtils.isNotEmpty(annotations)) {
+            for (Class<? extends Annotation> anno : annotations) {
+                cs.addIncludeFilter(new AnnotationTypeFilter(anno));
+            }
+        }
+
+        Set<Class<?>> classes = new HashSet<>();
+        for (String s : basePackages) {
+            classes.addAll(cs.doScan(s));
+        }
+        return classes;
+    }
+
+    public static Set<Class<?>> scan(String basePackages) {
+        return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), null);
+    }
+
+    public static Set<Class<?>> scan(String basePackages, Class<? extends Annotation>... annotations) {
+        return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
+    }
+
+    public final ResourceLoader getResourceLoader() {
+        return this.resourcePatternResolver;
+    }
+
+    @Override
+    public void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
+        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
+    }
+
+    public void addIncludeFilter(TypeFilter includeFilter) {
+        this.includeFilters.add(includeFilter);
+    }
+
+    public void addExcludeFilter(TypeFilter excludeFilter) {
+        this.excludeFilters.add(0, excludeFilter);
+    }
+
+    public void resetFilters(boolean useDefaultFilters) {
+        this.includeFilters.clear();
+        this.excludeFilters.clear();
+    }
+
+    public Set<Class<?>> doScan(String basePackage) {
+        Set<Class<?>> classes = new HashSet<>();
+        try {
+            String packageSearchPath = String.format("%s%s%s",
+                    ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX,
+                    org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)),
+                    "/**/*.class");
+            Resource[] resources = this.resourcePatternResolver
+                    .getResources(packageSearchPath);
+
+            for (Resource resource : resources) {
+                if (resource.isReadable()) {
+                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
+                    if ((includeFilters.size() == 0 && excludeFilters.size() == 0) || matches(metadataReader)) {
+                        try {
+                            classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
+                        } catch (ClassNotFoundException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
+        }
+        return classes;
+    }
+
+    protected boolean matches(MetadataReader metadataReader) throws IOException {
+        for (TypeFilter tf : this.excludeFilters) {
+            if (tf.match(metadataReader, this.metadataReaderFactory)) {
+                return false;
+            }
+        }
+        for (TypeFilter tf : this.includeFilters) {
+            if (tf.match(metadataReader, this.metadataReaderFactory)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}