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);
}
}