├── .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 | 
27 |
28 | GifVCGenerator:
29 | 
30 |
31 | GifVCGenerator2:
32 | 
33 |
34 | GifVCGenerator3:
35 | 
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 | 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