当前位置: 首页 > news >正文

Java图片加水印 实战demo


/**
 * 图片水印工具类
 */
@Slf4j
@Component
public class WatermarkUtils {

    @Autowired
    private IStWatermarkManageService stWatermarkManageService;

    @Autowired
    private RedisCache redisCache;



    /**
     * 给图片添加水印
     *
     * @param file          需要加水印的图片文件,支持 File、MultipartFile、InputStream 类型
     * @param isCompress    图片压缩比例,true=大压缩  false=微压缩
     * @return              返回图片的输入流
     * @throws IOException
     */
    public InputStream addWatermark(Object file, Boolean isCompress) throws Exception {

        // 获取水印名称
        String watermarkName = getWatermarkName();

        // 字体
        String fontName = "WenQuanYi Zen Hei Mono"; // 文泉驿等宽正黑

        // 创建文件输入流
        try (InputStream inputStream = getInputStream(file)) {

            // 这里添加了对输入流的格式检查,确保是图片格式文件
            if (inputStream == null) {
                throw new IOException("无法读取输入流,文件可能为空或格式不正确");
            }

            // 读取图片使用ImageIO
            BufferedImage image = ImageIO.read(inputStream);
            if (image == null) {
                throw new IOException("无法加载图片,图片格式可能不受支持");
            }

            // 获取图片宽度和高度
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();

            // 创建一个新的图片对象,带有背景(使用白色或中性色背景)
            // 这里只对PNG图片处理时使用透明背景,其它格式不需要额外添加背景
            BufferedImage backgroundImage = image;

            // 判断图片格式是否是PNG,如果是PNG,才添加透明背景
            String formatName = getFileExtension(file); // 获取文件扩展名(如 PNG、JPEG)
            if ("png".equalsIgnoreCase(formatName)) {
                backgroundImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); // 使用 BufferedImage.TYPE_INT_ARGB 类型,支持透明度
                Graphics2D graphics = backgroundImage.createGraphics();

                // 设置交替背景颜色的网格
                // 根据图片宽度动态计算 gridSize,保持合适的比例
                int minGridSize = 30;  // 最小网格大小
                int maxGridSize = 100; // 最大网格大小
                int gridSize = Math.max(minGridSize, Math.min(maxGridSize, imageWidth / 15));
                // 计算每张图片的网格并填充
                for (int y = 0; y < imageHeight; y += gridSize) {
                    for (int x = 0; x < imageWidth; x += gridSize) {
                        // 计算当前格子宽度和高度,防止最后一行或最后一列超出边界
                        int cellWidth = (x + gridSize > imageWidth) ? imageWidth - x : gridSize;
                        int cellHeight = (y + gridSize > imageHeight) ? imageHeight - y : gridSize;

                        // 交替填充浅灰色和浅黑色
                        Color fillColor = ((x / gridSize + y / gridSize) % 2 == 0) ? new Color(230, 230, 230) : new Color(190, 190, 190);
                        graphics.setColor(fillColor);
                        graphics.fillRect(x, y, cellWidth, cellHeight);  // 使用固定的格子大小填充每个格子
                    }
                }

                // 绘制原始图片到背景上(如果原始图片有透明部分,可以保留)
                graphics.drawImage(image, 0, 0, null);
                graphics.dispose();  // 释放Graphics2D资源
            }

            // 设置水印的字体和样式
            Font font = new Font(fontName, Font.PLAIN, 50); // 动态计算的字体大小
            Graphics2D graphics = backgroundImage.createGraphics();
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            // 计算水印的中心位置
            FontMetrics metrics = graphics.getFontMetrics(font);
            int textWidth = metrics.stringWidth(watermarkName);
            int textHeight = metrics.getHeight();
            int x = (imageWidth - textWidth) / 2; // 水平居中
            int y = (imageHeight - textHeight) / 2 + metrics.getAscent(); // 垂直居中,调整y值以确保文字垂直居中

            // 水印透明度和旋转角度
            float opacity = 0.2f; // 水印透明度
            int shadowOffset = 5; // 阴影偏移
            float rotationAngle = -25f; // 逆时针旋转角度

            // 设置水印字体和样式
            graphics.setFont(font);

            // 逆时针旋转Graphics2D对象
            graphics.rotate(rotationAngle, x, y);

            // 先绘制阴影效果(稍微偏移和透明度较低的灰色)
            graphics.setColor(new Color(0, 0, 0, 50)); // 阴影色,透明度为50
            graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
            graphics.drawString(watermarkName, x + shadowOffset, y + shadowOffset);

            // 再绘制水印文字
            graphics.setColor(Color.WHITE); // 水印颜色为白色
            graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
            graphics.drawString(watermarkName, x, y);

            // 释放资源
            graphics.dispose();

            // TODO  访问该地址复制源码: https://stjl.xyz

            // 返回图片的输入流
            return new ByteArrayInputStream(baos.toByteArray());
        } catch (IOException e) {
            throw new IOException("处理文件时发生错误: " + e.getMessage(), e);
        }
    }




    /**
     * 获取图片格式(PNG/JPEG等)
     *
     * @param file 图片文件
     * @return 图片格式
     * @throws IOException
     */
    private String getImageFormat(Object file) throws IOException {
        try (InputStream inputStream = getInputStream(file)) {
            BufferedImage image = ImageIO.read(inputStream);
            String formatName = null;
            if (image != null) {
                formatName = "png"; // 默认PNG
                Iterator<ImageReader> readers = ImageIO.getImageReaders(image);
                if (readers.hasNext()) {
                    ImageReader reader = readers.next();
                    formatName = reader.getFormatName();
                }
            }
            return formatName != null ? formatName.toLowerCase() : "png"; // 默认返回"png"
        }
    }



    /**
     * 获取水印名称
     * 如果缓存中存在则直接返回,如果缓存中不存在则从数据库中查询并存入缓存。
     *
     * @return 水印名称
     */
    public String getWatermarkName() {
        // 尝试从缓存中获取水印名称
        String watermarkName = redisCache.getCacheObject(ConstantUtils.WATERMARK_NAME_KEY);

        if (watermarkName == null) {
            // 缓存中不存在,从数据库中获取水印名称
            watermarkName = stWatermarkManageService.getNameOne();

            // 将水印名称存入缓存,设置为永久有效
            redisCache.setCacheObject(ConstantUtils.WATERMARK_NAME_KEY, watermarkName);
        }

        return watermarkName;
    }



    /**
     * 获取输入流的方法,支持不同类型的文件输入
     *
     * @param file 支持 File、MultipartFile、InputStream 类型
     * @return 输入流
     * @throws IOException 如果无法处理文件
     */
    private static InputStream getInputStream(Object file) throws IOException {
        if (file instanceof File) {
            return new FileInputStream((File) file);
        } else if (file instanceof MultipartFile) {
            return ((MultipartFile) file).getInputStream();
        } else if (file instanceof InputStream) {
            return (InputStream) file;
        } else {
            throw new IllegalArgumentException("不支持的文件类型");
        }
    }



    /**
     * 获取文件扩展名
     *
     * @param file 文件对象
     * @return 文件扩展名
     */
    private String getFileExtension(Object file) {
        String fileName = null;

        if (file instanceof File) {
            fileName = ((File) file).getName();
        } else if (file instanceof MultipartFile) {
            fileName = ((MultipartFile) file).getOriginalFilename();
        } else if (file instanceof InputStream) {
            // 如果是 InputStream,暂时无法直接获取文件扩展名,可以尝试通过其他方式传递扩展名
            throw new IllegalArgumentException("无法通过 InputStream 获取文件扩展名");
        }

        // 从文件名中提取扩展名(例如 .png 或 .jpg)
        if (fileName != null) {
            int index = fileName.lastIndexOf('.');
            if (index > 0) {
                return fileName.substring(index + 1);
            }
        }
        return "";
    }


    // 水印配置类
    public static class Watermark {
        private String text;       // 水印文字
        private float x;             // 水印X位置
        private float y;             // 水印Y位置
        private Color color;       // 水印颜色
        private float opacity;     // 透明度
        private String fontName;   // 字体名称
        private int fontSize;      // 字体大小
        private float angle;       // 倾斜度(度数)

        public Watermark(String text, float x, float y, Color color, float opacity, String fontName, int fontSize, float angle) {
            this.text = text;
            this.x = x;
            this.y = y;
            this.color = color;
            this.opacity = opacity;
            this.fontName = fontName;
            this.fontSize = fontSize;
            this.angle = angle;
        }

        public String getText() {
            return text;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }

        public Color getColor() {
            return color;
        }

        public float getOpacity() {
            return opacity;
        }

        public String getFontName() {
            return fontName;
        }

        public int getFontSize() {
            return fontSize;
        }

        public float getAngle() {
            return angle;
        }
    }
}

捡图

 

相关文章:

  • Linux中逻辑卷的使用、扩容与磁盘配额
  • LeetCode hot 100—排序链表
  • 通俗易懂的讲解SpringBean生命周期
  • 21-bfs-走迷宫(简单模板题)
  • 3132_nodejs_express后端框架
  • Spring中@Value的使用详解,以及和@ConfigurationProperties使用对比
  • 开源项目解读(https://github.com/zjunlp/DeepKE)
  • 补Java基础之重生(14)API(下)
  • 鸿蒙应用权限深度解析:构建安全可信的分布式应用
  • 绿盟春招面试题
  • 输出3行3列矩阵的鞍点
  • 大商创多用户商城系统存在SQL注入漏洞
  • 【蓝桥杯—单片机】通信总线专项 | 真题整理、解析与拓展 (更新ing...)
  • 如何完全卸载宝塔面板?
  • 媒体直播的力量:解锁新时代传播密码,引爆传播效应,媒介盒子分享
  • dotnet core web api linux主机公网发布
  • 深入解析:ElasticSearch Query 查询方式
  • Redis的Java客户端
  • C++笔记-模板初阶,string(上)
  • Notepad++正则匹配案例
  • 网站建设相关的书籍/企业网站建设的步骤
  • 厦门网站建设 软件园/关键词优化是什么意思
  • wordpress会计模板下载/青岛seo建站
  • 广州天与地网站建设/沈阳疫情最新消息
  • 微餐饮网站建设/聚名网官网
  • wordpress删主题/百度seo新站优化