diff --git a/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java index 50e985a..2d09012 100644 --- a/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java @@ -3,16 +3,15 @@ package cloud.tianai.captcha.generator; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; +import cloud.tianai.captcha.generator.common.util.ImgWriter; import cloud.tianai.captcha.resource.common.model.dto.Resource; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.InputStream; import java.util.Base64; import java.util.Map; @@ -59,7 +58,7 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener * @param formatType 格式化类型 * @return String */ - @SneakyThrows(IOException.class) + @SneakyThrows(Exception.class) public String transform(BufferedImage bufferedImage, String formatType) { // 这里判断处理一下,加一些警告日志 String result = beforeTransform(bufferedImage, formatType); @@ -67,7 +66,9 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener return result; } ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ImageIO.write(bufferedImage, formatType, byteArrayOutputStream); + long currentTimeMillis = System.currentTimeMillis(); + ImgWriter.write(bufferedImage, formatType, byteArrayOutputStream, -1); + System.out.println("耗时:" + (System.currentTimeMillis() - currentTimeMillis)); //转换成字节码 byte[] data = byteArrayOutputStream.toByteArray(); String base64 = Base64.getEncoder().encodeToString(data); diff --git a/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java b/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java index af5d42d..944867c 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java @@ -9,9 +9,8 @@ import java.awt.geom.Area; import java.awt.geom.CubicCurve2D; import java.awt.geom.QuadCurve2D; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; import java.awt.image.PixelGrabber; -import java.awt.image.WritableRaster; +import java.awt.image.RenderedImage; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; @@ -25,6 +24,10 @@ import java.util.concurrent.ThreadLocalRandom; */ public class CaptchaImageUtils { + public static final String TYPE_JPG = "jpg"; + public static final String TYPE_JPEG = "jpeg"; + public static final String TYPE_PNG = "png"; + @SneakyThrows public static BufferedImage wrapFile2BufferedImage(URL resourceImage) { if (resourceImage == null) { @@ -133,19 +136,6 @@ public class CaptchaImageUtils { return rec; } - /** - * 深度拷贝图片 - * - * @param bi 原图片 - * @return BufferedImage - */ - public static BufferedImage deepCopyBufferedImage(BufferedImage bi) { - ColorModel cm = bi.getColorModel(); - boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); - WritableRaster raster = bi.copyData(bi.getRaster().createCompatibleWritableRaster()); - return new BufferedImage(cm, raster, isAlphaPremultiplied, null); - } - /** * 通过模板图片抠图(不透明部分) * @@ -180,6 +170,13 @@ public class CaptchaImageUtils { return image; } + /** + * 旋转图片 + * + * @param bufferedimage + * @param degree + * @return + */ public static BufferedImage rotateImage(final BufferedImage bufferedimage, final double degree) { // 得到图片宽度。 @@ -463,6 +460,91 @@ public class CaptchaImageUtils { } + public static RenderedImage toRenderedImage(Image img) { + if (img instanceof RenderedImage) { + return (RenderedImage) img; + } + return copyImage(img, BufferedImage.TYPE_INT_RGB); + } + + /** + * 转换成指定类型的 BufferedImage + * + * @param image image + * @param imageType imageType + * @return BufferedImage + */ + public static BufferedImage toBufferedImage(Image image, String imageType) { + final int type = TYPE_PNG.equalsIgnoreCase(imageType) + ? BufferedImage.TYPE_INT_ARGB + : BufferedImage.TYPE_INT_RGB; + return toBufferedImage(image, type); + } + + /** + * 转换成指定类型的 BufferedImage + * + * @param image image + * @param imageType imageType + * @return BufferedImage + */ + public static BufferedImage toBufferedImage(Image image, int imageType) { + BufferedImage bufferedImage; + if (image instanceof BufferedImage) { + bufferedImage = (BufferedImage) image; + if (imageType != bufferedImage.getType()) { + bufferedImage = copyImage(image, imageType); + } + } else { + bufferedImage = copyImage(image, imageType); + } + return bufferedImage; + } + + /** + * 拷贝图片 + * + * @param img img + * @param imageType imageType + * @return BufferedImage + */ + public static BufferedImage copyImage(Image img, int imageType) { + return copyImage(img, imageType, null); + } + + /** + * 拷贝图片 + * + * @param img img + * @param imageType imageType + * @param backgroundColor backgroundColor + * @return BufferedImage + */ + public static BufferedImage copyImage(Image img, int imageType, Color backgroundColor) { + final BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), imageType); + final Graphics2D bGr = createGraphics(bimage, backgroundColor); + bGr.drawImage(img, 0, 0, null); + bGr.dispose(); + return bimage; + } + + /** + * 创建画板 + * + * @param image image + * @param color color + * @return Graphics2D + */ + public static Graphics2D createGraphics(BufferedImage image, Color color) { + final Graphics2D g = image.createGraphics(); + if (null != color) { + // 填充背景 + g.setColor(color); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } + return g; + } + /** * 后缀是否是jpg * @@ -470,7 +552,7 @@ public class CaptchaImageUtils { * @return boolean */ public static boolean isJpeg(String type) { - return "jpg".equalsIgnoreCase(type) || "jpeg".equalsIgnoreCase(type); + return TYPE_JPG.equalsIgnoreCase(type) || TYPE_JPEG.equalsIgnoreCase(type); } /** @@ -480,7 +562,6 @@ public class CaptchaImageUtils { * @return boolean */ public static boolean isPng(String type) { - return "png".equalsIgnoreCase(type); + return TYPE_PNG.equalsIgnoreCase(type); } - } diff --git a/src/main/java/cloud/tianai/captcha/generator/common/util/ImgWriter.java b/src/main/java/cloud/tianai/captcha/generator/common/util/ImgWriter.java new file mode 100644 index 0000000..47e099f --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/generator/common/util/ImgWriter.java @@ -0,0 +1,120 @@ +package cloud.tianai.captcha.generator.common.util; + +import cloud.tianai.captcha.common.util.ObjectUtils; +import lombok.SneakyThrows; + +import javax.imageio.*; +import javax.imageio.stream.ImageOutputStream; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; + +/** + * @Author: 天爱有情 + * @date 2022/5/9 11:47 + * @Description 拷贝from hutool(https://gitee.com/dromara/hutool/blob/v5-master/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java) + * 为了不依赖更多无用包, 单独拷贝出来 + */ +public class ImgWriter { + + /** + * 输出 + * + * @param image image + * @param imageType imageType + * @param destImageStream destImageStream + * @param quality quality 0~1 + * @return + */ + public static boolean write(Image image, String imageType, OutputStream destImageStream, float quality) { + if (ObjectUtils.isEmpty(imageType)) { + imageType = CaptchaImageUtils.TYPE_JPG; + } + ImageOutputStream imageOutputStream = transformImageOutputStream(destImageStream); + final BufferedImage bufferedImage = CaptchaImageUtils.toBufferedImage(image, imageType); + final ImageWriter writer = getWriter(bufferedImage, imageType); + return write(bufferedImage, writer, imageOutputStream, quality); + } + + /** + * 输出 + * + * @param image image + * @param writer writer + * @param output output + * @param quality quality + * @return boolean + */ + public static boolean write(Image image, ImageWriter writer, ImageOutputStream output, float quality) { + if (writer == null) { + return false; + } + writer.setOutput(output); + final RenderedImage renderedImage = toRenderedImage(image); + // 设置质量 + ImageWriteParam imgWriteParams = null; + if (quality > 0 && quality < 1) { + imgWriteParams = writer.getDefaultWriteParam(); + if (imgWriteParams.canWriteCompressed()) { + imgWriteParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + imgWriteParams.setCompressionQuality(quality); + final ColorModel colorModel = renderedImage.getColorModel();// ColorModel.getRGBdefault(); + imgWriteParams.setDestinationType(new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16))); + } + } + + try { + if (null != imgWriteParams) { + writer.write(null, new IIOImage(renderedImage, null, null), imgWriteParams); + } else { + writer.write(renderedImage); + } + output.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + writer.dispose(); + } + return true; + } + + public static RenderedImage toRenderedImage(Image img) { + if (img instanceof RenderedImage) { + return (RenderedImage) img; + } + return CaptchaImageUtils.copyImage(img, BufferedImage.TYPE_INT_RGB); + } + + /** + * 获取 ImageWriter + * + * @param img img + * @param formatName formatName + * @return ImageWriter + */ + public static ImageWriter getWriter(Image img, String formatName) { + final ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(CaptchaImageUtils.toBufferedImage(img, formatName)); + final Iterator iter = ImageIO.getImageWriters(type, formatName); + return iter.hasNext() ? iter.next() : null; + } + + /** + * 将 OutputStream 转换为 ImageOutputStream + * + * @param out out + * @return ImageOutputStream + * @throws RuntimeException + */ + @SneakyThrows(IOException.class) + public static ImageOutputStream transformImageOutputStream(OutputStream out) throws RuntimeException { + ImageOutputStream result = ImageIO.createImageOutputStream(out); + if (null == result) { + throw new IllegalArgumentException("Image type is not supported!"); + } + return result; + } +} diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java index 8ac573d..4572633 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java @@ -79,7 +79,7 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen inputStreams.add(resourceInputStream); BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream); // 拷贝一份图片 - BufferedImage targetBackground = CaptchaImageUtils.deepCopyBufferedImage(cutBackground); + BufferedImage targetBackground = CaptchaImageUtils.copyImage(cutBackground, cutBackground.getType()); InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME); inputStreams.add(fixedTemplateInput); diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java index e4e990d..a881c67 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java @@ -69,7 +69,7 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen inputStreams.add(resourceInputStream); BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream); // 拷贝一份图片 - BufferedImage targetBackground = CaptchaImageUtils.deepCopyBufferedImage(cutBackground); + BufferedImage targetBackground = CaptchaImageUtils.copyImage(cutBackground, cutBackground.getType()); InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME); inputStreams.add(fixedTemplateInput); diff --git a/src/main/test/java/example/CaptchaGenTest.java b/src/main/test/java/example/CaptchaGenTest.java index d5a0da6..f9fb32f 100644 --- a/src/main/test/java/example/CaptchaGenTest.java +++ b/src/main/test/java/example/CaptchaGenTest.java @@ -26,7 +26,7 @@ public class CaptchaGenTest { ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager(); ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore(); // 添加一些系统的资源文件 - resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(FileResourceProvider.NAME, "C:\\Users\\Thinkpad\\Desktop\\111\\test.png")); + resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(FileResourceProvider.NAME, "C:\\Users\\Thinkpad\\Desktop\\111\\66.jpg")); // 添加一些系统的 模板文件 Map template1 = new HashMap<>(4); @@ -40,18 +40,20 @@ public class CaptchaGenTest { MultiImageCaptchaGenerator imageCaptchaGenerator = new MultiImageCaptchaGenerator(imageCaptchaResourceManager, false); GenerateParam generateParam = GenerateParam.builder() .type(CaptchaTypeConstant.SLIDER) - .backgroundFormatName("jpg") - .sliderFormatName("png") + .backgroundFormatName("webp") + .sliderFormatName("webp") .obfuscate(false) .build(); - ImageCaptchaInfo imageCaptchaInfo = imageCaptchaGenerator.generateCaptchaImage(generateParam); - System.out.println(imageCaptchaInfo.getBackgroundImage()); -// System.out.println(imageCaptchaInfo.getSliderImage()); +// for (int i = 0; i < 10; i++) { + ImageCaptchaInfo imageCaptchaInfo = imageCaptchaGenerator.generateCaptchaImage(generateParam); + System.out.println(imageCaptchaInfo.getBackgroundImage()); + System.out.println(imageCaptchaInfo.getSliderImage()); - // 负责计算一些数据存到缓存中,用于校验使用 - // ImageCaptchaValidator负责校验用户滑动滑块是否正确和生成滑块的一些校验数据; 比如滑块到凹槽的百分比值 - ImageCaptchaValidator imageCaptchaValidator = new BasicCaptchaTrackValidator(); - // 这个map数据应该存到缓存中,校验的时候需要用到该数据 - Map map = imageCaptchaValidator.generateImageCaptchaValidData(imageCaptchaInfo); + // 负责计算一些数据存到缓存中,用于校验使用 + // ImageCaptchaValidator负责校验用户滑动滑块是否正确和生成滑块的一些校验数据; 比如滑块到凹槽的百分比值 + ImageCaptchaValidator imageCaptchaValidator = new BasicCaptchaTrackValidator(); + // 这个map数据应该存到缓存中,校验的时候需要用到该数据 + Map map = imageCaptchaValidator.generateImageCaptchaValidData(imageCaptchaInfo); +// } } }