/* * 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); } }