Search code examples
javacaptchatapestrynoisedistortion

convert Random text to image with random noise in java


I am implementing captcha feature in our project. Its basically Tapestry framework application. I am generating random alfa-numeric string and convert it to image and display it in web page.

Now what i need is, i want to add random noise like dots lines etc to make image with text unclear. How to proceed please help.

keeping the code for reference .

-- This method gives text captcha.

`

 public String generateCaptcha() {
          Random random = new Random();
          int min = 4; // Inclusive
          int max = 9; // Exclusive
          int length = random.nextInt(max-min) + min;
          StringBuilder captchaStringBuffer = new StringBuilder();
          for (int i = 0; i < length; i++) {
           int captchaNumber = Math.abs(random.nextInt()) % 60;
           int charNumber = 0;
           if (captchaNumber < 26) {
            charNumber = 65 + captchaNumber;
           }
           else if (captchaNumber < 52){
            charNumber = 97 + (captchaNumber - 26);
           }
           else {
            charNumber = 48 + (captchaNumber - 52);
           }
           captchaStringBuffer.append((char)charNumber);
          }
          return captchaStringBuffer.toString();
         }

`

-- This method converts generated captcha to Image with out any noise.

`

 public void textToImage(String displayCode){
            String text = displayCode;
            BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = img.createGraphics();
            Font font = new Font("Arial", Font.PLAIN, 48);
            g2d.setFont(font);
            FontMetrics fm = g2d.getFontMetrics();
            int width = fm.stringWidth(text);
            int height = fm.getHeight();
            g2d.dispose();

            img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            g2d = img.createGraphics();
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_OFF);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING,
                RenderingHints.VALUE_DITHER_DISABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                RenderingHints.VALUE_STROKE_DEFAULT);
            g2d.setFont(font);
            fm = g2d.getFontMetrics();
            g2d.setColor(Color.BLACK);
            g2d.drawString(text, 0, fm.getAscent());
            g2d.dispose();
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ImageIO.write(img, "png", baos);
                byte[] res=baos.toByteArray();
                setBinaryImage("data:image/png;base64,"+Base64.encode(res));
            } catch (IOException e) {
            }
     }

`

I am newbie so please tell in clear what ever you say.

Thanks in Advance :-)


Solution

  • When trying to obstruct the text that is being displayed, you can use:

    • Dots/Circles, spread randomly over the image with varying size and color
    • Lines, coming from a random point on the edge of the image to another point on the edge of the image. These can also vary in color and thickness.

    As far as i know, you can use g2d.drawLine(x1, y1, x2, y2) to draw a line. Since you want it to go from the edge to another point on the edge, you have to limit your random-point-generation. You can use this approach:

    public Point pointOnEdge(int width, int height) {
        int side = (int) (Math.random() * 3); //0=top, 1=bot, 2=left, 3=right
    
        int x = 0;
        int y = 0;
    
        switch(side) {
        case 0:
            //when on top, y is at the top of the image (0) and x is something in [0, width]
            y = 0;
            x = (int) (Math.random() * width);
            break;
        case 1:
            //when on bottom, y is at the bottom of the image (image height) and x is something in [0, width]
            y = height;
            x = (int) (Math.random() * width);
        case 2:
            //when on left, x is at the left side (0) of the image and y is something in [0, height]
            y = (int) (Math.random() * height);
            x = 0;
            break;
        case 3:
            //when on left, x is at the left side (0) of the image and y is something in [0, height]
            y = (int) (Math.random() * height);
            x = width;
            break;
        }
    
        return new Point(x, y);
    }
    

    If you create two Points like that, and connect them with a line, then you have a pretty simple way of obstructing your Image Partially, thus distorting it.

    Now to the Circles:

    public void drawCircles(Graphics2D g2d, int width, int height) {
        //draw 10 of them
        for(int i = 0; i < 10; i++) {
            //select a random size
            int x = 10 + (int) (Math.random() * 10);
            //draw circle at random position with the created size
            g2d.fillOval((int) (Math.random() * width), (int) (Math.random() * height), x, x);
        }
    }
    

    And like that you are now able to distort your image to make it hard to read. I hope you have enough common code understanding to know where to put these function calls. If not, I can add it if nescessary.

    EDIT 1

    If you want a dotted Background for your Captcha, you can use this code before rendering the String or anything else:

    boolean r = false;
    boolean g = false;
    for(int y = 0; y < height; y++) {
        r = !r;
        g = r;
        for(int x = 0; x < width; x++) {
            g = !g;
            if(g) {
                g2d.setColor(Color.GRAY);
            }else {
                g2d.setColor(Color.WHITE);
            }
            g2d.drawLine(x, y, x, y);
        }
    }
    

    EDIT 2

    I would recommend, that you use a different font. Then you dont have to do any streching. Good fonts for that are e.g. Gigi. You could also select a font randomly by using GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames() which returns all Fonts that Java has.