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

Java实现图片缩放裁剪,图片像素比例变更,批量转换图片像素比

文章目录

    • 概述
    • 上代码实现
    • 主要使用方法讲解
        • 1. Graphics2D 类的 drawImage 方法
        • 2. BufferedImage 类的 getSubimage 方法
        • 3. ImageIO 类的 write 方法
    • 图片缩放插值算法选择
    • 常用插值算法及其优缺点

概述

很多时候,我们从不同地方获取到的图片,它的尺寸比例(宽高比)可能都是各种各样的参数,而我们想要切换成我们需要的比例,比如:9:16 / 16/9这种尺寸,这时候发现用工具很麻烦,且需要一个个的处理,所以用程序写一个能够实现批量处理。
效果:*将图片根据传入的宽高,进行缩放,然后从中心开始裁剪,生成满足你需要的图片尺寸

上代码实现

package com.xgf.file;

import com.xgf.common.LogUtil;
import com.xgf.constant.StringConstantUtil;
import com.xgf.constant.enumclass.FileTypeEnum;
import com.xgf.system.SystemUtil;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author strive_day
 * @create 2023-04-08 1:00
 * @description 图片文件工具类
 */
public class ImageFileUtil {


    public static void main(String[] args) {
    	// 处理目录下的所有jpg格式文件,裁剪图片尺寸变为3840:2160
        String sourceImgDir = "F:\\wqq";
        String targetImgDir = "F:\\wqq\\11";
        Arrays.stream(new File(sourceImgDir).listFiles()).filter(p -> p.getName().endsWith(".jpg"))
                .forEach(p -> {
                    try {
                        imgDpiModify(p, new File(StringConstantUtil.defaultEndWith(targetImgDir, SystemUtil.getFileSeparator()) + p.getName()), 3840, 2160, Boolean.TRUE);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
    }


    public static void imgDpiModify(String sourceImgPath, String targetImgPath, Integer targetWidth, Integer targetHeight, Boolean dealOptimizeFlag) throws IOException {
        imgDpiModify(new File(sourceImgPath), new File(targetImgPath), targetWidth, targetHeight, dealOptimizeFlag);
    }

    /**
     * 图片按照中心点进行缩放和裁剪,达到需要的图片宽高像素比
     *
     * @param sourceImgFile 原图片文件
     * @param targetImgFile 目标图片文件
     * @param targetWidth 目标图片像素宽度
     * @param targetHeight 目标图片像素高度
     * @return true: 成功,false: 失败
     * @param dealOptimizeFlag 处理优化标识,true:处理优化,如果原图片宽高宽高比大,则自动替换宽和高的值
     *                         eg: 图片宽度: 1000,高度: 800,传入参数: targetWidth: 3840, targetHeight: 2160,那么会优化成:targetWidth: 2160, targetHeight: 3840,保证最佳展示
     * @throws IOException IO异常
     */
    public static boolean imgDpiModify(File sourceImgFile, File targetImgFile, Integer targetWidth, Integer targetHeight, Boolean dealOptimizeFlag) throws IOException {

        BufferedImage sourceBufImg = ImageIO.read(sourceImgFile);
        int originalWidth = sourceBufImg.getWidth();
        int originalHeight = sourceBufImg.getHeight();

        // 优化宽高比
        if (Boolean.TRUE.equals(dealOptimizeFlag)) {
            // 原图片宽度比高度大,那么应该满足: targetWidth >= targetHeight,如果不满足,则替换,反之,原图片宽度比高度小,那么应该满足: targetWidth <= targetHeight,否则替换
            if ((originalWidth > originalHeight && targetWidth < targetHeight)
                    || (originalWidth < originalHeight && targetWidth > targetHeight)) {
                Integer temp = targetWidth;
                targetWidth = targetHeight;
                targetHeight = temp;
            }
        }


        // 计算图片需要的缩放比例(宽高和原宽高做对比,取大值)【就是满足目标宽度和高度,需要对图片进行的缩放比】
        double imgScaleValue = Math.max((double) targetWidth / originalWidth, (double) targetHeight / originalHeight);
        // 缩放后的图片宽度
        int scaledImgWidth = (int) Math.round(originalWidth * imgScaleValue);
        // 缩放后的图片高度
        int scaledImgHeight = (int) Math.round(originalHeight * imgScaleValue);

        // 取中间区域的左上角坐标x,y开始截图下标【缩放后变更为要求的图片比例尺寸,进行截取的开始坐标】
        int xStartIndex = (scaledImgWidth - targetWidth) / 2;
        int yStartIndex = (scaledImgHeight - targetHeight) / 2;

        // 对图像进行缩放,获取缩放后的图片BufferedImage
        BufferedImage scaleBufImg = ImageScaleUtil.scale(sourceBufImg, scaledImgWidth, scaledImgHeight);

        // 对缩放后的图片,从中间区域开始截取(用于获取原始图像中指定区域的子图像),达到希望得到的目标图片的宽高比例
        // 参数分别是:截取图像左上角的 x 坐标、y 坐标, 截取图像的宽度、高度
        BufferedImage croppedTargetBufImg = scaleBufImg.getSubimage(xStartIndex, yStartIndex, targetWidth, targetHeight);

        // 将截取好的目标图像写入到目标文件中,参数1 (RenderedImage): 要写入文件或输出流的图像数据。
        // 参数2 (formatName): 指定要写入的图像格式的名称,如 jpg、png、bmp、gif,(这里默认使用JPG),告知 ImageIO 使用哪个图像编解码器来处理图像数据
        // 参数3 (output): 要写入的文件或输出流。可以是一个 File 对象或 OutputStream 对象
        boolean writeFlag = ImageIO.write(croppedTargetBufImg, FileTypeEnum.JPG.getCode(), targetImgFile);

        LogUtil.info("====== sourcePath = {}, targetPath = {}, result success = {}", sourceImgFile.getPath(), targetImgFile.getPath(), writeFlag);

        return writeFlag;
    }

    /**
     * 图片缩放工具类
     */
    public static class ImageScaleUtil {

        /**
         * 定义默认的最优插值算法映射表(根据图像类型自动选择最优插值算法,以获得最佳效果)
         *
         * 主要是三种插值算法:
         * 1. RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR(最近邻插值算法),特点: 像素值直接采用原来最近的像素值,优势和场景: 速度快,适用于图像放大倍数不超过2倍的情况
         * 2. RenderingHints.VALUE_INTERPOLATION_BILINEAR(双线性插值算法),特点:根据周围4个像素计算新像素值, 优势和场景:速度快,适用于图像放大倍数不超过2倍的情况
         * 3. RenderingHints.VALUE_INTERPOLATION_BICUBIC(双三次插值算法), 特点:根据周围16个像素计算新像素值,图像质量高,适用于图像放大倍数较大的情况
         */
        private static final Map<Integer, Object> DEFAULT_INTERPOLATION_MAP = new HashMap<>();

        static {
            // 将 BufferedImage.TYPE_BYTE_GRAY(表示一个 8 位灰度图像,每个像素值存储在 8 位无符号字节中) 类型的图像对应的最优插值算法设置为 NEAREST_NEIGHBOR(最近邻插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_BYTE_GRAY, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
            // 将 BufferedImage.TYPE_USHORT_GRAY(表示一个 16 位灰度图像,每个像素值存储在 16 位无符号 short 类型中) 类型的图像对应的最优插值算法设置为 NEAREST_NEIGHBOR(最近邻插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_USHORT_GRAY, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
            // 将 BufferedImage.TYPE_3BYTE_BGR(表示一个 8 位 BGR 颜色分量的图像,颜色存储在 24 位字节数组中) 类型的图像对应的最优插值算法设置为 BILINEAR(双线性插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_3BYTE_BGR, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            // 将 BufferedImage.TYPE_4BYTE_ABGR(表示一个 8 位 ABGR 颜色分量的图像,颜色存储在 32 位字节数组中) 类型的图像对应的最优插值算法设置为 BILINEAR(双线性插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_4BYTE_ABGR, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            // 将 BufferedImage.TYPE_INT_ARGB(表示一个 8 位 RGBA 颜色分量的图像,颜色存储在 32 位整数中) 类型的图像对应的最优插值算法设置为 BICUBIC(双三次插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_INT_ARGB, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            // 将 BufferedImage.TYPE_INT_RGB(表示一个 8 位 RGB 颜色分量的图像,颜色存储在 32 位整数中) 类型的图像对应的最优插值算法设置为 BICUBIC(双三次插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_INT_RGB, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            // 将 BufferedImage.TYPE_CUSTOM(表示一个自定义类型的图像,其颜色模型由开发人员指定) 类型的图像对应的最优插值算法设置为 BICUBIC(双三次插值)
            DEFAULT_INTERPOLATION_MAP.put(BufferedImage.TYPE_CUSTOM, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        }

        /**
         * 对图像进行最优缩放,返回缩放后的图片
         *
         * @param sourceImg 原始图像 BufferedImage
         * @param width 缩放后的宽度
         * @param height 缩放后的高度
         * @return 缩放后的图像 BufferedImage
         */
        public static BufferedImage scale(BufferedImage sourceImg, int width, int height) {
            // 根据图像类型选择最优插值算法,默认为 RenderingHints.VALUE_INTERPOLATION_BICUBICC(双三次插值)
            RenderingHints renderingHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION,
                    DEFAULT_INTERPOLATION_MAP.getOrDefault(sourceImg.getType(), RenderingHints.VALUE_INTERPOLATION_BICUBIC));

            // 创建 BufferedImage,对图片宽高进行缩放,并使用原始图像相同的颜色模型,参数分别为:图像宽度、图像高度、图像的颜色模型类型(这里使用原图像类型)
            BufferedImage scaledImage = new BufferedImage(width, height, sourceImg.getType());

            // 创建缩放图像上进行绘制操作的 Graphics2D 对象(图形上下文)
            Graphics2D graphics2D = scaledImage.createGraphics();
            try {
                // 设置 最优插值算法
                graphics2D.setRenderingHints(renderingHints);
                // 缩放裁剪图片,将指定的图像绘制在Graphics2D对象的当前坐标系中,在目标矩形区域内进行缩放和裁剪。
                // 就是:如果目标矩形区域和源图像的宽高比不同,则源图像将按照目标区域的宽高比例进行缩放。如果目标矩形区域超出了源图像的边界,则会进行裁剪
                // observer为null,表示使用默认的图像观察者(java.awt.image.ImageObserver)
                graphics2D.drawImage(sourceImg, 0, 0, width, height, null);
            } finally {
                // 释放 Graphics2D 资源
                graphics2D.dispose();
            }

            // 返回缩放完成后的图片内容
            return scaledImage;
        }
    }

}

主要使用方法讲解

1. Graphics2D 类的 drawImage 方法

public abstract boolean drawImage(Image img, int x, int y,
                                   int width, int height,
                                   ImageObserver observer);

方法含义:
对图像进行缩放和裁剪:将指定的图像绘制在 Graphics2D 对象的当前坐标系中,在目标矩形区域内进行缩放和裁剪
eg: 如果目标矩形区域和源图像的宽高比不同,则源图像将按照目标区域的宽高比例进行缩放。如果目标矩形区域超出了源图像的边界,则会进行裁剪

参数

  • img:要绘制的图像对象
  • x:目标矩形区域左上角的 x 坐标
  • y:目标矩形区域左上角的 y 坐标
  • width:目标矩形区域的宽度
  • height:目标矩形区域的高度
  • observer:是一个ImageObserver接口,用于接收有关加载和绘制图像的通知。可以自定义实现ImageObserver接口,该对象将被通知图像的加载和绘制进度。如果为null,表示使用默认的图像观察者(java.awt.image.ImageObserver默认实现)

2. BufferedImage 类的 getSubimage 方法

public BufferedImage getSubimage (int x, int y, int w, int h) {
     return new BufferedImage (colorModel,
                               raster.createWritableChild(x, y, w, h,
                                                          0, 0, null),
                               colorModel.isAlphaPremultiplied(),
                               properties);
 }

方法含义:
java.awt.image.BufferedImage.BufferedImage 是一个带有可访问或可修改图像数据缓冲区的图像。getSubimage()方法,用于获取原始图像中指定区域的子图像

参数:

  • xStartIndex:子图像左上角的 x 坐标
  • yStartIndex:子图像左上角的 y 坐标
  • targetWidth:子图像的宽度
  • targetHeight:子图像的高度

【注意】使用 getSubimage() 方法时,要确保指定的子图像区域不超出原始图像的边界。否则,将抛出 RasterFormatException 异常。

3. ImageIO 类的 write 方法

public static boolean write(RenderedImage im,
                             String formatName,
                             File output) throws IOException {}

方法含义:
javax.imageio.ImageIO 类提供了一组静态方法,用于读取、写入和处理图像,write()方法用于将 BufferedImage 对象写入到目标文件中

参数:

  • RenderedImage:要写入目标文件的 BufferedImage 对象

  • formatName:目标文件格式,如jpg(jpeg)、png、bmp、gif

  • output:目标文件路径信息

    【注意】使用 ImageIO.write() 方法时,必须确保指定的 BufferedImage 对象已经完全加载到内存并且目标文件路径已经存在并且可以写入。如果图像过大或目标文件路径无法写入,则可能导致方法抛出 异常。

formatName取值,常用图片格式和使用场景:如果希望在保证图像质量的前提下,尽可能地减小图像文件大小,可以考虑使用

  • jpg:jpg/jpeg 格式可通过调整压缩比例来控制图像文件大小和质量,但是在高压缩比例下会出现失真问题
  • png:PNG格式支持透明度通道,并且压缩后文件大小相对较小,但是读写速度可能较慢,适用于 需要支持透明背景或者需要保存非常小的图像文件的场景
  • bmp:BMP格式对图像数据没有压缩,因此可以保留最高的图像质量,但是文件大小相对较大。如果要保存位图图像,则可以考虑使用BMP格式。
  • gif:GIF格式支持多帧动画和透明背景,并且文件大小相对较小,但是只支持256种颜色,不适用于复杂的图像。如果要保存动画或者简单的图形元素,则可以考虑使用GIF格式。

图片缩放插值算法选择

图像类型常量图像类型常量描述最佳插值算法常量插值算法名称
BufferedImage.TYPE_BYTE_GRAY一个8位灰度图像,每个像素值存储在8位无符号字节中RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR最近邻插值
BufferedImage.TYPE_USHORT_GRAY一个16位灰度图像,每个像素值存储在16位无符号 short 类型中RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
BufferedImage.TYPE_3BYTE_BGR一个8位BGR颜色分量的图像,颜色存储在24位字节数组中RenderingHints.VALUE_INTERPOLATION_BILINEAR双线性插值
BufferedImage.TYPE_4BYTE_ABGR一个8位ABGR颜色分量的图像,颜色存储在32位字节数组中RenderingHints.VALUE_INTERPOLATION_BILINEAR
BufferedImage.TYPE_INT_ARGB一个8位RGBA颜色分量的图像,颜色存储在32位整数中RenderingHints.VALUE_INTERPOLATION_BICUBIC双三次插值
BufferedImage.TYPE_INT_RGB一个8位RGB颜色分量的图像,颜色存储在32位整数中RenderingHints.VALUE_INTERPOLATION_BICUBIC
BufferedImage.TYPE_CUSTOM一个自定义类型的图像,其颜色模型由开发人员指定RenderingHints.VALUE_INTERPOLATION_BICUBIC

常用插值算法及其优缺点

插值算法算法描述优缺点主要应用场景
最近邻插值
(Nearest Neighbor Interpolation)
像素点直接采用原来最近的像素点的值计算简单,速度快,但容易出现锯齿状走样效果图像缩小、实时计算等场景
双线性插值
(Bilinear Interpolation)
在横向和纵向上分别进行一次线性插值图像质量较高,但计算复杂度较高,可能会出现平滑效果不够好的情况图像缩放、图像旋转等场景
双三次插值
(Bicubic Interpolation)
在横向和纵向上分别进行三次函数插值,使用周围 16 个像素计算新像素值图像质量最高,能有效地解决图像缩放时出现的失真和锐度变化问题,但计算复杂度最高,避免图像出现锯齿状的伪影图像放大、图像修复等对图像质量要求较高的场景
Lanczos 插值
(Lanczos resampling)
基于 Sinc 函数加窗并截断产生的样条插值方法图像质量较高,能有效地处理图像拉伸、缩小等过程中出现的失真和锐度变化的问题,但计算复杂度较高图像缩放、图像旋转等需要考虑图像质量的场景
立方卷积插值
(Cubic Convolution Interpolation
使用立方体函数进行插值计算速度较快,能有效地处理图像缩放时出现的失真和锐度变化问题,但图像质量可能稍逊于双三次插值图像缩放、图像修复等对图像质量要求不太高的场景
Spline 插值
基于样条函数进行插值,包括自然边界、非自然边界、周期性等多种类型具有平滑效果较好、插值精度较高的优点,但计算复杂度较高图像缩放、图像修复等需要保持连续性和平滑性的场景

http://www.kler.cn/news/10249.html

相关文章:

  • 遗传算法优化深度信念网络DBN的分类预测,GA-DBN分类预测
  • C++ 的fcntl函数
  • ChatGPT搭建语音智能助手
  • 工作中英语学习的几个阶段
  • Three.js教程:第一个3D场景
  • 【MyBatis Plus】003 -- 配置(基本、进阶、DB策略) 条件构造器
  • Linux下使用ClamAV病毒查杀
  • Lottie加载的一些坑
  • 【OpenCV-Python】cvui 之 trackbar
  • 因果推断14--DRNet论文和代码学习
  • 如果让你做技术负责人,你会怎么设计后端架构?
  • 查看 Elasticsearch 分析器
  • selenium库有哪些功能呢?都是如何实现的呢?
  • ( “树” 之 DFS) 543. 二叉树的直径 ——【Leetcode每日一题】
  • Git的安装与基本使用
  • 2021蓝桥杯真题大写 C语言/C++
  • 计算机网络笔记(横向)
  • 代码随想录算法训练营第三十四天-贪心算法3| 1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果
  • 微服务+springcloud+springcloud alibaba学习笔记【Eureka服务注册中心】(3/9)
  • C++标准库--IO库(Primer C++ 第五版 · 阅读笔记)
  • 离散数学_第二章:基本结构:集合、函数、序列、求和和矩阵(1)
  • 探索树形数据结构,通识树、森林与二叉树的基础知识(专有名词),进一步利用顺序表和链表表示、遍历和线索树形结构
  • 梯度的看法
  • MyBatis配置文件 —— 相关标签详解
  • 干翻Hadoop系列之:Hadoop前瞻之分布式知识
  • Leetcode.1992 找到所有的农场组
  • NumPy 秘籍中文第二版:十、Scikits 的乐趣
  • vue3+TS+Pinia+Vite项目实战之一
  • 程序员的日常瞎想,个人规划,和企业把控之间的微妙关系。职场人你懂!!
  • WPF MVVM模式构建项目