封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

这篇具有很好参考价值的文章主要介绍了封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 一、前言
    • 二、工具类的依赖和简单介绍
    • 1、添加依赖
    • 2、简单的使用
    • 3、加载需要处理的图片
    • 4、添加图片处理规则
      • 4.1 Builder的方式
      • 4.2 使用规则工厂的方式
    • 5、输出处理后的图片
  • 三、使用方式
    • 1、图片裁剪
      • 图片裁剪规则实体类
      • 1.1 按长宽裁剪
        • 1.1.1 示例代码
        • 1.1.2 实现效果
      • 1.2 按比例裁剪
        • 1.2.1 示例代码
        • 1.2.2 实现效果
    • 2、图片压缩
      • 图片压缩规则实体类
      • 2.1 按长宽压缩 不保持比例
        • 2.1.1 示例代码
        • 2.1.2 实现效果
      • 2.2 按长宽压缩 按宽度保持长宽比例
        • 2.2.1 示例代码
        • 2.2.2 实现效果
      • 2.3 按长宽压缩 自动保持长宽比例
        • 2.3.1 示例代码
        • 2.3.2 实现效果
      • 2.4 按比例压缩
        • 2.4.1 示例代码
        • 2.4.2 实现效果
    • 3、结合图片压缩和图片裁剪生成缩略图
      • 3.1 错误的写法
        • 3.1.1 错误的代码示例:
      • 3.1.2 实现效果
      • 3.2 正确的写法
        • 3.2.1 正确的代码示例:
        • 3.2.2 实现效果
    • 4、添加图片水印
      • 添加图片水印规则实体类
      • 4.1 普通的添加图片水印
        • 4.1.1 示例代码
        • 4.1.2 实现效果
      • 4.2 添加图片水印 按占原图宽高比
        • 4.2.1 示例代码
        • 4.2.2 实现效果
    • 5、添加文字水印
      • 添加文字水印实体类
      • 5.1 普通的添加文字水印
        • 5.1.1 示例代码
        • 5.1.2 实现效果
      • 5.2 添加文字水印 按占原图宽高比
        • 5.2.1 示例代码
        • 5.2.2 实现效果
    • 5、添加多行文字水印
      • 添加多行文字水印规则实体类
        • 5.1 示例代码
        • 5.2 实现效果
    • 6、最后再来叠个buff,把几种规则一起使用
        • 5.1 示例代码
        • 5.2 实现效果
  • 四、主要的代码实现
    • 1、建造者的实现
    • 2、责任链的使用
      • 2.1 初始化责任链
      • 2.2 为责任链添加任务
    • 3、执行责任链
    • 4、图片裁剪的实现
      • 实现代码
    • 5、图片压缩的实现
      • 5.1 按比例压缩图片
      • 5.2 按长宽压缩图片
      • 5.3 实现代码
    • 6、添加水印的实现
      • 6.1 添加水印通用代码
      • 6.2 添加图片水印的实现
      • 6.3 添加文字水印的实现
      • 6.4 添加多行文字水印的实现
  • 总结

一、前言

最近用结合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,但是图片变形严重
封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

2.2 按长宽压缩 按宽度保持长宽比例

将图片压缩成300 X 300,这里的缩 按宽度保持比例采用了thumbnailator提供的默认的保持长宽比列的方式

2.2.1 示例代码

@Test
    public void testWidthHeightCompressKeepAspectRatioByWidth() 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)
                        .keepAspectRatio(KeepAspectRatioEnum.KEEP_BY_WITH).build())
                .toFile("D:\\test\\test-compress-widthHeight-keep-byWidth.jpg");
    }

2.2.2 实现效果

由下图可以看出thumbnailator默认的保持长宽比的做法,将宽度处理成了300,但是高度只有169,不到300了
封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

2.3 按长宽压缩 自动保持长宽比例

将图片压缩成300 X 300,因为thumbnailator默认的保持长宽比的做法在某些场景下不符合需求,所以我有自己扩展了自定义的一个保持长宽比例的做法,即在保持长宽比的情况下让处理后的图片宽高不会低于设定值。

2.3.1 示例代码

@Test
     public void testWidthHeightCompressKeepAspectRatioAuto() 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)
                        .keepAspectRatio(KeepAspectRatioEnum.KEEP_AUTO).build())
                .toFile("D:\\test\\test-compress-widthHeight-keep-auto.jpg");
    }

2.3.2 实现效果

由下图可以看出处理后的图片,宽度533,高度300
封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

2.4 按比例压缩

按图片0.5的比列压缩

2.4.1 示例代码

@Test
    public void testScaleCompress() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.compressRuleBuilder()
                        .compressType(CompressTypeEnum.SCALE_COMPRESS)
                        .scale(0.5d).build())
                .toFile("D:\\test\\test-compress-scale.jpg");
    }

2.4.2 实现效果

可以看出压缩之后长宽都是原图的一半
封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

3、结合图片压缩和图片裁剪生成缩略图

在需要展示图片的列表中,比如电商中常见的商品列表,为了提升列表的性能,列表里展示的图片往往都是缩略图。但是通上面的图片压缩和图片裁剪把图片处理成300 X300可以发现处理后的图片图片变形严重要么图片细节缺失过多展示效果不好,那我们应该怎么生成一张符合尺寸且保证展示效果的缩略图呢?方法就是将图片压缩和图片裁剪结合起来使用,先根据缩略图的尺寸要求压缩图片(压缩后的长宽都不能低于缩略图的尺寸),然后再根据缩略图的尺寸裁剪图片。代码示例和展示效果如下。

3.1 错误的写法

