001/* 002 * Shredzone Commons 003 * 004 * Copyright (C) 2012 Richard "Shred" Körber 005 * http://commons.shredzone.org 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU Library General Public License as 009 * published by the Free Software Foundation, either version 3 of the 010 * License, or (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU Library General Public License 018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 019 */ 020package org.shredzone.commons.captcha.impl; 021 022import static java.lang.Math.PI; 023 024import java.awt.Color; 025import java.awt.Font; 026import java.awt.FontMetrics; 027import java.awt.Graphics2D; 028import java.awt.RenderingHints; 029import java.awt.image.BufferedImage; 030import java.io.InputStream; 031import java.util.Random; 032 033import javax.annotation.PostConstruct; 034 035import org.shredzone.commons.captcha.CaptchaGenerator; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038import org.springframework.beans.factory.annotation.Value; 039import org.springframework.stereotype.Component; 040 041/** 042 * Default implementation of {@link CaptchaGenerator}. 043 * 044 * @author Richard "Shred" Körber 045 */ 046@Component("captchaGenerator") 047public class DefaultCaptchaGenerator implements CaptchaGenerator { 048 private static final Logger LOG = LoggerFactory.getLogger(DefaultCaptchaGenerator.class); 049 050 private final Random rnd = new Random(); 051 052 @Value("${captcha.width}") 053 private int width; 054 055 @Value("${captcha.height}") 056 private int height; 057 058 @Value("${captcha.fontPath}") 059 private String fontPath; 060 061 @Value("${captcha.fontSize}") 062 private float fontSize; 063 064 @Value("${captcha.grid}") 065 private boolean showGrid; 066 067 private int gridSize = 11; 068 private int rotationAmplitude = 10; 069 private int scaleAmplitude = 20; 070 071 private Font font; 072 073 @Override 074 public int getWidth() { return width; } 075 076 @Override 077 public int getHeight() { return height; } 078 079 /** 080 * Sets up the captcha generator. 081 */ 082 @PostConstruct 083 public void setup() { 084 if (width <= 0 || height <= 0) { 085 throw new IllegalStateException("Captcha size is not set"); 086 } 087 088 if (fontPath == null) { 089 throw new IllegalStateException("Font is not set"); 090 } 091 092 try (InputStream fontStream = DefaultCaptchaGenerator.class.getResourceAsStream(fontPath)) { 093 font = Font.createFont(Font.TRUETYPE_FONT, fontStream); 094 } catch (Exception ex) { 095 LOG.error("Could not open font " + fontPath, ex); 096 throw new IllegalStateException(); 097 } 098 } 099 100 @Override 101 public BufferedImage createCaptcha(char[] text) { 102 if (text == null || text.length == 0) { 103 throw new IllegalArgumentException("No captcha text given"); 104 } 105 106 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 107 Graphics2D g2d = image.createGraphics(); 108 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 109 g2d.setBackground(Color.WHITE); 110 g2d.setColor(Color.BLACK); 111 112 clearCanvas(g2d); 113 114 if (showGrid) { 115 drawGrid(g2d); 116 } 117 118 int charMaxWidth = width / text.length; 119 int xPos = 0; 120 for (char ch : text) { 121 drawCharacter(g2d, ch, xPos, charMaxWidth); 122 xPos += charMaxWidth; 123 } 124 125 g2d.dispose(); 126 return image; 127 } 128 129 /** 130 * Clears the canvas. 131 */ 132 private void clearCanvas(Graphics2D g2d) { 133 g2d.clearRect(0, 0, width, height); 134 } 135 136 /** 137 * Draws the background grid. 138 */ 139 private void drawGrid(Graphics2D g2d) { 140 for (int y = 2; y < height; y += gridSize) { 141 g2d.drawLine(0, y, width - 1, y); 142 } 143 144 for (int x = 2; x < width; x += gridSize) { 145 g2d.drawLine(x, 0, x, height -1); 146 } 147 } 148 149 /** 150 * Draws a single character. 151 * 152 * @param g2d 153 * {@link Graphics2D} context 154 * @param ch 155 * character to draw 156 * @param x 157 * left x position of the character 158 * @param boxWidth 159 * width of the box 160 */ 161 private void drawCharacter(Graphics2D g2d, char ch, int x, int boxWidth) { 162 double degree = (rnd.nextDouble() * rotationAmplitude * 2) - rotationAmplitude; 163 double scale = 1 - (rnd.nextDouble() * scaleAmplitude / 100); 164 165 Graphics2D cg2d = (Graphics2D) g2d.create(); 166 cg2d.setFont(font.deriveFont(fontSize)); 167 168 cg2d.translate(x + (boxWidth / 2), height / 2); 169 cg2d.rotate(degree * PI / 90); 170 cg2d.scale(scale, scale); 171 172 FontMetrics fm = cg2d.getFontMetrics(); 173 int charWidth = fm.charWidth(ch); 174 int charHeight = fm.getAscent() + fm.getDescent(); 175 176 cg2d.drawString(String.valueOf(ch), -(charWidth / 2), fm.getAscent() - (charHeight / 2)); 177 178 cg2d.dispose(); 179 } 180 181}