記事ディレクトリ
概要
さまざまな場所から取得した写真のサイズ比 (アスペクト比) にはさまざまなパラメータがあることが多く、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 オブジェクトの現在の座標系で描画し、対象の長方形領域で拡大縮小とトリミングを実行します 例: 対象の長方形領域とソース画像のアスペクト比が同じ
場合画像はターゲット領域のアスペクト比に従って拡大縮小されます。ターゲットの長方形領域がソース画像の境界を超える場合、その領域は切り取られます。パラメータ:
- 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 など)
出力: ターゲットファイルのパス情報
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 |
一般的な補間アルゴリズムとその長所と短所
補間アルゴリズム | アルゴリズムの説明 | 長所と短所 | 主な応用シナリオ |
---|---|---|---|
最近傍 補間 |
ピクセルは、元の最も近いピクセルの値を直接採用します。 | 計算はシンプルで高速ですが、ギザギザやエイリアス効果が発生しやすいです | 画像縮小やリアルタイムコンピューティングなどのシナリオ |
双一次 補間 |
水平方向と垂直方向に線形補間を実行します | 画質は高いですが、計算が複雑で平滑化効果が十分ではない場合があります | 画像の拡大縮小、画像の回転、その他のシナリオ |
バイキュービック 補間 |
周囲の 16 ピクセルを使用して水平および垂直に 3 次関数補間を実行し、新しいピクセル値を計算します | 最高の画質では、画像を拡大縮小したときの歪みやシャープネスの変化の問題を効果的に解決できますが、計算の複雑さは最も高く、画像内のギザギザのアーティファクトを回避できます。 | 画像の拡大や画像修復など、高画質が求められるシーン |
ランチョス補間 (ランチョス リサンプリング) |
Sinc関数の窓処理と切り捨てに基づくスプライン補間法 | 图像质量较高,能有效地处理图像拉伸、缩小等过程中出现的失真和锐度变化的问题,但计算复杂度较高 | 图像缩放、图像旋转等需要考虑图像质量的场景 |
立方卷积插值 (Cubic Convolution Interpolation |
使用立方体函数进行插值 | 计算速度较快,能有效地处理图像缩放时出现的失真和锐度变化问题,但图像质量可能稍逊于双三次插值 | 图像缩放、图像修复等对图像质量要求不太高的场景 |
Spline 插值 |
基于样条函数进行插值,包括自然边界、非自然边界、周期性等多种类型 | 具有平滑效果较好、插值精度较高的优点,但计算复杂度较高 | 图像缩放、图像修复等需要保持连续性和平滑性的场景 |