3.1.1 错误的代码示例:

		int width = 300;
        int height = 300;
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.compressRuleBuilder()
                        .compressType(CompressTypeEnum.WIDTH_HEIGHT_COMPRESS)
                        .width(width)
                        .height(height)
                        .keepAspectRatio(KeepAspectRatioEnum.KEEP_AUTO).build())
                .addRule(ImageHandRuleFactory.regionRuleBuilder()
                        .regionType(RegionTypeEnum.WIDTH_HEIGHT_REGION)
                        .positions(Positions.CENTER)
                        .width(width)
                        .height(height).build())
                .toFile("D:\\test\\test-compressAndRegion.jpg");

因为thumbnailator的裁剪都是对原图的裁剪这样使用只会使之前的压缩规则实现,造成只会按原图裁剪的情况。执行效果等同于

		int width = 300;
        int height = 300;
        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(width)
                        .height(height).build())
                .toFile("D:\\test\\test-compressAndRegion.jpg");

3.1.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

3.2 正确的写法

3.2.1 正确的代码示例:

所以正确的做法是需要压缩图片之后先输出图片,在第二次调用建造者加载图片,再进行图片裁剪。

正确的代码示例:

 @Test
    public void testCompressAndRegion() throws Exception {
        int width = 300;
        int height = 300;
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.compressRuleBuilder()
                        .compressType(CompressTypeEnum.WIDTH_HEIGHT_COMPRESS)
                        .width(width)
                        .height(height)
                        .keepAspectRatio(KeepAspectRatioEnum.KEEP_AUTO).build())
                .toFile("D:\\test\\test-compressAndRegion.jpg");
        ImageHandBuilder.load("D:\\test\\test-compressAndRegion.jpg")
                .addRule(ImageHandRuleFactory.regionRuleBuilder()
                        .regionType(RegionTypeEnum.WIDTH_HEIGHT_REGION)
                        .positions(Positions.CENTER)
                        .width(width)
                        .height(height).build())
                .toFile("D:\\test\\test-compressAndRegion.jpg");
    }

3.2.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

4、添加图片水印

水印规则基类:

import com.fhey.common.file.imagehand.rule.ImageHandRule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import net.coobird.thumbnailator.geometry.Positions;

@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseImageWatermarkRule implements ImageHandRule {

    /**
     * 水印位置
     */
    private Positions positions;

    /**
     * 水印透明度
     */
    private Double alpha;

    @Override
    //设置水印可以重复设置
    public boolean canRepeat() {
        return true;
    }
}

添加图片水印规则实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.awt.image.BufferedImage;

/**
 * @author fhey
 * @date 2022-07-08 18:00:57
 * @description: 
 */
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class ImageWatermarkRule extends BaseImageWatermarkRule {
    /**
     * 水印图片
     */
    private BufferedImage waterImage;

    /**
     * 水印占原图宽高比
     */
    private Double proportion;


    @Override
    public boolean check() {
        if(null == waterImage){
            throw new RuntimeException("水印图片(waterImage)不能为空!");
        }
        if(null == super.getPositions()){
            throw new RuntimeException("水印位置(positions)不能为空!");
        }
        return true;
    }
}

4.1 普通的添加图片水印

4.1.1 示例代码

 @Test
    public void testImageWatermark() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL syResource = classLoader.getResource("pic/watermark.png");
        InputStream syInput = syResource.openStream();
        BufferedImage syImage = ImageIO.read(syInput);
        ;
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .imageBuilder()
                        .waterImage(syImage)
                        .alpha(0.5d)
                        .positions(Positions.BOTTOM_RIGHT).build())
                .toFile("D:\\test\\test-watermark-image.jpg");
    }

4.1.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

4.2 添加图片水印 按占原图宽高比

按占原图0.3宽高比添加图片水印

4.2.1 示例代码

@Test
    public void testImageWatermarkProportion() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL syResource = classLoader.getResource("pic/watermark.png");
        InputStream syInput = syResource.openStream();
        BufferedImage syImage = ImageIO.read(syInput);
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .imageBuilder()
                        .waterImage(syImage)
                        .proportion(0.3d)
                        .alpha(0.5d)
                        .positions(Positions.BOTTOM_RIGHT).build())
                .toFile("D:\\test\\test-watermark-image-proportion.jpg");
    }

4.2.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

5、添加文字水印

添加文字水印实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.awt.*;

/**
 * @author fhey
 * @date 2022-07-08 18:00:57
 * @description: 
 */
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class TextWatermarkRule extends BaseImageWatermarkRule {
    /**
     * 水印文字
     */
    private String text;

    /**
     * 水印文字颜色
     */
    private Color color;

    /**
     * 水印文字字体
     */
    private Font font;

    /**
     * 水印占原图宽高比
     */
    private Double proportion;

    /**
     * 和边缘的间距
     */
    private int margin;

    @Override
    public boolean check() {
        if(null == text){
            throw new RuntimeException("水印文字(水印文字)不能为空!");
        }
        if (null == font){
            throw new RuntimeException("水印文字字体(font)不能为空!");
        }
        if(null == super.getPositions()){
            throw new RuntimeException("水印位置(positions)不能为空!");
        }
        return true;
    }
}

5.1 普通的添加文字水印

5.1.1 示例代码

@Test
    public void testTextWatermark() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .textBuilder()
                        .text("@知北游")
                        .color(Color.WHITE)
                        .font(new Font("宋体", Font.BOLD, 100))
                        .margin(20)
                        .alpha(0.8d)
                        .positions(Positions.BOTTOM_RIGHT).build())
                .toFile("D:\\test\\test-watermark-text.jpg");
    }

5.1.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

5.2 添加文字水印 按占原图宽高比

按占原图0.3宽高比添加文字水印

