前言
近期有个作业是需要自己绘制一个验证码,并布置到网页前端上。其中需要用到的类主要是BufferedImage、Graphics以及ImageIO,因为之前对于这3个类接触的并不多,所以就打算写一篇博客复习。
要求
- 验证码的字体一致
- 验证码的值包括[0-9a-zA-Z]
- 验证码的字体颜色、大小应该不一样
- 给验证码背部添加干扰元素
- 将验证码的值设置在session中,以便于验证
分析
- 通过BufferedImage类我们可以创建一个存在于内存的矩形框,方便我们对其自定义,选择具有8位像素的RGB颜色
BufferedImage image = new BufferedImage(Width, Height, BufferedImage.TYPE_INT_RGB);
- 创建了BufferedImage实例后,可以通过实例获取其对应的画笔,即Graphics类
Graphics graphics = image.getGraphics();
- 此后每次对画笔的操作,效果都会呈现在我们创建的矩形框中
// 为画笔设置新的颜色
graphics.setColor(new Color(bgr, bgg, bgb));
// 画笔绘画填充的整个范围
graphics.fillRect(0,0, Width,Height);
// 给验证码添加背景干扰
for (int i=0;i<20;i++){
int line_x = random.nextInt(400);
int line_y = random.nextInt(200);
int line_x1 = line_x + 100;
int line_y1 = line_y + 100;
graphics.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
graphics.drawOval(line_x,line_y, random.nextInt(100),random.nextInt(100));
// graphics.drawLine(line_x,line_y, line_x1, line_y1);
}
// 生成验证码的值,并将每个值的颜色大小设置成随机
for (int i=0;i<4;i++){
int font_r = random.nextInt(127)+128;
int font_g = random.nextInt(127)+128;
int font_b = random.nextInt(127)+128;
// 设置字体颜色
graphics.setColor(new Color(font_r, font_g, font_b));
int index = random.nextInt(61);
code.append(chars.charAt(index));
// 设置字体大小,样式
Font font = new Font("HGKY_CNKI",Font.PLAIN,random.nextInt(100)+50);
graphics.setFont(font);
graphics.drawString(String.valueOf(chars.charAt(index)), i*100,150);
}
- 最后通过ImageIO流,将绘制好的图片写出。
- 获取验证码的值,并设置在session中
VerifyImg类 完整代码
public class VerifyImg {
// 验证码大小
final int Width = 400;
final int Height = 200;
final String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 验证码的值
private StringBuffer code = null;
// 存放字节图片
private ByteArrayInputStream imgInputStream = null;
private VerifyImg(){
}
public static VerifyImg getInstance(){
return new VerifyImg();
}
public void Create(){
Random random = new Random();
code = new StringBuffer();
// 背景颜色的rgb,通过随机数决定颜色变化,实现每次刷新背景色都不一样
int bgr = random.nextInt(127);
int bgg = random.nextInt(127);
int bgb = random.nextInt(127);
BufferedImage image = new BufferedImage(Width, Height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = image.getGraphics();
// 给验证码添加背景色
graphics.setColor(new Color(bgr, bgg, bgb));
// 填充背景色的范围
graphics.fillRect(0,0, Width,Height);
// 给验证码添加背景干扰
for (int i=0;i<20;i++){
int line_x = random.nextInt(400);
int line_y = random.nextInt(200);
int line_x1 = line_x + 100;
int line_y1 = line_y + 100;
graphics.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
graphics.drawOval(line_x,line_y, random.nextInt(100),random.nextInt(100));
// graphics.drawLine(line_x,line_y, line_x1, line_y1);
}
// 生成验证码的值,并将每个值的颜色大小设置成随机
for (int i=0;i<4;i++){
int font_r = random.nextInt(127)+128;
int font_g = random.nextInt(127)+128;
int font_b = random.nextInt(127)+128;
graphics.setColor(new Color(font_r, font_g, font_b));
int index = random.nextInt(61);
code.append(chars.charAt(index));
Font font = new Font("HGKY_CNKI",Font.PLAIN,random.nextInt(100)+50);
graphics.setFont(font);
graphics.drawString(String.valueOf(chars.charAt(index)), i*100,150);
}
graphics.dispose();
ByteArrayInputStream input = null;
// 字节数组输出流
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
//创建image输出流
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(out);
//将image写到
ImageIO.write(image,"JPEG",imageOutputStream);
imageOutputStream.close();
input = new ByteArrayInputStream(out.toByteArray());
this.imgInputStream = input;
}catch (Exception e){
System.out.println("输出失败");
}
}
public StringBuffer getCode(){
return this.code;
}
public ByteArrayInputStream getImgInputStream(){
return this.imgInputStream;
}
}
难点
在实现过程中遇到的主要问题就是对于ImageIO的操作,在ImageIO中有一个write方法可以帮助我们将操作的bufferedimage类写出到指定位置
ImageIO.write(image,"JPEG",new File(path:)) // 表示将内存中的image写到本地磁盘的某个位置
可实现效果如下:
但是题目要求的是将生成的验证码写到浏览器上,显然我们还要换一种方式来实现,这里就需要了解一下ByteArrayInputStream,ByteArrayOutputStream。
- ByteArrayOutputStream是一个可以存放字节数组的输出流,缓冲区会随着数据的增长自动扩大,可以使用toByteArray和toString方法获取字节数组的值。
- ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含可以从流中读取的字节。 内部计数器跟踪由read方法提供的下一个字节。
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(out);
//在这里我把out的上面再次封装了一层image流,当我们用ImageIO.write()
//方法时,指定流向为imageOutputStream,然后因为out具有存放字节数组的属性,
//所以图片就可以成功写进了字节数组中。
input = new ByteArrayInputStream(out.toByteArray());
//这时候将字节数组的值取出,并创建input对象,就可以方便我们从流中读取字节。
//最后就能成功在浏览器上了
Servlet代码
@WebServlet(name = "VerifyCode", value = "/verify-code")
public class Verifycode extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
VerifyImg verifyImg = VerifyImg.getInstance();
verifyImg.Create();
ByteArrayInputStream imgInputStream = verifyImg.getImgInputStream();
StringBuffer verify_code = verifyImg.getCode();
req.getSession().setAttribute("Verify-Code",verify_code);
ServletOutputStream respOutputStream = resp.getOutputStream();
resp.setContentType("image/jpeg");
byte bytes[] = new byte[1024];
while (imgInputStream.read(bytes)!=-1){
respOutputStream.write(bytes);
}
}
}
后记
有理解错误的地方欢迎大家来指正。