文章目录
- 概述
- 上代码实现
- 主要使用方法讲解
- 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 插值 | 基于样条函数进行插值,包括自然边界、非自然边界、周期性等多种类型 | 具有平滑效果较好、插值精度较高的优点,但计算复杂度较高 | 图像缩放、图像修复等需要保持连续性和平滑性的场景 |