java实现为图片添加水印


这个水印功能是看的慕课网的视频自己跟着学的,视频连接如下:http://www.imooc.com/learn/482

 

图片文件的上传界面、上传功能以及页面展示,这里就不再写了,可以去struts2环境下实现文件上传看,也可以去慕课网看视频。这里就记录下生成水印的代码。

先说说实现水印功能的思路:
1、创建图片缓存对象
2、创建绘图工具对象(有点类似于画布)
3、使用绘图工具对象将原图绘制到缓存图像对象中
4、使用绘图工具对象将水印(文字/图片)绘制到缓存图片对象中
5、创建图像编码工具类
6、使用图像编码工具类输出缓存图像到目标文件中

以下为水印功能的具体实现:

1、用来集中处理业务逻辑的Action(这里用的例子是一次性为多张图片添加水印):

public class WaterMarkAction extends ActionSupport{
	private File[] image;
	private String[] imageFileName;
	private String uploadPath;
	private List picInfo = new ArrayList();
	public String waterMark() throws Exception{
		String realUploadPath = ServletActionContext.getServletContext().getRealPath(uploadPath);
		if(image!=null && image.length>0){
			PicInfo pic=null;
			UploadService uploadService = new UploadService();//该service用来上传文件
			//MarkService markService = new TextMarkService();//文字水印
			//MarkService markService = new ImageMarkService();//图片水印
			//MarkService markService = new MoreTextMarkService();//多文字水印
			MarkService markService = new MoreImageMarkService();//多图片水印
			for(int i=0;i

 

2、定义实现水印功能的接口:添加的水印包括四种情况:单一文字水印、单一图片水印、多文字水印、多图片水印。所以这里将实现水印的功能定义成了一个抽象类(除了有需要子类实现的方法外还有子类需要用到的公用方法),抽象类代码如下:

public abstract class MarkService {
	public static final String MARK_TEXT="慕课网";//添加水印的文字
	
	public static final String FONT_NAME="微软雅黑";//所用字体
	public static final int FONT_STYLE = Font.BOLD;//文字样式,这里设置为加粗
	public static final  int FONT_SIZE = 80;//文字大小
	public static final  Color FONT_COLOR = Color.BLACK;//文字颜色
	
	//文字/图片位置
	public static final int X=10;//横坐标
	public static final int Y=10;//纵坐标
	public static final float ALPHA=0.3F;//透明度
	
	//作为水印添加到图片文件中的图片名称
	public static final String LOGO="logo.png";
	
	/**
	 * 添加水印功能
	 * @param image:需要添加水印的图片文件
	 * @param imageFileName:图片文件名称
	 * @param uploadPath:文件上传的相对路径
	 * @param realUploadPath:处理后的文件所在的绝对路径(目前上传的文件与处理后的文件在同一目录下)
	 * */
	public abstract String watermark(File image,String imageFileName,String uploadPath,String realUploadPath);
	
	//获取文本长度
	public int getTextLength(String text){
		int length = text.length();//获取所有文本的长度
		for(int i=0;i1){//字节长度大于1,说明是中文,那么需要延长文本长度
				length++;
			}
		}
		//计算总共有多少个字节,也就是有多少个字
		length = (length%2==0)?length/2:length/2+1;
		return length;
	}
}

3、单一文字水印的实现:

public class TextMarkService extends MarkService {
	public String watermark(File image, String imageFileName,
			String uploadPath, String realUploadPath) {
		String logoImageName = "logo_"+imageFileName;
		OutputStream out = null;
		try{
			//1、创建图片缓存对象
			//获取原图信息
			Image image2 = ImageIO.read(image);
			int width = image2.getWidth(null);//宽
			int height = image2.getHeight(null);//高
			
			//构造一个类型为预定义图像类型之一的 BufferedImage
			//TYPE_INT_RGB:表示一个图像,它具有合成整数像素的 8 位 RGB 颜色分量
			BufferedImage bfImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
			
			//2、创建绘图工具对象(有点类似于画布)
			Graphics2D graph = bfImage.createGraphics();
			
			//3、使用绘图工具对象将原图绘制到缓存图像对象中
			graph.drawImage(image2, 0, 0, width, height, null);
			graph.setFont(new Font(FONT_NAME,FONT_STYLE,FONT_SIZE));//设置字体
			graph.setColor(FONT_COLOR);//设置字体颜色
			
			//4、使用绘图工具对象将水印(文字/图片)绘制到缓存图片对象中
			//获取水印文字的宽高
			int textW = FONT_SIZE * getTextLength(MARK_TEXT);
			int textH = FONT_SIZE;
			
			//获取原图与水印图的宽度、高度之差
			int wDiff = width-textW;
			int hDiff = height-textH;
			//设置初始位置
			int x=X;
			int y=Y;
			//保证当前图片至少可以放下一个水印文字
			if(x>wDiff){
				x = wDiff;
			}
			if(y>hDiff){
				y = hDiff;
			}
			//设置透明度
			//AlphaComposite.getInstance:创建一个 AlphaComposite 对象,
			//它具有指定的规则和用来乘源色 alpha 值的常量 alpha 值。
			//在将源色与目标色合成前,要将源色乘以指定的 alpha 值。
			graph.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,ALPHA));
			//添加水印效果
			graph.drawString(MARK_TEXT, x, y+FONT_SIZE);//y保证至少可以显示一个水印的高度
			//释放graph
			graph.dispose();
			
			out = new FileOutputStream(realUploadPath+File.separator+logoImageName);
			//5、创建图像编码工具类
			JPEGImageEncoder en = JPEGCodec.createJPEGEncoder(out);
			//6、使用图像编码工具类输出缓存图像到目标文件中
			en.encode(bfImage);
			
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			//关闭流
			try{
				if(out!=null){
					out.close();
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		return uploadPath+File.separator+logoImageName;
	}
}

 4、多文字水印功能实现:多文字水印与单一文字水印的实现差不多,所不同的是:需要旋转画布并且每次都需要计算水印与画布之间的间距以保证整个图片上都有水印。

public class MoreTextMarkService extends MarkService {
	public String watermark(File image, String imageFileName,
			String uploadPath, String realUploadPath) {
		String logoImageName = "logo_"+imageFileName;
		OutputStream out = null;
		//1、创建图片缓存对象
		try{
			//获取原图信息
			Image image2 = ImageIO.read(image);
			int width = image2.getWidth(null);//宽
			int height = image2.getHeight(null);//高
			
			BufferedImage bfImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
			
			//2、创建绘图工具对象
			Graphics2D graph = bfImage.createGraphics();
			
			//3、使用绘图工具对象将原图绘制到缓存图像对象中
			graph.drawImage(image2, 0, 0, width, height, null);
			graph.setFont(new Font(FONT_NAME,FONT_STYLE,FONT_SIZE));//设置字体
			graph.setColor(FONT_COLOR);//设置字体颜色
			
			//4、使用绘图工具对象将水印(文字/图片)绘制到缓存图片对象中
			//获取水印文字的宽高
			int textW = FONT_SIZE * getTextLength(MARK_TEXT);
			int textH = FONT_SIZE;
			
			//设置透明度
			graph.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,ALPHA));
			
			//设置旋转角度及旋转中心
			//Math.toDegrees():将用弧度表示的角转换为近似相等的用角度表示的角
			//Math.toRadians():将用角度表示的角转换为近似相等的用弧度表示的角,rotate的第一个参数是用弧度表示的,所以使用toRadians方法
			//相同的设置成30,结果为:toDegrees方法添加倒置的水印,toRadians添加正置的水印
			graph.rotate(Math.toRadians(30),bfImage.getWidth()/2, bfImage.getHeight()/2);
			
			int x = -width/2;
			int y= -height/2;
			while(x

代码中while循环是用来计算水印和画布之间的间距问题的,我能理解原理但是说不出来,可以看这个视频(http://www.imooc.com/video/10602)去参悟参悟,人家用动画说的可清楚啦。

这里还有个容易误导的地方:我们平时看到的水印都是相对于图片偏转一定角度的,实际上在实现的过程中,水印文字没有倾斜,图片也没有偏转,偏转的是画布。使用绘图工具Graphics2D绘画都是水平画的,也就是从左往右画,画布是水平的绘画的内容也是水平的,画出来的东西也自然是水平的。但是在绘画过程中将画布倾斜再水平画内容,画完后再将画布放水平,画出来的东西自然也就有一定的倾斜角度了。这是水印文字总会有一定倾斜幅度的原理。(讲原理的视频链接:http://www.imooc.com/video/10760

 

5、单一图片水印功能实现:与文字所不同的是,文字计算文字的大小这里计算图片的大小,且还需要读取图片文件。

public class ImageMarkService extends MarkService {
	public String watermark(File image, String imageFileName,
			String uploadPath, String realUploadPath) {
		String logoImageName = "logo_"+imageFileName;
		OutputStream out = null;
		//用来作为水印的图片位置
		String logoFilePath = realUploadPath+File.separator+LOGO;
		try{
			//1、创建图片缓存对象
			//获取原图信息
			Image image2 = ImageIO.read(image);
			int width = image2.getWidth(null);//宽
			int height = image2.getHeight(null);//高
			
			BufferedImage bfImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
			
			//2、创建绘图工具对象
			Graphics2D graph = bfImage.createGraphics();
			
			//3、使用绘图工具对象将原图绘制到缓存图像对象中
			graph.drawImage(image2, 0, 0, width, height, null);
			
			//获取水印图片文件
			File logoFile = new File(logoFilePath);
			Image logoImage = ImageIO.read(logoFile);
			
			//4、使用绘图工具对象将水印(文字/图片)绘制到缓存图片对象中
			//获取水印文字的宽高
			int imageW = logoImage.getWidth(null);
			int imageH = logoImage.getHeight(null);
			
			//获取原图与水印图的宽度、高度之差
			int wDiff = width-imageW;
			int hDiff = height-imageH;
			//设置初始位置
			int x=X;
			int y=Y;
			if(x>wDiff){
				x = wDiff;
			}
			if(y>hDiff){
				y = hDiff;
			}
			//设置透明度
			graph.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,ALPHA));
			//添加水印效果
			graph.drawImage(logoImage, x, y,null);//y保证至少可以显示一个水印的高度
			//释放graph
			graph.dispose();
			
			out = new FileOutputStream(realUploadPath+File.separator+logoImageName);
			//5、创建图像编码工具类
			JPEGImageEncoder en = JPEGCodec.createJPEGEncoder(out);
			//6、使用图像编码工具类输出缓存图像到目标文件中
			en.encode(bfImage);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			try{
				if(out!=null){
					out.close();//关闭流
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		return uploadPath+File.separator+logoImageName;
	}
}

6、多图片水印功能实现:这个就是综合了单一图片和多文字水印的功能。

public class MoreImageMarkService extends MarkService {
	public String watermark(File image, String imageFileName,
			String uploadPath, String realUploadPath) {
		String logoImageName = "logo_"+imageFileName;
		OutputStream out = null;
		String logoFilePath = realUploadPath+File.separator+LOGO;
		//1、创建图片缓存对象
		try{
			//获取原图信息
			Image image2 = ImageIO.read(image);
			int width = image2.getWidth(null);//宽
			int height = image2.getHeight(null);//高
			
			BufferedImage bfImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
			
			//2、创建绘图工具对象
			Graphics2D graph = bfImage.createGraphics();
			
			//3、使用绘图工具对象将原图绘制到缓存图像对象中
			graph.drawImage(image2, 0, 0, width, height, null);
			//获取水印图片文件
			File logoFile = new File(logoFilePath);
			Image logoImage = ImageIO.read(logoFile);
			
			//4、使用绘图工具对象将水印(文字/图片)绘制到缓存图片对象中
			//获取水印文字的宽高
			int imageW = logoImage.getWidth(null);
			int imageH = logoImage.getHeight(null);
			
			//设置透明度
			graph.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,ALPHA));
			
			//设置旋转角度及旋转中心
			//Math.toDegrees():将用弧度表示的角转换为近似相等的用角度表示的角
			//Math.toRadians():将用角度表示的角转换为近似相等的用弧度表示的角,rotate的第一个参数是用弧度表示的,所以使用toRadians方法
			//相同的设置成30,结果为:toDegrees方法添加倒置的水印,toRadians添加正置的水印
			graph.rotate(Math.toRadians(30),bfImage.getWidth()/2, bfImage.getHeight()/2);
			
			int x = -width/2;
			int y= -height/2;
			while(x