In web development, this topic is very popular and implement it in a web project is so simple. But in mobile programming in general and Android in particular, implementing captcha is not easy.(a backronym for "Completely Automated Public Turing test to tell Computers and Humans Apart") is a type of challenge-response test used in computing to determine whether or not the user is human.
Through this post, I will present the way to build a "captcha bitmap image" which can help you in developing authentication screens.
Create text and math captcha classes
Bitmap
. Now, please copy these 3 classes that define the "Bitmap captcha" to your project:
Captcha.java
package info.devexchanges.androidcaptcha;
import java.util.List;
import java.util.Random;
import android.graphics.Bitmap;
import android.graphics.Color;
public abstract class Captcha {
protected Bitmap image;
protected String answer = "";
private int width;
protected int height;
protected int x = 0;
protected int y = 0;
protected static List usedColors;
protected abstract Bitmap image();
public static int color(){
Random r = new Random();
int number;
do{
number = r.nextInt(9);
}while(usedColors.contains(number));
usedColors.add(number);
switch(number){
case 0: return Color.BLACK;
case 1: return Color.BLUE;
case 2: return Color.CYAN;
case 3: return Color.DKGRAY;
case 4: return Color.GRAY;
case 5: return Color.GREEN;
case 6: return Color.MAGENTA;
case 7: return Color.RED;
case 8: return Color.YELLOW;
case 9: return Color.WHITE;
default: return Color.WHITE;
}
}
public int getWidth(){
return this.width;
}
public void setWidth(int width){
if(width > 0 && width < 10000){
this.width = width;
}else{
this.width = 300;
}
}
public int getHeight(){
return this.height;
}
public void setHeight(int height){
if(height > 0 && height < 10000){
this.height = height;
}else{
this.height = 100;
}
}
public Bitmap getImage() {
return this.image;
}
public boolean checkAnswer(String ans) {
return ans.equals(this.answer);
}
}
TextCaptcha.java
package info.devexchanges.androidcaptcha;
import java.io.CharArrayWriter;
import java.util.ArrayList;
import java.util.Random;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.Bitmap.Config;
public class TextCaptcha extends Captcha {
protected TextOptions options;
private int wordLength;
private char mCh;
public enum TextOptions {
UPPERCASE_ONLY,
LOWERCASE_ONLY,
NUMBERS_ONLY,
LETTERS_ONLY,
NUMBERS_AND_LETTERS
}
public TextCaptcha(int wordLength, TextOptions opt) {
new TextCaptcha(0, 0, wordLength, opt);
}
public TextCaptcha(int width, int height, int wordLength, TextOptions opt) {
setHeight(height);
setWidth(width);
this.options = opt;
usedColors = new ArrayList<>();
this.wordLength = wordLength;
this.image = image();
}
@Override
protected Bitmap image() {
LinearGradient gradient = new LinearGradient(0, 0, getWidth() / this.wordLength, getHeight() / 2, color(), color(), Shader.TileMode.MIRROR);
Paint p = new Paint();
p.setDither(true);
p.setShader(gradient);
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
c.drawRect(0, 0, getWidth(), getHeight(), p);
Paint tp = new Paint();
tp.setDither(true);
tp.setTextSize(getWidth() / getHeight() * 20);
Random r = new Random(System.currentTimeMillis());
CharArrayWriter cab = new CharArrayWriter();
this.answer = "";
for (int i = 0; i < this.wordLength; i++) {
char ch = ' ';
switch (options) {
case UPPERCASE_ONLY:
ch = (char) (r.nextInt(91 - 65) + (65));
break;
case LOWERCASE_ONLY:
ch = (char) (r.nextInt(123 - 97) + (97));
break;
case NUMBERS_ONLY:
ch = (char) (r.nextInt(58 - 49) + (49));
break;
case LETTERS_ONLY:
ch = getLetters(r);
break;
case NUMBERS_AND_LETTERS:
ch = getLettersNumbers(r);
break;
default:
ch = getLettersNumbers(r);
break;
}
cab.append(ch);
this.answer += ch;
}
char[] data = cab.toCharArray();
for (int i = 0; i < data.length; i++) {
this.x += (30 - (3 * this.wordLength)) + (Math.abs(r.nextInt()) % (65 - (1.2 * this.wordLength)));
this.y = 50 + Math.abs(r.nextInt()) % 50;
Canvas cc = new Canvas(bitmap);
tp.setTextSkewX(r.nextFloat() - r.nextFloat());
tp.setColor(color());
cc.drawText(data, i, 1, this.x, this.y, tp);
tp.setTextSkewX(0);
}
return bitmap;
}
private char getLetters(Random r) {
int rint = (r.nextInt(123 - 65) + (65));
if (((rint > 90) && (rint < 97)))
getLetters(r);
else
mCh = (char) rint;
return mCh;
}
private char getLettersNumbers(Random r) {
int rint = (r.nextInt(123 - 49) + (49));
if (((rint > 90) && (rint < 97)))
getLettersNumbers(r);
else if (((rint > 57) && (rint < 65)))
getLettersNumbers(r);
else
mCh = (char) rint;
return mCh;
}
}
MathCaptcha.java
I get these classes from Android-Easy-Captcha project on Github and fix some errors in code!
package info.devexchanges.androidcaptcha;
import java.util.ArrayList;
import java.util.Random;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
public class MathCaptcha extends Captcha {
protected MathOptions options;
public enum MathOptions{
PLUS_MINUS,
PLUS_MINUS_MULTIPLY
}
public MathCaptcha(int width, int height, MathOptions opt){
this.height = height;
setWidth(width);
this.options = opt;
usedColors = new ArrayList<Integer>();
this.image = image();
}
@Override
protected Bitmap image() {
int one = 0;
int two = 0;
int math = 0;
LinearGradient gradient = new LinearGradient(0, 0, getWidth() / 2, this.height / 2, color(), color(), Shader.TileMode.MIRROR);
Paint p = new Paint();
p.setDither(true);
p.setShader(gradient);
Bitmap bitmap = Bitmap.createBitmap(getWidth(), this.height, Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
c.drawRect(0, 0, getWidth(), this.height, p);
LinearGradient fontGrad = new LinearGradient(0, 0, getWidth() / 2, this.height / 2, color(), color(), Shader.TileMode.CLAMP);
Paint tp = new Paint();
tp.setDither(true);
tp.setShader(fontGrad);
tp.setTextSize(getWidth() / this.height * 20);
Random r = new Random(System.currentTimeMillis());
one = r.nextInt(9) + 1;
two = r.nextInt(9) + 1;
math = r.nextInt((options == MathOptions.PLUS_MINUS_MULTIPLY)?3:2);
if (one < two) {
Integer temp = one;
one = two;
two = temp;
}
switch (math) {
case 0:
this.answer = (one + two) + "";
break;
case 1:
this.answer = (one - two) + "";
break;
case 2:
this.answer = (one * two) + "";
break;
}
char[] data = new char[]{String.valueOf(one).toCharArray()[0], oper(math), String.valueOf(two).toCharArray()[0]};
for (int i=0; i<data.length; i++) {
x += 30 + (Math.abs(r.nextInt()) % 65);
y = 50 + Math.abs(r.nextInt()) % 50;
Canvas cc = new Canvas(bitmap);
if(i != 1)
tp.setTextSkewX(r.nextFloat() - r.nextFloat());
cc.drawText(data, i, 1, x, y, tp);
tp.setTextSkewX(0);
}
return bitmap;
}
public static char oper(Integer math) {
switch (math) {
case 0:
return '+';
case 1:
return '-';
case 2:
return '*';
}
return '+';
}
}
Usage in Activity/Fragment
TextCaptcha textCaptcha = new TextCaptcha(600, 150, 4, TextCaptcha.TextOptions.LETTERS_ONLY);
MathCaptcha mathCaptcha = new MathCaptcha(600, 150, MathCaptcha.MathOptions.PLUS_MINUS);
Bitmap
with 600x150 pixels. In order to display it to ImageView
, only need to call getImage()
method (which return a Bitmap
object):
imageView.setImageBitmap(textCaptcha.getImage());
imageView1.setImageBitmap(mathCaptcha.getImage());
The "captcha image" may be look like this:
Check answer from user input
checkAnswer()
method, we can check whether user input true or false (from EditText
) with captcha value:
//checking text captcha
if (!textCaptcha.checkAnswer(edtTextCaptcha.getText().toString().trim())) {
edtTextCaptcha.setError("Captcha is not match");
numberOfCaptchaFalse++;
} else {
Log.d("Main", "captcha is match!");
}
Of course, this is the sample output when user input wrong value:
Important Note
: By default, text captcha distinguishes uppercase and lowercase.
You can make your own output when user input right captcha value, simply showing a
Toast
like this: