一、前言
最近用结合thumbnailator和Graphics2D封装了一个图片工具类,目前可以实现图片的裁剪、压缩、添加图片水印、文字水印、多行文字水印等功能,同时该工具类的实现使用了建造者模式、责任链模式、工厂模式、策略模式等多种设计模式,感觉图片处理的功能有一定的通用性,所以这次写一篇文章来分享一下这个工具类的使用方式和实现原理,代码不足之处还望大家指正,文末也会提供代码的github地址。
二、工具类的依赖和简单介绍
1、添加依赖
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.15</version>
</dependency>
<dependency>
<groupId>commons-chain</groupId>
<artifactId>commons-chain</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- 仅仅为了支持MultipartFile类型文件的加载,不加载MultipartFile这个依赖可以去掉-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.12</version>
<scope>compile</scope>
</dependency>
2、简单的使用
首先这个处理类提供了一个建造简化了工具栏使用,下面一个简单的使用示例,实现了图的裁剪裁剪,从这个示例代码可以看出这个图片处理工具类的建造者主要由load(加载需要处理的图片)、addRule(添加图片处理规则,可以多次添加)、toFile(输出处理后的图片)三部分组成。
ImageHandBuilder.load(imageResource)
.addRule(ImageHandRuleFactory.regionRuleBuilder()
.regionType(RegionTypeEnum.SCALE_REGION)
.positions(Positions.TOP_CENTER)
.scale(0.5d).build())
.toFile("D:\\test\\test-region-scale.jpg");
3、加载需要处理的图片
加载需要处理的图片的方法是load() 方法,建造者调用的第一个方法必须是这个load()方法。load()方法提供多个重载方法,支持以下传参。
/**
* 加载图片
* @param absolutePath 图片绝对路径
* @return Builder
*/
Builder load(String absolutePath);
/**
* 加载图片
* @param file 图片文件
* @return Builder
*/
Builder load(File file);
/**
* 加载图片
* @param file 图片文件
* @return Builder
*/
Builder load(MultipartFile file);
/**
* 加载图片
* @param url 图片url
* @return Builder
*/
Builder load(URL url);
/**
* 加载图片
* @param inputStream 图片输入流
* @return Builder
*/
Builder load(InputStream inputStream);
/**
* 加载图片
* @param image 图片
* @param fileType 图片类型
* @return Builder
*/
Builder load(BufferedImage image, String fileType);
4、添加图片处理规则
添加图片处理规则的方法是addRule(),支持RegionRule(裁剪规则)、CompressRule(压缩规则)、ImageWatermarkRule(图片水印规则)、TextWatermarkRule(文字水印规则)、MultipleTextWatermarkRule(多行文字水印规则)。规则的添加支持Builder方式和使用规则工厂的方法
4.1 Builder的方式
@Test
public void testBuilder() throws Exception {
ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
URL imageResource = classLoader.getResource("pic/example.jpg");
ImageHandBuilder.load(imageResource)
.addRule(RegionRule.builder()
.regionType(RegionTypeEnum.SCALE_REGION)
.positions(Positions.TOP_CENTER)
.scale(0.5d).build())
.toFile("D:\\test\\test-region-scale.jpg");
}
4.2 使用规则工厂的方式
@Test
public void testFactory() throws Exception {
ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
URL imageResource = classLoader.getResource("pic/example.jpg");
ImageHandBuilder.load(imageResource)
.addRule(ImageHandRuleFactory.regionRuleBuilder()
.regionType(RegionTypeEnum.SCALE_REGION)
.positions(Positions.TOP_CENTER)
.scale(0.5d).build())
.toFile("D:\\test\\test-region-scale.jpg");
}
5、输出处理后的图片
输出处理后的图片的方法是toFile()或toOutputStream()方法,是建造者最后需要调用的一个方法。输出处理后的图片的方法如下
/**
* 将处理后的图片导出到文件
* @param file 图片文件
*/
void toFile(File file);
/**
* 将处理后的图片导出到文件
* @param absolutePath 图片绝对路径
*/
void toFile(String absolutePath);
/**
* 将处理后的图片导出到输出流
* @param out 输出流
*/
void toOutputStream(OutputStream out);
三、使用方式
下面我们使用同一张示例图片依次演示一下RegionRule(裁剪规则)、CompressRule(压缩规则)、ImageWatermarkRule(图片水印规则)、TextWatermarkRule(文字水印规则)、MultipleTextWatermarkRule(多行文字水印规则)这五种规则的设置和实现效果,下图处理前的示例原图,图片的原始比例为3840 X 2160:
1、图片裁剪
图片裁剪提供了按长宽裁剪和按比例裁剪两种裁剪方式
图片裁剪规则实体类
import com.fhey.common.file.imagehand.enums.RegionTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.coobird.thumbnailator.geometry.Positions;
/**
* @author fhey
* @date 2022-07-08 17:42:51
* @description: 裁剪规则
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RegionRule implements ImageHandRule {
/**
* 位置裁剪
*/
private Positions positions;
/**
* 裁剪宽度
*/
private Integer width;
/**
* 裁剪高度
*/
private Integer height;
/**
* 裁剪比例
*/
private Double scale;
/**
* 裁剪类型
*/
private RegionTypeEnum regionType;
@Override
public boolean check() {
if (null == regionType){
throw new RuntimeException("裁剪类型(compressType)不能为空!");
}
if (RegionTypeEnum.SCALE_REGION.equals(regionType) && null == scale){
throw new RuntimeException("按比列裁剪模式裁剪比例(scale)不能为空!");
}
if (RegionTypeEnum.WIDTH_HEIGHT_REGION.equals(regionType) && (null == width || null == height)){
throw new RuntimeException("按宽高裁剪模式宽高不能为空!");
}
return true;
}
}
1.1 按长宽裁剪
从图片中间裁剪300 X 300的区域
1.1.1 示例代码
@Test
public void testWidthHeightRegion() throws Exception {
ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
URL imageResource = classLoader.getResource("pic/example.jpg");
ImageHandBuilder.load(imageResource)
.addRule(ImageHandRuleFactory.regionRuleBuilder()
.regionType(RegionTypeEnum.WIDTH_HEIGHT_REGION)
.positions(Positions.CENTER)
.width(300)
.height(300).build())
.toFile("D:\\test\\test-region-widthHeight.jpg");
}
1.1.2 实现效果
由下图可以看出处理后的图片正好是300 X 300,但是只截取了中间一小块
1.2 按比例裁剪
从上中开始按图片0.5的比列裁剪
1.2.1 示例代码
@Test
public void testScaleRegion() throws Exception {
ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
URL imageResource = classLoader.getResource("pic/example.jpg");
ImageHandBuilder.load(imageResource)
.addRule(ImageHandRuleFactory.regionRuleBuilder()
.regionType(RegionTypeEnum.SCALE_REGION)
.positions(Positions.TOP_CENTER)
.scale(0.5d).build())
.toFile("D:\\test\\test-region-scale.jpg");
}
1.2.2 实现效果
有下图可以看出裁剪之后长宽都是原图的一半,因为从上中开始裁剪所以原图下方一些图片被裁剪掉了
2、图片压缩
图片压缩提供了按长宽压缩和按比例压缩两种压缩方式,其中按长宽压缩又提供了不保持比列、但宽度保持比列、自动保持比列。
图片压缩规则实体类
import com.fhey.common.file.imagehand.enums.CompressTypeEnum;
import com.fhey.common.file.imagehand.enums.KeepAspectRatioEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author fhey
* @date 2022-07-08 17:28:28
* @description: 压缩规则
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CompressRule implements ImageHandRule {
/**
* 压缩宽度
*/
private Integer width;
/**
* 压缩高度
*/
private Integer height;
/**
* 压缩比例
*/
private Double scale;
/**
* 压缩类型
*/
private CompressTypeEnum compressType;
/**
* 是否保持宽高比
*/
private KeepAspectRatioEnum keepAspectRatio;
@Override
public boolean check() {
if (null == compressType){
throw new RuntimeException("压缩类型(compressType)不能为空!");
}
if (CompressTypeEnum.SCALE_COMPRESS.equals(compressType) && null == scale){
throw new RuntimeException("按比列压缩模式压缩比例(scale)不能为空!");
}
if (CompressTypeEnum.WIDTH_HEIGHT_COMPRESS.equals(compressType) && (null == width || null == height)){
throw new RuntimeException("按宽高压缩模式宽高不能为空!");
}
return true;
}
}
2.1 按长宽压缩 不保持比例
2.1.1 示例代码
将图片压缩成300 X 300
@Test
public void testWidthHeightCompressNoKeepAspectRatio() throws Exception {
ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
URL imageResource = classLoader.getResource("pic/example.jpg");
ImageHandBuilder.load(imageResource)
.addRule(ImageHandRuleFactory.compressRuleBuilder()
.compressType(CompressTypeEnum.WIDTH_HEIGHT_COMPRESS)
.width(300)
.height(300).build())
.toFile("D:\\test\\test-compress-widthHeight-noKeep.jpg");
}
2.1.2 实现效果
由下图可以看出处理后的图片是300 X300,但是图片变形严重