Java實(shí)現(xiàn)簡(jiǎn)單文字驗(yàn)證碼以及人機(jī)驗(yàn)證
一、代碼引用
首先,如果你想直接用,可以直接用下面這個(gè)類。
可以調(diào)用CaptchaGenerator類中的captchaCreateImage方法,其方法參數(shù)列表為(int width, int height, int captchaLength, String[] returnCaptcha, int degree),方法返回驗(yàn)證碼圖像。
width -----------------------文字驗(yàn)證碼圖片的寬度
height-----------------------文字驗(yàn)證碼圖片的高度
captchaLength-----------文字驗(yàn)證碼的長(zhǎng)度
returnCaptcha-----------返回的文字驗(yàn)證碼
degree---------------------干擾的程度(1-5,不在范圍默認(rèn)為5)
程度展示
一 | ![]() |
二 | ![]() |
三 | ![]() |
四 | ![]() |
五 | ![]() |
代碼引用處
import java.awt.*; // 導(dǎo)入 AWT 圖形庫(kù) import java.awt.geom.AffineTransform; // 導(dǎo)入用于執(zhí)行幾何變換的類 import java.awt.image.BufferedImage; // 導(dǎo)入用于處理圖像的類 import java.util.Random; // 導(dǎo)入隨機(jī)數(shù)生成器類 import java.util.concurrent.ThreadLocalRandom; public class CaptchaGenerator { // 創(chuàng)建隨機(jī)數(shù)生成器 private final Random random = ThreadLocalRandom.current(); // 創(chuàng)建驗(yàn)證碼的方法 private String createCaptcha(int length) { // 定義可選字符池(不包含容易混淆的字符0和O等) String charPool = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789"; StringBuilder result = new StringBuilder(); // 用于構(gòu)建驗(yàn)證碼字符串 // 隨機(jī)選擇字符生成驗(yàn)證碼 for (int i = 0; i < length; i++) { result.append(charPool.charAt(random.nextInt(charPool.length()))); // 從字符池中隨機(jī)選擇字符 } return result.toString(); // 返回生成的驗(yàn)證碼字符串 } // 創(chuàng)建顏色的方法,生成指定范圍內(nèi)隨機(jī)顏色 private Color createColor(int min, int max) { int r = min + random.nextInt(max - min+1); // 隨機(jī)生成紅色分量 int g = min + random.nextInt(max - min+1); // 隨機(jī)生成綠色分量 int b = min + random.nextInt(max - min+1); // 隨機(jī)生成藍(lán)色分量 return new Color(r, g, b); // 創(chuàng)建并返回顏色對(duì)象 } // 添加干擾元素的方法 private void addInterference(Graphics2D g, int degree, int width, int height) { // 確保干擾元素的數(shù)量在0到5之間 degree = (degree <= 0 || degree > 5) ? 5 : degree; // 根據(jù)度數(shù)生成干擾元素 for (int i = 0; i < degree * 20; i++) { int x = random.nextInt(width); // 隨機(jī)生成x坐標(biāo) int y = random.nextInt(height); // 隨機(jī)生成y坐標(biāo) // 隨機(jī)選擇干擾元素的顏色 Color color = (random.nextBoolean()) ? createColor(0, 255) : ((random.nextBoolean()) ? Color.WHITE : Color.BLACK); g.setColor(color); // 設(shè)置畫(huà)筆顏色 // 隨機(jī)選擇干擾元素的類型并畫(huà)出 switch (random.nextInt(3)) { case 0 -> g.fillOval(x, y, random.nextInt(3) + 1, random.nextInt(3) + 1); // 畫(huà)圓點(diǎn) case 1 -> { int change = random.nextInt(3); // 隨機(jī)變化值 // 畫(huà)出線條構(gòu)成的隨機(jī)圖形 g.drawLine(x, y, x + change, y + change); g.drawLine(x + change, y + change, x + 2 * change, y); g.drawLine(x, y, x + change, y - change); g.drawLine(x + change, y - change, x + 2 * change, y); } case 2 -> g.drawLine(x, y, x + random.nextInt(5) + 1, y + random.nextInt(5) + 1); // 畫(huà)線 } } // 生成更多隨機(jī)干擾線 for (int i = 0; i < 5 * degree; i++) { Color color = (random.nextBoolean()) ? createColor(0, 255) : ((random.nextBoolean()) ? Color.WHITE : Color.BLACK); g.setColor(color); // 隨機(jī)生成干擾線的起止點(diǎn) g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height)); } } // 創(chuàng)建驗(yàn)證碼圖像的方法 private BufferedImage createImage(int width, int height, int captchaLength, String[] returnCaptcha, int degree) { // 創(chuàng)建新圖像 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); // 獲取圖形上下文 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 啟用抗鋸齒 // 創(chuàng)建背景色 Color backgroundColor = createColor(0, 255); g.setColor(backgroundColor); g.fillRect(0, 0, width, height); // 填充背景 // 生成驗(yàn)證碼 String captcha = createCaptcha(captchaLength); returnCaptcha[0] = captcha; // 將生成的驗(yàn)證碼存入數(shù)組 // 設(shè)置字體大小和干擾詳細(xì)參數(shù) int fontSize = (int) (height * 0.5); AffineTransform at = new AffineTransform(); // 創(chuàng)建變換對(duì)象 at.shear(random.nextDouble() * 0.4 - 0.2, random.nextDouble() * 0.4 - 0.2); // 隨機(jī)傾斜變換 Font font = new Font(Font.SANS_SERIF, Font.BOLD, fontSize); // 創(chuàng)建字體對(duì)象 font = font.deriveFont(at); // 生成傾斜字體 addInterference(g, degree, width, height); // 添加干擾元素 Color fontColor, prevFontColor = null; // 字體顏色和前一個(gè)字體顏色 int fontX, fontY, fontWidth, changeX; // 不同的坐標(biāo)和寬度 FontMetrics fontMetrics = g.getFontMetrics(); // 字體度量 fontWidth = fontMetrics.stringWidth(captcha) + (captchaLength - 1) * (int) (width * 0.05); // 計(jì)算驗(yàn)證碼的寬度 fontX = (width - fontWidth) / 2; // 計(jì)算x坐標(biāo)以居中對(duì)齊 fontY = (height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent(); // 計(jì)算y坐標(biāo) double tempX = fontX; // 保存當(dāng)前x坐標(biāo) // 逐個(gè)繪制驗(yàn)證碼字符 for (int i = 0; i < captchaLength; i++) { // 根據(jù)背景色的亮度生成對(duì)比度較強(qiáng)的字體顏色 fontColor = (backgroundColor.getRed() > 180) ? createColor(0, 160) : createColor(200, 255); int maxAttempts = 10,count=0;//設(shè)置最大循環(huán)數(shù),避免死循環(huán) // 確保字體顏色與前一個(gè)字體顏色不相近 while (prevFontColor != null&&count++<maxAttempts) { double brightness = (backgroundColor.getRed() * 299 + backgroundColor.getBlue() * 114 + backgroundColor.getGreen() * 587) / 1000.0; fontColor = (brightness > 128) ? createColor(0, 128) : createColor(128, 255); // 確定字體顏色 int dr = fontColor.getBlue() - prevFontColor.getBlue(); int dg = fontColor.getGreen() - prevFontColor.getGreen(); int db = fontColor.getRed() - prevFontColor.getRed(); prevFontColor = fontColor; // 更新前一個(gè)顏色 // 如果顏色差異大于亮度則退出循環(huán) if (Math.sqrt(dr * dr + dg * dg + db * db) > brightness) break; } prevFontColor = fontColor; // 更新前一個(gè)顏色 g.setFont(font); // 設(shè)置當(dāng)前字體 g.setColor(fontColor); // 設(shè)置當(dāng)前字體顏色 // 隨機(jī)旋轉(zhuǎn)角度 int RotationAngle = random.nextInt(60) - 30; g.rotate(Math.toRadians(RotationAngle), tempX, fontY); // 繞中心點(diǎn)旋轉(zhuǎn) // 繪制字符 g.drawString(String.valueOf(captcha.charAt(i)), (int) tempX, fontY); g.rotate(-Math.toRadians(RotationAngle), tempX, fontY); // 逆旋轉(zhuǎn)恢復(fù)狀態(tài) changeX = fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); // 獲取當(dāng)前字符寬度 tempX += (changeX + (int) (width * 0.05)); // 更新臨時(shí)x坐標(biāo),為下一個(gè)字符準(zhǔn)備空間 } g.dispose(); // 釋放圖形上下文資源 if(captchaLength <= 0|| returnCaptcha[0].isEmpty()) { throw new IllegalArgumentException("returnCaptcha array must be non-null and have at least one element"); } return image; // 返回生成的圖像 } public BufferedImage captchaCreateImage(int width, int height, int captchaLength, String[] returnCaptcha, int degree){ return createImage(width, height, captchaLength, returnCaptcha, degree); } }
二、代碼實(shí)現(xiàn)
1.生成驗(yàn)證碼
由于0和O等字符容易混淆,因此需要去除這些字符。
private String createCaptcha(int length){ String charPool = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789"; StringBuilder result = new StringBuilder(); for (int i = 0; i < length; i++) { result.append(charPool.charAt(random.nextInt(charPool.length()))); } return result.toString(); }
2.生成圖片前的準(zhǔn)備
(1)創(chuàng)建一個(gè)指定寬度、高度和類型的BufferedImage對(duì)象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
(2)獲取Graphics2D對(duì)象,用于繪制圖像,并啟用抗鋸齒
Graphics2D g = image.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
(3)填充背景顏色
Color backgroundColor = createColor(0, 255); // 隨機(jī)生成背景顏色 g.setColor(backgroundColor); // 設(shè)置背景顏色 g.fillRect(0, 0, width, height); // 填充背景色
(4)獲取驗(yàn)證碼
String captcha = createCaptcha(captchaLength); returnCaptcha[0]=captcha;
3.圖片添加干擾
總體預(yù)覽
private void addInterference(Graphics2D g,int degree,int width,int height){ degree=(degree <= 0||degree > 5)?5:degree; for (int i = 0; i < degree*20; i++) { int x=random.nextInt(width); int y=random.nextInt(height); Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK); g.setColor(color); switch (random.nextInt(3)){ case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1); case 1-> { int change=random.nextInt(3); g.drawLine(x,y,x+change,y+change); g.drawLine(x+change,y+change,x+2*change,y); g.drawLine(x,y,x+change,y-change); g.drawLine(x+change,y-change,x+2*change,y); } case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1); } } for(int i=0;i<5*degree;i++){ Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK); g.setColor(color); g.drawLine(random.nextInt(width),random.nextInt(height),random.nextInt(width),random.nextInt(height)); } }
(1)確保程度合法
degree=(degree <= 0||degree > 5)?5:degree;
(2)生成噪點(diǎn)
a.隨機(jī)坐標(biāo)
int x=random.nextInt(width); int y=random.nextInt(height);
b.顏色設(shè)置
要么彩色,要么黑白。
Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK); g.setColor(color);
c.噪點(diǎn)樣式選擇
總體預(yù)覽
switch (random.nextInt(3)){ case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1); case 1-> { int change=random.nextInt(3); g.drawLine(x,y,x+change,y+change); g.drawLine(x+change,y+change,x+2*change,y); g.drawLine(x,y,x+change,y-change); g.drawLine(x+change,y-change,x+2*change,y); } case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1); }
1.橢圓形
case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);
2.菱形
case 1-> { int change=random.nextInt(3); g.drawLine(x,y,x+change,y+change); g.drawLine(x+change,y+change,x+2*change,y); g.drawLine(x,y,x+change,y-change); g.drawLine(x+change,y-change,x+2*change,y); }
3.隨機(jī)短線段
case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);
4.驗(yàn)證碼內(nèi)容處理
(1)處理字體
總體預(yù)覽
int fontSize=(int)(height*0.5); AffineTransform at = new AffineTransform(); at.shear(random.nextDouble()*0.4-0.2,random.nextDouble()*0.4-0.2); Font font=new Font("微軟雅黑", Font.BOLD,fontSize); font=font.deriveFont(at); g.setFont(font);
a.設(shè)置字體大小
int fontSize=(int)(height*0.5);
b.創(chuàng)建AffineTransform對(duì)象,用于幾何變換,將字體扭曲,程度為[-0.2,0.2]。
AffineTransform at = new AffineTransform(); at.shear(random.nextDouble()*0.4-0.2,random.nextDouble()*0.4-0.2);
c.設(shè)置字體樣式,并應(yīng)用扭曲
Font font=new Font("微軟雅黑", Font.BOLD,fontSize); font=font.deriveFont(at);
(2)字符居中處理
總體預(yù)覽
int fontX,fontY,fontWidth,changeX; FontMetrics fontMetrics = g.getFontMetrics(); fontWidth=fontMetrics.stringWidth(captcha)+(captchaLength-1)*(int)(width*0.05); fontX=(width-fontWidth)/2; fontY=(height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent();
a.獲取橫坐標(biāo)
fontMetrics.stringWidth(captcha)是所有的字符的總寬度。
(captchaLength-1)*(int)(width*0.05)是字符之間的總間隔大小,其中(captchaLength-1)為間隔數(shù),(width*0.05)為間隔大小。
fontWidth=fontMetrics.stringWidth(captcha)+(captchaLength-1)*(int)(width*0.05); fontX=(width-fontWidth)/2;
b.獲取縱坐標(biāo)
FontMetrics fontMetrics = g.getFontMetrics(); fontY=(height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent();
fontMetrics.getAscent():基線到字體最高點(diǎn)的距離
fontMetrics.getDescent():基線到字體最低點(diǎn)的距離
怎么理解?
FontMetrics
類提供了關(guān)鍵參數(shù):
Ascent:基線到字體最高點(diǎn)的距離(如字母"h"的頂部)。
Descent:基線到字體最低點(diǎn)的距離(如字母"g"的尾部)。
Leading:行間距(通常較?。?。
Height:總高度(
Ascent + Descent + Leading
)。
一、將文字想象成一個(gè)“盒子”
假設(shè)每個(gè)字符是一個(gè)矩形盒子,其高度由三部分組成:
Ascent(上升) :基線(baseline)到盒子頂部的距離(如字母"h"的頂部)。
Descent(下降) :基線到盒子底部的距離(如字母"g"的尾部)。
Leading(行間距) :盒子下方預(yù)留的空白(通常很小,可暫時(shí)忽略)。
二、Java繪圖的“基線對(duì)齊”
當(dāng)調(diào)用 g.drawString(text, x, y)
時(shí):
參數(shù)
y
是基線的位置,而非文字區(qū)域的頂部或中心。如果直接將畫(huà)布中點(diǎn)(
height/2
)作為基線位置,文字會(huì)整體偏下,因?yàn)锳scent部分會(huì)向上延伸,而Descent部分會(huì)向下延伸。
畫(huà)布頂部(y=50) | | | | | —————————— ← Ascent(y=35) | | —————————— ← 基線(y=30) | | —————————— ← 畫(huà)布中心(y=25) | ▲ | | | ▼ | —————————— ← Descent(y=15) | | | | 畫(huà)布底部(y=0)
(3)字符顏色處理
總體預(yù)覽
fontColor=(backgroundColor.getRed()>180)?createColor(0,160):createColor(200,255); int maxAttempts = 10,count=0; while (prevFontColor!=null&&count++<maxAttempts) { double brightness=(backgroundColor.getRed()*299+backgroundColor.getBlue()*114+backgroundColor.getGreen()*587)/1000.0; fontColor=(brightness>128)?createColor(0,128):createColor(128,255); int dr=fontColor.getBlue()-prevFontColor.getBlue(); int dg=fontColor.getGreen()-prevFontColor.getGreen(); int db=fontColor.getRed()-prevFontColor.getRed(); prevFontColor=fontColor; if(Math.sqrt(dr*dr+dg*dg+db*db)>brightness)break; } prevFontColor=fontColor; g.setColor(fontColor);
a.隨機(jī)顏色
避免與背景顏色相近。
fontColor=(backgroundColor.getRed()>180)?createColor(0,160):createColor(200,255);
b.處理相鄰顏色相近與對(duì)比度不明顯問(wèn)題
總體預(yù)覽
while (prevFontColor!=null&&count++<maxAttempts) { double brightness=(backgroundColor.getRed()*299+backgroundColor.getBlue()*114+backgroundColor.getGreen()*587)/1000.0; fontColor=(brightness>128)?createColor(0,128):createColor(128,255); int dr=fontColor.getBlue()-prevFontColor.getBlue(); int dg=fontColor.getGreen()-prevFontColor.getGreen(); int db=fontColor.getRed()-prevFontColor.getRed(); prevFontColor=fontColor; if(Math.sqrt(dr*dr+dg*dg+db*db)>brightness)break; } prevFontColor=fontColor;
1.顏色差異檢測(cè)機(jī)制(歐氏距離)
顏色距離公式:
使用歐氏距離公式計(jì)算顏色差異:
\Delta = \sqrt{(R_1-R_2)^2 + (G_1-G_2)^2 + (B_1-B_2)^2}
該公式能綜合衡量RGB三個(gè)通道的差異,更符合人眼對(duì)顏色差異的感知
2.背景對(duì)比度優(yōu)化(YIQ模型)
YIQ亮度模型:
使用公式:Y = 0.299R + 0.587G + 0.114B
該模型更貼近人眼對(duì)亮度的敏感度(綠色權(quán)重最高,藍(lán)色最低)。
對(duì)比度標(biāo)準(zhǔn):
若背景亮度
Y > 128
(亮色背景),選擇深色字體(如黑色、深藍(lán))若
Y ≤ 128
(暗色背景),選擇淺色字體(如白色、亮黃)綜合效果
抗機(jī)器識(shí)別:
顏色差異和色相跳躍破壞OCR工具的顏色聚類算法,使其難以分割相鄰字符。
邊緣描邊干擾輪廓識(shí)別算法。
人類可讀性:
高對(duì)比度確保文字清晰,符合人眼對(duì)亮度、色相的敏感特性。
隨機(jī)旋轉(zhuǎn)和扭曲保留驗(yàn)證碼的防機(jī)器特性,但不會(huì)影響人類閱讀。
(4)字符旋轉(zhuǎn)處理
總體預(yù)覽
int RotationAngle=random.nextInt(60)-30; g.rotate(Math.toRadians(RotationAngle),tempX,fontY); g.drawString(String.valueOf(captcha.charAt(i)),(int)tempX,fontY); g.rotate(-Math.toRadians(RotationAngle),tempX,fontY); changeX=fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); tempX+=(changeX+(int)(width*0.05));
a.隨機(jī)角度[-30°,30°]
int RotationAngle=random.nextInt(60)-30;
b.旋轉(zhuǎn)字符
1.旋轉(zhuǎn)畫(huà)布
g.rotate(Math.toRadians(RotationAngle),tempX,fontY);
g.rotate(double theta, double x, double y)
方法用于旋轉(zhuǎn)當(dāng)前的Graphics
上下文,影響之后的繪圖操作。Math.toRadians(RotationAngle)
:將角度從度轉(zhuǎn)換為弧度,因?yàn)?Java 的rotate
方法需要以弧度為單位的旋轉(zhuǎn)角度。tempX
和fontY
:這里指定了旋轉(zhuǎn)中心的坐標(biāo)。tempX
:是文本的當(dāng)前 x 坐標(biāo),通常根據(jù)文本的長(zhǎng)度動(dòng)態(tài)計(jì)算,使得字符繪制在適當(dāng)?shù)奈恢谩?/p>fontY
:是文本的 y 坐標(biāo),通常是一個(gè)固定值,確保文本在畫(huà)布的某一高度。
通過(guò)這行代碼,當(dāng)前畫(huà)布將圍繞指定的 (tempX, fontY)
點(diǎn)旋轉(zhuǎn),旋轉(zhuǎn)角度為 RotationAngle
。
2.繪制字符
g.drawString(String.valueOf(captcha.charAt(i)),(int)tempX,fontY);
g.drawString(String str, int x, int y)
方法用于在畫(huà)布上繪制字符串。String.valueOf(captcha.charAt(i))
:從名為captcha
的字符串中獲取索引為i
的字符,并將其轉(zhuǎn)換為字符串(通常可以直接使用captcha.charAt(i)
)。(int)tempX
和fontY
:這是繪制文本的坐標(biāo)。tempX
:是在之前計(jì)算的 x 坐標(biāo),代表文本在水平方向上的位置。fontY
:是文本在垂直方向上的固定高度。
這行代碼則在旋轉(zhuǎn)后的畫(huà)布上繪制當(dāng)前字符。
3.恢復(fù)畫(huà)布的旋轉(zhuǎn)狀態(tài)
g.rotate(-Math.toRadians(RotationAngle),tempX,fontY);
這行代碼用于撤銷之前的旋轉(zhuǎn)操作。
-Math.toRadians(RotationAngle)
:使用負(fù)的角度進(jìn)行旋轉(zhuǎn),與之前的旋轉(zhuǎn)相反。tempX
和fontY
:依然是旋轉(zhuǎn)中心的坐標(biāo)。
這個(gè)步驟確保在繪制完當(dāng)前字符后,畫(huà)布的狀態(tài)返回到原始位置,方便后續(xù)繪制其他字符時(shí)不會(huì)受到影響。
c.繪制字符位置處理
changeX=fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); tempX+=(changeX+(int)(width*0.05));
1. 計(jì)算字符寬度
captcha.charAt(i)
:從captcha
字符串中獲取索引i
處的字符。這是當(dāng)前需要繪制的字符。String.valueOf(...)
:將獲取的字符轉(zhuǎn)換為字符串。雖然在 Java 中,char
類型可以直接用于字符串操作,但使用String.valueOf
方法能確保我們?cè)谛枰獣r(shí)轉(zhuǎn)換為字符串類型。fontMetrics.stringWidth(...)
:這個(gè)方法是FontMetrics
類中的一個(gè)方法,它可以返回指定字符串在當(dāng)前字體下的寬度(以像素為單位)。也就是說(shuō),它計(jì)算出當(dāng)前正在使用的字體繪制String.valueOf(captcha.charAt(i))
這一個(gè)字符所需要的寬度。changeX
:這是一個(gè)變量,用于存儲(chǔ)當(dāng)前字符的寬度。這將被用來(lái)調(diào)整下一個(gè)字符的繪制位置。
2.更新繪制位置
tempX
:這是一個(gè)變量,表示當(dāng)前字符在繪圖上下文中的 x 坐標(biāo)。它記錄了下一個(gè)字符應(yīng)該繪制的 horizontal (水平方向) 位置。changeX
:上面計(jì)算獲得的當(dāng)前字符寬度。(int)(width * 0.05)
:這部分代碼用于在字符之間添加一定的間距。在這里,width
是整張圖片的寬度,而width * 0.05
表示取寬度的 5%。通過(guò)將結(jié)果轉(zhuǎn)換為int
,確保我們將這個(gè)浮點(diǎn)值作為整數(shù)處理,適配繪圖 API 對(duì)坐標(biāo)的要求。+=
:這個(gè)操作符用于將當(dāng)前的tempX
值加上當(dāng)前字符的寬度和額外的間距,然后更新tempX
的值。這一步確保下一個(gè)字符繪制時(shí)不會(huì)重疊,而是位于上一個(gè)字符的右側(cè),并且留有一定的間隙。
總結(jié)
到此這篇關(guān)于Java實(shí)現(xiàn)簡(jiǎn)單文字驗(yàn)證碼以及人機(jī)驗(yàn)證的文章就介紹到這了,更多相關(guān)Java文字驗(yàn)證碼及人機(jī)驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot高級(jí)教程之使用Redis實(shí)現(xiàn)session共享
這篇文章主要為大家詳細(xì)介紹了Spring Boot高級(jí)教程之使用Redis實(shí)現(xiàn)session共享,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10基于Process#waitFor()阻塞問(wèn)題的解決
這篇文章主要介紹了Process#waitFor()阻塞問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java toString方法重寫(xiě)工具之ToStringBuilder案例詳解
這篇文章主要介紹了Java toString方法重寫(xiě)工具之ToStringBuilder案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08SpringCloud客戶端的負(fù)載均衡Ribbon的實(shí)現(xiàn)
微服務(wù)架構(gòu),不可避免的存在單個(gè)微服務(wù)有多個(gè)實(shí)例,這篇文章主要介紹了SpringCloud客戶端的負(fù)載均衡Ribbon的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06被kafka-client和springkafka版本坑到自閉及解決
這篇文章主要介紹了被kafka-client和springkafka版本坑到自閉及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Spring cloud Gateway簡(jiǎn)介及相關(guān)配置方法
這篇文章主要介紹了Spring cloud Gateway簡(jiǎn)介及相關(guān)配置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04Spring Boot實(shí)戰(zhàn)之發(fā)送郵件示例代碼
本篇文章主要介紹了Spring Boot實(shí)戰(zhàn)之發(fā)送郵件示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03Java實(shí)現(xiàn)List集合手動(dòng)分頁(yè)的方法
在工作中難免會(huì)遇到,將組裝的集合數(shù)據(jù)進(jìn)行分頁(yè)處理,本文主要介紹了Java實(shí)現(xiàn)List集合手動(dòng)分頁(yè)的方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03