之前一直觉得实现在图片上添加文字很厉害,后来机缘巧合之下,我也需要自己去实现这个步骤,所以在下面梳理一下踩过的坑和实现的原理,希望能帮到大家。
从网上搜索JAVA相关生成GIF加文字或者加水印会发现已经有很多已经实现的了,不过大都是使用gif4j进行的合成,这种做法在个人使用肯定是没有什么问题,但是要自己公司用或者用于商用的话,就会有很多的License问题,秉着能省则省的原则,所以打算另辟蹊径。
首先要感谢Kevin Weiner提供的实现基础。这是工具类的链接,实现原理就是基于这个来实现的。
链接:https://github.com/rtyley/animated-gif-lib-for-java 尊重原版。
先贴实现代码:
public static void main(String[] args) throws Exception {
long timestart = System.currentTimeMillis();
InputStream inputStream1 = new FileInputStream("C:\\Users\\TMP\\3_5.gif");
new ImageUtil2().getGifSticker("测试问题","SIMSUN.ttf",inputStream1);
long timeend = System.currentTimeMillis();
System.out.println("用时"+(timeend-timestart));
}
public void getGifSticker(String markContent,String fontName, InputStream inputStream) throws FontFormatException, IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
GifDecoder decoder = new GifDecoder();
//读入gif数据流
decoder.read(inputStream);
//获取GIF的宽高
Dimension dimension = decoder.getFrameSize();
int height = (int)dimension.getHeight();
//获取字体问津数据流,用于规范生成文字的字体格式
InputStream intput = ImageUtil2.class.getResourceAsStream(fontName);
//生成字体
Font font1 = Font.createFont(Font.TRUETYPE_FONT, intput);
//要是想使用deriveFont设置字体大小必须重新指定Font,而且不支持整数型,只能使用浮点类型
Font font = font1.deriveFont(25.0f);
//读取帧数
int frameCount = decoder.getFrameCount();
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
String url = "C:\\Users\\TMP\\o\\" + + System.currentTimeMillis()+".gif";
encoder.start(url);
encoder.setRepeat(0);
Graphics2D g = null;
/**
* 对GIF进行拆分
* 每一帧进行文字处理
* 组装
*/
for (int i = 0; i < frameCount; i++) {
//初始化图像
g = (Graphics2D) decoder.getFrame(i).getGraphics();
/**
* RenderingHint是对图片像素,锯齿等等做的优化,可保证生成的图片放大锯齿点阵也不会很明显
*/
g.setRenderingHint(SunHints.KEY_ANTIALIASING, SunHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(SunHints.KEY_TEXT_ANTIALIASING, SunHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
// g.setRenderingHint(SunHints.KEY_STROKE_CONTROL, SunHints.VALUE_STROKE_PURE);
// g.setRenderingHint(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST, 100);
// g.setRenderingHint(SunHints.KEY_FRACTIONALMETRICS, SunHints.VALUE_FRACTIONALMETRICS_OFF);
// g.setRenderingHint(SunHints.KEY_RENDERING, SunHints.VALUE_RENDER_DEFAULT);
g.setColor(Color.black);
g.setFont(font);
g.setFont(font);
//设置打印文字和坐标
g.drawString(markContent, 12, 81);
g.dispose();
//组装每一帧
encoder.addFrame(decoder.getFrame(i));
//设置每帧的切换时间
if (i != frameCount - 1) {
encoder.setDelay(decoder.getDelay(i));
}
}
encoder.finish();
byte b[] = outputStream.toByteArray();
}finally {
try {
if (null != outputStream)
outputStream.close();
} catch (IOException e) {
}
}
}
传入参数 :markContent : 生成文字 fontName 字体文字 inputStream 图片数据流
原理分析:
根据传入的字体文字去当先CLASSPATH下面找到对应字体文件(建议使用ttf文件,对otf的支持还不是很理想,要想要没有License的ttf字体文件欢迎私聊)。
然后根据传入的image数据流进行操作,代码的分析我都在注释中有声明。
后来发现每次生成的所有GIF虽然都很正常,但是发现放大来看文字会有点阵锯齿问题,研究良久,对这个问题也颇有了解,后来根据JDK源码进行了优化,使用边缘抖动模糊进行的一个处理,这样的话如果放大的话边缘处也会很平滑,但是有一点缺点就是会有略微的模糊,不过对比来看,问题不大!
锯齿优化思路如下:
由于是使用的Graphics2D进行的图像的制作和渲染,所以这个支持RenderingHints的一个处理,关于RenderingHints声明如下:
* The {@code RenderingHints} class defines and manages collections of keys and associated values which allow an application to provide input into the choice of algorithms used by other classes which perform rendering and image manipulation services.
* The {@link java.awt.Graphics2D} class, and classes that implement {@link java.awt.image.BufferedImageOp} and {@link java.awt.image.RasterOp} all provide methods to get and possibly to set individual or groups of {@code RenderingHints} keys and their associated values.
* When those implementations perform any rendering or image manipulation operations they should examine the values of any {@code RenderingHints} that were requested by the caller and tailor the algorithms used accordingly and to the best of their ability.
*
* Note that since these keys and values are hints, there is no requirement that a given implementation supports all possible choices indicated below or that it can respond to requests to modify its choice of algorithm.
* The values of the various hint keys may also interact such that while all variants of a given key are supported in one situation,
* the implementation may be more restricted when the values associated with other keys are modified.
* For example, some implementations may be able to provide several types of dithering when the antialiasing hint is turned off, but have little control over dithering when antialiasing is on.
* The full set of supported keys and hints may also vary by destination since runtimes may use different underlying modules to render to the screen, or to {@link java.awt.image.BufferedImage} objects, or while printing.
*
* Implementations are free to ignore the hints completely, but should try to use an implementation algorithm that is as close as possible to the request.
* If an implementation supports a given algorithm when any value is used for an associated hint key, then minimally it must do so when the value for that key is the exact value that specifies the algorithm.
*
* The keys used to control the hints are all special values that subclass the associated {@link RenderingHints.Key} class.
* Many common hints are expressed below as static constants in this class, but the list is not meant to be exhaustive.
* Other hints may be created by other packages by defining new objects which subclass the {@code Key} class and defining the associated values.
粗略翻译如下:
* {@code RenderingHints}类定义和管理键和相关值的集合,允许应用程序为执行渲染和图像处理服务的其他类所使用的算法选择提供输入。
* {@link java.awt.Graphics2D}类和实现{@link java.awt.image.BufferedImageOp}和{@link java.awt.image.RasterOp}的类都提供了获取和可能设置个体的方法或{@code RenderingHints}键组及其相关值。
*当这些实现执行任何渲染或图像处理操作时,他们应检查调用者请求的任何{@code RenderingHints}的值,并根据他们的能力量身定制所使用的算法。
*
*请注意,由于这些键和值是提示 i>,因此不要求给定的实现支持下面指出的所有可能的选择,或者它可以响应修改其算法选择的请求。
*各种提示键的值也可以相互作用,以便在一种情况下支持给定键的所有变体,
*当修改与其他键相关联的值时,实现可能会受到更多限制。
*例如,某些实现可能能够在关闭抗锯齿提示时提供几种类型的抖动,但在打开抗锯齿时几乎无法控制抖动。
*完整的受支持的键和提示集也可能因目的地而异,因为运行时可能使用不同的底层模块呈现到屏幕,或{@link java.awt.image.BufferedImage}对象,或者在打印时。
*
*实现可以完全忽略提示,但应该尝试使用尽可能接近请求的实现算法。
*如果某个实现支持给定的算法,当任何值用于关联的提示键时,那么当该键的值是指定算法的确切值时,它必须最低限度地执行此操作。
*
*用于控制提示的键都是特殊值,它们是关联的{@link RenderingHints.Key}类的子类。
*许多常见提示在下面表示为此类中的静态常量,但该列表并不是详尽无遗的。
*其他提示可以通过定义新的对象来创建,这些对象继承了{@code Key}类并定义了相关的值。
所以我在本地使用的优化有这些:
g.setRenderingHint(SunHints.KEY_ANTIALIASING, SunHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(SunHints.KEY_TEXT_ANTIALIASING, SunHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
// g.setRenderingHint(SunHints.KEY_STROKE_CONTROL, SunHints.VALUE_STROKE_PURE);
// g.setRenderingHint(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST, 100);
// g.setRenderingHint(SunHints.KEY_FRACTIONALMETRICS, SunHints.VALUE_FRACTIONALMETRICS_OFF);
// g.setRenderingHint(SunHints.KEY_RENDERING, SunHints.VALUE_RENDER_DEFAULT);
第一行是说明对整个图画的一个矩阵优化,当然如果你和我一样是在图片上添加文字的话,第一行就和第二行没有什么区别。
简而言之,前两行都是对图片点阵锯齿的一个模糊话处理,但是JDK中也说了,会有不可控的抖动会出现,所以在原图放大来看会略微模糊一点。
支持并发,每次读入的图片数据库都有close不用手动去关闭。
以上。
良心手打,有疑问或者好的优化建议欢迎找茬。