JAVA实现图片质量压缩和加水印

这个世界没有什么好畏惧的,反正我们只来一次。


前言

主要实现了两个功能:

  • 加水印
  • 质量压缩

编写代码

1.编写工具类

ImageUtil代码如下:

package com.example.demo.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

/**
 * 图片相关的工具类
 * @author GMaya
 * @dateTime 2022/3/29 14:21
 */
@Slf4j
public class ImageUtil {
    /**
     * 最小压缩质量参数
     */
    private static final float QUALITY    = 0.09f;
    /**
     * 指定压缩大小,单位为b字节 1kb = 1024b
     */
    private static final float LIMIT_SIZE = 500 * 1024;

    /**
     * 质量压缩图片,循环压缩到指定大小
     * @param bytes 原图片byte数组
     * @return 压缩后图片byte数组
     */
    public static byte[] compressPic(byte[] bytes) {
        log.info("压缩前大小:{}{}", bytes.length / 1024, "kb");
        // 如果图片小于指定压缩大小,则直接返回。
        if (bytes.length < LIMIT_SIZE) {
            return bytes;
        }
        TimeInterval timer = DateUtil.timer();
        BufferedImage bufferedImage = null;
        // 初始化质量参数
        float q = 0.9f;
        byte[] result = null;
        try {
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            bufferedImage = ImageIO.read(in);
            // 最小压缩到0.1质量,不管有没有到500kb
            while (q > QUALITY) {
                result = compressPic(bufferedImage, q);
                log.info("图片压缩后大小:{}{},质量参数:{}", result.length / 1024, "kb", q);
                if (result.length > LIMIT_SIZE) {
                    // 默认压缩到500kb,如果大于500kb,继续压缩
                    q -= 0.1f;
                } else {
                    q = 0f;
                }
            }
            //花费毫秒数
            long interval = timer.interval();
            log.info("图片压缩耗时:{}{}", interval, "ms");
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 图片加水印
     * @param pressText 水印文字
     * @param targetImg 要加水印的图片路径
     */
    public static void pressText(String pressText, String targetImg) {
        try {
            TimeInterval timer = DateUtil.timer();

            BufferedImage src = ImageIO.read(new File(targetImg));
            int width = src.getWidth(null);
            int height = src.getHeight(null);
            // 动态设置字体大小 , 以300像素15的基数大小设置
            int fontSize = width < height ? width / 300 * 15 : height / 300 * 15;
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = image.createGraphics();
            g.drawImage(src, 0, 0, width, height, null);
            // 设置水印字体颜色. TODO 也可以变为动态参数传进来.
            g.setColor(Color.RED);
            // 设置水印字体,Font.PLAIN正常字体/BOLD加粗/ITALIC斜体. TODO 也可以变为动态参数传进来.
            g.setFont(new Font("宋体", Font.PLAIN, fontSize));
            // 透明度
//            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.9f));
            /* 消除java.awt.Font字体的锯齿 */
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            /**
             * TODO 如果是多行水印,以及超长水印,需要特殊处理.后期可升级.
             * 思路:将水印分隔成数组pressTextList,循环数组
             * int y = 1
             * for(String text : pressTextList){
             *     g.drawString(text, 20, y * fontSize * 12 / 10);
             * }
             */
            // 开始加水印
            g.drawString(pressText, 20, fontSize * 12 / 10);
            // 输出图像
            g.dispose();

            File f = new File(targetImg);
            ImageIO.write(image, targetImg.substring(targetImg.lastIndexOf(".") + 1), f);

            //花费毫秒数
            long interval = timer.interval();
            log.info("图片加水印耗时:{}{}", interval, "ms");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param image
     * @param quality 参数qality是取值0~1范围内,压缩程度
     * @return
     * @throws IOException
     */
    public static byte[] compressPic(BufferedImage image, float quality) throws IOException {
        ByteArrayOutputStream outArray = new ByteArrayOutputStream();
        // 指定写图片的方式为 jpg
        ImageWriter imgWrier = ImageIO.getImageWritersByFormatName("jpg").next();
        ImageWriteParam imgWriteParams = new javax.imageio.plugins.jpeg.JPEGImageWriteParam(null);
        // 要使用压缩,必须指定压缩方式为MODE_EXPLICIT
        imgWriteParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        // 设置压缩质量参数,参数qality是取值0~1范围内,
        imgWriteParams.setCompressionQuality(quality);
        imgWriteParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
        ColorModel colorModel = image.getColorModel();
        // 指定压缩时使用的色彩模式
        imgWriteParams.setDestinationType(
            new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16)));
        try {
            imgWrier.reset();
            // 必须先指定 out值,才能调用write方法, ImageOutputStream可以通过任何
            // OutputStream构造
            imgWrier.setOutput(ImageIO.createImageOutputStream(outArray));
            // 调用write方法,就可以向输入流写图片
            imgWrier.write(null, new IIOImage(image, null, null), imgWriteParams);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            imgWrier.dispose();
        }
        return outArray.toByteArray();
    }

}


2.编写接口

ImageController代码如下:

package com.example.demo.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.demo.util.ImageUtil;
import com.example.demo.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

/**
 * 用于图片处理的接口
 * @author GMaya
 * @dateTime 2022/3/29 14:13
 */
@Slf4j
@RestController
@RequestMapping("/image")
public class ImageController {

    /**
     * 压缩图片/图片加水印
     * @param file 原图片文件
     * @param jsonObject 传递的其他参数
     * @return
     * @throws IOException
     */
    @PostMapping("/compressPic")
    public R compressPic(MultipartFile file, String jsonObject) throws IOException {
        if (file == null || file.isEmpty()) {
            return R.error("文件不能为空,请检查!");
        }
        // 计时开始
        TimeInterval timer = DateUtil.timer();
        // json 转换
        JSONObject json = JSONUtil.parseObj(jsonObject);
        // 先将文件保存到本地
        String targetImg = "D:\\work\\test-gmaya\\image\\" + IdUtil.simpleUUID() + ".jpg";
        File destImageFile = new File(targetImg);
        FileUtil.writeBytes(file.getBytes(), destImageFile);


        // 是否需要加水印,默认为0是,1否
        String isPressText = json.getStr("isPressText");
        if ("0".equals(isPressText)) {
            String pressText = json.getStr("pressText");
            if(StrUtil.isEmpty(pressText)){
                return R.error("水印文字不能为空,请检查!");
            }
            ImageUtil.pressText(pressText, targetImg);
        }

        // 是否需要压缩,默认为0是,1否
        String isCompress = json.getStr("isCompress");
        if ("0".equals(isCompress)) {
            // 压缩并保存
            byte[] bytes = ImageUtil.compressPic(file.getBytes());
            FileUtil.writeBytes(bytes, destImageFile);
        }

        //花费毫秒数
        long interval = timer.interval();
        log.info("图片处理总耗时:{}{}", interval, "ms");
        return R.data(targetImg);
    }

}


3.测试接口

请求接口测试
日志输出
添加完水印图片展示


总结

问题:
选择图片过大,导致失败

The field file exceeds its maximum permitted size of 1048576 bytes.

解决办法:

spring:
  servlet:
    multipart:
      max-file-size: 10MB # 单个上传文件大小限制10MB以下
      max-request-size: 100MB #总上传文件大小限制100MB以下

配置文件展示

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:书香水墨 设计师:CSDN官方博客 返回首页
评论

打赏作者

GMaya

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值