xiejun
2024-11-01 80b6cbfc9c861469146318d0b3dd5f8b8b525b8a
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.core.tool.utils;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.core.tool.support.IMultiOutputStream;
import org.springblade.core.tool.support.ImagePosition;
 
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.*;
import java.net.URL;
 
/**
 * 图片工具类
 *
 * @author Chill
 */
public final class ImageUtil {
 
    /**
     * Logger for this class
     */
    private static Logger LOGGER = LoggerFactory.getLogger(ImageUtil.class);
 
    /**
     * 默认输出图片类型
     */
    public static final String DEFAULT_IMG_TYPE = "JPEG";
 
    private ImageUtil() {
 
    }
 
    /**
     * 转换输入流到byte
     *
     * @param src  源
     * @param type 类型
     * @return byte[]
     * @throws IOException 异常
     */
    public static byte[] toByteArray(BufferedImage src, String type) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(src, defaultString(type, DEFAULT_IMG_TYPE), os);
        return os.toByteArray();
    }
 
    /**
     * 获取图像内容
     *
     * @param srcImageFile 文件路径
     * @return BufferedImage
     */
    public static BufferedImage readImage(String srcImageFile) {
        try {
            return ImageIO.read(new File(srcImageFile));
        } catch (IOException e) {
            LOGGER.error("Error readImage", e);
        }
        return null;
    }
 
    /**
     * 获取图像内容
     *
     * @param srcImageFile 文件
     * @return BufferedImage
     */
    public static BufferedImage readImage(File srcImageFile) {
        try {
            return ImageIO.read(srcImageFile);
        } catch (IOException e) {
            LOGGER.error("Error readImage", e);
        }
        return null;
    }
 
    /**
     * 获取图像内容
     *
     * @param srcInputStream 输入流
     * @return BufferedImage
     */
    public static BufferedImage readImage(InputStream srcInputStream) {
        try {
            return ImageIO.read(srcInputStream);
        } catch (IOException e) {
            LOGGER.error("Error readImage", e);
        }
        return null;
    }
 
    /**
     * 获取图像内容
     *
     * @param url URL地址
     * @return BufferedImage
     */
    public static BufferedImage readImage(URL url) {
        try {
            return ImageIO.read(url);
        } catch (IOException e) {
            LOGGER.error("Error readImage", e);
        }
        return null;
    }
 
 
    /**
     * 缩放图像(按比例缩放)
     *
     * @param src    源图像
     * @param output 输出流
     * @param type   类型
     * @param scale  缩放比例
     * @param flag   缩放选择:true 放大; false 缩小;
     */
    public final static void zoomScale(BufferedImage src, OutputStream output, String type, double scale, boolean flag) {
        try {
            // 得到源图宽
            int width = src.getWidth();
            // 得到源图长
            int height = src.getHeight();
            if (flag) {
                // 放大
                width = Long.valueOf(Math.round(width * scale)).intValue();
                height = Long.valueOf(Math.round(height * scale)).intValue();
            } else {
                // 缩小
                width = Long.valueOf(Math.round(width / scale)).intValue();
                height = Long.valueOf(Math.round(height / scale)).intValue();
            }
            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, defaultString(type, DEFAULT_IMG_TYPE), output);
 
            output.close();
        } catch (IOException e) {
            LOGGER.error("Error in zoom image", e);
        }
    }
 
    /**
     * 缩放图像(按高度和宽度缩放)
     *
     * @param src       源图像
     * @param output    输出流
     * @param type      类型
     * @param height    缩放后的高度
     * @param width     缩放后的宽度
     * @param bb        比例不对时是否需要补白:true为补白; false为不补白;
     * @param fillColor 填充色,null时为Color.WHITE
     */
    public final static void zoomFixed(BufferedImage src, OutputStream output, String type, int height, int width, boolean bb, Color fillColor) {
        try {
            double ratio = 0.0;
            Image itemp = src.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH);
            // 计算比例
            if (src.getHeight() > src.getWidth()) {
                ratio = Integer.valueOf(height).doubleValue() / src.getHeight();
            } else {
                ratio = Integer.valueOf(width).doubleValue() / src.getWidth();
            }
            AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
            itemp = op.filter(src, null);
 
            if (bb) {
                //补白
                BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics2D g = image.createGraphics();
                Color fill = fillColor == null ? Color.white : fillColor;
                g.setColor(fill);
                g.fillRect(0, 0, width, height);
                if (width == itemp.getWidth(null)) {
                    g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2, itemp.getWidth(null), itemp.getHeight(null), fill, null);
                } else {
                    g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0, itemp.getWidth(null), itemp.getHeight(null), fill, null);
                }
                g.dispose();
                itemp = image;
            }
            // 输出为文件
            ImageIO.write((BufferedImage) itemp, defaultString(type, DEFAULT_IMG_TYPE), output);
            // 关闭流
            output.close();
        } catch (IOException e) {
            LOGGER.error("Error in zoom image", e);
        }
    }
 
    /**
     * 图像裁剪(按指定起点坐标和宽高切割)
     *
     * @param src    源图像
     * @param output 切片后的图像地址
     * @param type   类型
     * @param x      目标切片起点坐标X
     * @param y      目标切片起点坐标Y
     * @param width  目标切片宽度
     * @param height 目标切片高度
     */
    public final static void crop(BufferedImage src, OutputStream output, String type, int x, int y, int width, int height) {
        try {
            // 源图宽度
            int srcWidth = src.getWidth();
            // 源图高度
            int srcHeight = src.getHeight();
            if (srcWidth > 0 && srcHeight > 0) {
                Image image = src.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
                // 四个参数分别为图像起点坐标和宽高
                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, defaultString(type, DEFAULT_IMG_TYPE), output);
                // 关闭流
                output.close();
            }
        } catch (Exception e) {
            LOGGER.error("Error in cut image", e);
        }
    }
 
    /**
     * 图像切割(指定切片的行数和列数)
     *
     * @param src   源图像地址
     * @param mos   切片目标文件夹
     * @param type  类型
     * @param prows 目标切片行数。默认2,必须是范围 [1, 20] 之内
     * @param pcols 目标切片列数。默认2,必须是范围 [1, 20] 之内
     */
    public final static void sliceWithNumber(BufferedImage src, IMultiOutputStream mos, String type, int prows, int pcols) {
        try {
            int rows = prows <= 0 || prows > 20 ? 2 : prows;
            int cols = pcols <= 0 || pcols > 20 ? 2 : pcols;
            // 源图宽度
            int srcWidth = src.getWidth();
            // 源图高度
            int srcHeight = src.getHeight();
            if (srcWidth > 0 && srcHeight > 0) {
                Image img;
                ImageFilter cropFilter;
                Image image = src.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
                // 每张切片的宽度
                int destWidth = (srcWidth % cols == 0) ? (srcWidth / cols) : (srcWidth / cols + 1);
                // 每张切片的高度
                int destHeight = (srcHeight % rows == 0) ? (srcHeight / rows) : (srcHeight / rows + 1);
                // 循环建立切片
                // 改进的想法:是否可用多线程加快切割速度
                for (int i = 0; i < rows; i++) {
                    for (int j = 0; j < cols; j++) {
                        // 四个参数分别为图像起点坐标和宽高
                        cropFilter = new CropImageFilter(j * destWidth, i * destHeight, destWidth, destHeight);
                        img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter));
                        BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB);
                        Graphics g = tag.getGraphics();
                        // 绘制缩小后的图
                        g.drawImage(img, 0, 0, null);
                        g.dispose();
                        // 输出为文件
                        ImageIO.write(tag, defaultString(type, DEFAULT_IMG_TYPE), mos.buildOutputStream(i, j));
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("Error in slice image", e);
        }
    }
 
    /**
     * 图像切割(指定切片的宽度和高度)
     *
     * @param src         源图像地址
     * @param mos         切片目标文件夹
     * @param type        类型
     * @param pdestWidth  目标切片宽度。默认200
     * @param pdestHeight 目标切片高度。默认150
     */
    public final static void sliceWithSize(BufferedImage src, IMultiOutputStream mos, String type, int pdestWidth, int pdestHeight) {
        try {
            int destWidth = pdestWidth <= 0 ? 200 : pdestWidth;
            int destHeight = pdestHeight <= 0 ? 150 : pdestHeight;
            // 源图宽度
            int srcWidth = src.getWidth();
            // 源图高度
            int srcHeight = src.getHeight();
            if (srcWidth > destWidth && srcHeight > destHeight) {
                Image img;
                ImageFilter cropFilter;
                Image image = src.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
                // 切片横向数量
                int cols = (srcWidth % destWidth == 0) ? (srcWidth / destWidth) : (srcWidth / destWidth + 1);
                // 切片纵向数量
                int rows = (srcHeight % destHeight == 0) ? (srcHeight / destHeight) : (srcHeight / destHeight + 1);
                // 循环建立切片
                // 改进的想法:是否可用多线程加快切割速度
                for (int i = 0; i < rows; i++) {
                    for (int j = 0; j < cols; j++) {
                        // 四个参数分别为图像起点坐标和宽高
                        cropFilter = new CropImageFilter(j * destWidth, i * destHeight, destWidth, destHeight);
                        img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter));
                        BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB);
                        Graphics g = tag.getGraphics();
                        // 绘制缩小后的图
                        g.drawImage(img, 0, 0, null);
                        g.dispose();
                        // 输出为文件
                        ImageIO.write(tag, defaultString(type, DEFAULT_IMG_TYPE), mos.buildOutputStream(i, j));
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("Error in slice image", e);
        }
    }
 
    /**
     * 图像类型转换:GIF-JPG、GIF-PNG、PNG-JPG、PNG-GIF(X)、BMP-PNG
     *
     * @param src        源图像地址
     * @param formatName 包含格式非正式名称的 String:如JPG、JPEG、GIF等
     * @param output     目标图像地址
     */
    public final static void convert(BufferedImage src, OutputStream output, String formatName) {
        try {
            // 输出为文件
            ImageIO.write(src, formatName, output);
            // 关闭流
            output.close();
        } catch (Exception e) {
            LOGGER.error("Error in convert image", e);
        }
    }
 
    /**
     * 彩色转为黑白
     *
     * @param src    源图像地址
     * @param output 目标图像地址
     * @param type      类型
     */
    public final static void gray(BufferedImage src, OutputStream output, String type) {
        try {
            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
            ColorConvertOp op = new ColorConvertOp(cs, null);
            src = op.filter(src, null);
            // 输出为文件
            ImageIO.write(src, defaultString(type, DEFAULT_IMG_TYPE), output);
            // 关闭流
            output.close();
        } catch (IOException e) {
            LOGGER.error("Error in gray image", e);
        }
    }
 
    /**
     * 给图片添加文字水印
     *
     * @param src      源图像
     * @param output   输出流
     * @param type      类型
     * @param text     水印文字
     * @param font     水印的字体
     * @param color    水印的字体颜色
     * @param position 水印位置 {@link ImagePosition}
     * @param x        修正值
     * @param y        修正值
     * @param alpha    透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
     */
    public final static void textStamp(BufferedImage src, OutputStream output, String type, String text, Font font, Color color
        , int position, int x, int y, float alpha) {
        try {
            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(color);
            g.setFont(font);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            // 在指定坐标绘制水印文字
            ImagePosition boxPos = new ImagePosition(width, height, calcTextWidth(text) * font.getSize(), font.getSize(), position);
            g.drawString(text, boxPos.getX(x), boxPos.getY(y));
            g.dispose();
            // 输出为文件
            ImageIO.write((BufferedImage) image, defaultString(type, DEFAULT_IMG_TYPE), output);
            // 关闭流
            output.close();
        } catch (Exception e) {
            LOGGER.error("Error in textStamp image", e);
        }
    }
 
    /**
     * 给图片添加图片水印
     *
     * @param src      源图像
     * @param output   输出流
     * @param type      类型
     * @param stamp    水印图片
     * @param position 水印位置 {@link ImagePosition}
     * @param x        修正值
     * @param y        修正值
     * @param alpha    透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
     */
    public final static void imageStamp(BufferedImage src, OutputStream output, String type, BufferedImage stamp
        , int position, int x, int y, float alpha) {
        try {
            int width = src.getWidth();
            int height = src.getHeight();
            BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
            Graphics2D g = image.createGraphics();
            g.drawImage(src, 0, 0, width, height, null);
            // 水印文件
            int stampWidth = stamp.getWidth();
            int stampHeight = stamp.getHeight();
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            ImagePosition boxPos = new ImagePosition(width, height, stampWidth, stampHeight, position);
            g.drawImage(stamp, boxPos.getX(x), boxPos.getY(y), stampWidth, stampHeight, null);
            // 水印文件结束
            g.dispose();
            // 输出为文件
            ImageIO.write((BufferedImage) image, defaultString(type, DEFAULT_IMG_TYPE), output);
            // 关闭流
            output.close();
        } catch (Exception e) {
            LOGGER.error("Error imageStamp", e);
        }
    }
 
    /**
     * 计算text的长度(一个中文算两个字符)
     *
     * @param text text
     * @return int
     */
    public final static int calcTextWidth(String text) {
        int length = 0;
        for (int i = 0; i < text.length(); i++) {
            if (new String(text.charAt(i) + "").getBytes().length > 1) {
                length += 2;
            } else {
                length += 1;
            }
        }
        return length / 2;
    }
 
    /**
     * 默认字符串
     * @param str 字符串
     * @param defaultStr 默认值
     * @return
     */
    public static String defaultString(String str, String defaultStr) {
        return ((str == null) ? defaultStr : str);
    }
 
}