app后端设计(13)--IM4JAVA+GraphicsMagick实现中文水印

在app的后台中,有时候为了标示版权,需要给图片加上水印。

在liunx中,IM4JAVA+GraphicsMagick是个高效处理图片的方案,图片的裁剪是使用了这个技术方案,为了减少不必要的开发成本和运维成本,对应水印,我们是打算继续采用这个方案。

但在开发的过程中,发现这个方案对中文水印支持得不好。

根据网上的搜索结果,就算采用了im4java的GMOperation,并将水印的字符串转成GBK的编码,添加中文水印时,对于奇数个数的中文,没问题;但对于偶数个数的中文,就出现乱码了。

试了多次后,发现这个问题没法解决,于是试了JMagick+ ImageMagick。

但是,在网上找到一份资料,http://javantsky.iteye.com/blog/747807, 里面提到JMagick+ImageMagick作为应用服务的缺点,并建议可以使用IM4JAVA:

The "JNI hazard" here is that if something you use (f.ex libtiff for reading
TIFF files) has a memory bug then it can make your whole JVM crash. Thats of
course frustrating and therefore its great to have im4java around, which
starts IM as an external process, so JVM crashes are avoided.
 * *
Coolest thing would be if JMagick and im4java could have the same API so it
was easy to switch depending on luckyness. Ive asked the author of im4java
to attemt to be as compatible as possible, but as im4java is very much
different internally its limited how much can be done in that direction.  

If you don't like the risk, stick to im4java. If your want optimal
performance give JMagick a try.  

And, its not JMagick that is buggy, its what it depends on (hereunder IM)
that is not always (memory) bug free on long running processes.
I also have never seen a mismatch between JMagick binary and ImageMagick
binaries leading to crashes.

最后,采用了以下的方案视线图片的中文水印:

1.先把中文生成背景透明的png图片

2.把这些png图片作为图片水印贴在图上

代码如下:

package test;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.im4java.core.CompositeCmd;
import org.im4java.core.ConvertCmd;
import org.im4java.core.GMOperation;
import org.im4java.core.GraphicsMagickCmd;
import org.im4java.core.IM4JavaException;
import org.im4java.core.IMOperation;

public class Watermark {

	/**
	 * 按九宫格位置添加水印
	 * @param srcPath		原图片路径
	 * @param distPath		新图片路径
	 * @param watermarkImg		水印图片路径
	 * @param position		九宫格位置[1-9],从上往下,从左到右排序
	 * @param x 		横向边距
	 * @param y 		纵向边距
	 * @param alpha		透明度
	 * @throws IOException
	 * @throws InterruptedException
	 * @throws IM4JavaException
	 */
	public  void WatermarkImg(String srcPath,String distPath,String watermarkImg, int position, int x, int y, int alpha) throws IOException, InterruptedException, IM4JavaException{
		int[] watermarkImgSide = getImageSide(watermarkImg);
		int[] srcImgSide = getImageSide(srcPath);
		int[] xy = getXY(srcImgSide, watermarkImgSide, position, y, x);
		watermarkImg(srcPath,distPath,watermarkImg,watermarkImgSide[0],watermarkImgSide[1],xy[0],xy[1],alpha);
	}

	private  int[] getImageSide(String imgPath) throws IOException {
    	int[] side = new int[2];
    	Image img = ImageIO.read(new File(imgPath));
    	side[0] = img.getWidth(null);
    	side[1] =img.getHeight(null);
    	return side;
	}

	/**
	 * 添加图片水印
	 * @param srcPath		原图片路径
	 * @param distPath		新图片路径
	 * @param watermarkImg		水印图片路径
	 * @param width		水印宽度(可以于水印图片大小不同)
	 * @param height	水印高度(可以于水印图片大小不同)
	 * @param x		水印开始X坐标
	 * @param y		水印开始Y坐标
	 * @param alpha		透明度[0-100]
	 * @throws IOException
	 * @throws InterruptedException
	 * @throws IM4JavaException
	 */
	private synchronized  void watermarkImg(String srcPath,String distPath,String watermarkImg, int width, int height, int x, int y, int alpha) throws IOException, InterruptedException, IM4JavaException{
        CompositeCmd cmd = new CompositeCmd(true);
        String path = "C://Program Files//GraphicsMagick-1.3.19-Q8";
		cmd.setSearchPath(path);
		IMOperation op = new IMOperation();
		op.dissolve(alpha);
		op.geometry(width, height, x, y);
		op.addImage(watermarkImg);
		op.addImage(srcPath);
		op.addImage(distPath);
		cmd.run(op);
	}	

	private  int[] getXY(int[] image, int[] watermark, int position, int x, int y) {
    	int[] xy = new int[2];
		if(position==1){
			xy[0] = x;
			xy[1] = y;
		}else if(position==2){
			xy[0] = (image[0]-watermark[0])/2;			//横向边距
			xy[1] = y;  //纵向边距
		}else if(position==3){
			xy[0] = image[0]-watermark[0]-x;
			xy[1] = y;
		}else if(position==4){
			xy[0] = x;
			xy[1] = (image[1]-watermark[1])/2;
		}else if(position==5){
			xy[0] = (image[0]-watermark[0])/2;
			xy[1] =  (image[1]-watermark[1])/2;
		}else if(position==6){
			xy[0] = image[0]-watermark[0]-x;
			xy[1] = (image[1] - watermark[1])/2;
		}else if(position==7){
			xy[0] = x;
			xy[1] = image[1] - watermark[1] - y;
		}else if(position==8){
			xy[0] =  (image[0]-watermark[0])/2;
			xy[1] = image[1] - watermark[1] - y;
		}else{
			xy[0] = image[0]-watermark[0]-x;
			xy[1] = image[1] - watermark[1] - y;
		}
		return xy;
	}

	/**
	 * 把文字转化为一张背景透明的png图片
	 * @param str 文字的内容
	 * @param fontType 字体,例如宋体
	 * @param fontSize 字体大小
	 * @param colorStr 字体颜色,不带#号,例如"990033"
	 * @param outfile  png图片的路径
	 * @throws Exception
	 */
	public  void converFontToImage(String str,String fontType,int fontSize,String colorStr, String outfile) throws Exception{

		Font font=new Font(fontType,Font.BOLD,fontSize);
		File file=new File(outfile);
		//获取font的样式应用在str上的整个矩形
		  Rectangle2D r=font.getStringBounds(str, new FontRenderContext(AffineTransform.getScaleInstance(1, 1),false,false));
		  int unitHeight=(int)Math.floor(r.getHeight());//获取单个字符的高度
		//获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度
		  int width=(int)Math.round(r.getWidth())+1;
		  int height=unitHeight+3;//把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
		//创建图片

		  BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		  Graphics2D g2d = image.createGraphics();
		  image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
		  g2d.dispose();
		  g2d = image.createGraphics();
		  g2d.setColor(Color.WHITE);
		  g2d.setStroke(new BasicStroke(1));
		  g2d.setColor(new Color(Integer.parseInt(colorStr, 16)));//在换成所需要的字体颜色
		  g2d.setFont(font);
		  g2d.drawString(str, 0,font.getSize());

		  ImageIO.write(image, "png", file);//输出png图片
		}	

}

测试实例:

package test;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

import org.im4java.core.CompositeCmd;
import org.im4java.core.ConvertCmd;
import org.im4java.core.GMOperation;
import org.im4java.core.IMOperation;

public class test {