5.2.1 示例代码

@Test
    public void testTextWatermarkProportion() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .textBuilder()
                        .text("@知北游")
                        .color(Color.WHITE)
                        .font(new Font("宋体", Font.BOLD, 100))
                        .margin(20)
                        .proportion(0.3d)
                        .alpha(0.8d)
                        .positions(Positions.BOTTOM_RIGHT).build())
                .toFile("D:\\test\\test-watermark-text-proportion.jpg");
    }

5.2.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

5、添加多行文字水印

添加多行文字水印规则实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import net.coobird.thumbnailator.geometry.Positions;
import java.awt.*;

/**
 * @author fhey
 * @date 2022-07-08 18:00:57
 * @description: 
 */
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class MultipleTextWatermarkRule extends BaseImageWatermarkRule {
    /**
     * 水印文字
     */
    private String text;

    /**
     * 水印文字颜色
     */
    private Color color;

    /**
     * 水印文字字体
     */
    private Font font;

    /**
     * 旋转角度
     */
    private Double rotateDegree;

    /**
     * x轴间距
     */
    private Integer xSpace;

    /**
     * y轴间距
     */
    private Integer ySpace;

    @Override
    public boolean check() {
        super.setPositions(Positions.CENTER);
        if(null == text){
            throw new RuntimeException("水印文字(水印文字)不能为空!");
        }
        if (null == font){
            throw new RuntimeException("水印文字字体(font)不能为空!");
        }
        if (null == rotateDegree){
            throw new RuntimeException("水印透明度(rotateDegree)不能为空!");
        }
        if (null == rotateDegree){
            throw new RuntimeException("旋转角度(rotateDegree)不能为空!");
        }
        if (null == xSpace){
            throw new RuntimeException("x轴间距(xSpace)不能为空!");
        }
        if (null == ySpace){
            throw new RuntimeException("y轴间距(ySpace)不能为空!");
        }
        return true;
    }
}

5.1 示例代码

@Test
    public void testMultipleTextWatermarkProportion() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        ImageHandBuilder.load(imageResource)
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .multipleTextBuilder()
                        .rotateDegree(-35d)
                        .ySpace(500)
                        .xSpace(350)
                        .color(Color.RED)
                        .font(new Font("宋体", Font.BOLD, 30))
                        .text("三十功名尘与土,八千里路云和月")
                        .alpha(0.7d)
                        .build())
                .toFile("D:\\test\\test-watermark-multipleText.jpg");
    }

5.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

6、最后再来叠个buff,把几种规则一起使用

5.1 示例代码

@Test
    public void testAddBuff() throws Exception {
        ClassLoader classLoader = ImageHandBuilderTest.class.getClassLoader();
        URL imageResource = classLoader.getResource("pic/example.jpg");
        URL syResource = classLoader.getResource("pic/watermark.png");
        InputStream syInput = syResource.openStream();
        BufferedImage syImage = ImageIO.read(syInput);
        ImageHandBuilder.load(imageResource)
                //按比例压缩
                .addRule(ImageHandRuleFactory.compressRuleBuilder()
                        .compressType(CompressTypeEnum.SCALE_COMPRESS)
                        .scale(0.5d).build())
                //添加图片水印
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .imageBuilder()
                        .waterImage(syImage)
                        .alpha(0.5d)
                        .positions(Positions.BOTTOM_RIGHT).build())
                //添加文字水印
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .textBuilder()
                        .text("@知北游")
                        .color(Color.WHITE)
                        .font(new Font("宋体", Font.BOLD, 70))
                        .margin(20)
                        //.proportion(0.3d)
                        .alpha(0.8d)
                        .positions(Positions.BOTTOM_LEFT).build())
                //添加多行文字水印
                .addRule(ImageHandRuleFactory.waterRuleBuilder()
                        .multipleTextBuilder()
                        .rotateDegree(-35d)
                        .ySpace(300)
                        .xSpace(250)
                        .color(Color.RED)
                        .font(new Font("宋体", Font.BOLD, 30))
                        .text("三十功名尘与土,八千里路云和月")
                        .alpha(0.7d)
                        .build())
                .toFile("D:\\test\\test-addBuff.jpg");
    }

5.2 实现效果

封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能

四、主要的代码实现

1、建造者的实现

建造者的实现的实现很简单在ImageHandBuilder类里建立一个Builder的内部类,通过调用静态方法load()实例化Builder来实现建造者,具体代码如下:

package com.fhey.common.file.imagehand;

import cn.hutool.core.io.FileTypeUtil;
import com.fhey.common.file.imagehand.constants.ImageHandConstants;
import com.fhey.common.file.imagehand.rule.CompressRule;
import com.fhey.common.file.imagehand.rule.ImageHandRule;
import com.fhey.common.file.imagehand.base.ImageHandCommand;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.chain.Chain;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.impl.ContextBase;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * @author fhey
 * @date 2022-07-08 17:02:11
 * @description: 图片处理器
 */
public final class ImageHandBuilder {

    /**
     * 加载图片
     * @param absolutePath 图片绝对路径
     * @return Builder
     */
    public static Builder load(String absolutePath) {
        return load(new File(absolutePath));
    }

