├── .gitignore ├── README.md ├── docs └── imgs │ ├── 1.gif │ ├── 1.png │ ├── 2.gif │ └── 3.gif ├── pom.xml ├── src └── com │ └── github │ └── botaruibo │ └── xvcode │ ├── generator │ ├── Generator.java │ ├── Gif2VCGenerator.java │ ├── Gif3VCGenerator.java │ ├── GifVCGenerator.java │ ├── PngVCGenerator.java │ └── XRandoms.java │ ├── gifencoder │ ├── AnimatedGifEncoder.java │ ├── LZWEncoder.java │ └── NeuQuant.java │ └── main │ └── APP.java └── xvcode-1.0-SNAPSHOT-cl.jar /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.jar 3 | !xvcode-1.0-SNAPSHOT-cl.jar 4 | */target 5 | 6 | */classes 7 | */lib 8 | 9 | **/classes/ 10 | **/lib/ 11 | 12 | *.md.html 13 | *.prefs 14 | 15 | *.class 16 | bin 17 | *.component 18 | org.eclipse.wst.common.project.facet.core.xml 19 | *.jsdtscope 20 | org.eclipse.wst.jsdt.ui.superType.container 21 | org.eclipse.wst.jsdt.ui.superType.name 22 | *.launch 23 | target 24 | .idea 25 | .metadata/ 26 | .project 27 | .classpath 28 | *.classpath 29 | *.MF 30 | *.DS_Store 31 | *.iml 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xvcode (x-validation code) 2 | 3 | 4 | project description: 5 | 6 | this project only for web validation code generating. we provide this tool cause there no security enough validation-code-image generator. 7 | this tool provide 4 kind of valid-code image generators. as follows: 8 | 9 | this project refer the [gifencoder][1] 10 | 11 | **项目作用:** 12 | 主要用于生成web动态验证码图片。主要应用场景为web 页面上需要用户输入验证码才能进行操作的地方。 13 | **开发原因:** 14 | 目前网络广为流传的代码所生成的验证码图片太简单,并易于破解。所以我们自己开发了一个简单的验证码生成包,增加了背景的干扰性。 15 | **features:** 16 | > - 提供1中png格式的图片生成器,3种gif格式图片生成器。 17 | > - 随机码由生成器自身生成。 18 | > - 可一定程度自定义背景干扰图形参数 19 | 20 | **感谢:** 21 | 该项目用于生成gif图片编码器使用了 [gifencoder][1] 项目 22 | 23 | *图片示例 mapping from generator to image style* 24 | 25 | PngVCGenerator: 26 | ![image](https://github.com/botaruibo/xvcode/blob/master/docs/imgs/1.png) 27 | 28 | GifVCGenerator: 29 | ![image](https://github.com/botaruibo/xvcode/blob/master/docs/imgs/1.gif) 30 | 31 | GifVCGenerator2: 32 | ![image](https://github.com/botaruibo/xvcode/blob/master/docs/imgs/2.gif) 33 | 34 | GifVCGenerator3: 35 | ![image](https://github.com/botaruibo/xvcode/blob/master/docs/imgs/3.gif) 36 | 37 | [1]: https://github.com/cloader/gifencoder 38 | 39 | *国内如果不能查看github中readme的图片,可参考https://blog.51cto.com/14234228/2492600 更新自己本地的github dns* 40 | ## Getting Started 41 | 42 | ### For Java Developers 43 | 44 | ###### Add Dependency(maven) 45 | 46 | add dependency to your pom.xml: 47 | 48 | ``` 49 | 50 | com.github.botaruibo 51 | xvcode 52 | 1.0 53 | 54 | ``` 55 | ####### Code Examples 56 | ``` 57 | package test 58 | 59 | import java.io.FileOutputStream; 60 | import java.io.IOException; 61 | import com.github.botaruibo.xvcode.generator.Generator; 62 | import com.github.botaruibo.xvcode.generator.Gif2VCGenerator; 63 | import com.github.botaruibo.xvcode.generator.Gif3VCGenerator; 64 | import com.github.botaruibo.xvcode.generator.GifVCGenerator; 65 | import com.github.botaruibo.xvcode.generator.PngVCGenerator; 66 | 67 | class Test { 68 | //生成验证码图片到本地磁盘 draw image and save to disk 69 | public void main(String args[]) throws IOException { 70 | String path = ".";//图片存储路径 path for image save 71 | Integer height = 40;//image 高度。 image height. count as pixel 72 | Integer width = 200;//image 宽度。 image width. count as pixel 73 | Integer count = 5; // validation code length. 74 | String validCode = null; //验证码 75 | Generator generator = new PngVCGenerator(width, height, count); 76 | generator.write2out(new FileOutputStream(path + "/1.png")).close(); 77 | validCode = generator.text(); //get the validation code as 'String' 78 | System.out.println(validCode); 79 | generator = new GifVCGenerator(width, height, count);// gif 80 | generator.write2out(new FileOutputStream(path + "/1.gif")).close(); 81 | validCode = generator.text(); 82 | System.out.println(validCode); 83 | generator = new Gif2VCGenerator(width, height, count);// gif 84 | generator.write2out(new FileOutputStream(path + "/2.gif")).close(); 85 | validCode = generator.text(); 86 | System.out.println(validCode); 87 | generator = new Gif3VCGenerator(width, height, count);// gif 88 | generator.write2out(new FileOutputStream(path + "/3.gif")).close(); 89 | validCode = generator.text(); 90 | System.out.println(validCode); 91 | } 92 | } 93 | ``` 94 | 95 | the *generator.write2out()* method proved the ability to write the image binary to any OutputStream object. this especially convenience for servlet request 96 | 97 | 如果要将验证码图片以流的方式穿到前端,可以直接使用*generator.write2out()*方法 98 | 99 | 100 | ### Use Under Command(require ${JAVA_HOME} set) 101 | 102 | use the *xvcode-1.0-SNAPSHOT-cl.jar* file to generate valid-code image directly to disk. command like: 103 | 104 | 可以使用jar包直接生成本地图片。命令: 105 | ``` 106 | java -jar xvcode-1.0-SNAPSHOT-cl 107 | ``` 108 | usage : 109 | 110 | 支持参数如下: 111 | ``` 112 | usage: 113 | -p dir path for the image, default generate in current dir 114 | -h image height, between 50 to 500, default 200 115 | -w image width, between 30 to 300, default 40 116 | -cl validation code length, between 2 to 10, default 5 117 | 118 | ``` 119 | 120 | example: 121 | 122 | 例如: 123 | ``` 124 | java -jar xvcode-1.0-SNAPSHOT-cl -p test/ -h 300 -w 60 -cl 7 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/imgs/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botaruibo/xvcode/bbcf414f4fb918a014b7de51091bd7da43c8e272/docs/imgs/1.gif -------------------------------------------------------------------------------- /docs/imgs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botaruibo/xvcode/bbcf414f4fb918a014b7de51091bd7da43c8e272/docs/imgs/1.png -------------------------------------------------------------------------------- /docs/imgs/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botaruibo/xvcode/bbcf414f4fb918a014b7de51091bd7da43c8e272/docs/imgs/2.gif -------------------------------------------------------------------------------- /docs/imgs/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botaruibo/xvcode/bbcf414f4fb918a014b7de51091bd7da43c8e272/docs/imgs/3.gif -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.github.botaruibo 4 | xvcode 5 | 1.0-SNAPSHOT 6 | 7 | src 8 | 9 | 10 | maven-compiler-plugin 11 | 3.3 12 | 13 | 1.7 14 | 1.7 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-shade-plugin 20 | 1.4 21 | 22 | 27 | 28 | 29 | package 30 | 31 | shade 32 | 33 | 34 | 35 | 36 | com.github.botaruibo.xvcode.main.APP 37 | 38 | 39 | true 40 | cl 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/generator/Generator.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.generator; 2 | 3 | import static com.github.botaruibo.xvcode.generator.XRandoms.alpha; 4 | import static com.github.botaruibo.xvcode.generator.XRandoms.num; 5 | 6 | import java.awt.Color; 7 | import java.awt.Font; 8 | import java.awt.image.BufferedImage; 9 | import java.io.OutputStream; 10 | 11 | /** 12 | * abstract class for validation code generator 13 | * @author brui 14 | * 15 | */ 16 | public abstract class Generator { 17 | 18 | protected Font font = new Font("Verdana", Font.ITALIC | Font.BOLD, 28); // Font 19 | protected int len = 5; // validation code length 20 | protected int width = 150; // image width 21 | protected int height = 40; // image height 22 | private String chars = null; // valid string 23 | 24 | /**generate the random validation strings 25 | * @return 26 | */ 27 | protected char[] alphas() { 28 | char[] cs = new char[len]; 29 | for (int i = 0; i < len; i++) { 30 | cs[i] = alpha(); 31 | } 32 | chars = new String(cs); 33 | return cs; 34 | } 35 | 36 | public Font getFont() { 37 | return font; 38 | } 39 | 40 | public void setFont(Font font) { 41 | this.font = font; 42 | } 43 | 44 | public int getLen() { 45 | return len; 46 | } 47 | 48 | public void setLen(int len) { 49 | this.len = len; 50 | } 51 | 52 | public int getWidth() { 53 | return width; 54 | } 55 | 56 | public void setWidth(int width) { 57 | this.width = width; 58 | } 59 | 60 | public int getHeight() { 61 | return height; 62 | } 63 | 64 | public void setHeight(int height) { 65 | this.height = height; 66 | } 67 | 68 | /** 69 | * pick color from a random range 70 | * 71 | * @return Color random color 72 | */ 73 | protected Color color(int fc, int bc) { 74 | if (fc > 255) 75 | fc = 255; 76 | if (bc > 255) 77 | bc = 255; 78 | int r = fc + num(bc - fc); 79 | int g = fc + num(bc - fc); 80 | int b = fc + num(bc - fc); 81 | return new Color(r, g, b); 82 | } 83 | 84 | /** 85 | * @param os source OutputStream 86 | * @return OutputStream return OutputStream wrote the validation code image 87 | */ 88 | public abstract OutputStream write2out(OutputStream os); 89 | 90 | /**no result provide for gif validation code. only for PNG validation code image. 91 | * only @see com.github.botaruibo.xvcode.generator.Captcha#write2out(OutputStream os) avaliable for GIF 92 | * @return 93 | * 1.BufferedImage for PNG 94 | * 2.null for GIF 95 | */ 96 | public abstract BufferedImage getValidCodeImage(); 97 | 98 | /** get random validation code string as lower case 99 | * @return validation code 100 | */ 101 | public String text() { 102 | return chars.toLowerCase(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/generator/Gif2VCGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.generator; 2 | 3 | import static com.github.botaruibo.xvcode.generator.XRandoms.num; 4 | 5 | import java.awt.AlphaComposite; 6 | import java.awt.BasicStroke; 7 | import java.awt.Color; 8 | import java.awt.Font; 9 | import java.awt.Graphics2D; 10 | import java.awt.image.BufferedImage; 11 | import java.io.OutputStream; 12 | 13 | import com.github.botaruibo.xvcode.gifencoder.AnimatedGifEncoder; 14 | 15 | public class Gif2VCGenerator extends Generator { 16 | 17 | /** background graphic alpha value **/ 18 | private static float bkAlpha = 0.7f; 19 | 20 | /** oval stroke size**/ 21 | private static float ovalSize = 4.0f; 22 | 23 | /** oval count. decide to draw how many ovals as background**/ 24 | private static int ovalCount = 10; 25 | 26 | /** git delay. unit ms**/ 27 | private static int gifDelayTime = 500; 28 | 29 | Color color = color(150, 250); 30 | 31 | public Gif2VCGenerator() { 32 | } 33 | 34 | public Gif2VCGenerator(int width, int height) { 35 | this.width = width; 36 | this.height = height; 37 | } 38 | 39 | public Gif2VCGenerator(int width, int height, int len) { 40 | this(width, height); 41 | this.len = len; 42 | } 43 | 44 | public Gif2VCGenerator(int width, int height, int len, Font font) { 45 | this(width, height, len); 46 | this.font = font; 47 | } 48 | 49 | /**this constructor is different from others. 50 | * parameters with prefix 'p' are static field. 51 | * be care for the side effect to other beans 52 | * @param width image width 53 | * @param height image height 54 | * @param len validation code length 55 | * @param font font features 56 | * @param pbkAlpha alpha channel for image background. default 7f 57 | * @param pOvalSize the interference oval strike size. default 4 58 | * @param pOvalCount the interference oval count. default 10 59 | * @param pGifDelayTime gif frame delay time.default 500ms 60 | */ 61 | public Gif2VCGenerator(int width, int height, int len, Font font, 62 | float pbkAlpha, float pOvalSize, int pOvalCount, int pGifDelayTime) { 63 | this(width, height, len, font); 64 | bkAlpha = pbkAlpha; 65 | ovalSize = pOvalSize; 66 | ovalCount = pOvalCount; 67 | gifDelayTime = pGifDelayTime; 68 | } 69 | 70 | @Override 71 | public OutputStream write2out(OutputStream out) { 72 | if (out == null) { 73 | return null; 74 | } 75 | AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); 76 | // 生成字符 77 | gifEncoder.start(out); 78 | gifEncoder.setQuality(180); 79 | gifEncoder.setDelay(gifDelayTime); 80 | gifEncoder.setRepeat(0); 81 | BufferedImage frame; 82 | char[] rands = alphas(); 83 | Color fontcolor[] = new Color[len]; 84 | for (int i = 0; i < len; i++) { 85 | fontcolor[i] = new Color(20 + num(110), 20 + num(110), 20 + num(110)); 86 | } 87 | int[] ovalPosition = new int[ovalCount * 4]; 88 | for (int i = 0; i < ovalPosition.length; i += 4) { 89 | ovalPosition[i] = num(width); 90 | ovalPosition[i+1] = num(height); 91 | ovalPosition[i+2] = 10 + num(10); 92 | ovalPosition[i+3] = 10 + num(10); 93 | } 94 | for (int i = 0; i < len; i++) { 95 | frame = getValidCodeImage(fontcolor, rands, ovalPosition, i); 96 | gifEncoder.addFrame(frame); 97 | frame.flush(); 98 | } 99 | gifEncoder.finish(); 100 | return out; 101 | } 102 | 103 | /** draw one frame for the GIF image 104 | * @param fontcolor font color 105 | * @param strs validation string 106 | * @param flag 107 | * @return 108 | */ 109 | private BufferedImage getValidCodeImage(Color[] fontcolor, char[] strs, int[] ovalPosition, int flag) { 110 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 111 | Graphics2D g2d = (Graphics2D) image.getGraphics(); 112 | // set background color to white 113 | g2d.setColor(Color.WHITE); 114 | g2d.fillRect(0, 0, width, height); 115 | g2d.setStroke(new BasicStroke(ovalSize)); 116 | AlphaComposite ac3; 117 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, bkAlpha);// specify the background graphic's alpha channel 118 | g2d.setComposite(ac3); 119 | // draw random ovals 120 | for (int i = 0; i < ovalPosition.length; i += 4) { 121 | g2d.setColor(color(150, 250)); 122 | g2d.drawOval(ovalPosition[i], ovalPosition[i+1], ovalPosition[i+2], ovalPosition[i+3]); 123 | } 124 | int h = height - ((height - font.getSize()) >> 1); 125 | int w = width / len; 126 | g2d.setFont(font); 127 | for (int i = 0; i < len; i++) { 128 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); 129 | g2d.setComposite(ac3); 130 | g2d.setColor(fontcolor[i]); 131 | int degree = num(90); // random rotate degree. -90 < degree < 90 132 | degree = num(2) == 0 ? -degree : degree; 133 | g2d.rotate(Math.toRadians(degree), (width - (len - i) * w) + (w >> 1), (height >> 1) + 2); 134 | g2d.drawString(strs[i] + "", (width - (len - i) * w) + (w - font.getSize()) + 1, h - 4); 135 | g2d.rotate(-Math.toRadians(degree), (width - (len - i) * w) + (w >> 1), (height >> 1) + 2); 136 | } 137 | g2d.dispose(); 138 | return image; 139 | } 140 | 141 | /** Calculate alpha 142 | * @param i 143 | * @param j 144 | * @return 145 | */ 146 | private float getAlpha(int i, int j) { 147 | int num = i + j; 148 | float r = 1f / len, s = (len + 1) * r; 149 | return num > len ? (num * r - s) : num * r; 150 | } 151 | 152 | /* return null for all invoke. 153 | * @see com.github.botaruibo.xvcode.generator.Captcha#getValidCodeImage(char[]) 154 | */ 155 | @Override 156 | public BufferedImage getValidCodeImage() { 157 | return null; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/generator/Gif3VCGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.generator; 2 | 3 | import static com.github.botaruibo.xvcode.generator.XRandoms.num; 4 | 5 | import java.awt.AlphaComposite; 6 | import java.awt.BasicStroke; 7 | import java.awt.Color; 8 | import java.awt.Font; 9 | import java.awt.Graphics2D; 10 | import java.awt.image.BufferedImage; 11 | import java.io.OutputStream; 12 | 13 | import com.github.botaruibo.xvcode.gifencoder.AnimatedGifEncoder; 14 | 15 | public class Gif3VCGenerator extends Generator { 16 | 17 | /** background graphic alpha value **/ 18 | private static float bkAlpha = 0.7f; 19 | 20 | /** oval stroke size**/ 21 | private static float ovalSize = 4.0f; 22 | 23 | /** oval count. decide to draw how many ovals as background**/ 24 | private static int ovalCount = 10; 25 | 26 | /** git delay. unit ms**/ 27 | private static int gifDelayTime = 500; 28 | 29 | public Gif3VCGenerator() { 30 | } 31 | 32 | public Gif3VCGenerator(int width, int height) { 33 | this.width = width; 34 | this.height = height; 35 | } 36 | 37 | public Gif3VCGenerator(int width, int height, int len) { 38 | this(width, height); 39 | this.len = len; 40 | } 41 | 42 | public Gif3VCGenerator(int width, int height, int len, Font font) { 43 | this(width, height, len); 44 | this.font = font; 45 | } 46 | 47 | /**this constructor is different from others. 48 | * parameters with prefix 'p' are static field. 49 | * be care for the side effect to other beans 50 | * @param width image width 51 | * @param height image height 52 | * @param len validation code length 53 | * @param font font features 54 | * @param pbkAlpha alpha channel for image background. default 7f 55 | * @param pOvalSize the interference oval strike size. default 4 56 | * @param pOvalCount the interference oval count. default 10 57 | * @param pGifDelayTime gif frame delay time.default 500ms 58 | */ 59 | public Gif3VCGenerator(int width, int height, int len, Font font, 60 | float pbkAlpha, float pOvalSize, int pOvalCount, int pGifDelayTime) { 61 | this(width, height, len, font); 62 | bkAlpha = pbkAlpha; 63 | ovalSize = pOvalSize; 64 | ovalCount = pOvalCount; 65 | gifDelayTime = pGifDelayTime; 66 | } 67 | 68 | @Override 69 | public OutputStream write2out(OutputStream out) { 70 | if (out == null) { 71 | return null; 72 | } 73 | AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); 74 | gifEncoder.start(out); 75 | gifEncoder.setQuality(180); 76 | gifEncoder.setDelay(gifDelayTime); 77 | gifEncoder.setRepeat(0); 78 | BufferedImage frame; 79 | char[] rands = alphas(); 80 | Color fontcolor[] = new Color[len]; 81 | for (int i = 0; i < len; i++) { 82 | fontcolor[i] = new Color(20 + num(110), 20 + num(110), 20 + num(110)); 83 | } 84 | int[] ovalPosition = new int[ovalCount * 4]; 85 | Color[] colors = new Color[ovalCount]; 86 | for (int i = 0; i < ovalPosition.length; i += 4) { 87 | ovalPosition[i] = num(width); 88 | ovalPosition[i+1] = num(height); 89 | ovalPosition[i+2] = 10 + num(10); 90 | ovalPosition[i+3] = 10 + num(10); 91 | colors[i >> 2] = color(150, 250); 92 | } 93 | for (int i = 0; i < len; i++) { 94 | frame = getValidCodeImage(fontcolor, rands, ovalPosition, colors, i); 95 | gifEncoder.addFrame(frame); 96 | frame.flush(); 97 | } 98 | gifEncoder.finish(); 99 | return out; 100 | } 101 | 102 | /** draw one frame for the GIF image 103 | * @param fontcolor font color 104 | * @param strs validation string 105 | * @param flag 106 | * @return 107 | */ 108 | private BufferedImage getValidCodeImage(Color[] fontcolor, char[] strs, int[] ovalPosition, Color[] colors, int flag) { 109 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 110 | Graphics2D g2d = (Graphics2D) image.getGraphics(); 111 | // set background color to white 112 | g2d.setColor(Color.WHITE); 113 | g2d.fillRect(0, 0, width, height); 114 | g2d.setStroke(new BasicStroke(ovalSize)); 115 | AlphaComposite ac3; 116 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, bkAlpha);// specify the background graphic's alpha channel 117 | g2d.setComposite(ac3); 118 | 119 | // draw random ovals 120 | for (int i = 0; i < ovalPosition.length; i += 4) { 121 | g2d.setColor(colors[i >> 2]); 122 | g2d.drawOval(ovalPosition[i], ovalPosition[i+1], ovalPosition[i+2], ovalPosition[i+3]); 123 | } 124 | int h = height - ((height - font.getSize()) >> 1); 125 | int w = width / len; 126 | g2d.setFont(font); 127 | for (int i = 0; i < len; i++) { 128 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); 129 | g2d.setComposite(ac3); 130 | g2d.setColor(fontcolor[i]); 131 | int degree = num(90); // random rotate degree. -90 < degree < 90 132 | degree = num(2) == 0 ? -degree : degree; 133 | g2d.rotate(Math.toRadians(degree), (width - (len - i) * w) + (w >> 1), (height >> 1) + 2); 134 | g2d.drawString(strs[i] + "", (width - (len - i) * w) + (w - font.getSize()) + 1, h - 4); 135 | g2d.rotate(-Math.toRadians(degree), (width - (len - i) * w) + (w >> 1), (height >> 1) + 2); 136 | } 137 | g2d.dispose(); 138 | return image; 139 | } 140 | 141 | /** Calculate alpha 142 | * @param i 143 | * @param j 144 | * @return 145 | */ 146 | private float getAlpha(int i, int j) { 147 | int num = i + j; 148 | float r = (float) 1 / len, s = (len + 1) * r; 149 | return num > len ? (num * r - s) : num * r; 150 | } 151 | 152 | /* return null for all invoke. 153 | * @see com.github.botaruibo.xvcode.generator.Captcha#getValidCodeImage(char[]) 154 | */ 155 | @Override 156 | public BufferedImage getValidCodeImage() { 157 | return null; 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/generator/GifVCGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.generator; 2 | 3 | import static com.github.botaruibo.xvcode.generator.XRandoms.*; 4 | 5 | import java.awt.AlphaComposite; 6 | import java.awt.BasicStroke; 7 | import java.awt.Color; 8 | import java.awt.Font; 9 | import java.awt.Graphics2D; 10 | import java.awt.image.BufferedImage; 11 | import java.io.OutputStream; 12 | 13 | import com.github.botaruibo.xvcode.gifencoder.AnimatedGifEncoder; 14 | 15 | public class GifVCGenerator extends Generator { 16 | 17 | /** oval stroke size**/ 18 | private static float ovalSize = 4.0f; 19 | 20 | /** git delay. unit ms**/ 21 | private static int gifDelayTime = 500; 22 | 23 | public GifVCGenerator() { 24 | } 25 | 26 | public GifVCGenerator(int width, int height) { 27 | this.width = width; 28 | this.height = height; 29 | } 30 | 31 | public GifVCGenerator(int width, int height, int len) { 32 | this(width, height); 33 | this.len = len; 34 | } 35 | 36 | public GifVCGenerator(int width, int height, int len, Font font) { 37 | this(width, height, len); 38 | this.font = font; 39 | } 40 | 41 | /**this constructor is different from others. 42 | * parameters with prefix 'p' are static field. 43 | * be care for the side effect to other beans 44 | * @param width image width 45 | * @param height image height 46 | * @param len validation code length 47 | * @param font font features 48 | * @param pOvalSize the interference oval strike size. default 4 49 | * @param pGifDelayTime gif frame delay time.default 500ms 50 | */ 51 | public GifVCGenerator(int width, int height, int len, Font font, int pOvalSize, int pGifDelayTime) { 52 | this(width, height, len, font); 53 | ovalSize = pOvalSize; 54 | gifDelayTime = pGifDelayTime; 55 | } 56 | 57 | @Override 58 | public OutputStream write2out(OutputStream out) { 59 | if (out == null) { 60 | return null; 61 | } 62 | AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); 63 | gifEncoder.start(out); 64 | gifEncoder.setQuality(180); 65 | gifEncoder.setDelay(gifDelayTime); 66 | gifEncoder.setRepeat(0); 67 | BufferedImage frame; 68 | char[] rands = alphas(); 69 | Color fontcolor[] = new Color[len]; 70 | for (int i = 0; i < len; i++) { 71 | fontcolor[i] = new Color(20 + num(110), 20 + num(110), 20 + num(110)); 72 | } 73 | for (int i = 0; i < len; i++) { 74 | frame = getValidCodeImage(fontcolor, rands, i); 75 | gifEncoder.addFrame(frame); 76 | frame.flush(); 77 | } 78 | gifEncoder.finish(); 79 | return out; 80 | } 81 | 82 | /** draw one frame for the GIF image 83 | * @param fontcolor font color 84 | * @param strs validation string 85 | * @param flag 86 | * @return 87 | */ 88 | private BufferedImage getValidCodeImage(Color[] fontcolor, char[] strs, int flag) { 89 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 90 | Graphics2D g2d = (Graphics2D) image.getGraphics(); 91 | //set background color to white 92 | g2d.setColor(Color.WHITE); 93 | g2d.fillRect(0, 0, width, height); 94 | g2d.setStroke(new BasicStroke(ovalSize)); 95 | AlphaComposite ac3; 96 | int h = height - ((height - font.getSize()) >> 1); 97 | int w = width / len; 98 | g2d.setFont(font); 99 | for (int i = 0; i < len; i++) { 100 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i) / 2); 101 | g2d.setComposite(ac3); 102 | g2d.setColor(fontcolor[i]); 103 | // one frame with tow ovals 104 | g2d.drawOval(num(width), num(height), 10 + num(10), 10 + num(10)); 105 | g2d.drawOval(num(width), num(height), 10 + num(10), 10 + num(10)); 106 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); 107 | g2d.setComposite(ac3); 108 | g2d.drawString(strs[i] + "", (width - (len - i) * w) + (w - font.getSize()) + 1, h - 4); 109 | } 110 | g2d.dispose(); 111 | return image; 112 | } 113 | 114 | /** Calculate alpha 115 | * @param i 116 | * @param j 117 | * @return 118 | */ 119 | private float getAlpha(int i, int j) { 120 | int num = i + j; 121 | float r = 1f / len, s = (len + 1) * r; 122 | return num > len ? (num * r - s) : num * r; 123 | } 124 | 125 | /* return null for all invoke. 126 | * @see com.github.botaruibo.xvcode.generator.Captcha#getValidCodeImage(char[]) 127 | */ 128 | @Override 129 | public BufferedImage getValidCodeImage() { 130 | return null; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/generator/PngVCGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.generator; 2 | 3 | import java.awt.AlphaComposite; 4 | 5 | import static com.github.botaruibo.xvcode.generator.XRandoms.num; 6 | 7 | import java.awt.*; 8 | import java.awt.Font; 9 | import java.awt.Graphics2D; 10 | import java.awt.image.BufferedImage; 11 | import java.io.IOException; 12 | import java.io.OutputStream; 13 | 14 | import javax.imageio.ImageIO; 15 | 16 | /** 17 | * PNG validation code generator 18 | * @author brui 19 | * 20 | */ 21 | public class PngVCGenerator extends Generator { 22 | 23 | /** background graphic alpha value **/ 24 | private static float bkAlpha = 0.7f; 25 | 26 | /** validation code font alpha value **/ 27 | private static float fontAlpha = 0.7f; 28 | 29 | /** oval stroke size**/ 30 | private static float ovalSize = 4.0f; 31 | 32 | /** oval count. decide to draw how many ovals as background**/ 33 | private static int ovalCount = 20; 34 | 35 | public PngVCGenerator() { 36 | } 37 | 38 | public PngVCGenerator(int width, int height) { 39 | this.width = width; 40 | this.height = height; 41 | } 42 | 43 | public PngVCGenerator(int width, int height, int len) { 44 | this(width, height); 45 | this.len = len; 46 | } 47 | 48 | public PngVCGenerator(int width, int height, int len, Font font) { 49 | this(width, height, len); 50 | this.font = font; 51 | } 52 | 53 | /** this constructor is different from others. 54 | * parameters with prefix 'p' are static field. 55 | * be care for the side effect to other beans 56 | * @param width image width 57 | * @param height image height 58 | * @param len validation code length 59 | * @param font font features 60 | * @param pbkAlpha alpha channel for image background. default 7f 61 | * @param pFontAlpha alpha channel for validation code font. default 7f 62 | * @param pOvalSize the interference oval strike size. default 4 63 | * @param pOvalCount the interference oval count. default 20 64 | */ 65 | public PngVCGenerator(int width, int height, int len, Font font, 66 | float pbkAlpha, float pFontAlpha, float pOvalSize, int pOvalCount) { 67 | this(width, height, len, font); 68 | bkAlpha = pbkAlpha; 69 | fontAlpha = pFontAlpha; 70 | ovalSize = pOvalSize; 71 | ovalCount = pOvalCount; 72 | } 73 | 74 | @Override 75 | public OutputStream write2out(OutputStream os) { 76 | if (os == null) { 77 | return null; 78 | } 79 | BufferedImage bi = getValidCodeImage(alphas()); 80 | 81 | try { 82 | ImageIO.write(bi, "png", os); 83 | os.flush(); 84 | } catch (IOException e) { 85 | //ignore 86 | e.printStackTrace(); 87 | } 88 | return os; 89 | } 90 | 91 | /**draw random validation code image 92 | * @param strs the random validation code 93 | * @return BufferedImage with validation code 94 | */ 95 | private BufferedImage getValidCodeImage(char[] strs) { 96 | BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 97 | Graphics2D g2d = (Graphics2D) bi.getGraphics(); 98 | AlphaComposite ac3; 99 | Color color; 100 | int len = strs.length; 101 | //set background color to white 102 | g2d.setColor(Color.WHITE); 103 | g2d.fillRect(0, 0, width, height); 104 | g2d.setStroke(new BasicStroke(ovalSize)); //set the stroke to 4 pixel 105 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, bkAlpha);// specify the background graphic's alpha channel 106 | g2d.setComposite(ac3); 107 | // draw random ovals 108 | for (int i = 0; i < ovalCount; i++) { 109 | color = color(150, 250); 110 | g2d.setColor(color); 111 | g2d.drawOval(num(width), num(height), 10 + num(10), 10 + num(10)); 112 | } 113 | g2d.setFont(font); 114 | ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fontAlpha);// specify the validation code's alpha channel 115 | g2d.setComposite(ac3); 116 | int h = height - ((height - font.getSize()) >> 1); 117 | int w = width / len, size = w - font.getSize() + 2; 118 | /* draw font */ 119 | for (int i = 0; i < len; i++) { 120 | color = new Color(20 + num(110), 20 + num(110), 20 + num(110));// random color for each font 121 | g2d.setColor(color); 122 | int degree = num(90); // random rotate degree. -90 < degree < 90 123 | degree = num(2) == 0 ? -degree : degree; 124 | g2d.rotate(Math.toRadians(degree), (width - (len - i) * w) + w / 2, height / 2 + 2); 125 | g2d.drawString(strs[i] + "", (width - (len - i) * w) + size, h - 4); 126 | g2d.rotate(-Math.toRadians(degree), (width - (len - i) * w) + w / 2, height / 2 + 2); 127 | } 128 | g2d.dispose(); 129 | return bi; 130 | } 131 | 132 | @Override 133 | public BufferedImage getValidCodeImage() { 134 | return getValidCodeImage(alphas()); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/generator/XRandoms.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.generator; 2 | 3 | import java.util.Random; 4 | 5 | public class XRandoms { 6 | private static final Random RANDOM = new Random(); 7 | // alhpa table without 'O' and 'I' eg. 8 | public static final char ALPHA[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'G', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 9 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'p', 10 | 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7', '8', '9' }; 11 | 12 | /**generator random number between min and max. 13 | * including min but not max 14 | * @param min 15 | * @param max 16 | * @return random number 17 | */ 18 | public static int num(int min, int max) { 19 | return min + RANDOM.nextInt(max - min); 20 | } 21 | 22 | /**generator random number between 0 and num. 23 | * including 0 but not num 24 | * @param num 25 | * @return 26 | */ 27 | public static int num(int num) { 28 | return RANDOM.nextInt(num); 29 | } 30 | 31 | public static char alpha() { 32 | return ALPHA[num(0, ALPHA.length)]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/gifencoder/AnimatedGifEncoder.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.gifencoder; 2 | 3 | import java.io.*; 4 | import java.awt.*; 5 | import java.awt.image.*; 6 | 7 | /** 8 | * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more 9 | * frames. 10 | * 11 | *
 12 |  * Example:
 13 |  *    AnimatedGifEncoder e = new AnimatedGifEncoder();
 14 |  *    e.start(outputFileName);
 15 |  *    e.setDelay(1000);   // 1 frame per sec
 16 |  *    e.addFrame(image1);
 17 |  *    e.addFrame(image2);
 18 |  *    e.finish();
 19 |  * 
20 | * 21 | * No copyright asserted on the source code of this class. May be used for any 22 | * purpose, however, refer to the Unisys LZW patent for restrictions on use of 23 | * the associated LZWEncoder class. Please forward any corrections to 24 | * kweiner@fmsware.com. 25 | * 26 | * @author Kevin Weiner, FM Software 27 | * @version 1.03 November 2003 28 | * 29 | */ 30 | 31 | public class AnimatedGifEncoder { 32 | 33 | protected int width; // image size 34 | protected int height; 35 | protected Color transparent = null; // transparent color if given 36 | protected int transIndex; // transparent index in color table 37 | protected int repeat = -1; // no repeat 38 | protected int delay = 0; // frame delay (hundredths) 39 | protected boolean started = false; // ready to output frames 40 | protected OutputStream out; 41 | protected BufferedImage image; // current frame 42 | protected byte[] pixels; // BGR byte array from frame 43 | protected byte[] indexedPixels; // converted frame indexed to palette 44 | protected int colorDepth; // number of bit planes 45 | protected byte[] colorTab; // RGB palette 46 | protected boolean[] usedEntry = new boolean[256]; // active palette entries 47 | protected int palSize = 7; // color table size (bits-1) 48 | protected int dispose = -1; // disposal code (-1 = use default) 49 | protected boolean closeStream = false; // close stream when finished 50 | protected boolean firstFrame = true; 51 | protected boolean sizeSet = false; // if false, get size from first frame 52 | protected int sample = 10; // default sample interval for quantizer 53 | 54 | /** 55 | * Sets the delay time between each frame, or changes it for subsequent 56 | * frames (applies to last frame added). 57 | * 58 | * @param ms 59 | * int delay time in milliseconds 60 | */ 61 | public void setDelay(int ms) { 62 | delay = Math.round(ms / 10.0f); 63 | } 64 | 65 | /** 66 | * Sets the GIF frame disposal code for the last added frame and any 67 | * subsequent frames. Default is 0 if no transparent color has been set, 68 | * otherwise 2. 69 | * 70 | * @param code 71 | * int disposal code. 72 | */ 73 | public void setDispose(int code) { 74 | if (code >= 0) { 75 | dispose = code; 76 | } 77 | } 78 | 79 | /** 80 | * Sets the number of times the set of GIF frames should be played. Default 81 | * is 1; 0 means play indefinitely. Must be invoked before the first image 82 | * is added. 83 | * 84 | * @param iter 85 | * int number of iterations. 86 | * @return 87 | */ 88 | public void setRepeat(int iter) { 89 | if (iter >= 0) { 90 | repeat = iter; 91 | } 92 | } 93 | 94 | /** 95 | * Sets the transparent color for the last added frame and any subsequent 96 | * frames. Since all colors are subject to modification in the quantization 97 | * process, the color in the final palette for each frame closest to the 98 | * given color becomes the transparent color for that frame. May be set to 99 | * null to indicate no transparent color. 100 | * 101 | * @param c 102 | * Color to be treated as transparent on display. 103 | */ 104 | public void setTransparent(Color c) { 105 | transparent = c; 106 | } 107 | 108 | /** 109 | * Adds next GIF frame. The frame is not written immediately, but is 110 | * actually deferred until the next frame is received so that timing data 111 | * can be inserted. Invoking finish() flushes all frames. If 112 | * setSize was not invoked, the size of the first image is used 113 | * for all subsequent frames. 114 | * 115 | * @param im 116 | * BufferedImage containing frame to write. 117 | * @return true if successful. 118 | */ 119 | public boolean addFrame(BufferedImage im) { 120 | if ((im == null) || !started) { 121 | return false; 122 | } 123 | boolean ok = true; 124 | try { 125 | if (!sizeSet) { 126 | // use first frame's size 127 | setSize(im.getWidth(), im.getHeight()); 128 | } 129 | image = im; 130 | getImagePixels(); // convert to correct format if necessary 131 | analyzePixels(); // build color table & map pixels 132 | if (firstFrame) { 133 | writeLSD(); // logical screen descriptior 134 | writePalette(); // global color table 135 | if (repeat >= 0) { 136 | // use NS app extension to indicate reps 137 | writeNetscapeExt(); 138 | } 139 | } 140 | writeGraphicCtrlExt(); // write graphic control extension 141 | writeImageDesc(); // image descriptor 142 | if (!firstFrame) { 143 | writePalette(); // local color table 144 | } 145 | writePixels(); // encode and write pixel data 146 | firstFrame = false; 147 | } catch (IOException e) { 148 | ok = false; 149 | } 150 | 151 | return ok; 152 | } 153 | 154 | /** 155 | * Flushes any pending data and closes output file. If writing to an 156 | * OutputStream, the stream is not closed. 157 | */ 158 | public boolean finish() { 159 | if (!started) 160 | return false; 161 | boolean ok = true; 162 | started = false; 163 | try { 164 | out.write(0x3b); // gif trailer 165 | out.flush(); 166 | if (closeStream) { 167 | out.close(); 168 | } 169 | } catch (IOException e) { 170 | ok = false; 171 | } 172 | 173 | // reset for subsequent use 174 | transIndex = 0; 175 | out = null; 176 | image = null; 177 | pixels = null; 178 | indexedPixels = null; 179 | colorTab = null; 180 | closeStream = false; 181 | firstFrame = true; 182 | 183 | return ok; 184 | } 185 | 186 | /** 187 | * Sets frame rate in frames per second. Equivalent to 188 | * setDelay(1000/fps). 189 | * 190 | * @param fps 191 | * float frame rate (frames per second) 192 | */ 193 | public void setFrameRate(float fps) { 194 | if (fps != 0f) { 195 | delay = Math.round(100f / fps); 196 | } 197 | } 198 | 199 | /** 200 | * Sets quality of color quantization (conversion of images to the maximum 201 | * 256 colors allowed by the GIF specification). Lower values (minimum = 1) 202 | * produce better colors, but slow processing significantly. 10 is the 203 | * default, and produces good color mapping at reasonable speeds. Values 204 | * greater than 20 do not yield significant improvements in speed. 205 | * 206 | * @param quality 207 | * int greater than 0. 208 | * @return 209 | */ 210 | public void setQuality(int quality) { 211 | if (quality < 1) 212 | quality = 1; 213 | sample = quality; 214 | } 215 | 216 | /** 217 | * Sets the GIF frame size. The default size is the size of the first frame 218 | * added if this method is not invoked. 219 | * 220 | * @param w 221 | * int frame width. 222 | * @param h 223 | * int frame width. 224 | */ 225 | public void setSize(int w, int h) { 226 | if (started && !firstFrame) 227 | return; 228 | width = w; 229 | height = h; 230 | if (width < 1) 231 | width = 320; 232 | if (height < 1) 233 | height = 240; 234 | sizeSet = true; 235 | } 236 | 237 | /** 238 | * Initiates GIF file creation on the given stream. The stream is not closed 239 | * automatically. 240 | * 241 | * @param os 242 | * OutputStream on which GIF images are written. 243 | * @return false if initial write failed. 244 | */ 245 | public boolean start(OutputStream os) { 246 | if (os == null) 247 | return false; 248 | boolean ok = true; 249 | closeStream = false; 250 | out = os; 251 | try { 252 | writeString("GIF89a"); // header 253 | } catch (IOException e) { 254 | ok = false; 255 | } 256 | return started = ok; 257 | } 258 | 259 | /** 260 | * Initiates writing of a GIF file with the specified name. 261 | * 262 | * @param file 263 | * String containing output file name. 264 | * @return false if open or initial write failed. 265 | */ 266 | public boolean start(String file) { 267 | boolean ok = true; 268 | try { 269 | out = new BufferedOutputStream(new FileOutputStream(file)); 270 | ok = start(out); 271 | closeStream = true; 272 | } catch (IOException e) { 273 | ok = false; 274 | } 275 | return started = ok; 276 | } 277 | 278 | /** 279 | * Analyzes image colors and creates color map. 280 | */ 281 | protected void analyzePixels() { 282 | int len = pixels.length; 283 | int nPix = len / 3; 284 | indexedPixels = new byte[nPix]; 285 | NeuQuant nq = new NeuQuant(pixels, len, sample); 286 | // initialize quantizer 287 | colorTab = nq.process(); // create reduced palette 288 | // convert map from BGR to RGB 289 | for (int i = 0; i < colorTab.length; i += 3) { 290 | byte temp = colorTab[i]; 291 | colorTab[i] = colorTab[i + 2]; 292 | colorTab[i + 2] = temp; 293 | usedEntry[i / 3] = false; 294 | } 295 | // map image pixels to new palette 296 | int k = 0; 297 | for (int i = 0; i < nPix; i++) { 298 | int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, 299 | pixels[k++] & 0xff); 300 | usedEntry[index] = true; 301 | indexedPixels[i] = (byte) index; 302 | } 303 | pixels = null; 304 | colorDepth = 8; 305 | palSize = 7; 306 | // get closest match to transparent color if specified 307 | if (transparent != null) { 308 | transIndex = findClosest(transparent); 309 | } 310 | } 311 | 312 | /** 313 | * Returns index of palette color closest to c 314 | * 315 | */ 316 | protected int findClosest(Color c) { 317 | if (colorTab == null) 318 | return -1; 319 | int r = c.getRed(); 320 | int g = c.getGreen(); 321 | int b = c.getBlue(); 322 | int minpos = 0; 323 | int dmin = 256 * 256 * 256; 324 | int len = colorTab.length; 325 | for (int i = 0; i < len;) { 326 | int dr = r - (colorTab[i++] & 0xff); 327 | int dg = g - (colorTab[i++] & 0xff); 328 | int db = b - (colorTab[i] & 0xff); 329 | int d = dr * dr + dg * dg + db * db; 330 | int index = i / 3; 331 | if (usedEntry[index] && (d < dmin)) { 332 | dmin = d; 333 | minpos = index; 334 | } 335 | i++; 336 | } 337 | return minpos; 338 | } 339 | 340 | /** 341 | * Extracts image pixels into byte array "pixels" 342 | */ 343 | protected void getImagePixels() { 344 | int w = image.getWidth(); 345 | int h = image.getHeight(); 346 | int type = image.getType(); 347 | if ((w != width) || (h != height) 348 | || (type != BufferedImage.TYPE_3BYTE_BGR)) { 349 | // create new image with right size/format 350 | BufferedImage temp = new BufferedImage(width, height, 351 | BufferedImage.TYPE_3BYTE_BGR); 352 | Graphics2D g = temp.createGraphics(); 353 | g.drawImage(image, 0, 0, null); 354 | image = temp; 355 | } 356 | pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); 357 | } 358 | 359 | /** 360 | * Writes Graphic Control Extension 361 | */ 362 | protected void writeGraphicCtrlExt() throws IOException { 363 | out.write(0x21); // extension introducer 364 | out.write(0xf9); // GCE label 365 | out.write(4); // data block size 366 | int transp, disp; 367 | if (transparent == null) { 368 | transp = 0; 369 | disp = 0; // dispose = no action 370 | } else { 371 | transp = 1; 372 | disp = 2; // force clear if using transparent color 373 | } 374 | if (dispose >= 0) { 375 | disp = dispose & 7; // user override 376 | } 377 | disp <<= 2; 378 | 379 | // packed fields 380 | out.write(0 | // 1:3 reserved 381 | disp | // 4:6 disposal 382 | 0 | // 7 user input - 0 = none 383 | transp); // 8 transparency flag 384 | 385 | writeShort(delay); // delay x 1/100 sec 386 | out.write(transIndex); // transparent color index 387 | out.write(0); // block terminator 388 | } 389 | 390 | /** 391 | * Writes Image Descriptor 392 | */ 393 | protected void writeImageDesc() throws IOException { 394 | out.write(0x2c); // image separator 395 | writeShort(0); // image position x,y = 0,0 396 | writeShort(0); 397 | writeShort(width); // image size 398 | writeShort(height); 399 | // packed fields 400 | if (firstFrame) { 401 | // no LCT - GCT is used for first (or only) frame 402 | out.write(0); 403 | } else { 404 | // specify normal LCT 405 | out.write(0x80 | // 1 local color table 1=yes 406 | 0 | // 2 interlace - 0=no 407 | 0 | // 3 sorted - 0=no 408 | 0 | // 4-5 reserved 409 | palSize); // 6-8 size of color table 410 | } 411 | } 412 | 413 | /** 414 | * Writes Logical Screen Descriptor 415 | */ 416 | protected void writeLSD() throws IOException { 417 | // logical screen size 418 | writeShort(width); 419 | writeShort(height); 420 | // packed fields 421 | out.write((0x80 | // 1 : global color table flag = 1 (gct used) 422 | 0x70 | // 2-4 : color resolution = 7 423 | 0x00 | // 5 : gct sort flag = 0 424 | palSize)); // 6-8 : gct size 425 | 426 | out.write(0); // background color index 427 | out.write(0); // pixel aspect ratio - assume 1:1 428 | } 429 | 430 | /** 431 | * Writes Netscape application extension to define repeat count. 432 | */ 433 | protected void writeNetscapeExt() throws IOException { 434 | out.write(0x21); // extension introducer 435 | out.write(0xff); // app extension label 436 | out.write(11); // block size 437 | writeString("NETSCAPE" + "2.0"); // app id + auth code 438 | out.write(3); // sub-block size 439 | out.write(1); // loop sub-block id 440 | writeShort(repeat); // loop count (extra iterations, 0=repeat forever) 441 | out.write(0); // block terminator 442 | } 443 | 444 | /** 445 | * Writes color table 446 | */ 447 | protected void writePalette() throws IOException { 448 | out.write(colorTab, 0, colorTab.length); 449 | int n = (3 * 256) - colorTab.length; 450 | for (int i = 0; i < n; i++) { 451 | out.write(0); 452 | } 453 | } 454 | 455 | /** 456 | * Encodes and writes pixel data 457 | */ 458 | protected void writePixels() throws IOException { 459 | LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, 460 | colorDepth); 461 | encoder.encode(out); 462 | } 463 | 464 | /** 465 | * Write 16-bit value to output stream, LSB first 466 | */ 467 | protected void writeShort(int value) throws IOException { 468 | out.write(value & 0xff); 469 | out.write((value >> 8) & 0xff); 470 | } 471 | 472 | /** 473 | * Writes string to output stream 474 | */ 475 | protected void writeString(String s) throws IOException { 476 | for (int i = 0; i < s.length(); i++) { 477 | out.write((byte) s.charAt(i)); 478 | } 479 | } 480 | } -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/gifencoder/LZWEncoder.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.gifencoder; 2 | 3 | import java.io.OutputStream; 4 | import java.io.IOException; 5 | 6 | //============================================================================== 7 | // Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. 8 | // K Weiner 12/00 9 | 10 | class LZWEncoder { 11 | 12 | private static final int EOF = -1; 13 | 14 | private int imgW, imgH; 15 | private byte[] pixAry; 16 | private int initCodeSize; 17 | private int remaining; 18 | private int curPixel; 19 | 20 | // GIFCOMPR.C - GIF Image compression routines 21 | // 22 | // Lempel-Ziv compression based on 'compress'. GIF modifications by 23 | // David Rowley (mgardi@watdcsu.waterloo.edu) 24 | 25 | // General DEFINEs 26 | 27 | static final int BITS = 12; 28 | 29 | static final int HSIZE = 5003; // 80% occupancy 30 | 31 | // GIF Image compression - modified 'compress' 32 | // 33 | // Based on: compress.c - File compression ala IEEE Computer, June 1984. 34 | // 35 | // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) 36 | // Jim McKie (decvax!mcvax!jim) 37 | // Steve Davies (decvax!vax135!petsd!peora!srd) 38 | // Ken Turkowski (decvax!decwrl!turtlevax!ken) 39 | // James A. Woods (decvax!ihnp4!ames!jaw) 40 | // Joe Orost (decvax!vax135!petsd!joe) 41 | 42 | int n_bits; // number of bits/code 43 | int maxbits = BITS; // user settable max # bits/code 44 | int maxcode; // maximum code, given n_bits 45 | int maxmaxcode = 1 << BITS; // should NEVER generate this code 46 | 47 | int[] htab = new int[HSIZE]; 48 | int[] codetab = new int[HSIZE]; 49 | 50 | int hsize = HSIZE; // for dynamic table sizing 51 | 52 | int free_ent = 0; // first unused entry 53 | 54 | // block compression parameters -- after all codes are used up, 55 | // and compression rate changes, start over. 56 | boolean clear_flg = false; 57 | 58 | // Algorithm: use open addressing double hashing (no chaining) on the 59 | // prefix code / next character combination. We do a variant of Knuth's 60 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime 61 | // secondary probe. Here, the modular division first probe is gives way 62 | // to a faster exclusive-or manipulation. Also do block compression with 63 | // an adaptive reset, whereby the code table is cleared when the compression 64 | // ratio decreases, but after the table fills. The variable-length output 65 | // codes are re-sized at this point, and a special CLEAR code is generated 66 | // for the decompressor. Late addition: construct the table according to 67 | // file size for noticeable speed improvement on small files. Please direct 68 | // questions about this implementation to ames!jaw. 69 | 70 | int g_init_bits; 71 | 72 | int ClearCode; 73 | int EOFCode; 74 | 75 | // output 76 | // 77 | // Output the given code. 78 | // Inputs: 79 | // code: A n_bits-bit integer. If == -1, then EOF. This assumes 80 | // that n_bits =< wordsize - 1. 81 | // Outputs: 82 | // Outputs code to the file. 83 | // Assumptions: 84 | // Chars are 8 bits long. 85 | // Algorithm: 86 | // Maintain a BITS character long buffer (so that 8 codes will 87 | // fit in it exactly). Use the VAX insv instruction to insert each 88 | // code in turn. When the buffer fills up empty it and start over. 89 | 90 | int cur_accum = 0; 91 | int cur_bits = 0; 92 | 93 | int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 94 | 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 95 | 0x7FFF, 0xFFFF }; 96 | 97 | // Number of characters so far in this 'packet' 98 | int a_count; 99 | 100 | // Define the storage for the packet accumulator 101 | byte[] accum = new byte[256]; 102 | 103 | // ---------------------------------------------------------------------------- 104 | LZWEncoder(int width, int height, byte[] pixels, int color_depth) { 105 | imgW = width; 106 | imgH = height; 107 | pixAry = pixels; 108 | initCodeSize = Math.max(2, color_depth); 109 | } 110 | 111 | // Add a character to the end of the current packet, and if it is 254 112 | // characters, flush the packet to disk. 113 | void char_out(byte c, OutputStream outs) throws IOException { 114 | accum[a_count++] = c; 115 | if (a_count >= 254) 116 | flush_char(outs); 117 | } 118 | 119 | // Clear out the hash table 120 | 121 | // table clear for block compress 122 | void cl_block(OutputStream outs) throws IOException { 123 | cl_hash(hsize); 124 | free_ent = ClearCode + 2; 125 | clear_flg = true; 126 | 127 | output(ClearCode, outs); 128 | } 129 | 130 | // reset code table 131 | void cl_hash(int hsize) { 132 | for (int i = 0; i < hsize; ++i) 133 | htab[i] = -1; 134 | } 135 | 136 | void compress(int init_bits, OutputStream outs) throws IOException { 137 | int fcode; 138 | int i /* = 0 */; 139 | int c; 140 | int ent; 141 | int disp; 142 | int hsize_reg; 143 | int hshift; 144 | 145 | // Set up the globals: g_init_bits - initial number of bits 146 | g_init_bits = init_bits; 147 | 148 | // Set up the necessary values 149 | clear_flg = false; 150 | n_bits = g_init_bits; 151 | maxcode = MAXCODE(n_bits); 152 | 153 | ClearCode = 1 << (init_bits - 1); 154 | EOFCode = ClearCode + 1; 155 | free_ent = ClearCode + 2; 156 | 157 | a_count = 0; // clear packet 158 | 159 | ent = nextPixel(); 160 | 161 | hshift = 0; 162 | for (fcode = hsize; fcode < 65536; fcode *= 2) 163 | ++hshift; 164 | hshift = 8 - hshift; // set hash code range bound 165 | 166 | hsize_reg = hsize; 167 | cl_hash(hsize_reg); // clear hash table 168 | 169 | output(ClearCode, outs); 170 | 171 | outer_loop: while ((c = nextPixel()) != EOF) { 172 | fcode = (c << maxbits) + ent; 173 | i = (c << hshift) ^ ent; // xor hashing 174 | 175 | if (htab[i] == fcode) { 176 | ent = codetab[i]; 177 | continue; 178 | } else if (htab[i] >= 0) // non-empty slot 179 | { 180 | disp = hsize_reg - i; // secondary hash (after G. Knott) 181 | if (i == 0) 182 | disp = 1; 183 | do { 184 | if ((i -= disp) < 0) 185 | i += hsize_reg; 186 | 187 | if (htab[i] == fcode) { 188 | ent = codetab[i]; 189 | continue outer_loop; 190 | } 191 | } while (htab[i] >= 0); 192 | } 193 | output(ent, outs); 194 | ent = c; 195 | if (free_ent < maxmaxcode) { 196 | codetab[i] = free_ent++; // code -> hashtable 197 | htab[i] = fcode; 198 | } else 199 | cl_block(outs); 200 | } 201 | // Put out the final code. 202 | output(ent, outs); 203 | output(EOFCode, outs); 204 | } 205 | 206 | // ---------------------------------------------------------------------------- 207 | void encode(OutputStream os) throws IOException { 208 | os.write(initCodeSize); // write "initial code size" byte 209 | 210 | remaining = imgW * imgH; // reset navigation variables 211 | curPixel = 0; 212 | 213 | compress(initCodeSize + 1, os); // compress and write the pixel data 214 | 215 | os.write(0); // write block terminator 216 | } 217 | 218 | // Flush the packet to disk, and reset the accumulator 219 | void flush_char(OutputStream outs) throws IOException { 220 | if (a_count > 0) { 221 | outs.write(a_count); 222 | outs.write(accum, 0, a_count); 223 | a_count = 0; 224 | } 225 | } 226 | 227 | final int MAXCODE(int n_bits) { 228 | return (1 << n_bits) - 1; 229 | } 230 | 231 | // ---------------------------------------------------------------------------- 232 | // Return the next pixel from the image 233 | // ---------------------------------------------------------------------------- 234 | private int nextPixel() { 235 | if (remaining == 0) 236 | return EOF; 237 | 238 | --remaining; 239 | 240 | byte pix = pixAry[curPixel++]; 241 | 242 | return pix & 0xff; 243 | } 244 | 245 | void output(int code, OutputStream outs) throws IOException { 246 | cur_accum &= masks[cur_bits]; 247 | 248 | if (cur_bits > 0) 249 | cur_accum |= (code << cur_bits); 250 | else 251 | cur_accum = code; 252 | 253 | cur_bits += n_bits; 254 | 255 | while (cur_bits >= 8) { 256 | char_out((byte) (cur_accum & 0xff), outs); 257 | cur_accum >>= 8; 258 | cur_bits -= 8; 259 | } 260 | 261 | // If the next entry is going to be too big for the code size, 262 | // then increase it, if possible. 263 | if (free_ent > maxcode || clear_flg) { 264 | if (clear_flg) { 265 | maxcode = MAXCODE(n_bits = g_init_bits); 266 | clear_flg = false; 267 | } else { 268 | ++n_bits; 269 | if (n_bits == maxbits) 270 | maxcode = maxmaxcode; 271 | else 272 | maxcode = MAXCODE(n_bits); 273 | } 274 | } 275 | 276 | if (code == EOFCode) { 277 | // At EOF, write the rest of the buffer. 278 | while (cur_bits > 0) { 279 | char_out((byte) (cur_accum & 0xff), outs); 280 | cur_accum >>= 8; 281 | cur_bits -= 8; 282 | } 283 | 284 | flush_char(outs); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/gifencoder/NeuQuant.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.gifencoder; 2 | 3 | /* NeuQuant Neural-Net Quantization Algorithm 4 | * ------------------------------------------ 5 | * 6 | * Copyright (c) 1994 Anthony Dekker 7 | * 8 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. 9 | * See "Kohonen neural networks for optimal colour quantization" 10 | * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. 11 | * for a discussion of the algorithm. 12 | * 13 | * Any party obtaining a copy of these files from the author, directly or 14 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable, 15 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal 16 | * in this software and documentation files (the "Software"), including without 17 | * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 18 | * and/or sell copies of the Software, and to permit persons who receive 19 | * copies from any such party to do so, with the only requirement being 20 | * that this copyright notice remain intact. 21 | */ 22 | 23 | // Ported to Java 12/00 K Weiner 24 | 25 | public class NeuQuant { 26 | 27 | protected static final int netsize = 256; /* number of colours used */ 28 | 29 | /* four primes near 500 - assume no image has a length so large */ 30 | /* that it is divisible by all four primes */ 31 | protected static final int prime1 = 499; 32 | protected static final int prime2 = 491; 33 | protected static final int prime3 = 487; 34 | protected static final int prime4 = 503; 35 | 36 | protected static final int minpicturebytes = (3 * prime4); 37 | /* minimum size for input image */ 38 | 39 | /* 40 | * Program Skeleton ---------------- [select samplefac in range 1..30] [read 41 | * image from input file] pic = (unsigned char*) malloc(3*width*height); 42 | * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write 43 | * output image header, using writecolourmap(f)] inxbuild(); write output 44 | * image using inxsearch(b,g,r) 45 | */ 46 | 47 | /* 48 | * Network Definitions ------------------- 49 | */ 50 | 51 | protected static final int maxnetpos = (netsize - 1); 52 | protected static final int netbiasshift = 4; /* bias for colour values */ 53 | protected static final int ncycles = 100; /* no. of learning cycles */ 54 | 55 | /* defs for freq and bias */ 56 | protected static final int intbiasshift = 16; /* bias for fractions */ 57 | protected static final int intbias = (((int) 1) << intbiasshift); 58 | protected static final int gammashift = 10; /* gamma = 1024 */ 59 | protected static final int gamma = (((int) 1) << gammashift); 60 | protected static final int betashift = 10; 61 | protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */ 62 | protected static final int betagamma = (intbias << (gammashift - betashift)); 63 | 64 | /* defs for decreasing radius factor */ 65 | protected static final int initrad = (netsize >> 3); /* 66 | * for 256 cols, radius 67 | * starts 68 | */ 69 | protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ 70 | protected static final int radiusbias = (((int) 1) << radiusbiasshift); 71 | protected static final int initradius = (initrad * radiusbias); /* 72 | * and 73 | * decreases 74 | * by a 75 | */ 76 | protected static final int radiusdec = 30; /* factor of 1/30 each cycle */ 77 | 78 | /* defs for decreasing alpha factor */ 79 | protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */ 80 | protected static final int initalpha = (((int) 1) << alphabiasshift); 81 | 82 | protected int alphadec; /* biased by 10 bits */ 83 | 84 | /* radbias and alpharadbias used for radpower calculation */ 85 | protected static final int radbiasshift = 8; 86 | protected static final int radbias = (((int) 1) << radbiasshift); 87 | protected static final int alpharadbshift = (alphabiasshift + radbiasshift); 88 | protected static final int alpharadbias = (((int) 1) << alpharadbshift); 89 | 90 | /* 91 | * Types and Global Variables -------------------------- 92 | */ 93 | 94 | protected byte[] thepicture; /* the input image itself */ 95 | protected int lengthcount; /* lengthcount = H*W*3 */ 96 | 97 | protected int samplefac; /* sampling factor 1..30 */ 98 | 99 | // typedef int pixel[4]; /* BGRc */ 100 | protected int[][] network; /* the network itself - [netsize][4] */ 101 | 102 | protected int[] netindex = new int[256]; 103 | /* for network lookup - really 256 */ 104 | 105 | protected int[] bias = new int[netsize]; 106 | /* bias and freq arrays for learning */ 107 | protected int[] freq = new int[netsize]; 108 | protected int[] radpower = new int[initrad]; 109 | 110 | /* radpower for precomputation */ 111 | 112 | /* 113 | * Initialise network in range (0,0,0) to (255,255,255) and set parameters 114 | * ----------------------------------------------------------------------- 115 | */ 116 | public NeuQuant(byte[] thepic, int len, int sample) { 117 | 118 | int i; 119 | int[] p; 120 | 121 | thepicture = thepic; 122 | lengthcount = len; 123 | samplefac = sample; 124 | 125 | network = new int[netsize][]; 126 | for (i = 0; i < netsize; i++) { 127 | network[i] = new int[4]; 128 | p = network[i]; 129 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; 130 | freq[i] = intbias / netsize; /* 1/netsize */ 131 | bias[i] = 0; 132 | } 133 | } 134 | 135 | public byte[] colorMap() { 136 | byte[] map = new byte[3 * netsize]; 137 | int[] index = new int[netsize]; 138 | for (int i = 0; i < netsize; i++) 139 | index[network[i][3]] = i; 140 | int k = 0; 141 | for (int i = 0; i < netsize; i++) { 142 | int j = index[i]; 143 | map[k++] = (byte) (network[j][0]); 144 | map[k++] = (byte) (network[j][1]); 145 | map[k++] = (byte) (network[j][2]); 146 | } 147 | return map; 148 | } 149 | 150 | /* 151 | * Insertion sort of network and building of netindex[0..255] (to do after 152 | * unbias) 153 | * ------------------------------------------------------------------ 154 | * ------------- 155 | */ 156 | public void inxbuild() { 157 | 158 | int i, j, smallpos, smallval; 159 | int[] p; 160 | int[] q; 161 | int previouscol, startpos; 162 | 163 | previouscol = 0; 164 | startpos = 0; 165 | for (i = 0; i < netsize; i++) { 166 | p = network[i]; 167 | smallpos = i; 168 | smallval = p[1]; /* index on g */ 169 | /* find smallest in i..netsize-1 */ 170 | for (j = i + 1; j < netsize; j++) { 171 | q = network[j]; 172 | if (q[1] < smallval) { /* index on g */ 173 | smallpos = j; 174 | smallval = q[1]; /* index on g */ 175 | } 176 | } 177 | q = network[smallpos]; 178 | /* swap p (i) and q (smallpos) entries */ 179 | if (i != smallpos) { 180 | j = q[0]; 181 | q[0] = p[0]; 182 | p[0] = j; 183 | j = q[1]; 184 | q[1] = p[1]; 185 | p[1] = j; 186 | j = q[2]; 187 | q[2] = p[2]; 188 | p[2] = j; 189 | j = q[3]; 190 | q[3] = p[3]; 191 | p[3] = j; 192 | } 193 | /* smallval entry is now in position i */ 194 | if (smallval != previouscol) { 195 | netindex[previouscol] = (startpos + i) >> 1; 196 | for (j = previouscol + 1; j < smallval; j++) 197 | netindex[j] = i; 198 | previouscol = smallval; 199 | startpos = i; 200 | } 201 | } 202 | netindex[previouscol] = (startpos + maxnetpos) >> 1; 203 | for (j = previouscol + 1; j < 256; j++) 204 | netindex[j] = maxnetpos; /* really 256 */ 205 | } 206 | 207 | /* 208 | * Main Learning Loop ------------------ 209 | */ 210 | public void learn() { 211 | 212 | int i, j, b, g, r; 213 | int radius, rad, alpha, step, delta, samplepixels; 214 | byte[] p; 215 | int pix, lim; 216 | 217 | if (lengthcount < minpicturebytes) 218 | samplefac = 1; 219 | alphadec = 30 + ((samplefac - 1) / 3); 220 | p = thepicture; 221 | pix = 0; 222 | lim = lengthcount; 223 | samplepixels = lengthcount / (3 * samplefac); 224 | delta = samplepixels / ncycles; 225 | alpha = initalpha; 226 | radius = initradius; 227 | 228 | rad = radius >> radiusbiasshift; 229 | if (rad <= 1) 230 | rad = 0; 231 | for (i = 0; i < rad; i++) 232 | radpower[i] = alpha 233 | * (((rad * rad - i * i) * radbias) / (rad * rad)); 234 | 235 | // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); 236 | 237 | if (lengthcount < minpicturebytes) 238 | step = 3; 239 | else if ((lengthcount % prime1) != 0) 240 | step = 3 * prime1; 241 | else { 242 | if ((lengthcount % prime2) != 0) 243 | step = 3 * prime2; 244 | else { 245 | if ((lengthcount % prime3) != 0) 246 | step = 3 * prime3; 247 | else 248 | step = 3 * prime4; 249 | } 250 | } 251 | 252 | i = 0; 253 | while (i < samplepixels) { 254 | b = (p[pix + 0] & 0xff) << netbiasshift; 255 | g = (p[pix + 1] & 0xff) << netbiasshift; 256 | r = (p[pix + 2] & 0xff) << netbiasshift; 257 | j = contest(b, g, r); 258 | 259 | altersingle(alpha, j, b, g, r); 260 | if (rad != 0) 261 | alterneigh(rad, j, b, g, r); /* alter neighbours */ 262 | 263 | pix += step; 264 | if (pix >= lim) 265 | pix -= lengthcount; 266 | 267 | i++; 268 | if (delta == 0) 269 | delta = 1; 270 | if (i % delta == 0) { 271 | alpha -= alpha / alphadec; 272 | radius -= radius / radiusdec; 273 | rad = radius >> radiusbiasshift; 274 | if (rad <= 1) 275 | rad = 0; 276 | for (j = 0; j < rad; j++) 277 | radpower[j] = alpha 278 | * (((rad * rad - j * j) * radbias) / (rad * rad)); 279 | } 280 | } 281 | // fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha); 282 | } 283 | 284 | /* 285 | * Search for BGR values 0..255 (after net is unbiased) and return colour 286 | * index 287 | * -------------------------------------------------------------------- 288 | * -------- 289 | */ 290 | public int map(int b, int g, int r) { 291 | 292 | int i, j, dist, a, bestd; 293 | int[] p; 294 | int best; 295 | 296 | bestd = 1000; /* biggest possible dist is 256*3 */ 297 | best = -1; 298 | i = netindex[g]; /* index on g */ 299 | j = i - 1; /* start at netindex[g] and work outwards */ 300 | 301 | while ((i < netsize) || (j >= 0)) { 302 | if (i < netsize) { 303 | p = network[i]; 304 | dist = p[1] - g; /* inx key */ 305 | if (dist >= bestd) 306 | i = netsize; /* stop iter */ 307 | else { 308 | i++; 309 | if (dist < 0) 310 | dist = -dist; 311 | a = p[0] - b; 312 | if (a < 0) 313 | a = -a; 314 | dist += a; 315 | if (dist < bestd) { 316 | a = p[2] - r; 317 | if (a < 0) 318 | a = -a; 319 | dist += a; 320 | if (dist < bestd) { 321 | bestd = dist; 322 | best = p[3]; 323 | } 324 | } 325 | } 326 | } 327 | if (j >= 0) { 328 | p = network[j]; 329 | dist = g - p[1]; /* inx key - reverse dif */ 330 | if (dist >= bestd) 331 | j = -1; /* stop iter */ 332 | else { 333 | j--; 334 | if (dist < 0) 335 | dist = -dist; 336 | a = p[0] - b; 337 | if (a < 0) 338 | a = -a; 339 | dist += a; 340 | if (dist < bestd) { 341 | a = p[2] - r; 342 | if (a < 0) 343 | a = -a; 344 | dist += a; 345 | if (dist < bestd) { 346 | bestd = dist; 347 | best = p[3]; 348 | } 349 | } 350 | } 351 | } 352 | } 353 | return (best); 354 | } 355 | 356 | public byte[] process() { 357 | learn(); 358 | unbiasnet(); 359 | inxbuild(); 360 | return colorMap(); 361 | } 362 | 363 | /* 364 | * Unbias network to give byte values 0..255 and record position i to 365 | * prepare for sort 366 | * ---------------------------------------------------------- 367 | * ------------------------- 368 | */ 369 | public void unbiasnet() { 370 | 371 | int i; 372 | 373 | for (i = 0; i < netsize; i++) { 374 | network[i][0] >>= netbiasshift; 375 | network[i][1] >>= netbiasshift; 376 | network[i][2] >>= netbiasshift; 377 | network[i][3] = i; /* record colour no */ 378 | } 379 | } 380 | 381 | /* 382 | * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in 383 | * radpower[|i-j|] 384 | * ---------------------------------------------------------- 385 | * ----------------------- 386 | */ 387 | protected void alterneigh(int rad, int i, int b, int g, int r) { 388 | 389 | int j, k, lo, hi, a, m; 390 | int[] p; 391 | 392 | lo = i - rad; 393 | if (lo < -1) 394 | lo = -1; 395 | hi = i + rad; 396 | if (hi > netsize) 397 | hi = netsize; 398 | 399 | j = i + 1; 400 | k = i - 1; 401 | m = 1; 402 | while ((j < hi) || (k > lo)) { 403 | a = radpower[m++]; 404 | if (j < hi) { 405 | p = network[j++]; 406 | try { 407 | p[0] -= (a * (p[0] - b)) / alpharadbias; 408 | p[1] -= (a * (p[1] - g)) / alpharadbias; 409 | p[2] -= (a * (p[2] - r)) / alpharadbias; 410 | } catch (Exception e) { 411 | } // prevents 1.3 miscompilation 412 | } 413 | if (k > lo) { 414 | p = network[k--]; 415 | try { 416 | p[0] -= (a * (p[0] - b)) / alpharadbias; 417 | p[1] -= (a * (p[1] - g)) / alpharadbias; 418 | p[2] -= (a * (p[2] - r)) / alpharadbias; 419 | } catch (Exception e) { 420 | } 421 | } 422 | } 423 | } 424 | 425 | /* 426 | * Move neuron i towards biased (b,g,r) by factor alpha 427 | * ---------------------------------------------------- 428 | */ 429 | protected void altersingle(int alpha, int i, int b, int g, int r) { 430 | 431 | /* alter hit neuron */ 432 | int[] n = network[i]; 433 | n[0] -= (alpha * (n[0] - b)) / initalpha; 434 | n[1] -= (alpha * (n[1] - g)) / initalpha; 435 | n[2] -= (alpha * (n[2] - r)) / initalpha; 436 | } 437 | 438 | /* 439 | * Search for biased BGR values ---------------------------- 440 | */ 441 | protected int contest(int b, int g, int r) { 442 | 443 | /* finds closest neuron (min dist) and updates freq */ 444 | /* finds best neuron (min dist-bias) and returns position */ 445 | /* 446 | * for frequently chosen neurons, freq[i] is high and bias[i] is 447 | * negative 448 | */ 449 | /* bias[i] = gamma*((1/netsize)-freq[i]) */ 450 | 451 | int i, dist, a, biasdist, betafreq; 452 | int bestpos, bestbiaspos, bestd, bestbiasd; 453 | int[] n; 454 | 455 | bestd = ~(((int) 1) << 31); 456 | bestbiasd = bestd; 457 | bestpos = -1; 458 | bestbiaspos = bestpos; 459 | 460 | for (i = 0; i < netsize; i++) { 461 | n = network[i]; 462 | dist = n[0] - b; 463 | if (dist < 0) 464 | dist = -dist; 465 | a = n[1] - g; 466 | if (a < 0) 467 | a = -a; 468 | dist += a; 469 | a = n[2] - r; 470 | if (a < 0) 471 | a = -a; 472 | dist += a; 473 | if (dist < bestd) { 474 | bestd = dist; 475 | bestpos = i; 476 | } 477 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); 478 | if (biasdist < bestbiasd) { 479 | bestbiasd = biasdist; 480 | bestbiaspos = i; 481 | } 482 | betafreq = (freq[i] >> betashift); 483 | freq[i] -= betafreq; 484 | bias[i] += (betafreq << gammashift); 485 | } 486 | freq[bestpos] += beta; 487 | bias[bestpos] -= betagamma; 488 | return (bestbiaspos); 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /src/com/github/botaruibo/xvcode/main/APP.java: -------------------------------------------------------------------------------- 1 | package com.github.botaruibo.xvcode.main; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import com.github.botaruibo.xvcode.generator.Generator; 12 | import com.github.botaruibo.xvcode.generator.Gif2VCGenerator; 13 | import com.github.botaruibo.xvcode.generator.Gif3VCGenerator; 14 | import com.github.botaruibo.xvcode.generator.GifVCGenerator; 15 | import com.github.botaruibo.xvcode.generator.PngVCGenerator; 16 | 17 | /**just a common dynamic validation code image generator 18 | * provide 1 png generator and 3 gif generators 19 | * @author brui 20 | * 21 | */ 22 | public class APP { 23 | /**run on console to generate the images.use: 24 | * java -jar xvcode.jar -p "/test" -h 50 -w 120 -cl 5 25 | * @param args 26 | * @throws FileNotFoundException 27 | * @throws IOException 28 | */ 29 | public static void main(String[] args) throws FileNotFoundException, IOException { 30 | 31 | if (args.length > 0 && (args.length % 2) != 0) { 32 | System.err.println("parameter error"); 33 | return; 34 | } 35 | ArrayList cmd = new ArrayList(); 36 | cmd.add("-p"); 37 | cmd.add("-h"); 38 | cmd.add("-w"); 39 | cmd.add("-cl"); 40 | String path = "."; 41 | Integer height = 40; 42 | Integer width = 200; 43 | Integer count = 5; 44 | if (args.length > 0 && (args.length % 2) == 0) { 45 | HashMap map = new HashMap(); 46 | for (int i = 0; i < args.length; i += 2) { 47 | if (cmd.contains(args[i])){ 48 | map.put(args[i], args[i+1]); 49 | } 50 | } 51 | Matcher m = null; 52 | if (map.get("-p") != null) { 53 | Pattern p = Pattern.compile("^[A-z]:\\\\(.+?\\\\)*$"); 54 | m = p.matcher(map.get("-p").toString()); 55 | if (!m.matches()) { 56 | System.err.println("-p format error"); 57 | return; 58 | }; 59 | } 60 | if (map.get("-w") != null) { 61 | Pattern p = Pattern.compile("^[0-9]*$"); 62 | m = p.matcher(map.get("-w").toString()); 63 | if (!m.matches() || Integer.valueOf(map.get("-w").toString()) < 50 || Integer.valueOf(map.get("-w").toString()) > 500) { 64 | System.err.println("-w must be number, and big than 10 and litter than 500"); 65 | return; 66 | }; 67 | } 68 | if (map.get("-h") != null) { 69 | Pattern p = Pattern.compile("^[0-9]*$"); 70 | m = p.matcher(map.get("-h").toString()); 71 | if (!m.matches() || Integer.valueOf(map.get("-h").toString()) < 30 || Integer.valueOf(map.get("-h").toString()) > 300) { 72 | System.err.println("-h must be number, and big than 10 and litter than 500"); 73 | return; 74 | }; 75 | } 76 | if (map.get("-cl") != null) { 77 | Pattern p = Pattern.compile("^[0-9]*$"); 78 | m = p.matcher(map.get("-cl").toString()); 79 | if (!m.matches() || Integer.valueOf(map.get("-cl").toString()) > 10 || Integer.valueOf(map.get("-cl").toString()) < 2) { 80 | System.err.println("-cl must be number, and bigger than 1 and no more than 10"); 81 | return; 82 | }; 83 | } 84 | if (map.get("-p") != null) { 85 | path = map.get("-p"); 86 | } 87 | if (map.get("-w") != null) { 88 | width = Integer.valueOf(map.get("-w")); 89 | } 90 | if (map.get("-h") != null) { 91 | height = Integer.valueOf(map.get("-h")); 92 | } 93 | if (map.get("-cl") != null) { 94 | count = Integer.valueOf(map.get("-cl")); 95 | } 96 | }; 97 | 98 | 99 | Generator generator = new PngVCGenerator(width, height, count); 100 | generator.write2out(new FileOutputStream(path + "/1.png")).close(); 101 | System.out.println(generator.text()); 102 | generator = new GifVCGenerator(width, height, count);// gif 103 | generator.write2out(new FileOutputStream(path + "/1.gif")).close(); 104 | System.out.println(generator.text()); 105 | generator = new Gif2VCGenerator(width, height, count);// gif 106 | generator.write2out(new FileOutputStream(path + "/2.gif")).close(); 107 | System.out.println(generator.text()); 108 | generator = new Gif3VCGenerator(width, height, count);// gif 109 | generator.write2out(new FileOutputStream(path + "/3.gif")).close(); 110 | System.out.println(generator.text()); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /xvcode-1.0-SNAPSHOT-cl.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botaruibo/xvcode/bbcf414f4fb918a014b7de51091bd7da43c8e272/xvcode-1.0-SNAPSHOT-cl.jar --------------------------------------------------------------------------------