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

2023-11-01

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


前言

主要实现了两个功能:

  • 加水印
  • 质量压缩

编写代码

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以下

配置文件展示

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA实现图片质量压缩和加水印 的相关文章

随机推荐

  • 吉林大学超星MOOC学习通高级语言程序设计 C++ 实验02 分支与循环程序设计(2021级)(1)

    由于本章节题目太多 我将分几次发布 实验02 分支与循环程序设计 2021级 一 程序题 共15题 100分 1 程序题 题目编号 Exp02 Enhance02 GJBook3 04 0102 题目名称 公式累乘 题目描述 用如下的展开式
  • 数据库表名和列名大小写问题

    在postgresql中表名列名会自动转换为全小写 如果想大小写混用或纯大写 必须要用双引号将表 列 名引起来 而在oracle中 表名和列名会自动转换为大写 如果想大小写混用或纯小写 必须用双引号将表 列 名引起来
  • 读取文件解析文件发生的错误,已解决

    错误背景 从文件服务器上面读取zip压缩文件下来 然后解压 放指定目录下 获取所有解压后文件的路径地址保存在list集合里面 最后要求是把所有获取到zip里面的pdf文件合成为一个总pdf文件 放入压缩包 上传到文件服务器上面就完成了 发生
  • 连接池自动重新连接数据库的测试

    我对数据库重启后 是否自动重新初始化连接池进行了测试 一 使用oracle数据库 1 resin 3 0 22 无需特殊配置 自动支持 2 jboss 4 0 4 GA 需加如下配置
  • 微信小程序——页面跳转

    wx switchTab Object object 跳转到 tabBar 页面 并关闭其他所有非 tabBar 页面 wx reLaunch Object object 关闭所有页面 打开到应用内的某个页面 wx redirectTo O
  • pcl::getTranslationAndEulerAngles精度缺失问题

    pcl getTranslationAndEulerAngles的功能是根据仿射矩阵计算x y z roll pitch yaw 但发现这种计算的rpy有一定的精度问题 于是进行了实验 一个是从一个四元数 根据eulerAngles计算rp
  • mobileemumaster文件夹怎么删除_origin平台加载不出游戏怎么解决?只需删除这两个文件夹...

    EA这家公司做游戏还是挺用心的 然而它的游戏平台做得实在是垃圾 跟育碧的Uplay平台比起来提鞋都不配 要不是 战地1 只能在这个平台上玩 我是绝对不会用origin这个垃圾平台的 origin平台下载游戏速度比其他平台都要慢 游戏下载慢就
  • 转:C语言头文件下包含函数(math.h stdio.h stdlib.h string.h)

    math h常用函数 int abs int x double acos double x double asin double x double atan double x double atan2 double y double x d
  • 重学JavaScript 第三天

    1 算术运算符 加 减 乘 除 取余 取余 看某些数是否被整除 优先级 先乘除取余 后加减 小括号优先级最高 2 一元运算符 前置自增 符号 num 先让自己 加1 然后再参与运算 后置自增 num 先参与运算 再自己 加1 使用场景 用于
  • 2013年12月15日

    socket mFactory createSocket host port assertNotNull socket assertNotNull socket getOutputStream assertNotNull socket ge
  • Jetbrains DataGrip 2020中文版

    教程 1 下载好文件包 得到安装程序和文件等 2 然后运行datagrip 2020 1 exe安装程序进行软件安装 3 选择软件安装路径 可更改 也可默认 4 根据用户系统位数选择版本进行创建桌面快捷方式 5 接下来一直点击next完成软
  • 网络编程项目——在线电子词典

    目录 项目要求 代码 服务器代码 客户端 运行截图 注册 首次注册 重复注册 数据库 登录 正常登录 重复登录 数据库 查询 数据库 查询历史 退出 数据库 编辑 项目要求 登录注册功能 不能重复登录 重复注册 单词查询功能 历史记录功能
  • 初始化mcu程序选用32k_程序的组成、存储与运行

    摘抄整理自 1 RT Thread编程手册 um4003 rtthread programming manual 2 野火 零死角玩转STM32 F429挑战者V2 一般 MCU 包含的存储空间有 片内 Flash 与片内 RAM RAM
  • U盘安装Win系统遇到“Windows 无法安装到这个磁盘。这台计算机的硬件可能不支持………”解决方法

    U盘安装Win系统遇到 Windows 无法安装到这个磁盘 这台计算机的硬件可能不支持 解决方法 方法1 bios里切换为Legacy启动方式 方法2 在错误提示界面 1 按下 Shift F10 快捷键 2 依次输入 diskpart 回
  • 浏览器前缀、BFC深入解析、flex布局简析、行盒line boxes对齐及vertical-align居中原理、line-height

    浏览器前缀 官方文档专业术语叫做 vendor specific extensions 供应商特定扩展 浏览器为了防止后续会修改名字给的新的属性添加了浏览器前缀 FC 格式化上下文 分为BFC IFC 元素在标准流里面都属于一个FC 块级元
  • PTA 浙大版《C语言程序设计(第3版)》题目集 练习5-3

    本题要求实现函数输出n行数字金字塔 函数接口定义 void pyramid int n 其中n是用户传入的参数 为 1 9 的正整数 要求函数按照如样例所示的格式打印出n行数字金字塔 注意每个数字后面跟一个空格 裁判测试程序样例 inclu
  • fortify代码扫描问题结果分析

    最近项目的代码使用fortify工具扫描了一下 发现了项目中存在的一些问题 在以后代码编写的过程中要注意 避免出现类似的错误 以下为本次代码分析工具FORTIFY对代码的分析结果 这些问题虽然古老 简单然而经典 也是需要引起重视 代码问题主
  • .bss段和.data段

    BSS段 BSS段 bss segment 通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域 BSS是英文Block Started by Symbol的简称 BSS段属于静态内存分配 数据段 数据段 data seg
  • 科目二练习总结

    第一次 方向盘课程 第二次 基础课程 上车先调座椅 垫坐垫 头部离顶部一拳 后背调整 舒服的姿势 不能太打直 座椅前后调整 离合踩到底 脚不是伸直的 膝盖离车体一拳 调后视镜 右手边上圆形 有L R的就是调整的 L 左视镜 后门把手在镜头3
  • JAVA实现图片质量压缩和加水印

    这个世界没有什么好畏惧的 反正我们只来一次 文章目录 前言 编写代码 1 编写工具类 2 编写接口 3 测试接口 总结 前言 主要实现了两个功能 加水印 质量压缩 编写代码 1 编写工具类 ImageUtil代码如下 package com