    /**
     * 加载图片
     * @param file 图片文件
     * @return Builder
     */
    public static Builder load(File file) {
        try {
            return load(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加载图片
     * @param file 图片文件
     * @return Builder
     */
    public static Builder load(MultipartFile file) {
        try {
            return load(file.getInputStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加载图片
     * @param url 图片url
     * @return Builder
     */
    public static Builder load(URL url){
        try {
            return load(url.openStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加载图片
     * @param inputStream 图片输入流
     * @return Builder
     */
    public static Builder load(InputStream inputStream) {
        try (ByteArrayOutputStream byteArrayOutputStream = cloneInputStream(inputStream);
             InputStream inputStream1 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
             InputStream inputStream2 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray())){
            String fileType = FileTypeUtil.getType(inputStream1);
            BufferedImage image = ImageIO.read(inputStream2);
            inputStream.close();
            return load(image, fileType);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加载图片
     * @param image 图片
     * @param fileType 图片类型
     * @return Builder
     */
    public static Builder load(BufferedImage image, String fileType) {
        return new Builder(image, fileType);
    }

    private static ByteArrayOutputStream cloneInputStream(InputStream input) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = input.read(buffer)) > -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static class Builder {
        /**
         * 图片处理责任链
         */
        private Chain execChain;

        /**
         * 文件类型
         */
        private String fileType;

        /**
         * 需要处理的图片
         */
        private BufferedImage image;

        private  Thumbnails.Builder<? extends BufferedImage> thumbnails;

        private Set<Class<? extends ImageHandRule>> ruleSet;

        public Builder(BufferedImage image, String fileType) {
            this.image = image;
            this.fileType = fileType.toUpperCase();
            this.thumbnails = Thumbnails.of(image);
            this.execChain = new ChainBase();
            this.ruleSet = new HashSet<>();
        }

        /**
         * 添加图片处理规则
         * @param rule 图片处理规则
         * @return Builder
         */
        public Builder addRule(ImageHandRule rule) {
            //检查规则
            rule.check();
            //检查规则是否重复
            if(!rule.canRepeat()){
                if(this.ruleSet.contains(rule.getClass())){
                    throw new RuntimeException("规则:" + rule.getClass().getSimpleName() + "不能重复添加");
                }
                this.ruleSet.add(rule.getClass());
            }
            //给图片处理责任链添加任务
            ImageHandCommand instance = ImageHandCommand.getInstance(rule);
            execChain.addCommand(instance);
            return this;
        }

        /**
         * 返回Thumbnails.Builder 方便自定义处理
         * @return Thumbnails.Builder
         */
        public Thumbnails.Builder<? extends BufferedImage> getThumbnails() {
            return this.thumbnails;
        }

        /**
         * 开始处理图片
         * @return Builder
         */
        private Builder process() throws Exception {
            Context context = new ContextBase();
            context.put(ImageHandConstants.PROCESS_IMAGE_KEY, this.image);
            context.put(ImageHandConstants.TARGET_KEY, this.thumbnails);
            //开始执行图片处理责任链
            execChain.execute(context);
            thumbnails.outputQuality(1f).outputFormat(this.fileType);
            //如果没有压缩规则,则不压缩
            if(!ruleSet.contains(CompressRule.class)){
                thumbnails.scale(1f);
            }
            return this;
        }

        /**
         * 将处理后的图片导出到文件
         * @param file 图片文件
         */
        public void toFile(File file) throws Exception {
            this.process();
            this.thumbnails.toFile(file);
        }

        /**
         * 将处理后的图片导出到文件
         * @param absolutePath 图片绝对路径
         */
        public void toFile(String absolutePath) throws Exception {
            this.process();
            this.thumbnails.toFile(absolutePath);
        }

        /**
         * 将处理后的图片导出到输出流
         * @param out 输出流
         */
        public void toOutputStream(OutputStream out) throws Exception {
            this.process();
            this.thumbnails.toOutputStream(out);
        }
    }
}

2、责任链的使用

2.1 初始化责任链

工具类使用了apache包下的责任链ChainBase。在调用load())初始Builder类是创建了一个责任链

 	public static Builder load(BufferedImage image, String fileType) {
     	return new Builder(image, fileType);
  	}
  
  	public Builder(BufferedImage image, String fileType) {
         this.image = image;
         this.fileType = fileType.toUpperCase();
         this.thumbnails = Thumbnails.of(image);
         this.execChain = new ChainBase();
         this.ruleSet = new HashSet<>();
  	}

2.2 为责任链添加任务

在调用添加图片处理规则方法addRule()时会将图片处理规则封装成责任链命令并往初始化后的责任链里添加任务

 /**
         * 添加图片处理规则
         * @param rule 图片处理规则
         * @return Builder
         */
        public Builder addRule(ImageHandRule rule) {
            //检查规则
            rule.check();
            //检查规则是否重复
            if(!rule.canRepeat()){
                if(this.ruleSet.contains(rule.getClass())){
                    throw new RuntimeException("规则:" + rule.getClass().getSimpleName() + "不能重复添加");
                }
                this.ruleSet.add(rule.getClass());
            }
            //给图片处理责任链添加任务
            ImageHandCommand instance = ImageHandCommand.getInstance(rule);
            execChain.addCommand(instance);
            return this;
        }

责任链任务实例工厂:

import com.fhey.common.file.imagehand.constants.ImageHandConstants;
import com.fhey.common.file.imagehand.hand.BaseImageHand;
import com.fhey.common.file.imagehand.rule.ImageHandRule;
import lombok.Data;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

import java.awt.image.BufferedImage;
import java.io.InputStream;

@Data
public
class ImageHandCommand implements Command {
    private ImageHandCommand(){}

    /**
     * 获取图片处理器责任链任务实例
     * @param rule 图片处理规则
     * @return 图片处理器
     */
    public static ImageHandCommand getInstance(ImageHandRule rule) {
        ImageHandCommand imageHandCommand = new ImageHandCommand();
        imageHandCommand.setRule(rule);
        BaseImageHand imageHand = ImageHandConstants.getImageHand(rule.getClass());
        if (imageHand == null) {
            throw new RuntimeException("规则:" + rule.getClass().getSimpleName() + "未找到对应的处理器");
        }
        imageHandCommand.setImageHand(imageHand);
        return imageHandCommand;
    }

    /**
     * 图片处理规则
     */
    private ImageHandRule rule;

    /**
     * 图片处理器
     */
    private BaseImageHand imageHand;


    @Override
    public boolean execute(Context context) {
        BufferedImage image =(BufferedImage) context.get(ImageHandConstants.PROCESS_IMAGE_KEY);
        Thumbnails.Builder<? extends InputStream> thumbnails = (Thumbnails.Builder<? extends InputStream>) context.get(ImageHandConstants.TARGET_KEY);
        return this.imageHand.hand(thumbnails, this.rule, image);
    }
}

图片处理静态类:

/**
 * @author fhey
 * @date 2022-07-08 17:20:29
 * @description: 
 */
public class ImageHandConstants {

    public final  static  String PROCESS_IMAGE_KEY = "image";

    public final  static  String TARGET_KEY = "target";

    private final static Map<Class<? extends ImageHandRule>, BaseImageHand> ruleHandMap = new HashMap<>();

    static {
        ruleHandMap.put(RegionRule.class, new RegionHandImageHand());
        ruleHandMap.put(CompressRule.class, new CompressHandImageHand());
        ruleHandMap.put(ImageWatermarkRule.class, new ImageHandWatermarkHand());
        ruleHandMap.put(TextWatermarkRule.class, new TextImageHandWatermarkHand());
        ruleHandMap.put(MultipleTextWatermarkRule.class, new MultipleTextWatermarkHand());
    }

    public static BaseImageHand getImageHand(Class<? extends ImageHandRule> ruleClass) {
        return ruleHandMap.get(ruleClass);
    }
}

3、执行责任链

在最后执行的图片输出方法toFile()、toOutputStream()中都会调用process()方法

		/**
         * 将处理后的图片导出到文件
         * @param file 图片文件
         */
        public void toFile(File file) throws Exception {
            this.process();
            this.thumbnails.toFile(file);
        }

        /**
         * 将处理后的图片导出到文件
         * @param absolutePath 图片绝对路径
         */
        public void toFile(String absolutePath) throws Exception {
            this.process();
            this.thumbnails.toFile(absolutePath);
        }

        /**
         * 将处理后的图片导出到输出流
         * @param out 输出流
         */
        public void toOutputStream(OutputStream out) throws Exception {
            this.process();
            this.thumbnails.toOutputStream(out);
        }

而在process()方法中执行了执行责任链

		/**
         * 开始处理图片
         * @return Builder
         */
        private Builder process() throws Exception {
            Context context = new ContextBase();
            context.put(ImageHandConstants.PROCESS_IMAGE_KEY, this.image);
            context.put(ImageHandConstants.TARGET_KEY, this.thumbnails);
            //开始执行图片处理责任链
            execChain.execute(context);
            thumbnails.outputQuality(1f).outputFormat(this.fileType);
            //如果没有压缩规则,则不压缩
            if(!ruleSet.contains(CompressRule.class)){
                thumbnails.scale(1f);
            }
            return this;
        }

4、图片裁剪的实现

图片裁剪的实现的实现很简单其实就是使用thumbnailator的sourceRegion()方法,所谓按比例裁剪其实就是用原图的长宽 * 比例算出需要裁剪的长宽,再进行裁剪。

实现代码

public class RegionHandImageHand extends BaseImageHand<RegionRule> {
    @Override
    public boolean hand(Thumbnails.Builder<? extends InputStream> thumbnails, RegionRule rule, BufferedImage image) {
        if (RegionTypeEnum.SCALE_REGION.equals(rule.getRegionType())) {
            //按比例裁剪
            Double scale = rule.getScale();
            thumbnails.sourceRegion(rule.getPositions(), (int) (image.getWidth() * scale), (int) (image.getHeight() * scale));
        } else if (RegionTypeEnum.WIDTH_HEIGHT_REGION.equals(rule.getRegionType())) {
            //按宽高裁剪
            thumbnails.sourceRegion(rule.getPositions(), rule.getWidth(), rule.getHeight());
        }
        return false;
    }
}

5、图片压缩的实现

5.1 按比例压缩图片

按比例压缩图片使用thumbnailator的scale()方法

5.2 按长宽压缩图片

按长宽压缩图片使用thumbnailator的size()方法,因为thumbnailator提供的保持长宽比的方式和我的一些使用场景不一致,所以有自定义实现了一个保持长宽比的方式

5.3 实现代码

import com.fhey.common.file.imagehand.enums.CompressTypeEnum;
import com.fhey.common.file.imagehand.enums.KeepAspectRatioEnum;
import com.fhey.common.file.imagehand.rule.CompressRule;
import net.coobird.thumbnailator.Thumbnails;

import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.math.BigDecimal;

/**
 * @author fhey
 * @date 2022-07-08 17:02:11
 * @description:
 */
public class CompressHandImageHand extends BaseImageHand<CompressRule> {

    @Override
    public boolean hand(Thumbnails.Builder<? extends InputStream> thumbnails, CompressRule rule, BufferedImage image) {
        if (CompressTypeEnum.SCALE_COMPRESS.equals(rule.getCompressType())){//按比例缩放
            thumbnails.scale(rule.getScale());
        } else if (CompressTypeEnum.WIDTH_HEIGHT_COMPRESS.equals(rule.getCompressType())) {//按宽高缩放
            KeepAspectRatioEnum keepAspectRatio = rule.getKeepAspectRatio();
            if(null == keepAspectRatio){
                keepAspectRatio = KeepAspectRatioEnum.NO_KEEP;
            }
            switch (keepAspectRatio){
                case KEEP_BY_WITH:
                    thumbnails.size(rule.getWidth(), rule.getHeight()).keepAspectRatio(true);
                    break;
                case KEEP_AUTO:
                    compressKeepAspectRatio(thumbnails, rule, image);
                    break;
                default:
                    thumbnails.size(rule.getWidth(), rule.getHeight()).keepAspectRatio(false);
                    break;
            }
        }
        return false;
    }

    private void compressKeepAspectRatio(Thumbnails.Builder<? extends InputStream> thumbnails, CompressRule rule, BufferedImage image) {
        int width;
        int height;
        BigDecimal aspectRatio = BigDecimal.valueOf(image.getWidth()).divide(BigDecimal.valueOf(image.getHeight()),2,BigDecimal.ROUND_HALF_UP);
        BigDecimal aspectRatioWidth = BigDecimal.valueOf(rule.getHeight()).multiply(aspectRatio);
        BigDecimal aspectRatioHeight = BigDecimal.valueOf(rule.getWidth()).divide(aspectRatio,2,BigDecimal.ROUND_HALF_UP);

        if(aspectRatioHeight.compareTo(BigDecimal.valueOf(rule.getHeight())) < 0) {
            height = rule.getHeight();
            width = aspectRatioWidth.intValue();
        } else {
            width = rule.getWidth();
            height = aspectRatioHeight.intValue();
        }
        thumbnails.size(width, height).keepAspectRatio(true);
    }
}

6、添加水印的实现

6.1 添加水印通用代码

添加水印放入方式使用thumbnailator的watermark()方法,因为thumbnailator只支持添加图片水印,所以添加文字水印和多行文字水印都是先使用Graphics2D绘制一张图片在调用watermark()方法添加水印的。所有工具类先建立了添加水印的规则基类BaseImageWatermarkRule和添加水印的处理类基类AbstractWatermarkHand

BaseImageWatermarkRule:

import com.fhey.common.file.imagehand.rule.ImageHandRule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import net.coobird.thumbnailator.geometry.Positions;

@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseImageWatermarkRule implements ImageHandRule {

    /**
     * 水印位置
     */
    private Positions positions;

    /**
     * 水印透明度
     */
    private Double alpha;

    @Override
    //设置水印可以重复设置
    public boolean canRepeat() {
        return true;
    }
}

AbstractWatermarkHand:

import com.fhey.common.file.imagehand.rule.watermark.BaseImageWatermarkRule;
import com.fhey.common.file.imagehand.hand.BaseImageHand;
import net.coobird.thumbnailator.Thumbnails;

import java.awt.image.BufferedImage;
import java.io.InputStream;

public abstract class AbstractWatermarkHand<T extends BaseImageWatermarkRule> extends BaseImageHand<T> {
    @Override
    public boolean hand(Thumbnails.Builder<? extends InputStream> thumbnails, T rule, BufferedImage image) {
        if(rule.getAlpha() == null){
            rule.setAlpha(1d);
        }
        BufferedImage waterImg = getWaterImg(image, rule);
        thumbnails.watermark(rule.getPositions(), waterImg, rule.getAlpha().floatValue());
        return false;
    }

    /**
     * 获取水印图片
     * @param srcImg 原图
     * @param rule 水印规则
     * @return Builder
     */
     abstract BufferedImage getWaterImg(BufferedImage srcImg, T rule);
}

ImageHandWatermarkHand(添加图片水印处理类)、TextHandWatermarkHand(添加文字水印处理类)、MultipleTextWatermarkHand(添加多行文字水印处理类)继承添加水印的处理类基类AbstractWatermarkHand后只需要实现获取水印图片的方法getWaterImg()。

6.2 添加图片水印的实现

添加图片水印的实现的获取水印方法很简单,只有图片处理规则里没有proportion(水印占原图宽高比),就直接返回水印图片,如果传了则需要调整一下水印的长宽再返回。

代码实现如下:

import com.fhey.common.file.imagehand.rule.watermark.ImageWatermarkRule;
import net.coobird.thumbnailator.Thumbnails;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * @author fhey
 * @date 2022-07-08 18:31:09
 * @description: 
 */
public class ImageHandWatermarkHand extends AbstractWatermarkHand<ImageWatermarkRule> {
    /**
     * 获取水印图片
     * @param srcImg 原图
     * @param rule 图片水印规则
     * @return Builder
     */
    @Override
    public BufferedImage getWaterImg(BufferedImage srcImg, ImageWatermarkRule rule){
        try {
            if (rule.getProportion() == null){
                return rule.getWaterImage();
            }
            BufferedImage waterImg = rule.getWaterImage();
            int waterWidth = (int) (srcImg.getWidth() * rule.getProportion());
            int waterHeight = waterWidth * waterImg.getHeight() / waterImg.getWidth();
            return Thumbnails.of(waterImg).size(waterWidth, waterHeight).keepAspectRatio(true).asBufferedImage();
        } catch (IOException e){
           throw new RuntimeException(e);
        }
    }
}

6.3 添加文字水印的实现

因为thumbnailator的watermark()方法只支持添加图片水印,所以先使用Graphics2D绘制一张图片。

代码实现如下:

import cn.hutool.core.lang.Tuple;
import com.fhey.common.file.imagehand.rule.watermark.TextWatermarkRule;
import net.coobird.thumbnailator.geometry.Positions;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

/**
 * @author fhey
 * @date 2022-07-08 18:31:39
 * @description: 
 */
public class TextHandWatermarkHand extends AbstractWatermarkHand<TextWatermarkRule> {
    /**
     * 获取水印图片
     * @param srcImg 原图
     * @param rule 文字水印规则
     * @return Builder
     */
    //@Override
    public BufferedImage getWaterImg(BufferedImage srcImg, TextWatermarkRule rule) {
        int srcWidth = srcImg.getWidth();
        int srcHeight = srcImg.getHeight();
        int margin = rule.getMargin();
        String text = rule.getText();
        ///确定字体
        Font font;
        if (rule.getProportion() != null) {
            double fontLength = srcWidth * rule.getProportion();
            Integer frontSize = (int) (fontLength / text.length());
            font = new Font(rule.getFont().getName(), rule.getFont().getStyle(), frontSize);
        } else {
            font = rule.getFont();
        }
        FontMetrics metrics = new FontMetrics(font) {};
        Rectangle2D bounds = metrics.getStringBounds(text, null);
        //效果一样
//        FontRenderContext frc = new FontRenderContext(null, true, true);
//        Rectangle2D bounds = font.getStringBounds(text, frc);
        // 字符宽度
        int textWidth = (int) bounds.getWidth();
        // 字符高度
        int textHeight = (int) bounds.getHeight();
         // 根据字符宽度字、符高度设置画布大小
        BufferedImage sysImage = new BufferedImage(textWidth + margin, textHeight + margin, BufferedImage.TYPE_INT_RGB);
        // 获取画笔对象
        Graphics2D graphics = sysImage.createGraphics();
        //设置图片透明
        sysImage = graphics.getDeviceConfiguration().createCompatibleImage(textWidth + margin, textHeight + margin, Transparency.TRANSLUCENT);
        graphics = sysImage.createGraphics();
        //设置字体
        graphics.setFont(font);
        //设置水印字体颜色
        Color color;
        if (rule.getColor() != null) {
            color = rule.getColor();
        } else {
            color = new Color(0, 0, 0, 3);
        }
        graphics.setColor(color);
        // 消除java.awt.Font字体的锯齿
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 设置水印透明度
        graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, rule.getAlpha().floatValue()));
        Tuple marginInfo = getMarginInfo(rule.getPositions(), margin);
        graphics.drawString(text,  marginInfo.get(0) , metrics.getAscent() + (int) marginInfo.get(1));
        graphics.dispose();
        return sysImage;
    }

    public Tuple getMarginInfo(Positions positions, Integer margin) {
        int xMargin = 0;
        int yMargin = 0;
        switch (positions){
            case TOP_LEFT:
                xMargin = margin;
                yMargin = margin;
                break;
            case TOP_CENTER:
                xMargin = margin/2;
                yMargin = margin;
                break;
            case TOP_RIGHT:
                xMargin = 0;
                yMargin = margin;
                break;
            case CENTER_LEFT:
                xMargin = margin;
                yMargin = margin/2;
                break;
            case CENTER:
                xMargin = margin/2;
                yMargin = margin/2;
                break;
            case CENTER_RIGHT:
                xMargin = 0;
                yMargin = margin/2;
                break;
            case BOTTOM_LEFT:
                xMargin = margin;
                yMargin = 0;
                break;
            case BOTTOM_CENTER:
                xMargin = margin/2;
                yMargin = 0;
                break;
            case BOTTOM_RIGHT:
                xMargin = 0;
                yMargin = 0;
                break;
        }
        return new Tuple(xMargin, yMargin);
    }
}

6.4 添加多行文字水印的实现

因为thumbnailator的watermark()方法只支持添加图片水印,所以先使用Graphics2D绘制一张图片。

代码实现如下:

import com.fhey.common.file.imagehand.rule.watermark.MultipleTextWatermarkRule;
import net.coobird.thumbnailator.geometry.Positions;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.Optional;

public class MultipleTextWatermarkHand extends AbstractWatermarkHand<MultipleTextWatermarkRule> {

    /**
     * 获取全屏水印图片
     * @param srcImg 原图
     * @param rule 文字水印规则
     * @return Builder
     */
    @Override
    public BufferedImage getWaterImg(BufferedImage srcImg, MultipleTextWatermarkRule rule) {
        rule.setPositions(Positions.CENTER);
        int srcWidth = srcImg.getWidth();
        int srcHeight = srcImg.getHeight();
        String text = rule.getText();
        Optional<MultipleTextWatermarkRule> multipleTextRuleOptional = Optional.ofNullable(rule);
        // 将画布大小绘制成原图大小
        BufferedImage sysImage= new BufferedImage(srcWidth, srcHeight, BufferedImage.TYPE_INT_RGB);
        // 获取画笔对象
        Graphics2D graphics = sysImage.createGraphics();
        //清除矩形区域
        graphics.clearRect(0, 0, srcWidth, srcHeight);
        // 设置绘图区域透明
        sysImage = graphics.getDeviceConfiguration().createCompatibleImage(srcWidth, srcHeight, Transparency.TRANSLUCENT);
        graphics = sysImage.createGraphics();
        FontMetrics metrics = new FontMetrics(rule.getFont()) {};
        Rectangle2D bounds = metrics.getStringBounds(text, null);
        // 字符宽度
        int textWidth = (int) bounds.getWidth();
        // 字符高度
        int textHeight = (int) bounds.getHeight();
        //设置字体
        graphics.setFont( rule.getFont());
        //设置水印字体颜色
        Color color;
        if (rule.getColor() != null) {
            color = rule.getColor();
        } else {
            color = new Color(0, 0, 0);
        }
        graphics.setColor(color);
        // 消除java.awt.Font字体的锯齿
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 设置水印透明度
        graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, rule.getAlpha().floatValue()));
        // 设置倾斜角度
        graphics.rotate(Math.toRadians(multipleTextRuleOptional.map(MultipleTextWatermarkRule::getRotateDegree).orElse(0d))
                , (double) srcImg.getWidth() / 2
                , (double) srcImg.getHeight() / 2);
        int xCoordinate = -srcWidth / 2, yCoordinate;
        // 循环添加水印
        while (xCoordinate < srcWidth * 1.5) {
            yCoordinate = -srcHeight / 2;
            while (yCoordinate < srcHeight * 1.5) {
                graphics.drawString(rule.getText(), xCoordinate, yCoordinate);
                yCoordinate += textHeight + multipleTextRuleOptional.map(MultipleTextWatermarkRule::getYSpace).orElse(textHeight);
            }
            xCoordinate += textWidth + multipleTextRuleOptional.map(MultipleTextWatermarkRule::getXSpace).orElse(textWidth / text.length() * 2);
        }
        graphics.dispose();
        return sysImage;
    }
}

总结

​ 到此这篇图片处理工具类的使用方式和实现原理都已经介绍完了,这个工具类实现图片裁剪、图片压缩、添加图片、添加文字水印和添加多行文字水印功能。因为第一次封装这样的工具类难免有些简陋,甚至可能有些错误,还望各位大佬海涵和指正。

代码的git仓库如下

fhey223/fhey-common-imagehand (github.com)

最后
封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能文章来源地址https://www.toymoban.com/news/detail-549168.html

到了这里,关于封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于pdfbox实现的pdf添加文字水印工具

    最近有个需求需要给pdf加文字水印,于是开始搜索大法,但是发现网络上的代码基本都是将字体文件直接放在jar包里面。个人强迫症发作(手动狗头),想要像poi一样直接加载系统字体,于是研究了一下午pdfbox的源代码,发现 FontFileFinder 类可以实现这个功能。废话不多说,直

    2023年04月11日
    浏览(47)
  • 微信小程序---图片裁剪、旋转、预览、上传功能实现(已经封装成组件,需要的到资源下载)

    1、可以拍摄或选择本地图片上传图片数据 2、图片上传数据可以进行裁剪、选择、取消、裁剪后预览、上传以及限制大小,还可以缩放操作,需要的可以解除限制即可 1、点击图片上传按钮时,跳转页面到cropper进行图片选择剪切 wx.navigateTo({       url: `/pages/cropper/cropper?d

    2023年04月26日
    浏览(77)
  • Python操作Word水印:添加文字或图片水印

    在Word文档中,可以添加半透明的图形或文字作为水印,以保护文档的原创性,防止未经授权的复制或使用。除了提供安全功能外,水印还可以展示文档创作者的信息、附加的文档信息,或者仅用于文档的装饰。本文将介绍如何使用 Spire.Doc for Python 在程序中的轻松 添加文字和

    2024年02月08日
    浏览(47)
  • 【Opencv】PIL Opencv 向图片写入文字并旋转文字,Opencv图片旋转不截断,Opencv图片旋转不裁剪

    刚性变换: 只有物体的位置(平移变换)和朝向(旋转变换)发生改变,而形状不变,得到的变换称为刚性变换。刚性变换是最一般的变换。 使用透视变换,文字会扭曲失真。刚性变换就不会。 一些介绍: https://blog.csdn.net/liuweiyuxiang/article/details/86510191 创建一张空图写文字: 旋转

    2024年02月11日
    浏览(53)
  • Opencv基于文字检测去图片水印

    做了一个简单的去水印功能,基于文字检测去图片水印。效果如下: 插件功能代码参考如下: 《QT 插件化图像算法研究平台》其它内容: 欢迎广大朋友沟通交流,微信/QQ:23245175,加好友,请备注:插件软件。

    2024年01月20日
    浏览(47)
  • Python代码学习之给图片添加文字或图片水印

    图片加水印有什么好处?在现今的数字化时代,网络上的图片泛滥,盗图现象也越来越严重。因此,在发布文章时,为了保护自己的原创作品版权,很多人选择使用水印来保护他们的图片。这样就能更好地做到: 1.版权保护:在商业用途的照片中添加水印可以帮助保护作者的

    2024年02月09日
    浏览(47)
  • C# 图片转PDF,PDF增加水印文字

    好久没写博客了,今天给大家分享一个图片转PDF的相关操作,也算是一次总结吧。 首先需要准备动态库itextsharp.dll,这个dll去网上下载,都可以下载到,C#对PDF的操作都是基于这个类库来实现的。话不多说,直接上代码。

    2024年02月09日
    浏览(51)
  • python3去除图片中的文字水印

    以下为代码: 可根据水印颜色,将np.clip(1.4 * img + 20, 0, 255)中的1.4更改为1.1~1.7,20更改为-40~40,从而达到更好的效果。

    2024年01月25日
    浏览(40)
  • JAVA PDF 给PDF添加文字/图片水印(指定内容),并且设置位置

    提示:看完这个简单的demo 后就知道怎样去操作一个PDF了 文章目录 前言 一、前提准备 二、使用步骤 1.引入库 2.以下是部分代码的作用 总结 提示:操作PDF其实是一件很简单的事情,比一般的CRUD都简单 例如:我们拿到了一个需求,我需要给这个PDF设置一个 电子签名 ( 就是一

    2024年04月23日
    浏览(47)
  • 【实操】Java+百度ocr,实现图片识别文字小工具

    图片识别文字,咱用java也可以 通过 java+百度ocr ,实现一个截图或上传图片, 图片识别文字的小工具 。并通过 exe4j工具 将jar包封装成exe可执行桌面文件,方便使用及学习。 一位特别的老友即将 在2023年10月26日 年满 30周岁 ,愿 平安喜乐 公众号:【JavaDog程序狗】 关注公众号

    2024年02月08日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包