All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ajaxjs.util.io.ImageHelper Maven / Gradle / Ivy

package com.ajaxjs.util.io;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.function.BiFunction;
import java.util.function.Function;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

import com.ajaxjs.util.logger.LogHelper;

/**
 * 图片助手类
 * 
 * BufferedImage是Image的一个子类,BufferedImage生成的图片在内存里有一个图像缓冲区,利用这个缓冲区我们可以很方便的操作这个图片,通常用来做图片修改操作如大小变换、图片变灰、设置图片透明或不透明等
 * 
 * @author sp42 [email protected]
 *
 */
public class ImageHelper {
	private static final LogHelper LOGGER = LogHelper.getLog(ImageHelper.class);

	public File file;

	public int width;

	public int height;

	/**
	 * 提供一些方便的属性
	 * 
	 * @param filePath 图片文件完整路径
	 */
	public ImageHelper(String filePath) {
		file = new File(filePath);
		Integer[] arr = getBufferedImgSize.apply(getImg(file));
		width = arr[0];
		height = arr[1];
	}

	/**
	 * 根据文件名获取图片对象
	 * 
	 * @param filePath 图片文件完整路径
	 * @return 图片对象
	 */
	public static BufferedImage getImg(String filePath) {
		return getImg(new File(filePath));
	}

	/**
	 * 根据 文件对象获取图片对象
	 * 
	 * @param file 文件对象
	 * @return 图片对象
	 */
	public static BufferedImage getImg(File file) {
		try {
			return ImageIO.read(file);
		} catch (IOException e) {
			LOGGER.warning(e);
			return null;
		}
	}

	/**
	 * BufferedImage 转换为 byte[]。 在传输中,图片是不能直接传的,因此需要把图片变为字节数组,然后传输比较方便。
	 * 
	 * @param bImg   缓冲图片对象
	 * @param format 图片格式,例如 "jpg"
	 * @return 图片字节码
	 */
	public static byte[] img2byte(BufferedImage bImg, String format) {
		ByteArrayOutputStream out = new ByteArrayOutputStream(IoHelper.BUFFER_SIZE);// 1024 指定缓冲大小

		try {
			ImageIO.write(bImg, format, out);
		} catch (IOException e) {
			LOGGER.warning(e);
			return null;
		}

		return out.toByteArray();
	}

	/**
	 * 缓冲图片对象工厂函数
	 */
	public static final BiFunction getBufferedImg = (width,
			height) -> new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

	/**
	 * 获取图片对象分辨率大小。 数组中 0 = width 1 = height
	 */
	public static final Function getImgSize = img -> new Integer[] { img.getWidth(null),
			img.getHeight(null) };

	/**
	 * 获取缓冲图片对象分辨率大小。 数组中 0 = width 1 = height
	 */
	public static final Function getBufferedImgSize = img -> new Integer[] { img.getWidth(),
			img.getHeight() };

	/**
	 * 按照一定范围控制图片的高宽
	 * 
	 * @param originWidth  图片原始尺寸
	 * @param originHeight
	 * @param maxWidth     最大尺寸限制
	 * @param maxHeight
	 * @return 0=宽、1=高
	 */
	public static int[] resize(Integer originWidth, Integer originHeight, Integer maxWidth, Integer maxHeight) {
		// 目标尺寸
		int targetWidth = originWidth, targetHeight = originHeight;

		// 图片尺寸超过400x400的限制
		if (originWidth > maxWidth || originHeight > maxHeight) {
			if (originWidth / originHeight > maxWidth / maxHeight) {
				// 更宽,按照宽度限定尺寸
				targetWidth = maxWidth;
				targetHeight = Math.round(maxWidth * (originHeight.floatValue() / originWidth.floatValue()));
			} else {
				targetHeight = maxHeight;
				targetWidth = Math.round(maxHeight * (originWidth.floatValue() / originHeight.floatValue()));
			}
		}

		return new int[] { targetWidth, targetHeight };
	}

	/**
	 * 缩放比例
	 * 
	 * @param img    图片对象
	 * @param height
	 * @param width
	 * @return 缩放比例之后的高宽
	 */
	public int[] resize(Image img, int height, int width) {
		Integer[] arr = getImgSize.apply(img);
		int oWidth = arr[0], oHeight = arr[1];
		double ratio = (new Integer(oHeight)).doubleValue() / (new Integer(oWidth)).doubleValue();

		if (width != 0) {
			height = (int) (ratio * width);
		} else {
			width = (int) (height / ratio);
		}

		return new int[] { height, width };
	}

