验证码生成类(可生成gif动态验证码)
工具类一
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.util.Random;/*** 固定验证码生成类* @author zql* @createTime 2020-11-30 21:41:48* @version 1.1* @modifyLog 1.1 优化代码**/
public class VerifyCodeUtil {private static String randomTextCode = null;/*** 返回验证码值* * @return*/public static String getRandomCode() {return VerifyCodeUtil.randomTextCode;}/*** 设置验证码值* * @param randomCode*/private static void setRandomCode(String randomTextCode) {VerifyCodeUtil.randomTextCode = randomTextCode;}/*** 定义验证码序列,没有1 I L l 0 o*/private static char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T','U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' ,'a','b','c','d','e','f','g','h','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z'};/*** 验证码长度*/private static int charNum = codeSequence.length;private static String textCode() {// 创建一个随机数生成器类 用于随机选取验证码字符Random random = new Random();// 字符个数int fontCount = 4;// 定义StringBuffer,用于接收生成的验证码StringBuffer verifyCode = new StringBuffer();// 随机产生fontCount数字的验证码。for (int i = 0; i < fontCount; i++) {// 每次随机拿一个字符String str = String.valueOf(VerifyCodeUtil.codeSequence[random.nextInt(VerifyCodeUtil.charNum)]);verifyCode.append(str);}return verifyCode.toString();}/*** 获取验证码图片* @author zql* @createTime 2020-11-30 21:44:14** @return 图片缓存对象*/public static BufferedImage createImageCode() {String textCode = textCode();VerifyCodeUtil.setRandomCode(textCode);return VerifyCodeUtil.createImageCode(textCode);}/*** 获取验证码图片组,用于合成gif动态图 ,长度固定为10* @author zql* @createTime 2020-11-30 21:44:03** @return 图片缓存对象*/public static BufferedImage[] createImageCodes() {String textCode = textCode();BufferedImage[] bImg = new BufferedImage[10];for (int i = 0; i < 10; i++) {bImg[i] = VerifyCodeUtil.createImageCode(textCode);}setRandomCode(textCode);return bImg;}/*** 绘制验证码图片* @author zql* @createTime 2020-11-30 21:43:47** @param textCode * @return 图片缓存对象 */private static BufferedImage createImageCode(String textCode) {// 定义验证码图片框// 验证码图片的宽度,可自行修改int width = 100;// 验证码图片的高度,可自行修改int height = 36;BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 定义图片上的图形和干扰线Graphics graphics = bufferedImage.createGraphics();// 绘制背景图 为浅灰色graphics.setColor(Color.LIGHT_GRAY);// 填充一个矩形,第一个参数:要填充的矩形的起始x坐标;第二个参数:要填充的矩形的起始y坐标;第三个参数:要填充的矩形的宽度;第四个参数:要填充的矩形的高度;graphics.fillRect(0, 0, width, height);// 画边框graphics.setColor(Color.BLACK); graphics.drawRect(0, 0, width - 1, height - 1);// 随机产生16条灰色干扰线,使图像中的认证码不易识别graphics.setColor(Color.gray);// 创建一个随机数生成器类 用于随机产生干扰线Random random = new Random();int x1, y1, x2, y2;for (int i = 0; i < 16; i++) {x1 = random.nextInt(width);y1 = random.nextInt(height);x2 = random.nextInt(12);y2 = random.nextInt(12);// 第一个参数:第一个点的x坐标;第二个参数:第一个点的y坐标;第三个参数:第二个点的x坐标;第四个参数:第二个点的y坐标;graphics.drawLine(x1, y1, x1 + x2, y1 + y2);}// 字符个数int fontCount = 4;// 字体大小为图片高度的80%int fontSize = (int) (height * 0.8);// 第一个字符的x坐标,因为后面的字符坐标依次递增,所以它们的x轴值是fontX的倍数int fontX;// 验证字符的y坐标,因为并排所以值一样int fontY;// width-3 除去左右多余的位置,使验证码更加集中显示,减得越多越集中。// fontCount+1//等比分配显示的宽度,包括左右两边的空格fontX = (width - 3) / (fontCount + 1);// 第一个字母的起始位置fontY = height - 7;// 创建字体,字体的大小应该根据图片的高度来定。Font font = new Font("Fixedsys", Font.PLAIN, fontSize);graphics.setFont(font);// 随机产生fontCount数字的验证码。for (int i = 0; i < textCode.length(); i++) {// 赋予随机的颜色graphics.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));// 验证码字符显示到图象中graphics.drawString(String.valueOf(textCode.charAt(i)), (i + 1) * fontX, fontY);}return bufferedImage;}/*** 获取gif图片输入流* @author zql* @createTime 2020-11-30 21:43:31** @return 文件字节输入流*/public static FileInputStream createTempGifFile(){// 创建文件字节输入流FileInputStream fileIn = null;try {// 创建一个临时文件File file = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".gif");AnimatedGifEncoder e = new AnimatedGifEncoder();// 设置GIF重复次数e.setRepeat(0);// 设置合成位置e.start(file.getCanonicalPath());BufferedImage[] src = VerifyCodeUtil.createImageCodes();for (int i = 0; i < src.length; i++) {// 设置播放的延迟时间e.setDelay(200);// 添加到帧中e.addFrame(src[i]);}e.finish();fileIn = new FileInputStream(file);// 因为返回时InputStream已经关闭,所以不需要关闭文件字节输入流/// fileIn.close();// 删除临时文件,实际使用时,因文件字节输入流没有关闭,会导致删除失败,所以建议不要使用返回文件字节输入流的方式,而是直接使用本方法代码块代码file.delete();} catch (Exception e) {System.out.println("JPG to GIF failed:" + e.getMessage());e.printStackTrace();}return fileIn;}
}
工具类二
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.util.Objects;
import java.util.Random;/*** 多种类验证码生成类* @author zql* @createTime 2020-11-30 21:51:40* @version 1.1* @modifyLog 1.1 优化代码**/
public class VerifyCodeSUtil {/*** 生成的字符串验证码*/private static String randomTextCode = null;/*** 返回验证码值* @return*/public static String getRandomTextCode() {return VerifyCodeSUtil.randomTextCode;}/*** 设置验证码值* @param randomTextCode*/private static void setRandomTextCode(String randomTextCode) {VerifyCodeSUtil.randomTextCode = randomTextCode;}/**获取随机颜色 * @return*/private static Color getRandomColor() { Random random = new Random(); return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); }/*** 生成图片验证码 * @author zql* @createTime 2020-11-30 21:52:26** @param type 验证码类型* * type=0,验证码类型为仅数字,即0~9 * type=1,验证码类型为仅字母,即大小写字母混合* type=2, 验证码类型为数字和大小写字母混合 * type=3,验证码类型为数字和大写字母混合 * type=4,验证码类型为数字和小写字母混合* type=5,验证码类型为仅大写字母 * type=6,验证码类型为仅小写字母 *
* @param length 验证码字符长度,(即验证码的个数),如果长度小于0,则默认为4* @param excludeString 需排除的特殊字符 * @param width 图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度) * @param height 图片高度 * @param interLine 图片中干扰线的条数 * @param randomPosition 每个字符的高低位置是否随机 * @param hasBoder 是否画边框* @param boderColor 边框颜色,若为null则表示采用随机颜色 * @param backColor 图片颜色,若为null则表示采用随机颜色 * @param fontColor 字体颜色,若为null则表示采用随机颜色 * @param lineColor 干扰线颜色,若为null则表示采用随机颜色 * @return 图片缓存对象 */public static BufferedImage createImageCode(int type, int length, String excludeString, int width, int height, int interLine, boolean randomPosition, boolean hasBoder, Color boderColor, Color backColor, Color fontColor, Color lineColor) { VerifyCodeSUtil.randomTextCode = VerifyCodeSUtil.createTextCode(type, length, excludeString); VerifyCodeSUtil.setRandomTextCode(VerifyCodeSUtil.randomTextCode);return VerifyCodeSUtil.createImageCode(VerifyCodeSUtil.randomTextCode, width, height, interLine, randomPosition, hasBoder, boderColor, backColor, fontColor, lineColor); }/*** 生成图片验证码组,用于合成gif动态图 ,长度固定为10* @author zql* @createTime 2020-11-30 21:55:43** @param type type 验证码类型* * type=0,验证码类型为仅数字,即0~9 * type=1,验证码类型为仅字母,即大小写字母混合* type=2, 验证码类型为数字和大小写字母混合 * type=3,验证码类型为数字和大写字母混合 * type=4,验证码类型为数字和小写字母混合* type=5,验证码类型为仅大写字母 * type=6,验证码类型为仅小写字母 *
* @param length 验证码字符长度,(即验证码的个数),如果长度小于0,则默认为4* @param excludeString 需排除的特殊字符 * @param width 图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度) * @param height 图片高度 * @param interLine 图片中干扰线的条数 * @param randomPosition 每个字符的高低位置是否随机 * @param hasBoder 是否画边框* @param boderColor 边框颜色,若为null则表示采用随机颜色 * @param backColor 图片颜色,若为null则表示采用随机颜色 * @param fontColor 字体颜色,若为null则表示采用随机颜色 * @param lineColor 干扰线颜色,若为null则表示采用随机颜色 * @return 图片缓存对象 */public static BufferedImage[] createImageCodes(int type, int length, String excludeString, int width, int height, int interLine, boolean randomPosition, boolean hasBoder, Color boderColor, Color backColor, Color fontColor, Color lineColor) { VerifyCodeSUtil.randomTextCode = VerifyCodeSUtil.createTextCode(type, length, excludeString);BufferedImage[] bImg = new BufferedImage[10];for (int i = 0; i < 10; i++) {bImg[i] = VerifyCodeSUtil.createImageCode(VerifyCodeSUtil.randomTextCode, width, height, interLine, randomPosition, hasBoder, boderColor, backColor, fontColor, lineColor);}VerifyCodeSUtil.setRandomTextCode(VerifyCodeSUtil.randomTextCode);return bImg; }/*** 生成验证码字符串 * @author zql* @createTime 2020-11-30 21:57:59** @param type 验证码类型* * type=0,验证码类型为仅数字,即0~9 * type=1,验证码类型为仅字母,即大小写字母混合* type=2, 验证码类型为数字和大小写字母混合 * type=3,验证码类型为数字和大写字母混合 * type=4,验证码类型为数字和小写字母混合* type=5,验证码类型为仅大写字母 * type=6,验证码类型为仅小写字母 *
* @param length 验证码长度,要求大于0的整数 ,如果小于0,则默认为4* @param excludeString 需排除的特殊字符(无需排除则为null) * @return 验证码字符串 */private static String createTextCode(int type, int length, String excludeString) { if (length <= 0) {length = 4;}StringBuffer verifyCode = new StringBuffer();int i = 0;Random random = new Random();switch (type) {case 0:while (i < length) {int t = random.nextInt(10);// 排除特殊字符if (Objects.isNull(excludeString) || excludeString.indexOf(String.valueOf(t)) < 0) {verifyCode.append(t);i++;}}break;case 1:while (i < length) {// ASCII码表中,A-Z对应十进制是65-90,a-z对应十进制97-122int t = random.nextInt(123);if ((t >= 97 || (t >= 65 && t <= 90))&& (Objects.isNull(excludeString) || excludeString.indexOf((char) t) < 0)) {verifyCode.append((char) t);i++;}}break;case 2:while (i < length) {int t = random.nextInt(123);// ASCII码表中,0-9对应十进制是48-57if ((t >= 97 || (t >= 65 && t <= 90) || (t >= 48 && t <= 57))&& (Objects.isNull(excludeString) || excludeString.indexOf((char) t) < 0)) {verifyCode.append((char) t);i++;}}break;case 3:while (i < length) {int t = random.nextInt(91);if ((t >= 65 || (t >= 48 && t <= 57))&& (Objects.isNull(excludeString) || excludeString.indexOf((char) t) < 0)) {verifyCode.append((char) t);i++;}}break;case 4:while (i < length) {int t = random.nextInt(123);if ((t >= 97 || (t >= 48 && t <= 57))&& (Objects.isNull(excludeString) || excludeString.indexOf((char) t) < 0)) {verifyCode.append((char) t);i++;}}break;case 5:while (i < length) {int t = random.nextInt(91);if ((t >= 65) && (Objects.isNull(excludeString) || excludeString.indexOf((char) t) < 0)) {verifyCode.append((char) t);i++;}}break;case 6:while (i < length) {int t = random.nextInt(123);if ((t >= 97) && (Objects.isNull(excludeString) || excludeString.indexOf((char) t) < 0)) {verifyCode.append((char) t);i++;}}break;}return verifyCode.toString(); } /*** 生成验证码图片* @author zql* @createTime 2020-11-30 22:02:57** @param textCode 文本验证码 * @param width 图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度) * @param height 图片高度 * @param interLine 图片中干扰线的条数 * @param randomPosition 每个字符的高低位置是否随机 * @param hasBoder 是否画边框* @param boderColor 边框颜色,若为null则表示采用随机颜色 * @param backColor 图片颜色,若为null则表示采用随机颜色 * @param fontColor 字体颜色,若为null则表示采用随机颜色 * @param lineColor 干扰线颜色,若为null则表示采用随机颜色 * @return 图片缓存对象 * @return*/private static BufferedImage createImageCode(String textCode, int width, int height, int interLine, boolean randomPosition, boolean hasBoder, Color boderColor, Color backColor, Color fontColor, Color lineColor) { // 创建 图片缓存对象BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 获取图形上下文 Graphics graphics = bufferedImage.getGraphics(); // 绘制背景图 graphics.setColor(Objects.isNull(backColor) ? VerifyCodeSUtil.getRandomColor() : backColor); // 填充一个矩形,第一个参数:要填充的矩形的起始x坐标;第二个参数:要填充的矩形的起始y坐标;第三个参数:要填充的矩形的宽度;第四个参数:要填充的矩形的高度;graphics.fillRect(0, 0, width, height);if (hasBoder) {// 画边框graphics.setColor(Objects.isNull(boderColor) ? getRandomColor() : boderColor);graphics.drawRect(0, 0, width - 1, height - 1);}// 画干扰线Random random = new Random();if (interLine > 0) {// 定义坐标int x1 = 0, y1 = 0, x2 = width, y2 = 0;for (int i = 0; i < interLine; i++) {graphics.setColor(Objects.isNull(lineColor) ? VerifyCodeSUtil.getRandomColor() : lineColor);// 重直方向随机起点y1 = random.nextInt(height);// 重直方向随机终点y2 = random.nextInt(height);// 第一个参数:第一个点的x坐标;第二个参数:第一个点的y坐标;第三个参数:第二个点的x坐标;第四个参数:第二个点的y坐标;graphics.drawLine(x1, y1, x2, y2);}}// 字体大小为图片高度的80%int fontSize = (int) (height * 0.8);// 设置第一个字符x坐标int fontX = height - fontSize;// 设置第一个字符y坐标int fontY = fontSize;// 设定字体graphics.setFont(new Font("Default", Font.PLAIN, fontSize));// 写验证码字符for (int i = 0; i < textCode.length(); i++) {fontY = randomPosition ? (int) ((Math.random() * 0.3 + 0.6) * height) : fontY;graphics.setColor(Objects.isNull(fontColor) ? VerifyCodeSUtil.getRandomColor() : fontColor);// 将验证码字符显示到图象中,画字符串,x坐标即字符串左边位置,y坐标是指baseline的y坐标,即字体所在矩形的左上角y坐标+ascentgraphics.drawString(String.valueOf(textCode.charAt(i)), fontX, fontY);// 移动下一个字符的x坐标fontX += fontSize * 0.9;}graphics.dispose();return bufferedImage;} /*** 获取gif图片输入流* @author zql* @createTime 2020-11-30 22:03:27** @param type 验证码类型* * type=0,验证码类型为仅数字,即0~9 * type=1,验证码类型为仅字母,即大小写字母混合* type=2, 验证码类型为数字和大小写字母混合 * type=3,验证码类型为数字和大写字母混合 * type=4,验证码类型为数字和小写字母混合* type=5,验证码类型为仅大写字母 * type=6,验证码类型为仅小写字母 *
* @param length 验证码字符长度,(即验证码的个数),如果长度小于0,则默认为4* @param excludeString 需排除的特殊字符 * @param width 图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度) * @param height 图片高度 * @param interLine 图片中干扰线的条数 * @param randomPosition 每个字符的高低位置是否随机 * @param hasBoder 是否画边框* @param boderColor 边框颜色,若为null则表示采用随机颜色 * @param backColor 图片颜色,若为null则表示采用随机颜色 * @param fontColor 字体颜色,若为null则表示采用随机颜色 * @param lineColor 干扰线颜色,若为null则表示采用随机颜色 * @return 文件字节输入流*/public static FileInputStream createTempGifFile(int type, int length, String excludeString, int width, int height, int interLine, boolean randomPosition, boolean hasBoder, Color boderColor, Color backColor, Color fontColor, Color lineColor) {// 创建文件字节输入流FileInputStream fileIn = null;try {// 创建一个临时文件File file = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".gif");AnimatedGifEncoder e = new AnimatedGifEncoder();// 设置GIF重复次数e.setRepeat(0);// 设置合成位置e.start(file.getCanonicalPath());BufferedImage[] src = VerifyCodeSUtil.createImageCodes(type, length, excludeString, width, height, interLine, randomPosition, hasBoder, boderColor, backColor, fontColor, lineColor);for (int i = 0; i < src.length; i++) {// 设置播放的延迟时间e.setDelay(200);// 添加到帧中e.addFrame(src[i]);}e.finish();fileIn = new FileInputStream(file);// 因为返回时InputStream已经关闭,所以不需要关闭文件字节输入流/// fileIn.close();// 删除临时文件,实际使用时,因文件字节输入流没有关闭,会导致删除失败,所以建议不要使用返回文件字节输入流的方式,而是直接使用本方法代码块代码file.delete();} catch (Exception e) {System.out.println("JPG to GIF failed:" + e.getMessage());e.printStackTrace();}return fileIn;}
}
测试类
import org.junit.Test;import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;/*** 验证码生成测试类** @author zql* @version 1.0* @createTime 2021-01-03 0:12:00* @modifyLog*/
public class VerifyCodeUtilTest {@Testpublic void verifyCodeUtilTest() {verifyCodeUtil("E:/image/test.jpg");System.out.println("固定验证码生成类随机生成的验证码字符串:" + VerifyCodeUtil.getRandomCode());verifyCodeJpgToGif("E:/image/dynamic.gif");System.err.println("固定验证码生成类随机生成的验证码字符串(动态的):" + VerifyCodeUtil.getRandomCode());verifyCodeSUtil("E:/image/tests.jpg");System.out.println("多种类验证码生成类随机生成的验证码字符串:" + VerifyCodeSUtil.getRandomTextCode());verifyCodeJpgToGifs("E:/image/dynamics.gif");System.err.println("多种类验证码生成类随机生成的验证码字符串(动态的):" + VerifyCodeSUtil.getRandomTextCode());verifyCodeGifToJpgs("E:\\image\\dynamic.gif", "E:\\image");createTempGifFile();createTempGifFiles();// 实际使用gif生成方式FileInputStream fileIn = null;try {// 创建一个临时文件File file = File.createTempFile(System.currentTimeMillis() + "", ".gif");AnimatedGifEncoder e = new AnimatedGifEncoder();// 设置GIF重复次数e.setRepeat(0);// 设置合成位置e.start(file.getCanonicalPath());BufferedImage[] src = VerifyCodeUtil.createImageCodes();for (int i = 0; i < src.length; i++) {// 设置播放的延迟时间e.setDelay(200);// 添加到帧中e.addFrame(src[i]);}e.finish();fileIn = new FileInputStream(file);fileIn.close();// 删除临时文件,实际使用时,因文件字节输入流没有关闭,会导致删除失败,所以建议不要使用返回文件字节输入流的方式,而是直接使用本方法代码块代码file.delete();} catch (Exception e) {System.out.println( "JPG to GIF faileE:" + e.getMessage());e.printStackTrace();}}/*** 创建gif文件测试*/public void createTempGifFile() {try {InputStream is = VerifyCodeUtil.createTempGifFile();OutputStream out = new FileOutputStream(new File("E:\\image\\dynamicTest.gif"), true);int b = 0;byte[] buffer = new byte[1024];b = is.read(buffer);while (b != -1){// 写到输出流(out)中out.write(buffer, 0, b);b = is.read(buffer);}is.close();out.close();out.flush();} catch (Exception e) {e.printStackTrace();}}public void verifyCodeUtil(String path) {BufferedImage bufferedImage = VerifyCodeUtil.createImageCode();try {ImageIO.write(bufferedImage, "jpg", new File(path));} catch (IOException e) {e.printStackTrace();}}public void verifyCodeSUtil(String path) {// 一般来讲,1 I L l 0 o这几个字符比较相似,所以需要排除BufferedImage bufferedImage = VerifyCodeSUtil.createImageCode(2, 4, "1ILl0o", 100, 36, 5, true, false, null, new Color(238,242,237), new Color(0,0,0), null);try {ImageIO.write(bufferedImage, "jpg", new File(path));} catch (IOException e) {e.printStackTrace();}}/*** 把多张jpg图片合成一张** @param path String 生成的gif文件名 包含路径 */public void verifyCodeJpgToGif(String path) {try {AnimatedGifEncoder e = new AnimatedGifEncoder();// 设置GIF重复次数 e.setRepeat(0);//设置合成位置e.start(path);BufferedImage[] src = VerifyCodeUtil.createImageCodes();for (int i = 0; i < src.length; i++) {// 设置播放的延迟时间 e.setDelay(200);// 添加到帧中e.addFrame(src[i]);}e.finish();} catch (Exception e) {System.out.println( "JPG to GIF faileE:" + e.getMessage());e.printStackTrace();}}/*** 把多张jpg图片合成一张** @param path String 生成的gif文件名 包含路径 */public void verifyCodeJpgToGifs(String path) {try {AnimatedGifEncoder e = new AnimatedGifEncoder();// 设置GIF重复次数e.setRepeat(0);// 设置合成位置e.start(path);BufferedImage[] src = VerifyCodeSUtil.createImageCodes(2, 4, "1ILl0o", 100, 36, 5, true, false, null, new Color(238,242,237), new Color(0,0,0), null);for (int i = 0; i < src.length; i++) {// 设置播放的延迟时间e.setDelay(200);// 添加到帧中e.addFrame(src[i]);}e.finish();} catch (Exception e) {System.out.println( "JPG to GIF faileE:" + e.getMessage());e.printStackTrace();}}/*** 拆分gif图片** @param imagePath 需要拆分的图片路径* @param jpgPath 拆分出来的jpg图片路径* @throws IOException*/public void verifyCodeGifToJpgs(String imagePath, String jpgPath) {GifDecoder decoder = new GifDecoder();// imagePath源文件路径int status = decoder.read(imagePath);try {if (status != GifDecoder.STATUS_OK) {throw new IOException("read image " + imagePath + " error!");}// 获取GIF有多少个frameint frameCount = decoder.getFrameCount();for (int i = 0; i < frameCount; i++) {BufferedImage bufferedImage = decoder.getFrame(i);ImageIO.write(bufferedImage, "jpg", new File(jpgPath+"\\" + i + ".jpg"));}} catch (Exception e) {e.printStackTrace();}}/*** 创建gif文件测试*/public void createTempGifFiles() {try {InputStream is = VerifyCodeSUtil.createTempGifFile(2, 4, "1ILl0o", 100, 36, 5, true, false, null, new Color(238,242,237), new Color(0,0,0), null);OutputStream out = new FileOutputStream(new File("E:\\image\\dynamicTests.gif"), true);int b = 0;byte[] buffer = new byte[1024];b = is.read(buffer);while (b != -1){// 写到输出流(out)中 out.write(buffer, 0, b);b = is.read(buffer);}is.close();out.close();out.flush();} catch (Exception e) {e.printStackTrace();}}
}
如果是maven项目,可以直接引入依赖
<dependency><groupId>com.madgaggroupId><artifactId>animated-gif-libartifactId><version>1.2version>
dependency>
如果普通项目,由于jar比较难找,我直接把源码中需要的第三方类给贴出来了。
AnimatedGifEncoder.java
package top.zqlweb.tool.third.gif;import java.io.*;
import java.awt.*;
import java.awt.image.*;/*** Class AnimatedGifEncoder - Encodes a GIF file consisting of one or* more frames.* * Example:* AnimatedGifEncoder e = new AnimatedGifEncoder();* e.start(outputFileName);* e.setDelay(1000); // 1 frame per sec* e.addFrame(image1);* e.addFrame(image2);* e.finish();*
* No copyright asserted on the source code of this class. May be used* for any purpose, however, refer to the Unisys LZW patent for restrictions* on use of the associated LZWEncoder class. Please forward any corrections* to questions at fmsware.com.** @author Kevin Weiner, FM Software* @version 1.03 November 2003**/public class AnimatedGifEncoder {protected int width; // image sizeprotected int height;protected Color transparent = null; // transparent color if givenprotected Color background = null; // background color if givenprotected int transIndex; // transparent index in color tableprotected int repeat = -1; // no repeatprotected int delay = 0; // frame delay (hundredths)protected boolean started = false; // ready to output framesprotected OutputStream out;protected BufferedImage image; // current frameprotected byte[] pixels; // BGR byte array from frameprotected byte[] indexedPixels; // converted frame indexed to paletteprotected int colorDepth; // number of bit planesprotected byte[] colorTab; // RGB paletteprotected boolean[] usedEntry = new boolean[256]; // active palette entriesprotected int palSize = 7; // color table size (bits-1)protected int dispose = -1; // disposal code (-1 = use default)protected boolean closeStream = false; // close stream when finishedprotected boolean firstFrame = true;protected boolean sizeSet = false; // if false, get size from first frameprotected int sample = 10; // default sample interval for quantizer/*** Sets the delay time between each frame, or changes it* for subsequent frames (applies to last frame added).** @param ms int delay time in milliseconds*/public void setDelay(int ms) {delay = Math.round(ms / 10.0f);}/*** Sets the GIF frame disposal code for the last added frame* and any subsequent frames. Default is 0 if no transparent* color has been set, otherwise 2.* @param code int disposal code.*/public void setDispose(int code) {if (code >= 0) {dispose = code;}}/*** Sets the number of times the set of GIF frames* should be played. Default is 1; 0 means play* indefinitely. Must be invoked before the first* image is added.** @param iter int number of iterations.* @return*/public void setRepeat(int iter) {if (iter >= 0) {repeat = iter;}}/*** Sets the transparent color for the last added frame* and any subsequent frames.* Since all colors are subject to modification* in the quantization process, the color in the final* palette for each frame closest to the given color* becomes the transparent color for that frame.* May be set to null to indicate no transparent color.** @param c Color to be treated as transparent on display.*/public void setTransparent(Color c) {transparent = c;}/*** Sets the background color for the last added frame* and any subsequent frames.* Since all colors are subject to modification* in the quantization process, the color in the final* palette for each frame closest to the given color* becomes the background color for that frame.* May be set to null to indicate no background color* which will default to black.** @param c Color to be treated as background on display.*/public void setBackground(Color c) {background = c;}/*** Adds next GIF frame. The frame is not written immediately, but is* actually deferred until the next frame is received so that timing* data can be inserted. Invoking finish() flushes all* frames. If setSize was not invoked, the size of the* first image is used for all subsequent frames.** @param im BufferedImage containing frame to write.* @return true if successful.*/public boolean addFrame(BufferedImage im) {if ((im == null) || !started) {return false;}boolean ok = true;try {if (!sizeSet) {// use first frame's sizesetSize(im.getWidth(), im.getHeight());}image = im;getImagePixels(); // convert to correct format if necessaryanalyzePixels(); // build color table & map pixelsif (firstFrame) {writeLSD(); // logical screen descriptiorwritePalette(); // global color tableif (repeat >= 0) {// use NS app extension to indicate repswriteNetscapeExt();}}writeGraphicCtrlExt(); // write graphic control extensionwriteImageDesc(); // image descriptorif (!firstFrame) {writePalette(); // local color table}writePixels(); // encode and write pixel datafirstFrame = false;} catch (IOException e) {ok = false;}return ok;}/*** Flushes any pending data and closes output file.* If writing to an OutputStream, the stream is not* closed.*/public boolean finish() {if (!started) return false;boolean ok = true;started = false;try {out.write(0x3b); // gif trailerout.flush();if (closeStream) {out.close();}} catch (IOException e) {ok = false;}// reset for subsequent usetransIndex = 0;out = null;image = null;pixels = null;indexedPixels = null;colorTab = null;closeStream = false;firstFrame = true;return ok;}/*** Sets frame rate in frames per second. Equivalent to* setDelay(1000/fps).** @param fps float frame rate (frames per second)*/public void setFrameRate(float fps) {if (fps != 0f) {delay = Math.round(100f / fps);}}/*** Sets quality of color quantization (conversion of images* to the maximum 256 colors allowed by the GIF specification).* Lower values (minimum = 1) produce better colors, but slow* processing significantly. 10 is the default, and produces* good color mapping at reasonable speeds. Values greater* than 20 do not yield significant improvements in speed.** @param quality int greater than 0.* @return*/public void setQuality(int quality) {if (quality < 1) quality = 1;sample = quality;}/*** Sets the GIF frame size. The default size is the* size of the first frame added if this method is* not invoked.** @param w int frame width.* @param h int frame width.*/public void setSize(int w, int h) {if (started && !firstFrame) return;width = w;height = h;if (width < 1) width = 320;if (height < 1) height = 240;sizeSet = true;}/*** Initiates GIF file creation on the given stream. The stream* is not closed automatically.** @param os OutputStream on which GIF images are written.* @return false if initial write failed.*/public boolean start(OutputStream os) {if (os == null) return false;boolean ok = true;closeStream = false;out = os;try {writeString("GIF89a"); // header} catch (IOException e) {ok = false;}return started = ok;}/*** Initiates writing of a GIF file with the specified name.** @param file String containing output file name.* @return false if open or initial write failed.*/public boolean start(String file) {boolean ok = true;try {out = new BufferedOutputStream(new FileOutputStream(file));ok = start(out);closeStream = true;} catch (IOException e) {ok = false;}return started = ok;}public boolean isStarted() {return started;}/*** Analyzes image colors and creates color map.*/protected void analyzePixels() {int len = pixels.length;int nPix = len / 3;indexedPixels = new byte[nPix];NeuQuant nq = new NeuQuant(pixels, len, sample);// initialize quantizercolorTab = nq.process(); // create reduced palette// convert map from BGR to RGBfor (int i = 0; i < colorTab.length; i += 3) {byte temp = colorTab[i];colorTab[i] = colorTab[i + 2];colorTab[i + 2] = temp;usedEntry[i / 3] = false;}// map image pixels to new paletteint k = 0;for (int i = 0; i < nPix; i++) {int index =nq.map(pixels[k++] & 0xff,pixels[k++] & 0xff,pixels[k++] & 0xff);usedEntry[index] = true;indexedPixels[i] = (byte) index;}pixels = null;colorDepth = 8;palSize = 7;// get closest match to transparent color if specifiedif (transparent != null) {transIndex = findClosest(transparent);}}/*** Returns index of palette color closest to c**/protected int findClosest(Color c) {if (colorTab == null) return -1;int r = c.getRed();int g = c.getGreen();int b = c.getBlue();int minpos = 0;int dmin = 256 * 256 * 256;int len = colorTab.length;for (int i = 0; i < len;) {int dr = r - (colorTab[i++] & 0xff);int dg = g - (colorTab[i++] & 0xff);int db = b - (colorTab[i] & 0xff);int d = dr * dr + dg * dg + db * db;int index = i / 3;if (usedEntry[index] && (d < dmin)) {dmin = d;minpos = index;}i++;}return minpos;}/*** Extracts image pixels into byte array "pixels"*/protected void getImagePixels() {int w = image.getWidth();int h = image.getHeight();int type = image.getType();if ((w != width)|| (h != height)|| (type != BufferedImage.TYPE_3BYTE_BGR)) {// create new image with right size/formatBufferedImage temp =new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);Graphics2D g = temp.createGraphics();g.setColor(background);g.fillRect(0, 0, width, height);g.drawImage(image, 0, 0, null);image = temp;}pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();}/*** Writes Graphic Control Extension*/protected void writeGraphicCtrlExt() throws IOException {out.write(0x21); // extension introducerout.write(0xf9); // GCE labelout.write(4); // data block sizeint transp, disp;if (transparent == null) {transp = 0;disp = 0; // dispose = no action} else {transp = 1;disp = 2; // force clear if using transparent color}if (dispose >= 0) {disp = dispose & 7; // user override}disp <<= 2;// packed fieldsout.write(0 | // 1:3 reserveddisp | // 4:6 disposal0 | // 7 user input - 0 = nonetransp); // 8 transparency flagwriteShort(delay); // delay x 1/100 secout.write(transIndex); // transparent color indexout.write(0); // block terminator}/*** Writes Image Descriptor*/protected void writeImageDesc() throws IOException {out.write(0x2c); // image separatorwriteShort(0); // image position x,y = 0,0writeShort(0);writeShort(width); // image sizewriteShort(height);// packed fieldsif (firstFrame) {// no LCT - GCT is used for first (or only) frameout.write(0);} else {// specify normal LCTout.write(0x80 | // 1 local color table 1=yes0 | // 2 interlace - 0=no0 | // 3 sorted - 0=no0 | // 4-5 reservedpalSize); // 6-8 size of color table}}/*** Writes Logical Screen Descriptor*/protected void writeLSD() throws IOException {// logical screen sizewriteShort(width);writeShort(height);// packed fieldsout.write((0x80 | // 1 : global color table flag = 1 (gct used)0x70 | // 2-4 : color resolution = 70x00 | // 5 : gct sort flag = 0palSize)); // 6-8 : gct sizeout.write(0); // background color indexout.write(0); // pixel aspect ratio - assume 1:1}/*** Writes Netscape application extension to define* repeat count.*/protected void writeNetscapeExt() throws IOException {out.write(0x21); // extension introducerout.write(0xff); // app extension labelout.write(11); // block sizewriteString("NETSCAPE" + "2.0"); // app id + auth codeout.write(3); // sub-block sizeout.write(1); // loop sub-block idwriteShort(repeat); // loop count (extra iterations, 0=repeat forever)out.write(0); // block terminator}/*** Writes color table*/protected void writePalette() throws IOException {out.write(colorTab, 0, colorTab.length);int n = (3 * 256) - colorTab.length;for (int i = 0; i < n; i++) {out.write(0);}}/*** Encodes and writes pixel data*/protected void writePixels() throws IOException {LZWEncoder encoder =new LZWEncoder(width, height, indexedPixels, colorDepth);encoder.encode(out);}/*** Write 16-bit value to output stream, LSB first*/protected void writeShort(int value) throws IOException {out.write(value & 0xff);out.write((value >> 8) & 0xff);}/*** Writes string to output stream*/protected void writeString(String s) throws IOException {for (int i = 0; i < s.length(); i++) {out.write((byte) s.charAt(i));}}
}
GifDecoder.java
package top.zqlweb.tool.third.gif;import java.net.*;
import java.io.*;
import java.util.*;import java.awt.*;
import java.awt.image.*;/*** Class GifDecoder - Decodes a GIF file into one or more frames.*
* Example:* GifDecoder d = new GifDecoder();* d.read("sample.gif");* int n = d.getFrameCount();* for (int i = 0; i < n; i++) {* BufferedImage frame = d.getFrame(i); // frame i* int t = d.getDelay(i); // display duration of frame in milliseconds* // do something with frame* }* * No copyright asserted on the source code of this class. May be used for* any purpose, however, refer to the Unisys LZW patent for any additional* restrictions. Please forward any corrections to questions at fmsware.com.** @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick.* @version 1.03 November 2003**/public class GifDecoder {/*** File read status: No errors.*/public static final int STATUS_OK = 0;/*** File read status: Error decoding file (may be partially decoded)*/public static final int STATUS_FORMAT_ERROR = 1;/*** File read status: Unable to open source.*/public static final int STATUS_OPEN_ERROR = 2;protected BufferedInputStream in;protected int status;protected int width; // full image widthprotected int height; // full image heightprotected boolean gctFlag; // global color table usedprotected int gctSize; // size of global color tableprotected int loopCount = 1; // iterations; 0 = repeat foreverprotected int[] gct; // global color tableprotected int[] lct; // local color tableprotected int[] act; // active color tableprotected int bgIndex; // background color indexprotected int bgColor; // background colorprotected int lastBgColor; // previous bg colorprotected int pixelAspect; // pixel aspect ratioprotected boolean lctFlag; // local color table flagprotected boolean interlace; // interlace flagprotected int lctSize; // local color table sizeprotected int ix, iy, iw, ih; // current image rectangleprotected Rectangle lastRect; // last image rectprotected BufferedImage image; // current frameprotected BufferedImage lastImage; // previous frameprotected byte[] block = new byte[256]; // current data blockprotected int blockSize = 0; // block size// last graphic control extension infoprotected int dispose = 0;// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prevprotected int lastDispose = 0;protected boolean transparency = false; // use transparent colorprotected int delay = 0; // delay in millisecondsprotected int transIndex; // transparent color indexprotected static final int MaxStackSize = 4096;// max decoder pixel stack size// LZW decoder working arraysprotected short[] prefix;protected byte[] suffix;protected byte[] pixelStack;protected byte[] pixels;protected ArrayList<GifFrame> frames; // frames read from current fileprotected int frameCount;static class GifFrame {public GifFrame(BufferedImage im, int del) {image = im;delay = del;}public BufferedImage image;public int delay;}/*** Gets display duration for specified frame.** @param n int index of frame* @return delay in milliseconds*/public int getDelay(int n) {//delay = -1;if ((n >= 0) && (n < frameCount)) {delay = ((GifFrame) frames.get(n)).delay;}return delay;}/*** Gets the number of frames read from file.* @return frame count*/public int getFrameCount() {return frameCount;}/*** Gets the first (or only) image read.** @return BufferedImage containing first frame, or null if none.*/public BufferedImage getImage() {return getFrame(0);}/*** Gets the "Netscape" iteration count, if any.* A count of 0 means repeat indefinitiely.** @return iteration count if one was specified, else 1.*/public int getLoopCount() {return loopCount;}/*** Creates new frame image from current data (and previous* frames as specified by their disposition codes).*/protected void setPixels() {// expose destination image's pixels as int arrayint[] dest =((DataBufferInt) image.getRaster().getDataBuffer()).getData();// fill in starting image contents based on last image's dispose codeif (lastDispose > 0) {if (lastDispose == 3) {// use image before lastint n = frameCount - 2;if (n > 0) {lastImage = getFrame(n - 1);} else {lastImage = null;}}if (lastImage != null) {int[] prev =((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();System.arraycopy(prev, 0, dest, 0, width * height);// copy pixelsif (lastDispose == 2) {// fill last image rect area with background colorGraphics2D g = image.createGraphics();Color c = null;if (transparency) {c = new Color(0, 0, 0, 0); // assume background is transparent} else {c = new Color(lastBgColor); // use given background color}g.setColor(c);g.setComposite(AlphaComposite.Src); // replace areag.fill(lastRect);g.dispose();}}}// copy each source line to the appropriate place in the destinationint pass = 1;int inc = 8;int iline = 0;for (int i = 0; i < ih; i++) {int line = i;if (interlace) {if (iline >= ih) {pass++;switch (pass) {case 2 :iline = 4;break;case 3 :iline = 2;inc = 4;break;case 4 :iline = 1;inc = 2;}}line = iline;iline += inc;}line += iy;if (line < height) {int k = line * width;int dx = k + ix; // start of line in destint dlim = dx + iw; // end of dest lineif ((k + width) < dlim) {dlim = k + width; // past dest edge}int sx = i * iw; // start of line in sourcewhile (dx < dlim) {// map color and insert in destinationint index = ((int) pixels[sx++]) & 0xff;int c = act[index];if (c != 0) {dest[dx] = c;}dx++;}}}}/*** Gets the image contents of frame n.** @return BufferedImage representation of frame, or null if n is invalid.*/public BufferedImage getFrame(int n) {BufferedImage im = null;if ((n >= 0) && (n < frameCount)) {im = ((GifFrame) frames.get(n)).image;}return im;}/*** Gets image size.** @return GIF image dimensions*/public Dimension getFrameSize() {return new Dimension(width, height);}/*** Reads GIF image from stream** @param BufferedInputStream containing GIF file.* @return read status code (0 = no errors)*/public int read(BufferedInputStream is) {init();if (is != null) {in = is;readHeader();if (!err()) {readContents();if (frameCount < 0) {status = STATUS_FORMAT_ERROR;}}} else {status = STATUS_OPEN_ERROR;}try {is.close();} catch (IOException e) {}return status;}/*** Reads GIF image from stream** @param InputStream containing GIF file.* @return read status code (0 = no errors)*/public int read(InputStream is) {init();if (is != null) {if (!(is instanceof BufferedInputStream))is = new BufferedInputStream(is);in = (BufferedInputStream) is;readHeader();if (!err()) {readContents();if (frameCount < 0) {status = STATUS_FORMAT_ERROR;}}} else {status = STATUS_OPEN_ERROR;}try {is.close();} catch (IOException e) {}return status;}/*** Reads GIF file from specified file/URL source * (URL assumed if name contains ":/" or "file:")** @param name String containing source* @return read status code (0 = no errors)*/public int read(String name) {status = STATUS_OK;try {name = name.trim().toLowerCase();if ((name.indexOf("file:") >= 0) ||(name.indexOf(":/") > 0)) {URL url = new URL(name);in = new BufferedInputStream(url.openStream());} else {in = new BufferedInputStream(new FileInputStream(name));}status = read(in);} catch (IOException e) {status = STATUS_OPEN_ERROR;}return status;}/*** Decodes LZW image data into pixel array.* Adapted from John Cristy's ImageMagick.*/protected void decodeImageData() {int NullCode = -1;int npix = iw * ih;int available, clear,code_mask,code_size,end_of_information,in_code,old_code,bits,code,count,i,datum,data_size,first,top,bi,pi;if ((pixels == null) || (pixels.length < npix)) {pixels = new byte[npix]; // allocate new pixel array}if (prefix == null) prefix = new short[MaxStackSize];if (suffix == null) suffix = new byte[MaxStackSize];if (pixelStack == null) pixelStack = new byte[MaxStackSize + 1];// Initialize GIF data stream decoder.data_size = read();clear = 1 << data_size;end_of_information = clear + 1;available = clear + 2;old_code = NullCode;code_size = data_size + 1;code_mask = (1 << code_size) - 1;for (code = 0; code < clear; code++) {prefix[code] = 0;suffix[code] = (byte) code;}// Decode GIF pixel stream.datum = bits = count = first = top = pi = bi = 0;for (i = 0; i < npix;) {if (top == 0) {if (bits < code_size) {// Load bytes until there are enough bits for a code.if (count == 0) {// Read a new data block.count = readBlock();if (count <= 0)break;bi = 0;}datum += (((int) block[bi]) & 0xff) << bits;bits += 8;bi++;count--;continue;}// Get the next code.code = datum & code_mask;datum >>= code_size;bits -= code_size;// Interpret the codeif ((code > available) || (code == end_of_information))break;if (code == clear) {// Reset decoder.code_size = data_size + 1;code_mask = (1 << code_size) - 1;available = clear + 2;old_code = NullCode;continue;}if (old_code == NullCode) {pixelStack[top++] = suffix[code];old_code = code;first = code;continue;}in_code = code;if (code == available) {pixelStack[top++] = (byte) first;code = old_code;}while (code > clear) {pixelStack[top++] = suffix[code];code = prefix[code];}first = ((int) suffix[code]) & 0xff;// Add a new string to the string table,if (available >= MaxStackSize) {pixelStack[top++] = (byte) first;continue;}pixelStack[top++] = (byte) first;prefix[available] = (short) old_code;suffix[available] = (byte) first;available++;if (((available & code_mask) == 0)&& (available < MaxStackSize)) {code_size++;code_mask += available;}old_code = in_code;}// Pop a pixel off the pixel stack.top--;pixels[pi++] = pixelStack[top];i++;}for (i = pi; i < npix; i++) {pixels[i] = 0; // clear missing pixels}}/*** Returns true if an error was encountered during reading/decoding*/protected boolean err() {return status != STATUS_OK;}/*** Initializes or re-initializes reader*/protected void init() {status = STATUS_OK;frameCount = 0;frames = new ArrayList<GifFrame>();gct = null;lct = null;}/*** Reads a single byte from the input stream.*/protected int read() {int curByte = 0;try {curByte = in.read();} catch (IOException e) {status = STATUS_FORMAT_ERROR;}return curByte;}/*** Reads next variable length block from input.** @return number of bytes stored in "buffer"*/protected int readBlock() {blockSize = read();int n = 0;if (blockSize > 0) {try {int count = 0;while (n < blockSize) {count = in.read(block, n, blockSize - n);if (count == -1) break;n += count;}} catch (IOException e) {}if (n < blockSize) {status = STATUS_FORMAT_ERROR;}}return n;}/*** Reads color table as 256 RGB integer values** @param ncolors int number of colors to read* @return int array containing 256 colors (packed ARGB with full alpha)*/protected int[] readColorTable(int ncolors) {int nbytes = 3 * ncolors;int[] tab = null;byte[] c = new byte[nbytes];int n = 0;try {n = in.read(c);} catch (IOException e) {}if (n < nbytes) {status = STATUS_FORMAT_ERROR;} else {tab = new int[256]; // max size to avoid bounds checksint i = 0;int j = 0;while (i < ncolors) {int r = ((int) c[j++]) & 0xff;int g = ((int) c[j++]) & 0xff;int b = ((int) c[j++]) & 0xff;tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;}}return tab;}/*** Main file parser. Reads GIF content blocks.*/protected void readContents() {// read GIF file content blocksboolean done = false;while (!(done || err())) {int code = read();switch (code) {case 0x2C : // image separatorreadImage();break;case 0x21 : // extensioncode = read();switch (code) {case 0xf9 : // graphics control extensionreadGraphicControlExt();break;case 0xff : // application extensionreadBlock();String app = "";for (int i = 0; i < 11; i++) {app += (char) block[i];}if (app.equals("NETSCAPE2.0")) {readNetscapeExt();}elseskip(); // don't carebreak;default : // uninteresting extensionskip();}break;case 0x3b : // terminatordone = true;break;case 0x00 : // bad byte, but keep going and see what happensbreak;default :status = STATUS_FORMAT_ERROR;}}}/*** Reads Graphics Control Extension values*/protected void readGraphicControlExt() {read(); // block sizeint packed = read(); // packed fieldsdispose = (packed & 0x1c) >> 2; // disposal methodif (dispose == 0) {dispose = 1; // elect to keep old image if discretionary}transparency = (packed & 1) != 0;delay = readShort() * 10; // delay in millisecondstransIndex = read(); // transparent color indexread(); // block terminator}/*** Reads GIF file header information.*/protected void readHeader() {String id = "";for (int i = 0; i < 6; i++) {id += (char) read();}if (!id.startsWith("GIF")) {status = STATUS_FORMAT_ERROR;return;}readLSD();if (gctFlag && !err()) {gct = readColorTable(gctSize);bgColor = gct[bgIndex];}}/*** Reads next frame image*/protected void readImage() {ix = readShort(); // (sub)image position & sizeiy = readShort();iw = readShort();ih = readShort();int packed = read();lctFlag = (packed & 0x80) != 0; // 1 - local color table flaginterlace = (packed & 0x40) != 0; // 2 - interlace flag// 3 - sort flag// 4-5 - reservedlctSize = 2 << (packed & 7); // 6-8 - local color table sizeif (lctFlag) {lct = readColorTable(lctSize); // read tableact = lct; // make local table active} else {act = gct; // make global table activeif (bgIndex == transIndex)bgColor = 0;}int save = 0;if (transparency) {save = act[transIndex];act[transIndex] = 0; // set transparent color if specified}if (act == null) {status = STATUS_FORMAT_ERROR; // no color table defined}if (err()) return;decodeImageData(); // decode pixel dataskip();if (err()) return;frameCount++;// create new image to receive frame dataimage =new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);setPixels(); // transfer pixel data to imageframes.add(new GifFrame(image, delay)); // add image to frame listif (transparency) {act[transIndex] = save;}resetFrame();}/*** Reads Logical Screen Descriptor*/protected void readLSD() {// logical screen sizewidth = readShort();height = readShort();// packed fieldsint packed = read();gctFlag = (packed & 0x80) != 0; // 1 : global color table flag// 2-4 : color resolution// 5 : gct sort flaggctSize = 2 << (packed & 7); // 6-8 : gct sizebgIndex = read(); // background color indexpixelAspect = read(); // pixel aspect ratio}/*** Reads Netscape extenstion to obtain iteration count*/protected void readNetscapeExt() {do {readBlock();if (block[0] == 1) {// loop count sub-blockint b1 = ((int) block[1]) & 0xff;int b2 = ((int) block[2]) & 0xff;loopCount = (b2 << 8) | b1;}} while ((blockSize > 0) && !err());}/*** Reads next 16-bit value, LSB first*/protected int readShort() {// read 16-bit value, LSB firstreturn read() | (read() << 8);}/*** Resets frame state for reading next image.*/protected void resetFrame() {lastDispose = dispose;lastRect = new Rectangle(ix, iy, iw, ih);lastImage = image;lastBgColor = bgColor;lct = null;}/*** Skips variable length blocks up to and including* next zero length block.*/protected void skip() {do {readBlock();} while ((blockSize > 0) && !err());}
}
LZWEncoder.java
package top.zqlweb.tool.third.gif;import java.io.OutputStream;
import java.io.IOException;//==============================================================================
// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
// K Weiner 12/00class LZWEncoder {private static final int EOF = -1;private int imgW, imgH;private byte[] pixAry;private int initCodeSize;private int remaining;private int curPixel;// GIFCOMPR.C - GIF Image compression routines//// Lempel-Ziv compression based on 'compress'. GIF modifications by// David Rowley (mgardi@watdcsu.waterloo.edu)// General DEFINEsstatic final int BITS = 12;static final int HSIZE = 5003; // 80% occupancy// GIF Image compression - modified 'compress'//// Based on: compress.c - File compression ala IEEE Computer, June 1984.//// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)// Jim McKie (decvax!mcvax!jim)// Steve Davies (decvax!vax135!petsd!peora!srd)// Ken Turkowski (decvax!decwrl!turtlevax!ken)// James A. Woods (decvax!ihnp4!ames!jaw)// Joe Orost (decvax!vax135!petsd!joe)int n_bits; // number of bits/codeint maxbits = BITS; // user settable max # bits/codeint maxcode; // maximum code, given n_bitsint maxmaxcode = 1 << BITS; // should NEVER generate this codeint[] htab = new int[HSIZE];int[] codetab = new int[HSIZE];int hsize = HSIZE; // for dynamic table sizingint free_ent = 0; // first unused entry// block compression parameters -- after all codes are used up,// and compression rate changes, start over.boolean clear_flg = false;// Algorithm: use open addressing double hashing (no chaining) on the// prefix code / next character combination. We do a variant of Knuth's// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime// secondary probe. Here, the modular division first probe is gives way// to a faster exclusive-or manipulation. Also do block compression with// an adaptive reset, whereby the code table is cleared when the compression// ratio decreases, but after the table fills. The variable-length output// codes are re-sized at this point, and a special CLEAR code is generated// for the decompressor. Late addition: construct the table according to// file size for noticeable speed improvement on small files. Please direct// questions about this implementation to ames!jaw.int g_init_bits;int ClearCode;int EOFCode;// output//// Output the given code.// Inputs:// code: A n_bits-bit integer. If == -1, then EOF. This assumes// that n_bits =< wordsize - 1.// Outputs:// Outputs code to the file.// Assumptions:// Chars are 8 bits long.// Algorithm:// Maintain a BITS character long buffer (so that 8 codes will// fit in it exactly). Use the VAX insv instruction to insert each// code in turn. When the buffer fills up empty it and start over.int cur_accum = 0;int cur_bits = 0;int masks[] ={0x0000,0x0001,0x0003,0x0007,0x000F,0x001F,0x003F,0x007F,0x00FF,0x01FF,0x03FF,0x07FF,0x0FFF,0x1FFF,0x3FFF,0x7FFF,0xFFFF };// Number of characters so far in this 'packet'int a_count;// Define the storage for the packet accumulatorbyte[] accum = new byte[256];//----------------------------------------------------------------------------LZWEncoder(int width, int height, byte[] pixels, int color_depth) {imgW = width;imgH = height;pixAry = pixels;initCodeSize = Math.max(2, color_depth);}// Add a character to the end of the current packet, and if it is 254// characters, flush the packet to disk.void char_out(byte c, OutputStream outs) throws IOException {accum[a_count++] = c;if (a_count >= 254)flush_char(outs);}// Clear out the hash table// table clear for block compressvoid cl_block(OutputStream outs) throws IOException {cl_hash(hsize);free_ent = ClearCode + 2;clear_flg = true;output(ClearCode, outs);}// reset code tablevoid cl_hash(int hsize) {for (int i = 0; i < hsize; ++i)htab[i] = -1;}void compress(int init_bits, OutputStream outs) throws IOException {int fcode;int i /* = 0 */;int c;int ent;int disp;int hsize_reg;int hshift;// Set up the globals: g_init_bits - initial number of bitsg_init_bits = init_bits;// Set up the necessary valuesclear_flg = false;n_bits = g_init_bits;maxcode = MAXCODE(n_bits);ClearCode = 1 << (init_bits - 1);EOFCode = ClearCode + 1;free_ent = ClearCode + 2;a_count = 0; // clear packetent = nextPixel();hshift = 0;for (fcode = hsize; fcode < 65536; fcode *= 2)++hshift;hshift = 8 - hshift; // set hash code range boundhsize_reg = hsize;cl_hash(hsize_reg); // clear hash tableoutput(ClearCode, outs);outer_loop : while ((c = nextPixel()) != EOF) {fcode = (c << maxbits) + ent;i = (c << hshift) ^ ent; // xor hashingif (htab[i] == fcode) {ent = codetab[i];continue;} else if (htab[i] >= 0) // non-empty slot{disp = hsize_reg - i; // secondary hash (after G. Knott)if (i == 0)disp = 1;do {if ((i -= disp) < 0)i += hsize_reg;if (htab[i] == fcode) {ent = codetab[i];continue outer_loop;}} while (htab[i] >= 0);}output(ent, outs);ent = c;if (free_ent < maxmaxcode) {codetab[i] = free_ent++; // code -> hashtablehtab[i] = fcode;} elsecl_block(outs);}// Put out the final code.output(ent, outs);output(EOFCode, outs);}//----------------------------------------------------------------------------void encode(OutputStream os) throws IOException {os.write(initCodeSize); // write "initial code size" byteremaining = imgW * imgH; // reset navigation variablescurPixel = 0;compress(initCodeSize + 1, os); // compress and write the pixel dataos.write(0); // write block terminator}// Flush the packet to disk, and reset the accumulatorvoid flush_char(OutputStream outs) throws IOException {if (a_count > 0) {outs.write(a_count);outs.write(accum, 0, a_count);a_count = 0;}}final int MAXCODE(int n_bits) {return (1 << n_bits) - 1;}//----------------------------------------------------------------------------// Return the next pixel from the image//----------------------------------------------------------------------------private int nextPixel() {if (remaining == 0)return EOF;--remaining;byte pix = pixAry[curPixel++];return pix & 0xff;}void output(int code, OutputStream outs) throws IOException {cur_accum &= masks[cur_bits];if (cur_bits > 0)cur_accum |= (code << cur_bits);elsecur_accum = code;cur_bits += n_bits;while (cur_bits >= 8) {char_out((byte) (cur_accum & 0xff), outs);cur_accum >>= 8;cur_bits -= 8;}// If the next entry is going to be too big for the code size,// then increase it, if possible.if (free_ent > maxcode || clear_flg) {if (clear_flg) {maxcode = MAXCODE(n_bits = g_init_bits);clear_flg = false;} else {++n_bits;if (n_bits == maxbits)maxcode = maxmaxcode;elsemaxcode = MAXCODE(n_bits);}}if (code == EOFCode) {// At EOF, write the rest of the buffer.while (cur_bits > 0) {char_out((byte) (cur_accum & 0xff), outs);cur_accum >>= 8;cur_bits -= 8;}flush_char(outs);}}
}
NeuQuant.java
package top.zqlweb.tool.third.gif;/* NeuQuant Neural-Net Quantization Algorithm* ------------------------------------------** Copyright (c) 1994 Anthony Dekker** NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.* See "Kohonen neural networks for optimal colour quantization"* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.* for a discussion of the algorithm.** Any party obtaining a copy of these files from the author, directly or* indirectly, is granted, free of charge, a full and unrestricted irrevocable,* world-wide, paid up, royalty-free, nonexclusive right and license to deal* in this software and documentation files (the "Software"), including without* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,* and/or sell copies of the Software, and to permit persons who receive* copies from any such party to do so, with the only requirement being* that this copyright notice remain intact.*/// Ported to Java 12/00 K Weinerpublic class NeuQuant {protected static final int netsize = 256; /* number of colours used *//* four primes near 500 - assume no image has a length so large *//* that it is divisible by all four primes */protected static final int prime1 = 499;protected static final int prime2 = 491;protected static final int prime3 = 487;protected static final int prime4 = 503;protected static final int minpicturebytes = (3 * prime4);/* minimum size for input image *//* Program Skeleton----------------[select samplefac in range 1..30][read image from input file]pic = (unsigned char*) malloc(3*width*height);initnet(pic,3*width*height,samplefac);learn();unbiasnet();[write output image header, using writecolourmap(f)]inxbuild();write output image using inxsearch(b,g,r) *//* Network Definitions------------------- */protected static final int maxnetpos = (netsize - 1);protected static final int netbiasshift = 4; /* bias for colour values */protected static final int ncycles = 100; /* no. of learning cycles *//* defs for freq and bias */protected static final int intbiasshift = 16; /* bias for fractions */protected static final int intbias = (((int) 1) << intbiasshift);protected static final int gammashift = 10; /* gamma = 1024 */protected static final int gamma = (((int) 1) << gammashift);protected static final int betashift = 10;protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */protected static final int betagamma =(intbias << (gammashift - betashift));/* defs for decreasing radius factor */protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */protected static final int radiusbias = (((int) 1) << radiusbiasshift);protected static final int initradius = (initrad * radiusbias); /* and decreases by a */protected static final int radiusdec = 30; /* factor of 1/30 each cycle *//* defs for decreasing alpha factor */protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */protected static final int initalpha = (((int) 1) << alphabiasshift);protected int alphadec; /* biased by 10 bits *//* radbias and alpharadbias used for radpower calculation */protected static final int radbiasshift = 8;protected static final int radbias = (((int) 1) << radbiasshift);protected static final int alpharadbshift = (alphabiasshift + radbiasshift);protected static final int alpharadbias = (((int) 1) << alpharadbshift);/* Types and Global Variables-------------------------- */protected byte[] thepicture; /* the input image itself */protected int lengthcount; /* lengthcount = H*W*3 */protected int samplefac; /* sampling factor 1..30 */// typedef int pixel[4]; /* BGRc */protected int[][] network; /* the network itself - [netsize][4] */protected int[] netindex = new int[256];/* for network lookup - really 256 */protected int[] bias = new int[netsize];/* bias and freq arrays for learning */protected int[] freq = new int[netsize];protected int[] radpower = new int[initrad];/* radpower for precomputation *//* Initialise network in range (0,0,0) to (255,255,255) and set parameters----------------------------------------------------------------------- */public NeuQuant(byte[] thepic, int len, int sample) {int i;int[] p;thepicture = thepic;lengthcount = len;samplefac = sample;network = new int[netsize][];for (i = 0; i < netsize; i++) {network[i] = new int[4];p = network[i];p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;freq[i] = intbias / netsize; /* 1/netsize */bias[i] = 0;}}public byte[] colorMap() {byte[] map = new byte[3 * netsize];int[] index = new int[netsize];for (int i = 0; i < netsize; i++)index[network[i][3]] = i;int k = 0;for (int i = 0; i < netsize; i++) {int j = index[i];map[k++] = (byte) (network[j][0]);map[k++] = (byte) (network[j][1]);map[k++] = (byte) (network[j][2]);}return map;}/* Insertion sort of network and building of netindex[0..255] (to do after unbias)------------------------------------------------------------------------------- */public void inxbuild() {int i, j, smallpos, smallval;int[] p;int[] q;int previouscol, startpos;previouscol = 0;startpos = 0;for (i = 0; i < netsize; i++) {p = network[i];smallpos = i;smallval = p[1]; /* index on g *//* find smallest in i..netsize-1 */for (j = i + 1; j < netsize; j++) {q = network[j];if (q[1] < smallval) { /* index on g */smallpos = j;smallval = q[1]; /* index on g */}}q = network[smallpos];/* swap p (i) and q (smallpos) entries */if (i != smallpos) {j = q[0];q[0] = p[0];p[0] = j;j = q[1];q[1] = p[1];p[1] = j;j = q[2];q[2] = p[2];p[2] = j;j = q[3];q[3] = p[3];p[3] = j;}/* smallval entry is now in position i */if (smallval != previouscol) {netindex[previouscol] = (startpos + i) >> 1;for (j = previouscol + 1; j < smallval; j++)netindex[j] = i;previouscol = smallval;startpos = i;}}netindex[previouscol] = (startpos + maxnetpos) >> 1;for (j = previouscol + 1; j < 256; j++)netindex[j] = maxnetpos; /* really 256 */}/* Main Learning Loop------------------ */public void learn() {int i, j, b, g, r;int radius, rad, alpha, step, delta, samplepixels;byte[] p;int pix, lim;if (lengthcount < minpicturebytes)samplefac = 1;alphadec = 30 + ((samplefac - 1) / 3);p = thepicture;pix = 0;lim = lengthcount;samplepixels = lengthcount / (3 * samplefac);delta = samplepixels / ncycles;alpha = initalpha;radius = initradius;rad = radius >> radiusbiasshift;if (rad <= 1)rad = 0;for (i = 0; i < rad; i++)radpower[i] =alpha * (((rad * rad - i * i) * radbias) / (rad * rad));//fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);if (lengthcount < minpicturebytes)step = 3;else if ((lengthcount % prime1) != 0)step = 3 * prime1;else {if ((lengthcount % prime2) != 0)step = 3 * prime2;else {if ((lengthcount % prime3) != 0)step = 3 * prime3;elsestep = 3 * prime4;}}i = 0;while (i < samplepixels) {b = (p[pix + 0] & 0xff) << netbiasshift;g = (p[pix + 1] & 0xff) << netbiasshift;r = (p[pix + 2] & 0xff) << netbiasshift;j = contest(b, g, r);altersingle(alpha, j, b, g, r);if (rad != 0)alterneigh(rad, j, b, g, r); /* alter neighbours */pix += step;if (pix >= lim)pix -= lengthcount;i++;if (delta == 0)delta = 1;if (i % delta == 0) {alpha -= alpha / alphadec;radius -= radius / radiusdec;rad = radius >> radiusbiasshift;if (rad <= 1)rad = 0;for (j = 0; j < rad; j++)radpower[j] =alpha * (((rad * rad - j * j) * radbias) / (rad * rad));}}//fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha);}/* Search for BGR values 0..255 (after net is unbiased) and return colour index---------------------------------------------------------------------------- */public int map(int b, int g, int r) {int i, j, dist, a, bestd;int[] p;int best;bestd = 1000; /* biggest possible dist is 256*3 */best = -1;i = netindex[g]; /* index on g */j = i - 1; /* start at netindex[g] and work outwards */while ((i < netsize) || (j >= 0)) {if (i < netsize) {p = network[i];dist = p[1] - g; /* inx key */if (dist >= bestd)i = netsize; /* stop iter */else {i++;if (dist < 0)dist = -dist;a = p[0] - b;if (a < 0)a = -a;dist += a;if (dist < bestd) {a = p[2] - r;if (a < 0)a = -a;dist += a;if (dist < bestd) {bestd = dist;best = p[3];}}}}if (j >= 0) {p = network[j];dist = g - p[1]; /* inx key - reverse dif */if (dist >= bestd)j = -1; /* stop iter */else {j--;if (dist < 0)dist = -dist;a = p[0] - b;if (a < 0)a = -a;dist += a;if (dist < bestd) {a = p[2] - r;if (a < 0)a = -a;dist += a;if (dist < bestd) {bestd = dist;best = p[3];}}}}}return (best);}public byte[] process() {learn();unbiasnet();inxbuild();return colorMap();}/* Unbias network to give byte values 0..255 and record position i to prepare for sort----------------------------------------------------------------------------------- */public void unbiasnet() {int i;for (i = 0; i < netsize; i++) {network[i][0] >>= netbiasshift;network[i][1] >>= netbiasshift;network[i][2] >>= netbiasshift;network[i][3] = i; /* record colour no */}}/* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]--------------------------------------------------------------------------------- */protected void alterneigh(int rad, int i, int b, int g, int r) {int j, k, lo, hi, a, m;int[] p;lo = i - rad;if (lo < -1)lo = -1;hi = i + rad;if (hi > netsize)hi = netsize;j = i + 1;k = i - 1;m = 1;while ((j < hi) || (k > lo)) {a = radpower[m++];if (j < hi) {p = network[j++];try {p[0] -= (a * (p[0] - b)) / alpharadbias;p[1] -= (a * (p[1] - g)) / alpharadbias;p[2] -= (a * (p[2] - r)) / alpharadbias;} catch (Exception e) {} // prevents 1.3 miscompilation}if (k > lo) {p = network[k--];try {p[0] -= (a * (p[0] - b)) / alpharadbias;p[1] -= (a * (p[1] - g)) / alpharadbias;p[2] -= (a * (p[2] - r)) / alpharadbias;} catch (Exception e) {}}}}/* Move neuron i towards biased (b,g,r) by factor alpha---------------------------------------------------- */protected void altersingle(int alpha, int i, int b, int g, int r) {/* alter hit neuron */int[] n = network[i];n[0] -= (alpha * (n[0] - b)) / initalpha;n[1] -= (alpha * (n[1] - g)) / initalpha;n[2] -= (alpha * (n[2] - r)) / initalpha;}/* Search for biased BGR values---------------------------- */protected int contest(int b, int g, int r) {/* finds closest neuron (min dist) and updates freq *//* finds best neuron (min dist-bias) and returns position *//* for frequently chosen neurons, freq[i] is high and bias[i] is negative *//* bias[i] = gamma*((1/netsize)-freq[i]) */int i, dist, a, biasdist, betafreq;int bestpos, bestbiaspos, bestd, bestbiasd;int[] n;bestd = ~(((int) 1) << 31);bestbiasd = bestd;bestpos = -1;bestbiaspos = bestpos;for (i = 0; i < netsize; i++) {n = network[i];dist = n[0] - b;if (dist < 0)dist = -dist;a = n[1] - g;if (a < 0)a = -a;dist += a;a = n[2] - r;if (a < 0)a = -a;dist += a;if (dist < bestd) {bestd = dist;bestpos = i;}biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));if (biasdist < bestbiasd) {bestbiasd = biasdist;bestbiaspos = i;}betafreq = (freq[i] >> betashift);freq[i] -= betafreq;bias[i] += (betafreq << gammashift);}freq[bestpos] += beta;bias[bestpos] -= betagamma;return (bestbiaspos);}
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
