Java图片处理相关

前言

最近总结了Java处理图片的一些方法,特此分享下。

其中主要涉及到2种不同的类包。

一种是Java自带的ImageIOGraphics2DBufferedImage等类的使用;另一种是使用了net.coobird.thumbnailator包下的图片相关类。

net.coobird.thumbnailator包中对图片的操作底层其实也使用了Java自带的图片操作类相关方法,这儿不做过多讨论,有兴趣的可以直接看下它的源码。

PS: net.coobird.thumbnailator这个工具包在2014年12月 0.4.8版本后就再也没有维护过了。

1
2
3
4
5
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>

正文

我们来看一下相关的处理方法。

获取图片真实格式(与后缀无关)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String getExtension(String filePath) {
File file = new File(filePath);
if (!file.exists()) {
throw new RuntimeException("图片地址不正确,请传入正确的图片地址!!!");
}
//图片真实格式
String format = "";
try (ImageInputStream iis = ImageIO.createImageInputStream(file)) {
Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
if (iter.hasNext()) {
format = iter.next().getFormatName();
}
} catch (IOException e) {
throw new RuntimeException("获取图片真实格式时出现异常!!" + e);
}
return format;
}

该方法可以获得图片的真实格式,这个格式和图片的后缀无关。

其返回的格式为大写,如JPEG,PNG等。

PS: jpg和jpeg类型的图片返回的格式均为JPEG。

图片彩色转为黑白

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 图片彩色转为黑白 
*
* @param sourceImagePath 源图像地址
* @param targetImagePath 生成的目标图像地址(不包含文件名称)
*/
public static String gray(String sourceImagePath, String targetImagePath) throws IOException {
//获取图片真实格式
String sourceFormat = getExtension(sourceImagePath).toLowerCase();

//黑白处理
BufferedImage src = ImageIO.read(new File(sourceImagePath));
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp op = new ColorConvertOp(cs, null);
src = op.filter(src, null);
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;
ImageIO.write(src, sourceFormat, new File(targetFile));

return targetFile;
}

该方法可对彩色图片进行黑白处理。

图片格式转换

A方法:使用Thumbnails进行图片格式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 图片格式转换 A 方案
*
* @param targetFormat 目标格式 jpg jpeg png 见枚举
* @param sourceFilePath 源文件路径
* @param targetFilePath 生成目标文件路径
* @return
* @throws IOException
*/
public static String convertA(String targetFormat, String sourceFilePath, String targetFilePath) throws IOException {
//校验
if (isBlank(targetFormat) || isBlank(sourceFilePath) || isBlank(targetFilePath)) {
throw new RuntimeException("参数错误!");
}

//获取图片真实格式
String sourceFormat = getExtension(sourceFilePath).toLowerCase();
//获取图片后缀格式
String suffixFormat = sourceFilePath.substring(sourceFilePath.lastIndexOf(".") + 1).toLowerCase();
//目标格式
String targetFormatStr = targetFormat.toLowerCase();

//如果是目标格式,就直接返回
if (sourceFormat.equals(targetFormatStr) && suffixFormat.equals(targetFormatStr)) {
return sourceFilePath;
}
//使用Thumbnails处理
String targetFile = targetFilePath + "/" + getUUID() + "." + targetFormatStr;
Thumbnails.of(sourceFilePath).scale(1.0f).outputFormat(targetFormatStr).toFile(targetFile);
return targetFile;
}

B方法:使用ImageIO进行图片格式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 图片格式转换 B 方案
*
* @param targetFormat 目标格式 jpg jpeg png 见枚举
* @param sourceFilePath 源文件路径
* @param targetFilePath 生成目标文件路径
* @return
* @throws IOException
*/
public static String convertB(String targetFormat, String sourceFilePath, String targetFilePath) throws IOException {
//校验
if (isBlank(targetFormat) || isBlank(sourceFilePath) || isBlank(targetFilePath)) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceFilePath).toLowerCase();
//获取图片后缀格式
String suffixFormat = sourceFilePath.substring(sourceFilePath.lastIndexOf(".") + 1).toLowerCase();
//目标格式
String targetFormatStr = targetFormat.toLowerCase();

//如果是目标格式,就直接返回
if (sourceFormat.equals(targetFormatStr) && suffixFormat.equals(targetFormatStr)) {
return sourceFilePath;
}

