From 8ddb2b425c03089460beadcede3e0d0312fac797 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 10 Aug 2021 20:54:31 +0800 Subject: [PATCH] add methods --- CHANGELOG.md | 3 +- .../src/main/java/cn/hutool/core/img/Img.java | 39 ++++++++------ .../main/java/cn/hutool/core/img/ImgUtil.java | 52 +++++++++++++++++-- .../extra/tokenizer/TokenizerUtilTest.java | 41 +++++++-------- 4 files changed, 90 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c9311a9..ab5e997bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.8 (2021-08-09) +# 5.7.8 (2021-08-10) ### 🐣新特性 * 【core 】 MapProxy支持return this的setter方法(pr#392@Gitee) @@ -14,6 +14,7 @@ ### 🐞Bug修复 * 【core 】 改进NumberChineseFormatter算法,补充完整单元测试,解决零问题 +* 【core 】 修复Img变换操作图片格式问题(issue#I44JRB@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 3627ba71a..a97557337 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -27,9 +27,7 @@ import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.RoundRectangle2D; -import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; -import java.awt.image.ColorConvertOp; import java.awt.image.CropImageFilter; import java.awt.image.FilteredImageSource; import java.awt.image.ImageFilter; @@ -230,19 +228,19 @@ public class Img implements Serializable { // 自动修正负数 scale = -scale; } - final Image srcImg = getValidSrcImg(); // PNG图片特殊处理 if (ImgUtil.IMAGE_TYPE_PNG.equals(this.targetImageType)) { - final AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(scale, scale), null); - this.targetImage = op.filter(ImgUtil.toBufferedImage(srcImg), null); + // 修正float转double导致的精度丢失 + final double scaleDouble = NumberUtil.toDouble(scale); + this.targetImage = ImgUtil.transform(AffineTransform.getScaleInstance(scaleDouble, scaleDouble), + ImgUtil.toBufferedImage(srcImg, this.targetImageType)); } else { - final String scaleStr = Float.toString(scale); // 缩放后的图片宽 - int width = NumberUtil.mul(Integer.toString(srcImg.getWidth(null)), scaleStr).intValue(); + final int width = NumberUtil.mul((Number) srcImg.getWidth(null), scale).intValue(); // 缩放后的图片高 - int height = NumberUtil.mul(Integer.toString(srcImg.getHeight(null)), scaleStr).intValue(); + final int height = NumberUtil.mul((Number) srcImg.getHeight(null), scale).intValue(); scale(width, height); } return this; @@ -277,8 +275,8 @@ public class Img implements Serializable { double sy = NumberUtil.div(height, srcHeight); if (ImgUtil.IMAGE_TYPE_PNG.equals(this.targetImageType)) { - final AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(sx, sy), null); - this.targetImage = op.filter(ImgUtil.toBufferedImage(srcImg), null); + this.targetImage = ImgUtil.transform(AffineTransform.getScaleInstance(sx, sy), + ImgUtil.toBufferedImage(srcImg, this.targetImageType)); } else { this.targetImage = srcImg.getScaledInstance(width, height, scaleType); } @@ -347,8 +345,7 @@ public class Img implements Serializable { fixRectangle(rectangle, srcImage.getWidth(null), srcImage.getHeight(null)); final ImageFilter cropFilter = new CropImageFilter(rectangle.x, rectangle.y, rectangle.width, rectangle.height); - final Image image = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(srcImage.getSource(), cropFilter)); - this.targetImage = ImgUtil.toBufferedImage(image); + this.targetImage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(srcImage.getSource(), cropFilter)); return this; } @@ -428,8 +425,7 @@ public class Img implements Serializable { * @return this */ public Img gray() { - final ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); - this.targetImage = op.filter(ImgUtil.toBufferedImage(getValidSrcImg()), null); + this.targetImage = ImgUtil.colorConvert(ColorSpace.getInstance(ColorSpace.CS_GRAY), getValidSrcBufferedImg()); return this; } @@ -456,7 +452,7 @@ public class Img implements Serializable { * @return 处理后的图像 */ public Img pressText(String pressText, Color color, Font font, int x, int y, float alpha) { - final BufferedImage targetImage = ImgUtil.toBufferedImage(getValidSrcImg()); + final BufferedImage targetImage = ImgUtil.toBufferedImage(getValidSrcImg(), this.targetImageType); final Graphics2D g = targetImage.createGraphics(); if (null == font) { @@ -477,6 +473,7 @@ public class Img implements Serializable { new Point(x, y)); } + // 收笔 g.dispose(); this.targetImage = targetImage; @@ -579,7 +576,7 @@ public class Img implements Serializable { * @since 5.4.1 */ public Img stroke(Color color, Stroke stroke){ - final BufferedImage image = ImgUtil.toBufferedImage(getValidSrcImg()); + final BufferedImage image = ImgUtil.toBufferedImage(getValidSrcImg(), this.targetImageType); int width = image.getWidth(null); int height = image.getHeight(null); Graphics2D g = image.createGraphics(); @@ -717,6 +714,16 @@ public class Img implements Serializable { return ObjectUtil.defaultIfNull(this.targetImage, this.srcImage); } + /** + * 获取有效的源{@link BufferedImage}图片,首先检查上一次处理的结果图片,如无则使用用户传入的源图片 + * + * @return 有效的源图片 + * @since 5.7.8 + */ + private BufferedImage getValidSrcBufferedImg() { + return ImgUtil.toBufferedImage(getValidSrcImg(), this.targetImageType); + } + /** * 修正矩形框位置,如果{@link Img#setPositionBaseCentre(boolean)} 设为{@code true},则坐标修正为基于图形中心,否则基于左上角 * diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java index 54e4b181e..a990f96c6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java @@ -30,10 +30,14 @@ import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; +import java.awt.color.ColorSpace; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorConvertOp; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.io.ByteArrayInputStream; @@ -451,9 +455,9 @@ public class ImgUtil { } /** - * 图像切割(指定切片的行数和列数) + * 图像切割(指定切片的行数和列数),默认RGB模式 * - * @param srcImage 源图像 + * @param srcImage 源图像,如果非{@link BufferedImage},则默认使用RGB模式 * @param destDir 切片目标文件夹 * @param rows 目标切片行数。默认2,必须是范围 [1, 20] 之内 * @param cols 目标切片列数。默认2,必须是范围 [1, 20] 之内 @@ -1167,8 +1171,8 @@ public class ImgUtil { */ public static BufferedImage toBufferedImage(Image image, String imageType) { final int type = IMAGE_TYPE_PNG.equalsIgnoreCase(imageType) - ? BufferedImage.TYPE_INT_ARGB - : BufferedImage.TYPE_INT_RGB; + ? BufferedImage.TYPE_INT_ARGB + : BufferedImage.TYPE_INT_RGB; return toBufferedImage(image, type); } @@ -1652,7 +1656,7 @@ public class ImgUtil { * @return {@link Image} * @since 5.5.8 */ - public static Image getImage(URL url){ + public static Image getImage(URL url) { return Toolkit.getDefaultToolkit().getImage(url); } @@ -2148,4 +2152,42 @@ public class ImgUtil { public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) { return BackgroundRemoval.backgroundRemoval(outputStream, override, tolerance); } + + /** + * 图片颜色转换
+ * 可以使用灰度 (gray)等 + * + * @param colorSpace 颜色模式,如灰度等 + * @param image 被转换的图片 + * @return 转换后的图片 + * @since 5.7.8 + */ + public static BufferedImage colorConvert(ColorSpace colorSpace, BufferedImage image) { + return filter(new ColorConvertOp(colorSpace, null), image); + } + + /** + * 转换图片
+ * 可以使用一系列平移 (translation)、缩放 (scale)、翻转 (flip)、旋转 (rotation) 和错切 (shear) 来构造仿射变换。 + * + * @param xform 2D仿射变换,它执行从 2D 坐标到其他 2D 坐标的线性映射,保留了线的“直线性”和“平行性”。 + * @param image 被转换的图片 + * @return 转换后的图片 + * @since 5.7.8 + */ + public static BufferedImage transform(AffineTransform xform, BufferedImage image) { + return filter(new AffineTransformOp(xform, null), image); + } + + /** + * 图片过滤转换 + * + * @param op 过滤操作实现,如二维转换可传入{@link AffineTransformOp} + * @param image 原始图片 + * @return 过滤后的图片 + * @since 5.7.8 + */ + public static BufferedImage filter(BufferedImageOp op, BufferedImage image) { + return op.filter(image, null); + } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java index ce20c3989..875d8880f 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java @@ -10,19 +10,16 @@ import cn.hutool.extra.tokenizer.engine.mmseg.MmsegEngine; import cn.hutool.extra.tokenizer.engine.mynlp.MynlpEngine; import cn.hutool.extra.tokenizer.engine.word.WordEngine; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; -import java.util.Iterator; - /** * 模板引擎单元测试 - * + * * @author looly * */ public class TokenizerUtilTest { - + String text = "这两个方法的区别在于返回值"; @Test @@ -32,73 +29,71 @@ public class TokenizerUtilTest { Result result = engine.parse(text); checkResult(result); } - + @Test public void hanlpTest() { TokenizerEngine engine = new HanLPEngine(); Result result = engine.parse(text); - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这 两 个 方法 的 区别 在于 返回 值", resultStr); } - + @Test public void ikAnalyzerTest() { TokenizerEngine engine = new IKAnalyzerEngine(); Result result = engine.parse(text); - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这两个 方法 的 区别 在于 返回值", resultStr); } - + @Test public void jcsegTest() { TokenizerEngine engine = new JcsegEngine(); Result result = engine.parse(text); checkResult(result); } - + @Test public void jiebaTest() { TokenizerEngine engine = new JiebaEngine(); Result result = engine.parse(text); - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回值", resultStr); } - + @Test public void mmsegTest() { TokenizerEngine engine = new MmsegEngine(); Result result = engine.parse(text); checkResult(result); } - + @Test public void smartcnTest() { TokenizerEngine engine = new SmartcnEngine(); Result result = engine.parse(text); - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这 两 个 方法 的 区别 在于 返回 值", resultStr); } - + @Test public void wordTest() { TokenizerEngine engine = new WordEngine(); Result result = engine.parse(text); - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这两个 方法 的 区别 在于 返回值", resultStr); } - + @Test - @Ignore public void mynlpTest() { - // 此单元测试需要JDK8,默认忽略 TokenizerEngine engine = new MynlpEngine(); Result result = engine.parse(text); - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回 值", resultStr); } - + private void checkResult(Result result) { - String resultStr = IterUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join(result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回 值", resultStr); } }