BlogContacts
BlogGamesContacts

{{item}}

Creating simple captcha

07.11.2017

Java

See GitHubSource code
A small Java code snippet, which fits in one servlet, to create an image containing captcha and it's text value.
See the implementation of this captcha in the Simple feedback form.
First, let's determine the algorithm. At the entrance we'll feed in a random combination of letters, then we'll rotate each letter 35 degrees clockwise or counterclockwise alternately and at the end we'll collect the resulting images into one line.Why 35 degrees? If you take the angle more, then it will be hard for yourself to read this captcha. For example letters N and Z will be similar like each other. If you take the angle less than 35 degrees, then it will be easy to read this captcha by OCR programs.We'll store a text string with the value of the captcha in the session attribute. For those, who are not familiar with Java, I'll say, that the session attributes and the session itself in Java are stored in the application server and are not available to user's browser. So any hidden ID under the captcha in our case, is simply not necessary. When a user submits a form to a server, it compares the saved value of the captcha with what the user entered, and then decides whether to handle his request or not.So now, move on to the algorithm:1. To start, we need a collection of any characters, for example, the capital English letters. For my blog I chose this variant:
letter_a.pngletter_b.pngletter_c.pngletter_d.pngletter_e.pngletter_f.pngletter_g.pngletter_h.pngletter_i.pngletter_j.pngletter_k.pngletter_l.pngletter_m.pngletter_n.pngletter_o.pngletter_p.pngletter_q.pngletter_r.pngletter_s.pngletter_t.pngletter_u.pngletter_v.pngletter_w.pngletter_x.pngletter_y.pngletter_z.png
But for the better protection against spam, it is recommended to expand this collection by adding to it lowercase letters and numbers. Images must be in a GIF or PNG format with transparency, because such images complicate the process of machine recognition of text - not all OCR programs work with such images.2. Let's create a servlet, which we'll call later:
Java
@WebServlet(name = "Captcha", urlPatterns = {"/captcha"})
public class Captcha extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doResponse(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doResponse(request, response);
    }

    protected void doResponse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //TODO
    }
}
Next, we'll consistently fill doResponse method and supplement it by additional methods.3. Get a random combination of letters:
Java
private static char[] getRandomStringImpl() {

    char[] randomString = new char[8];

    Random random = new Random();

    int capitalLetter;

    for (int i = 0; i < 8; i++) {
        // Capital English letters in the ASCII
        // symbol table start with code 65
        capitalLetter = 65 + random.nextInt(26);
        randomString[i] = (char) capitalLetter;
    }

    return randomString;
}
4. Call this method from doResponse method and write result into the session:
Java
char[] captcha = getRandomStringImpl();
request.getSession().setAttribute("captcha", String.valueOf(captcha));
Thus we got the value of the captcha, which we'll compare later with what a user enters into a form, unless of course, he is a user and not a robot.5. Now we collect an array of pictures with letters and rotate each of them 35 degrees:
Java
BufferedImage[] images = new BufferedImage[8];

for (int i = 0; i < captcha.length; i++) {
    images[i] = ImageIO.read(Captcha.class.getResourceAsStream("/captcha/" + captcha[i] + ".png"));
    if (i % 2 == 0) {
        images[i] = rotateImage(images[i], 35);
    } else {
        images[i] = rotateImage(images[i], -35);
    }
}
6. Create a method of turning the picture by a specified angle:
Java
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {

    double radian = Math.toRadians(angle);
    double sin = Math.abs(Math.sin(radian));
    double cos = Math.abs(Math.cos(radian));

    int width = buffImage.getWidth();
    int height = buffImage.getHeight();

    int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
    int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);

    BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);

    Graphics2D graphics = rotatedImage.createGraphics();

    graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
    graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
    graphics.drawImage(buffImage, 0, 0,null);
    graphics.dispose();

    return rotatedImage;
}
7. Now we combine the obtained images into a single line. In order to the letters were a bit covered by each other, we shift each subsequent letter to the left by 40% of the width of the previous letter:
Java
int imageSize = 30;
// Calculate the hypotenuse by Pythagorean theorem.
// The maximum image size obtains by rotation 45 degrees
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);

BufferedImage captcha_img = new BufferedImage(rotatedImageSize * 7 / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB);

for (int i = 0; i < captcha.length; i++) {
    captcha_img.getGraphics().drawImage(images[i],rotatedImageSize * i / 10 * 6, 0, null);
}
8. Last step - return an image containing captcha to the user:
Java
ImageIO.write(captcha_img, "png", response.getOutputStream());
Everything is ready, it remains only to collect all the code into a single unit.
Privacy policy
Back to Top