	/**
	 * 拉伸图片到指定尺寸
	 * 
	 * @param img    缓冲图片对象
	 * @param width  宽
	 * @param height 高
	 * @return 指定尺寸的缓冲图片对象
	 */
	public static BufferedImage resizeImg(Image img, int width, int height) {
		BufferedImage sImg = getBufferedImg.apply(width, height);
		sImg.getGraphics().drawImage(img, 0, 0, width, height, null);

		return sImg;
	}

	/**
	 * 修正 jpg 输出红色一层的问题
	 * 
	 * @param bImg 缓冲图片对象
	 * @return 缓冲图片对象
	 */
	public static BufferedImage fixICC(BufferedImage bImg) {
		Integer[] arr = getBufferedImgSize.apply(bImg);
		int w = arr[0], h = arr[1];

		int[] rgb = bImg.getRGB(0, 0, w, h, null, 0, w);
		BufferedImage _bImg = getBufferedImg.apply(w, h);
		_bImg.setRGB(0, 0, w, h, rgb, 0, w);

		return _bImg;
	}

	/**
	 * 加入边框
	 * 
	 * @param bImg        缓冲图片对象
	 * @param borderWidth 边框宽度
	 * @param borderColor 边框颜色
	 * @return 加入边框的图片
	 */
	public static BufferedImage addBorder(BufferedImage bImg, int borderWidth, Color borderColor) {
		Integer[] arr = getBufferedImgSize.apply(bImg);
		int w = arr[0], h = arr[1];

		Graphics2D g = bImg.createGraphics();
		BufferedImage imageNew = g.getDeviceConfiguration().createCompatibleImage(w + borderWidth * 2,
				h + borderWidth * 2);// 最终高宽
		g.dispose();
		g = imageNew.createGraphics();
		g.setBackground(borderColor);
		g.drawImage(bImg, borderWidth, borderWidth, w, h, null);

		// 添加边框
		g.setStroke(new BasicStroke(borderWidth));
		g.setColor(Color.white);
		g.drawRect(borderWidth / 2, borderWidth / 2, w + borderWidth, h + borderWidth);
		g.dispose();

		return imageNew;
	}

	/**
	 * 保存图片文件。 TODO 其实可以考虑使用 bufferedWrite(InputStream is, OutputStream out) 保存的
	 * 《使用ImageIO.write存储png格式图片性能较差问题》http://zhang-xzhi-xjtu.iteye.com/blog/1328084
	 * 
	 * @param bImg   缓冲图片对象
	 * @param format 图片格式,例如 "jpg"
	 * @param file   图片文件对象
	 * @return 是否操作成功
	 */
	public static boolean save(BufferedImage bImg, String format, File file) {
		try {
			return ImageIO.write(bImg, format, file);
		} catch (IOException e) {
			LOGGER.warning(e);
			return false;
		}
	}

	/**
	 * 保存图片文件
	 * 
	 * @param bImg     缓冲图片对象
	 * @param file     保存的文件对象
	 * @param isFixICC 是否修复 jpg 输出红色一层的问题
	 */
	public static void save(BufferedImage bImg, File file, boolean isFixICC) {
		String fileName = file.getName();
		String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
		FileHelper.mkDirByFileName(file.getPath()); // 如果目录不存在,先创建

		save(isFixICC ? fixICC(bImg) : bImg, ext, file);
	}

	/**
	 * 保存图片文件
	 * 
	 * @param bImg     缓冲图片对象
	 * @param filePath 保存的文件路径
	 */
	public static void save(BufferedImage bImg, String filePath) {
		save(bImg, new File(filePath), false);
	}

	/**
	 * 保存图片文件
	 * 
	 * @param bImg 缓冲图片对象
	 * @param file 保存的文件对象
	 */
	public static void save(BufferedImage bImg, File file) {
		save(bImg, file, false);
	}

	/**
	 * 保存图片文件
	 * 
	 * @param bImg     缓冲图片对象
	 * @param filePath 保存的文件路径
	 * @param isFixICC 是否修复 jpg 输出红色一层的问题
	 */
	public static void save(BufferedImage bImg, String filePath, boolean isFixICC) {
		save(bImg, new File(filePath), isFixICC);
	}

	/**
	 * 旋转图片
	 * 
	 * @param img   图片对象
	 * @param angel 旋转角度
	 * @return 缓冲图片对象
	 */
	public static BufferedImage rotate(Image img, int angel) {
		Integer[] arr = getImgSize.apply(img);
		int width = arr[0], height = arr[1];

		// calculate the new image size
		Rectangle rect_des = calcRotatedSize(new Rectangle(new Dimension(width, height)), angel);
		BufferedImage bImg = getBufferedImg.apply(rect_des.width, rect_des.height);

		Graphics2D g = bImg.createGraphics();
		g.translate((rect_des.width - width) / 2, (rect_des.height - height) / 2);// transform
		g.rotate(Math.toRadians(angel), width / 2, height / 2);
		g.drawImage(img, null, null);

		return bImg;
	}

	/**
	 * 
	 * @param src
	 * @param angel 旋转角度
	 * @return 矩形
	 */
	public static Rectangle calcRotatedSize(Rectangle src, int angel) {
		// if angel is greater than 90 degree, we need to do some conversion
		if (angel >= 90) {
			if (angel / 90 % 2 == 1) {
				int temp = src.height;
				src.height = src.width;
				src.width = temp;
			}

			angel = angel % 90;
		}

		double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;
		double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
		double angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;
		double angel_dalta_width = Math.atan((double) src.height / src.width);
		double angel_dalta_height = Math.atan((double) src.width / src.height);

		int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_width));
		int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_height));
		int des_width = src.width + len_dalta_width * 2;
		int des_height = src.height + len_dalta_height * 2;

		return new Rectangle(new Dimension(des_width, des_height));
	}

	/**
	 * 为图片添加水印图片
	 * 
	 * @param img       图片对象
	 * @param watermark 水印文件
	 * @return 缓冲图片对象
	 */
	public static BufferedImage mark(Image img, File watermark) {
		Image watermarkImg = getImg(watermark);

		Integer[] arr = getImgSize.apply(img), wArr = getImgSize.apply(watermarkImg);
		int width = arr[0], height = arr[1], w_width = wArr[0], w_height = wArr[1];

		BufferedImage bImg = getBufferedImg.apply(width, height);
		Graphics g = bImg.createGraphics();
		g.drawImage(img, 0, 0, width, height, null);
		g.drawImage(watermarkImg, width - w_width, height - w_height, w_width, w_height, null);// 水印文件在源文件的右下角
		g.dispose();

		return bImg;
	}

	/**
	 * 图片裁切
	 * 
	 * @param x      选择区域左上角的x坐标
	 * @param y      选择区域左上角的y坐标
	 * @param width  选择区域的宽度
	 * @param height 选择区域的高度
	 * @return 缓冲图片对象
	 */
	public static BufferedImage cut(String filePath, int x, int y, int width, int height) {
		Iterator it = ImageIO.getImageReadersByFormatName(FileHelper.getFileSuffix(filePath));
		ImageReader reader = it.next();

		try (ImageInputStream in = ImageIO.createImageInputStream(new FileInputStream(filePath));) {
			reader.setInput(in, true);
			ImageReadParam param = reader.getDefaultReadParam();
			param.setSourceRegion(new Rectangle(x, y, width, height));

			return reader.read(0, param);
		} catch (IOException e) {
			LOGGER.warning(e);
			return null;
		}
	}

	/**
	 * 为图片添加水印文字
	 * 
	 * @param img       图片对象
	 * @param watermark 水印文字
	 * @return 缓冲图片对象
	 */
	public static BufferedImage mark(Image img, String watermark) {
		// 读取原图片信息
		Integer[] arr = getImgSize.apply(img);
		int width = arr[0], height = arr[1];

		// 加水印
		BufferedImage bImg = getBufferedImg.apply(width, height);
		Graphics2D g = bImg.createGraphics();
		g.drawImage(img, 0, 0, width, height, null);
		g.setColor(Color.white); // 根据图片的背景设置水印颜色

		Font font = new Font("楷书", Font.PLAIN, 15);
		g.setFont(font);

		// int x = (srcImgWidth - getWatermarkLength(watermarkStr, g)) / 1;
		// int y = srcImgHeight / 1;

		FontMetrics fm = g.getFontMetrics(font);
		// 设置换行操作
		int fontHeight = fm.getHeight(); // 字符的高度
		int offsetLeft = 30, rowIndex = 12;

		for (int i = 0; i < watermark.length(); i++) {
			char c = watermark.charAt(i);
			int charWidth = fm.charWidth(c); // 字符的宽度

			// 另起一行
			if (Character.isISOControl(c) || offsetLeft >= (width - charWidth)) {
				rowIndex++;
				offsetLeft = 16;
			}

			g.drawString(String.valueOf(c), offsetLeft, rowIndex * fontHeight); // 把一个个写到图片上
			offsetLeft += charWidth; // 设置下字符的间距
		}
		// g.drawString(watermarkStr, x+10, y-5);

		g.dispose();

		return bImg;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy