public void generate()

in blocks/cocoon-captcha/cocoon-captcha-impl/src/main/java/org/apache/cocoon/reading/CaptchaReader.java [155:322]


    public void generate()
    throws IOException {

        /* Retrieve the current operational parameters from Cocoon's sitemap */
        final int width = this.parameters.getParameterAsInteger("width", 100);
        final int height = this.parameters.getParameterAsInteger("height", 50);
        Color background = this.getColor("background", Color.white);
        Color foreground = this.getColor("foreground", null);
        if (foreground == null) {
            int r = (RANDOM.nextInt(64) + 96 + background.getRed()) & 0x0ff;
            int g = (RANDOM.nextInt(64) + 96 + background.getGreen()) & 0x0ff;
            int b = (RANDOM.nextInt(64) + 96 + background.getBlue()) & 0x0ff;
            foreground = new Color(r, g, b);
        }
        final String fontName = this.parameters.getParameter("font", "serif");
        final int scale = this.parameters.getParameterAsInteger("scale", 5);
        final float amount = this.parameters.getParameterAsFloat("amount", 2);
        final float quality = this.parameters.getParameterAsFloat("quality", 0.75F);
        final String text = this.source;

        /* Create the final buffered image we will be writing to at the bottom */
        final BufferedImage result = new BufferedImage(width, height,
                                                       BufferedImage.TYPE_INT_RGB);

        /* Starting with a size of 100, evaluate how big the real font should be */
        final Font baseFont = new Font(fontName, Font.PLAIN, 100);
        final Graphics2D graphics = this.antialiasedGraphics(result);
        final FontMetrics metrics = graphics.getFontMetrics(baseFont);
        final Rectangle2D tempSize = metrics.getStringBounds(text, graphics);

        /* Evaluate the image size of the resulting image and prepare a ratio */
        final double tempWidth = tempSize.getWidth() + (2 * tempSize.getHeight());
        final double tempHeight = (tempSize.getHeight() * (1 + amount));
        final double ratioWidth = width * scale / tempWidth;
        final double ratioHeight = height * scale / tempHeight;
        final double ratio = ratioWidth < ratioHeight? ratioWidth: ratioHeight;
        final Font font = baseFont.deriveFont((float) (100 * ratio));

        /* Evaluate the final size of the text to write */
        final FontMetrics sourceMetrics = graphics.getFontMetrics(font);
        final Rectangle2D size = sourceMetrics.getStringBounds(text, graphics);
        final double textWidth = size.getWidth();
        final double textHeight = size.getHeight();

        /* Evaluate the final size of the interim images */
        int scaledWidth = (int) (tempWidth * ratio);
        int scaledHeight = (int) (tempHeight * ratio);
        
        /* Create a couple of images to write the plain string and the warped one */
        BufferedImage source = new BufferedImage(scaledWidth, scaledHeight,
                                                 BufferedImage.TYPE_BYTE_GRAY);
        BufferedImage warped = new BufferedImage(scaledWidth, scaledHeight,
                                                 BufferedImage.TYPE_INT_ARGB);

        /* Prepare the background and the font of the source image */
        final Graphics2D sourceGraphics = this.antialiasedGraphics(source);
        sourceGraphics.setColor(Color.black);
        sourceGraphics.fillRect(0, 0, scaledWidth, scaledHeight);
        sourceGraphics.setFont(font);

        /* Write the string exactly in the middle of the source image */
        float textX = (float) ((scaledWidth  - textWidth)  / 2); 
        float textY = (float) ((scaledHeight - textHeight) / 2);
        sourceGraphics.setColor(Color.white);
        sourceGraphics.drawString(text, textX, textY + sourceMetrics.getAscent());

        /* Randomize displacement factors for sine-waves */
        final int displaceTop = RANDOM.nextInt(scaledWidth);
        final int displaceBtm = RANDOM.nextInt(scaledWidth);
        final int displaceVer = RANDOM.nextInt(scaledHeight);

        /* Calculate the horizontal and vertical amplitude and wavelength of sines */
        final double amplitHor = textHeight * amount / 4;
        final double amplitVer = textHeight / 8;
        final double t = (RANDOM.nextDouble() * textWidth / 2) + (textWidth * 0.75);
        final double b = (RANDOM.nextDouble() * textWidth / 2) + (textWidth * 0.75);
        final double wlenTop = textHeight > t? textHeight: t;
        final double wlenBtm = textHeight > b? textHeight: b;

        /* Calculate the offsets for horizontal (top and bottom) sine waves */
        final double offsetTop = amplitHor;
        final double offsetBtm = scaledHeight - amplitHor;

        /* Prepare an array for vertical displacement sine wave */
        final double vert[] = new double[scaledHeight];
        for (int v = 0; v < scaledHeight ; v++) {
            vert[v] = Math.sin((Math.PI * (v + displaceVer)) / textHeight) * amplitVer;
        }

        /* Iterate all the target image pixels and render the distortion */
        int x1 = Integer.MAX_VALUE;
        int x2 = Integer.MIN_VALUE;
        int y1 = Integer.MAX_VALUE;
        int y2 = Integer.MIN_VALUE;
        final WritableRaster sourceRaster = source.getRaster();
        final WritableRaster warpedRaster = warped.getRaster();
        final double src[] = new double[9];
        final double col[] = new double[] { foreground.getRed(),
                                            foreground.getGreen(),
                                            foreground.getBlue(), 0};
        for (int h = 0; h < scaledWidth; h++) {
            final double baseTop = (Math.PI * (h + displaceTop)) / wlenTop;
            final double baseBtm = (Math.PI * (h + displaceBtm)) / wlenBtm; 
            final double top = offsetTop + Math.sin(baseTop) * amplitHor;
            final double btm = offsetBtm - Math.sin(baseBtm) * amplitHor;

            for (int v = 0; v < scaledHeight; v ++) {
                final double x = (h + vert[v]);
                final double y = (v * ((btm - top) / scaledHeight)) + top;

                if ((y > 0) && (y < scaledHeight - 1) &&
                    (x > 0) && (x < scaledWidth - 1)) {

                    /* Retrieve the nine pixels around the source one */
                    sourceRaster.getPixels((int)(x-1), (int)(y-1), 3, 3, src);

                    /* Average their value (it's grayscale) to have a better warp */
                    double alpha = ((src[1] + src[3] + src[5] + src[7]) * 0.1) +
                                   ((src[0] + src[2] + src[6] + src[8]) * 0.025) +
                                   (src[4] * 0.5);

                    /* Write the resultin pixel in the target image if necessary */
                    if (alpha > 0) {
                        col[3] = alpha;
                        warpedRaster.setPixel(h, v, col);
                        if (h < x1) x1 = h;
                        if (h > x2) x2 = h;
                        if (v < y1) y1 = v;
                        if (v > y2) y2 = v;
                    }
                }
            }
        }

        /* Crop the image to the maximum extent of the warped text (if visible) */
        source = null;
        int xd = x2 - x1 + 1;
        int yd = y2 - y1 + 1; 
        if ((xd > 1) && (yd > 1)) {
            warped = warped.getSubimage(x1, y1, xd, yd);
        }

        /* Rescale the cropped image to the required size */
        Image image = warped.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        graphics.setBackground(background);
        graphics.setColor(background);
        graphics.fillRect(0, 0, width, height);
        graphics.setColor(foreground);
        graphics.drawImage(image, 0, 0, null);

        /* Write the processed image as a JPEG image */
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
        ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
        jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        jpgWriteParam.setCompressionQuality(quality);
        jpgWriter.setOutput(buffer);

        IIOImage outputImage = new IIOImage((BufferedImage) image, null, null);
        jpgWriter.write(null, outputImage, jpgWriteParam);
        jpgWriter.dispose();

        buffer.flush();
        buffer.close();
        this.out.write(buffer.toByteArray());
        this.out.flush();
    }