	public static void main(String[] args) {  

				try {

					String src="D://src.jpg";  //需要加水印的源图片
					String desc="D://desc.jpg"; //生成的水印图片的路径
					String water="D://water.png"; //用中文转换成的背景透明的png图片
					String fontType="C:\\Windows\\Fonts\\simsun.ttc"; //指定字体文件为宋体
					String colorStr="990033"; //颜色
					int fontSize=18;

					Watermark watermark=new Watermark();

					/*
					 * 把文字转化为一张背景透明的png图片
					 * @param str 文字的内容
					 * @param fontType 字体,例如宋体
					 * @param fontSize 字体大小
					 * @param colorStr 字体颜色,不带#号,例如"990033"
					 * @param outfile  png图片的路径
					 * @throws Exception
					 */
					watermark.converFontToImage("中华人们", fontType, fontSize, colorStr, water);

					/*
					 * 把文字的png图片贴在原图上,生成水印
				 	 * @param srcPath		原图片路径
					 * @param distPath		新图片路径
					 * @param watermarkImg		水印图片路径
					 * @param position		九宫格位置[1-9],从上往下,从左到右排序
					 * @param x 		横向边距
					 * @param y 		纵向边距
					 * @param alpha		透明度
					 */
					watermark.WatermarkImg(src, desc, water, 1, 20,20, 100);

				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

		  }

}

源码已放在github:https://github.com/newjueqi/ChineseWaterMark#

app后端系列文章总目录

新建了“app后端技术” 交流qq群:254659220

[文章作者]曾健生

[作者邮箱][email protected]

[作者QQ]190678908

[新浪微博] @newjueqi

[博客]http://blog.csdn.net/newjueqi

时间: 2024-12-30 04:24:29

app后端设计(13)--IM4JAVA+GraphicsMagick实现中文水印的相关文章

app后端设计--总目录 (转)

特此说明,我转载的!!! app后端设计(1)--api app后端设计(2)--xmpp的使用 app后端设计(3)--短信,邮件,推送服务 app后端设计(4)-- 通讯的安全性 app后端设计(5)-- 表情的处理 app后端设计(6)-- LBS app后端设计(7)-- 项目管理 app后端设计(8)-- 数据库分表 app后端设计(9)-- 动态通知 app后端设计(10)--数据增量更新 app后端设计(11)-- 系统架构 app后端设计(12)--图片的处理 app后端设计(1

app后端设计(php)

来源:http://blog.csdn.net/column/details/mobilebackend.html?page=1 做了3年app相关的系统架构,api设计,先后在3个创业公司中工作,经历过手机网页端,android客户端,iphone客户端,现在从事日pv过5千万的云后端开发.其中的乐与苦,得与失,仰首问天有谁知?我觉得是时候来个总结,把相关的技术和心得记录下来. app后端设计(1)--api app后端设计(2)--xmpp的使用 app后端设计(3)--短信,邮件,推送服务

app后端设计(12)--图片的处理

app上线后,不断接受用户的反馈,于是,反馈非常差的情况下,都会有app的改版. 一旦app的改版,都会有比较大的UI改动,一改动UI,那么图片的尺寸也就必须要改变. 在app后端设计(1)—api(http://blog.csdn.net/newjueqi/article/details/14053733)这篇文章中,我提到过app后台图片处理的一个基本原则,数据库中只保存原图的路径.对于同一张图片来说,针对不同机型,不同app版本所需要的不同尺寸,使用动态生成的策略,大体思路如下: (1) 

app后端设计(12)--图片的处理.docx

app上线后,不断接受用户的反馈,于是,反馈非常差的情况下,都会有app的改版. 一旦app的改版,都会有比较大的UI改动,一改动UI,那么图片的尺寸也就必须要改变. 在app后端设计(1)-api(http://blog.csdn.net/newjueqi/article/details/14053733)这篇文章中,我提到过app后台图片处理的一个基本原则,数据库中只保存原图的路径.对于同一张图片来说,针对不同机型,不同app版本所需要的不同尺寸,使用动态生成的策略,大体思路如下: (1) 

app后端设计(0)--总目录(转)

原文:http://blog.csdn.net/newjueqi/article/details/19003775 做了接近两年app相关的系统架构,api设计,先后在两个创业公司中工作,经历过手机网页端,android客户端,iphone客户端,其中的乐与苦,得与失,仰首问天有谁知?我觉得是时候来个总结,把相关的技术和心得记录下来. 注:这系列文章谈到的经验是根据自身在小型创业团队中总结的,大牛们请飘过^-^ app后端设计(1)--api app后端设计(2)--xmpp的使用 app后端设

app后端设计(0)--总目录

原文:http://blog.csdn.net/newjueqi/article/details/19003775 做了接近两年app相关的系统架构,api设计,先后在两个创业公司中工作,经历过手机网页端,android客户端,iphone客户端,其中的乐与苦,得与失,仰首问天有谁知?我觉得是时候来个总结,把相关的技术和心得记录下来. 注:这系列文章谈到的经验是根据自身在小型创业团队中总结的,大牛们请飘过^-^ app后端设计(1)--api app后端设计(2)--xmpp的使用 app后端设

23.app后端如何架设文件系统

现在app展现内容的形式多种多样的,有文字,图片,声音,视频等等,其中文件占了一个很大的比重.随着app不断运营,文件会越来越多,占用的磁盘空间也不断增大,架设一套高效的文件系统,对于整个app架构有着巨大的影响. 1.    如果可能,使用成熟的文件云存储服务 对于创业公司来说,我一直推崇的架构原则是"尽量使用成熟的第三方服务和软件,自己只负责业务逻辑". 架设文件系统,需要牵涉到文件的分布式存储,图片水印,图片缩放,还有CDN等方面,每方面都能耗费掉巨大的开发成本和运维成本. 对于

14.app后端如何设计api

app和后端的交互,一般都是通过后端提供的api实现.api的设计,估计很多刚进入app后端的小伙伴会一无头绪,不知道怎么入门.下面根据自己3年的app后端经验,总结出下几个api设计原则,给小伙伴参考. 1. 什么是api? 这个问题在以前发表的文章"7.app和app后端的通讯"中其实已经回答了,这里再重复一次. 相信大家都用过银行的柜员机(ATM)的查询余额,转帐,取款等操作. 当在柜员机取款的时候,我们输入要取款的金额,隔一会钱就出来了,如果因为有什么问题不能取款(例如超过取款

app后端api设计【转】

博客:https://blog.csdn.net/newjueqi/article/details/44037011 app和后端的交互,一般都是通过后端提供的api实现.api的设计,估计很多刚进入app后端的小伙伴会一无头绪,不知道怎么入门.下面根据自己3年的app后端经验,总结出下几个api设计原则,给小伙伴参考. 1. 什么是api? 这个问题在以前发表的文章"7.app和app后端的通讯"中其实已经回答了,这里再重复一次. 相信大家都用过银行的柜员机(ATM)的查询余额,转帐