//getExtension方法已经校验了文件存不存在,这儿不用校验
File file = new File(sourceFilePath);
//使用ImageIO处理
String targetFile = targetFilePath + "/" + getUUID() + "." + targetFormatStr;
BufferedImage src = ImageIO.read(file);
ImageIO.write(src, targetFormatStr, new File(targetFile));
return targetFile;
}

两种方法都可以实现图片格式转换。

图片旋转相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 图片旋转
* @param sourceImage 原图片
* @param targetImagePath 生成图片路径
* @param angle 旋转角度 正数为顺时针旋转,负数为逆时针旋转
* @return
* @throws IOException
*/
public static String rotate(String sourceImage, String targetImagePath,double angle) throws IOException{
if(isBlank(sourceImage)||isBlank(targetImagePath)){
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

Thumbnails.of(sourceImage).scale(1.0f).rotate(angle).outputFormat(sourceFormat).toFile(targetFile);
return targetFile;
}

这儿我们使用Thumbnails工具来实现图片的旋转。

图片缩放相关

图片缩放有两种类型,一种是根据比例进行缩放,另一种是按照大小进行缩放。

我们分别来看一下。

A方法:使用Thumbnails进行图片比例缩放和大小缩放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 图片缩放
*
* @param sourceImage 原图片
* @param targetImagePath 生成的图片地址
* @param scaleWidth 宽度缩放比例 0< scaleWidth <1 表示缩小 scaleWidth >1 表示放大
* @param scaleHeight 高度缩放比例 0< scaleWidth <1 表示缩小 scaleWidth >1 表示放大 (宽高缩放比相同图片将进行等比缩放)
* @return
* @throws IOException
*/
public static String scale(String sourceImage, String targetImagePath, double scaleWidth, double scaleHeight) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || scaleWidth <= 0 || scaleHeight <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

Thumbnails.of(sourceImage).scale(scaleWidth, scaleHeight).toFile(targetFile);
return targetFile;
}

/**
* 将图片调整到指定大小
*
* @param sourceImage 原图片
* @param targetImagePath 生成目标图片地址
* @param width 调整后的宽度
* @param height 调整后的高度
* @param keep 是否保持宽高比
* keep = false
* 比如照片长宽为 100*97 ,则
* size(200,500) 后照片变为 200*198
* size(300,198)后照片变为200*198
* size(50,100)后照片变为50*49
* size(80,49)后照片变为50*49
* 可见该种缩放会保持长宽比,可能导致长或宽有一项看起来不符合我们的预设值
* 设置了keep = true ,就会按照指定的宽高变化了
* @return
* @throws IOException
*/
public static String size(String sourceImage, String targetImagePath, int width, int height, boolean keep) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || width <= 0 || height <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

Thumbnails.of(sourceImage).size(width, height).keepAspectRatio(keep).toFile(targetFile);
return targetFile;
}

B方法:使用ImageIO进行图片比例缩放和大小缩放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
* 图片缩放,不常用,使用ImageIO进行的缩放
*
* @param sourceImage 原图片
* @param targetImagePath 生成目标图片地址
* @param scaleWidth 宽度缩放比例 0< scaleWidth <1 表示缩小 scaleWidth >1 表示放大
* @param scaleHeight 高度缩放比例 0< scaleWidth <1 表示缩小 scaleWidth >1 表示放大
* @return
* @throws IOException
*/
public static String scaleB(String sourceImage, String targetImagePath, double scaleWidth, double scaleHeight) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || scaleWidth <= 0 || scaleHeight <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

//使用ImageIO处理
BufferedImage src = ImageIO.read(new File(sourceImage));
int width = src.getWidth();
int height = src.getHeight();
width = (int) (width * scaleWidth);
height = (int) (height * scaleHeight);

Image image = src.getScaledInstance(width, height, Image.SCALE_DEFAULT);
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
ImageIO.write(tag, sourceFormat, new File(targetFile));
return targetFile;
}


/**
* 缩放图像(按照高度和宽度),不推荐使用
*
* @param sourceImage 原图像
* @param targetImagePath 生成的图像地址
* @param width 要缩放到的宽
* @param height 要缩放到的高
* @param keep 比例不对时是否需要补白:true为补白; false为不补白
* @return
* @throws IOException
*/
public static String sizeB(String sourceImage, String targetImagePath, int width, int height, boolean keep) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || width <= 0 || height <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

//缩放比例
double ratio = 0.0;
BufferedImage bi = ImageIO.read(new File(sourceImage));
Image image = bi.getScaledInstance(width, height, Image.SCALE_SMOOTH);
// 计算比例
if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
if (bi.getHeight() > bi.getWidth()) {
ratio = (double) (height / bi.getHeight());
} else {
ratio = (double) (width / bi.getWidth());
}
AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
image = op.filter(bi, null);
}
//是否需要填充白色块
if (keep) {
BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = temp.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
if (width == image.getWidth(null)) {
g.drawImage(image, 0, (height - image.getHeight(null)) / 2,
image.getWidth(null), image.getHeight(null),Color.WHITE, null);
} else {
g.drawImage(image, (width - image.getWidth(null)) / 2, 0,
image.getWidth(null), image.getHeight(null),Color.WHITE, null);
}
g.dispose();
image = temp;
}
ImageIO.write((BufferedImage) image, sourceFormat, new File(targetFile));
return targetFile;
}

我们一般使用Thumbnails工具类提供的缩放来处理即可,这是比较常用的。

图片压缩相关

图片压缩也大致有两种形式,一种是指定图片质量系数进行压缩,另一种是根据大小进行压缩。

PS:这儿对于根据大小进行压缩的意思是图片压缩后大小不会超过指定值,而不是压缩到指定值,因为理论上压缩到指定值是十分困难的,也是不必要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 图片压缩
*
* @param sourceImage 原图片
* @param targetImagePath 生成的图片地址
* @param quality 图片质量系数 [ 0,1]
* @return
* @throws IOException
*/
public static String compress(String sourceImage, String targetImagePath, float quality) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || quality < 0 || quality > 1) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

//控制图片生成质量,实际会压缩图片
Thumbnails.of(sourceImage).scale(1.0f).outputQuality(quality).outputFormat(sourceFormat).toFile(targetFile);
return targetFile;
}

/**
* 压缩图片文件 (文件大小不会超过指定值)
*
* @param sourceImage 原文件
* @param targetImagePath 生成的文件地址
* @param maxSize 文件被压缩后允许的最大大小,单位 byte
* @return
* @throws IOException
*/
public static String compress(String sourceImage, String targetImagePath, long maxSize) throws IOException{
if (isBlank(sourceImage) || isBlank(targetImagePath) || maxSize <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

File source = new File(sourceImage);
long size = source.length();
//如果图片本身大小就不超过,就直接返回图片地址
if (size <= maxSize) {
return sourceImage;
}

//计算缩放比例
double scale = (double) (maxSize / size);

//图片尺寸不变,压缩图片大小
Thumbnails.of(source).scale(1.0f).outputQuality(scale).outputFormat(sourceFormat).toFile(targetFile);
return targetFile;
}

我们同样使用了Thumbnails工具类进行处理。

图片切割相关

切割主要有两种形式,一种是在原图指定位置切割一张指定大小的图片,另一种是把图片切割成若干份。

A形式:从原图指定位置切割(裁剪)一张小图片

可以通过Thumbnails工具类或者ImageIO实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* 从原图指定位置裁剪一张小图片
*
* @param sourceImage 原图
* @param targetImagePath 生成的图片位置
* @param positions 裁剪位置
* @param width 裁剪宽
* @param height 裁剪高
* @return
* @throws IOException
*/
public static String cutA(String sourceImage, String targetImagePath, Positions positions, int width, int height) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || positions == null || width <= 0 || height <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

Thumbnails.of(sourceImage).scale(1.0f).sourceRegion(positions, width, height).outputFormat(sourceFormat).toFile(targetFile);
return targetFile;
}

/**
* 从原图指定位置裁剪一张小图片
*
* @param sourceImage 原图
* @param targetImagePath 生成的图片位置
* @param x 裁剪位置x坐标
* @param y 裁剪位置y坐标
* @param width 裁剪宽
* @param height 裁剪高
* @return
* @throws IOException
*/
public static String cutB(String sourceImage, String targetImagePath, int x, int y, int width, int height) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || x < 0 || y < 0 || width <= 0 || height <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

Thumbnails.of(sourceImage).scale(1.0f).sourceRegion(x, y, width, height).outputFormat(sourceFormat).toFile(targetFile);
return targetFile;
}


/**
* 使用ImageIO进行的切割,切割指定位置的一张图片
*
* @param sourceImage 原图
* @param targetImagePath 生成图片位置
* @param x 切割图片的位置x
* @param y 切割图片的位置y
* @param width 切割图片的宽
* @param height 切割图片的高
* @return
* @throws IOException
*/
public static String cutC(String sourceImage, String targetImagePath, int x, int y, int width, int height) throws IOException {
if (isBlank(sourceImage) || isBlank(targetImagePath) || x < 0 || y < 0 || width <= 0 || height <= 0) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetImagePath + "/" + getUUID() + "." + sourceFormat;

// 使用ImageIO处理
BufferedImage bi = ImageIO.read(new File(sourceImage));
int srcWidth = bi.getHeight();
int srcHeight = bi.getWidth();
Image image = bi.getScaledInstance(srcWidth, srcHeight,Image.SCALE_DEFAULT);
// 四个参数分别为图像起点坐标和宽高
// 即: CropImageFilter(int x,int y,int width,int height)
ImageFilter cropFilter = new CropImageFilter(x, y, width, height);
Image img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter));
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(img, 0, 0, width, height, null);
g.dispose();
// 输出为文件
ImageIO.write(tag, sourceFormat, new File(targetFile));

return targetFile;
}

B形式:将原图分割成若干小图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* 分割图片
*
* @param filePath 图片地址
* @param rows 纵向分割份数
* @param cols 横向分割份数
* rows = 2 cols = 2 则图片会分成4个相等大小的图片
* rows = 2 cols = 1 则图片会分成2个大小相等的图片,分割为高度切割为2半
* @param splitImageBasePath 输出小图片的基础路径
* @return
* @throws IOException
*/
public static String[][] splitImage(String filePath, int rows, int cols, String splitImageBasePath) throws IOException {
if (rows <= 0 || cols <= 0 || isBlank(splitImageBasePath) || isBlank(filePath)) {
throw new RuntimeException("参数错误!!");
}

String imageType = getExtension(filePath);
imageType = imageType.toLowerCase();
if (!(JPG.equals(imageType) || JPEG.equals(imageType) || PNG.equals(imageType))) {
throw new RuntimeException("请使用jpg、jpeg、png类型的图片");
}

// 读入文件
File file = new File(filePath);
String fileName = file.getName();
String fileNamePrefix = fileName.substring(0, fileName.lastIndexOf("."));
FileInputStream fis = new FileInputStream(file);
BufferedImage image = ImageIO.read(fis);

// 计算每个小图的宽度和高度
int chunkWidth = image.getWidth() / cols;
int chunkHeight = image.getHeight() / rows;

BufferedImage[][] imgs = new BufferedImage[rows][cols];
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
//设置小图的大小和类型
imgs[x][y] = new BufferedImage(chunkWidth, chunkHeight, image.getType());

//写入图像内容
Graphics2D gr = imgs[x][y].createGraphics();
gr.drawImage(image, 0, 0,
chunkWidth, chunkHeight,
chunkWidth * y, chunkHeight * x,
chunkWidth * y + chunkWidth,
chunkHeight * x + chunkHeight, null);
gr.dispose();
}
}

String[][] splitImages = new String[rows][cols];
// 输出小图
for (int i = 0; i < imgs.length; i++) {
for (int j = 0; j < imgs[i].length; j++) {
String splitImagePath = splitImageBasePath + "/" + fileNamePrefix + i + "-" + j + "." + imageType;
splitImages[i][j] = splitImagePath;
ImageIO.write(imgs[i][j], imageType, new File(splitImagePath));
}
}
return splitImages;
}

/**
* 图片分割
*
* @param filePath
* @param rows
* @param cols
* @param splitImageBasePath
* @return
* @throws IOException
*/
public static List<String> splitImageReturnList(String filePath, int rows, int cols, String splitImageBasePath) throws IOException {
String[][] strings = splitImage(filePath, rows, cols, splitImageBasePath);
List<String> list = new ArrayList<>(rows * cols);
for (String[] strs : strings) {
list.addAll(Arrays.asList(strs));
}
return list;
}

/**
* 根据单位图片大小切割图片
* @param filePath 原图片
* @param imageWidth 单位图片宽度
* @param imageHeight 单位图片高度
* @param splitImageBasePath 生成图片基础路径
* @return
* @throws IOException
*/
public static String[][] splitImageB(String filePath, int imageWidth, int imageHeight, String splitImageBasePath) throws IOException {
if(isBlank(filePath)||imageWidth<=0||imageHeight<=0||isBlank(splitImageBasePath)){
throw new RuntimeException("参数错误!");
}
BufferedImage bufferedImage = ImageIO.read(new File(filePath));
int srcWidth = bufferedImage.getHeight();
int srcHeight = bufferedImage.getWidth();
if(srcWidth<imageWidth||srcHeight<imageHeight){
throw new RuntimeException("基础图片长宽不符合要求!");
}
int cols;
int rows;
// 计算切片的横向和纵向数量
if (srcWidth % imageWidth == 0) {
cols = srcWidth / imageWidth;
} else {
cols = (int) Math.floor((double)(srcWidth / imageWidth)) + 1;
}
if (srcHeight % imageHeight == 0) {
rows = srcHeight / imageHeight;
} else {
rows = (int) Math.floor((double)(srcHeight / imageHeight)) + 1;
}
return splitImage(filePath,rows,cols,splitImageBasePath);
}

上述图片分割代码我们可以把一张图片分成9块来制造九宫格图片等。

图片合并相关

图片合并可以将两张或者多张图片按照顺序合成一整张图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* 合并图片
*
* @param images 图片列表
* [1.jpg 2.jpg 3.jpg]
* [4.jpg 5.jpg 6.jpg]
* [7.jpg 8.jpg 9.jpg]
* @param finalImageBasePath 生成的图片要存放的位置
* @return
* @throws IOException
*/
public static String mergeImage(String[][] images, String finalImageBasePath) throws IOException {

if (images == null || images.length == 0 || isBlank(finalImageBasePath)) {
throw new RuntimeException("参数不正确!!");
}
int rows = 0;
int cols = 0;
for (int i = 0; i < images.length; i++) {
rows++;
int temp = 0;
for (int j = 0; j < images[i].length; j++) {
temp++;
}
if (i == 0) {
cols = temp;
} else {
if (cols != temp) {
throw new RuntimeException("需要保证a * b 格式的图片");
}
}
}

String imageType = getExtension(images[0][0]);
imageType = imageType.toLowerCase();
if (!(JPG.equals(imageType) || JPEG.equals(imageType) || PNG.equals(imageType))) {
throw new RuntimeException("请使用jpg、jpeg、png类型的图片");
}

int chunkWidth, chunkHeight;
int type;

//创建BufferedImage
BufferedImage[][] buffImages = new BufferedImage[rows][cols];

for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
getExtension(images[i][j]);
buffImages[i][j] = ImageIO.read(new File(images[i][j]));
}
}

type = buffImages[0][0].getType();
chunkWidth = buffImages[0][0].getWidth();
chunkHeight = buffImages[0][0].getHeight();

//设置拼接后图的大小和类型
BufferedImage finalImg = new BufferedImage(chunkWidth * cols, chunkHeight * rows, type);

//写入图像内容
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
finalImg.createGraphics().drawImage(buffImages[i][j], chunkWidth * j, chunkHeight * i, null);
}
}

String filePath = finalImageBasePath + "/" + getUUID() + "." + imageType;
//输出拼接后的图像
ImageIO.write(finalImg, imageType, new File(filePath));

return filePath;
}

/**
* 合并图片
*
* @param imageList
* @param rows
* @param finalImageBasePath
* @return
* @throws IOException
*/
public static String mergeImage(List<String> imageList, int rows, String finalImageBasePath) throws IOException {
if (isEmpty(imageList)) {
throw new RuntimeException("参数错误");
}
if (rows < 0 || imageList.size() % rows != 0) {
throw new RuntimeException("参数错误");
}
int cols = imageList.size() / rows;
String[][] strings = new String[rows][cols];
int temp = 0;
for (int i = 0; i < strings.length; i++) {
for (int j = 0; j < strings[i].length; j++) {
strings[i][j] = imageList.get(temp);
temp++;
}
}
return mergeImage(strings, finalImageBasePath);
}

图片水印相关

根据文字生成相应图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 根据文字生成JPG图片
*
* @param text 要生成图片的文字
* @param picFontSize 字体大小设置
* @param bgColor 图片整体背景色
* @param fontColor 文字颜色
* @param font 字体
* @param basePath 生成的图片地址
* @return
* @throws IOException
*/
public static String createImageByText(String text, int picFontSize, Color bgColor, Color fontColor, Font font, String basePath) throws IOException {
//校验
if (isBlank(text) || picFontSize <= 0 || bgColor == null || fontColor == null || font == null || isBlank(basePath)) {
throw new RuntimeException("参数错误!");
}

//用于字体适应的单位长度
float beautify = picFontSize / 20.0f;
//图片宽高
int width = text.length() * picFontSize + (int) (5 * beautify);
int height = picFontSize + (int) (5 * beautify);
//生成图片
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bufferedImage.createGraphics();
//背景颜色
g.setColor(bgColor);
g.fillRect(0, 0, width, height);
//抗锯齿设置
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//字体颜色
g.setColor(fontColor);
g.setFont(font.deriveFont((float) picFontSize));
//在指定坐标除添加文字
g.drawString(text, beautify, picFontSize);
g.dispose();
String fileName = basePath + "/" + getUUID() + "." + PictureConstants.JPG;
try (FileOutputStream out = new FileOutputStream(fileName)) {
ImageIO.write(bufferedImage, PictureConstants.JPEG, out);
}
return fileName;
}

该方法可以根据输入的文字、字体、颜色等生成一张图片。

为图片添加图片水印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 为图片在指定位置添加水印图片
*
* @param watermark 水印图片
* @param sourceImage 原图片
* @param positions 位置
* @param opacity 透明度 [0.0 - 1.0]
* @param targetFilePath 目标文件生成路径
* @return
* @throws IOException
*/
public static String watermarkPic(String watermark, String sourceImage, Positions positions, float opacity, String targetFilePath) throws IOException {
if (isBlank(watermark) || isBlank(sourceImage) || isBlank(targetFilePath) || positions == null || opacity < 0 || opacity > 1) {
throw new RuntimeException("参数错误!");
}
//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetFilePath + "/" + getUUID() + "." + sourceFormat;
Thumbnails.of(sourceImage).scale(1.0f).watermark(positions, ImageIO.read(new File(watermark)), opacity).outputFormat(sourceFormat).toFile(targetFile);
return targetFile;
}

/**
* 为图片在指定位置添加水印图片
*
* @param watermark 水印图片
* @param sourceImage 原图片
* @param x 水印位置(距原图片中心 x方向的偏移量) x>0 在图片中心下方
* @param y 水印位置(距原图片中心 y方向的偏移量) y>0 在图片中心右侧
* @param opacity 透明度 [0.0 - 1.0]
* @param targetFilePath 目标文件生成路径
* @return
* @throws IOException
*/
public static String watermarkPic(String watermark, String sourceImage, int x, int y, float opacity, String targetFilePath) throws IOException {
if (isBlank(watermark) || isBlank(sourceImage) || isBlank(targetFilePath) || opacity < 0 || opacity > 1) {
throw new RuntimeException("参数错误!");
}

//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetFilePath + "/" + getUUID() + "." + sourceFormat;

Image src = ImageIO.read(new File(sourceImage));
int width = src.getWidth(null);
int height = src.getHeight(null);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(src, 0, 0, width, height, null);
// 水印文件
Image waterPic = ImageIO.read(new File(watermark));
int waterWidth = waterPic.getWidth(null);
int waterHeight = waterPic.getHeight(null);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, opacity));
g.drawImage(waterPic, (width - waterWidth) / 2 + x,
(height - waterHeight) / 2 + y, waterWidth, waterHeight, null);
// 水印文件结束
g.dispose();
ImageIO.write(image, sourceFormat, new File(targetFile));
return targetFile;
}

上述方法分别使用了Thumbnails工具类和ImageIO类为原图片添加水印图片。

为图片添加文字水印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 添加文字水印
*
* @param text 水印文字
* @param sourceImage 原图片
* @param fontColor 字体颜色
* @param font 字体
* @param x 字体水印位置(距原图片中心 x方向的偏移量) x>0 在图片中心下方
* @param y 字体水印位置(距原图片中心 x方向的偏移量) y>0 在图片中心右侧
* @param opacity 透明度 [0.0 - 1.0]
* @param targetFilePath 目标文件生成路径
* @return
* @throws IOException
*/
public static String watermarkText(String text, String sourceImage, Color fontColor, Font font, int x, int y, float opacity, String targetFilePath) throws IOException {
if (isBlank(text) || isBlank(sourceImage) || fontColor == null || font == null || isBlank(targetFilePath) || opacity < 0 || opacity > 1) {
throw new RuntimeException("参数错误!");
}

//获取图片真实格式
String sourceFormat = getExtension(sourceImage).toLowerCase();
String targetFile = targetFilePath + "/" + getUUID() + "." + sourceFormat;

Image src = ImageIO.read(new File(sourceImage));
int width = src.getWidth(null);
int height = src.getHeight(null);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(src, 0, 0, width, height, null);
g.setColor(fontColor);
g.setFont(font);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, opacity));
//抗锯齿设置
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

// 在指定坐标绘制水印文字
g.drawString(text, (width - (getLength(text) * font.getSize()))
/ 2 + x, (height - font.getSize()) / 2 + y);
g.dispose();
ImageIO.write(image, sourceFormat, new File(targetFile));
return targetFile;
}

上述方法可为图片在指定位置添加文字水印。

PS:

这儿说下图片去水印的问题,一般情况下,对于一个项目,是保留原图,需要的时候再添加水印,从而实现添加水印和去除水印的效果。

而如果就是想给一张有水印的图片去除水印,可以使用[OpenCV](https://opencv.org/)等相关进行处理。

相关处理在这儿就不过多介绍了。

Java在图片深度处理这方面是很一般的,一般进行图片深度处理时,都会调用其它语言的API。

如上面提到的OpenCV,其核心算法就是由C++语言实现的,同时提供了其它语言的接口。

GIF图片的生成与转换

GIF相关操作可以使用com.madgag.animated-gif-lib相关Jar包,这个工具类Maven地址如下:

1
2
3
4
5
<dependency>
<groupId>com.madgag</groupId>
<artifactId>animated-gif-lib</artifactId>
<version>1.4</version>
</dependency>

这个工具包在 2017年7月维护到最新版本1.4后就再也没有维护过了。

这个工具包里有4个类AnimatedGifEncoder.javaGifDecoder.javaLZWEncoder.javaNeuQuant.java

将传入的若干图片生成gif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 根据传入的图片生成指定间隔的gif图像
*
* @param imageList 待处理图片
* @param basePath 生成gif文件路径
* @param delay 每张图片间隔
* @return
*/
public static String imageToGif(List<String> imageList, String basePath, int delay) throws IOException {
//校验
if(isEmpty(imageList) || isBlank(basePath) || delay < 0){
throw new RuntimeException("参数错误!");
}

String fileName = basePath + "/" + getUUID() + "." + PictureConstants.GIF;
File file = new File(fileName);
try (
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
FileOutputStream fileOutputStream = new FileOutputStream(file);
) {
//生成GIF
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.start(byteArrayOutputStream);
//设置延迟时间
e.setDelay(delay);
for (String imagePath : imageList) {
e.addFrame(ImageIO.read(new FileInputStream(imagePath)));
//e.setDelay(delay);//可以设置不同的延迟时间
}
e.finish();

//写入到文件
byte[] byteArray = byteArrayOutputStream.toByteArray();
fileOutputStream.write(byteArray);
fileOutputStream.flush();
}
return fileName;
}

调用此方法,可以将传入的一系列图片生成一张gif,我们应尽量保证传入的图片格式一致。

将gif图片拆分

我们这里以jpg为例,将gif图片拆分成一张张jpg格式的图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* GIF 图片转 jpg
* @param gifPath GIF图片地址
* @param basePath 生成图片地址
* @return
* @throws IOException
*/
public static List<String> gifToImage(String gifPath, String basePath) throws IOException {
//校验
if(isBlank(gifPath) || isBlank(basePath)){
throw new RuntimeException("参数错误!");
}

File file = new File(gifPath);
String fileName = file.getName();
String fileNamePrefix = fileName.substring(0, fileName.lastIndexOf("."));
String imagePath = basePath + "/" + fileNamePrefix + "_%s." + PictureConstants.JPG;

//GIF处理
GifDecoder gifDecoder = new GifDecoder();
gifDecoder.read(gifPath);
//frame个数
int count = gifDecoder.getFrameCount();
List<String> imageList = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
//得到帧
BufferedImage bufferedImage = gifDecoder.getFrame(i);
//int delay = gifDecoder.getDelay(i);//延迟时间
String imageFileName = String.format(imagePath, i);
//生成jpg文件
try (FileOutputStream fileOutputStream = new FileOutputStream(imageFileName)) {
ImageIO.write(bufferedImage, PictureConstants.JPEG, fileOutputStream);
}
imageList.add(imageFileName);
}
return imageList;
}

总结

将一些需要的代码记录下来形成自己的工具类包,提高自身经验总结,对以后是蛮有帮助的。

源码

源码详见我的 Github




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道