First Version Haiti Captcha
This commit is contained in:
commit
96a32b2e65
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
17
.gitlab-ci.yml
Normal file
17
.gitlab-ci.yml
Normal file
@ -0,0 +1,17 @@
|
||||
image: maven:3.8.5-openjdk-17
|
||||
variables:
|
||||
MAVEN_OPTS: "-Dmaven.repo.local=./.m2/repository"
|
||||
|
||||
stages:
|
||||
- deploy
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
only:
|
||||
- /^v.*$/
|
||||
except:
|
||||
- branches
|
||||
before_script:
|
||||
- gpg --pinentry-mode loopback --passphrase $GPG_PASSPHRASE --import $GPG_PRIVATE_KEY
|
||||
script:
|
||||
- 'mvn --settings $MAVEN_SETTINGS -U -P ossrh,release clean deploy'
|
169
pom.xml
Normal file
169
pom.xml
Normal file
@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>dev.struchkov.haiti.utils</groupId>
|
||||
<artifactId>haiti-utils-captcha</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<name>Haiti Captcha</name>
|
||||
<description>A quick and clean way to add a simple captcha to your app</description>
|
||||
<url>https://github.com/haiti-projects/haiti-utils-captcha</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<issueManagement>
|
||||
<system>GitHub</system>
|
||||
<url>https://github.com/haiti-projects/haiti-utils-captcha/issues</url>
|
||||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<plugin.maven.compiler.ver>3.10.1</plugin.maven.compiler.ver>
|
||||
<plugin.nexus.staging.ver>1.6.13</plugin.nexus.staging.ver>
|
||||
<plugin.maven.source.ver>3.2.1</plugin.maven.source.ver>
|
||||
<plugin.maven.javadoc.ver>3.4.0</plugin.maven.javadoc.ver>
|
||||
<plugin.maven.gpg.ver>3.0.1</plugin.maven.gpg.ver>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>${plugin.nexus.staging.ver}</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<serverId>ossrh</serverId>
|
||||
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
|
||||
<autoReleaseAfterClose>true</autoReleaseAfterClose>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${plugin.maven.source.ver}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>${plugin.maven.javadoc.ver}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>${plugin.maven.gpg.ver}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${plugin.maven.compiler.ver}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/haiti-projects/haiti-utils-captcha.git</connection>
|
||||
<url>https://github.com/haiti-projects/haiti-utils-captcha</url>
|
||||
<developerConnection>scm:git:https://github.com/haiti-projects/haiti-utils-captcha.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>uPagge</id>
|
||||
<name>Struchkov Mark</name>
|
||||
<email>mark@struchkov.dev</email>
|
||||
<url>https://struchkov.dev</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
</project>
|
209
src/main/java/dev/struchkov/haiti/util/captcha/Captcha.java
Normal file
209
src/main/java/dev/struchkov/haiti/util/captcha/Captcha.java
Normal file
@ -0,0 +1,209 @@
|
||||
package dev.struchkov.haiti.util.captcha;
|
||||
|
||||
import dev.struchkov.haiti.util.captcha.background.BackgroundProducer;
|
||||
import dev.struchkov.haiti.util.captcha.background.TransparentBackgroundProducer;
|
||||
import dev.struchkov.haiti.util.captcha.noise.CurvedLineNoiseProducer;
|
||||
import dev.struchkov.haiti.util.captcha.noise.NoiseProducer;
|
||||
import dev.struchkov.haiti.util.captcha.text.producer.DefaultTextProducer;
|
||||
import dev.struchkov.haiti.util.captcha.text.producer.TextProducer;
|
||||
import dev.struchkov.haiti.util.captcha.text.renderer.DefaultWordRenderer;
|
||||
import dev.struchkov.haiti.util.captcha.text.renderer.WordRenderer;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* A builder for generating a CAPTCHA image/answer pair.
|
||||
*
|
||||
* <p>
|
||||
* Example for generating a new CAPTCHA:
|
||||
* </p>
|
||||
* <pre>Captcha captcha = new Captcha.Builder(200, 50)
|
||||
* .addText()
|
||||
* .addBackground()
|
||||
* .build();</pre>
|
||||
* <p>Note that the <code>build()</code> must always be called last. Other methods are optional,
|
||||
* and can sometimes be repeated. For example:</p>
|
||||
* <pre>Captcha captcha = new Captcha.Builder(200, 50)
|
||||
* .addText()
|
||||
* .addNoise()
|
||||
* .addNoise()
|
||||
* .addNoise()
|
||||
* .addBackground()
|
||||
* .build();</pre>
|
||||
* <p>Adding multiple backgrounds has no affect; the last background added will simply be the
|
||||
* one that is eventually rendered.</p>
|
||||
* <p>To validate that <code>answerStr</code> is a correct answer to the CAPTCHA:</p>
|
||||
*
|
||||
* <code>captcha.isCorrect(answerStr);</code>
|
||||
*/
|
||||
public final class Captcha {
|
||||
|
||||
private final String answer;
|
||||
private final BufferedImage img;
|
||||
private final LocalDateTime timeStamp;
|
||||
|
||||
private Captcha(Builder builder) {
|
||||
img = builder.img;
|
||||
answer = builder.answer;
|
||||
timeStamp = builder.timeStamp;
|
||||
}
|
||||
|
||||
public static Builder builder(int width, int height) {
|
||||
return new Builder(width, height);
|
||||
}
|
||||
|
||||
public boolean isCorrect(String answer) {
|
||||
return this.answer.equals(answer);
|
||||
}
|
||||
|
||||
public String getAnswer() {
|
||||
return answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A png captcha image.
|
||||
*/
|
||||
public BufferedImage getImage() {
|
||||
return img;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Answer: " +
|
||||
answer +
|
||||
"][Timestamp: " +
|
||||
timeStamp +
|
||||
"][Image: " +
|
||||
img +
|
||||
"]";
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private String answer = "";
|
||||
private BufferedImage img;
|
||||
private BufferedImage backGround;
|
||||
private LocalDateTime timeStamp;
|
||||
private boolean addBorder = false;
|
||||
|
||||
public Builder(int width, int height) {
|
||||
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a background using the default {@link BackgroundProducer} (a {@link TransparentBackgroundProducer}).
|
||||
*/
|
||||
public Builder addBackground() {
|
||||
return addBackground(new TransparentBackgroundProducer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a background using the given {@link BackgroundProducer}.
|
||||
*/
|
||||
public Builder addBackground(BackgroundProducer bgProd) {
|
||||
backGround = bgProd.getBackground(img.getWidth(), img.getHeight());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the answer to the CAPTCHA using the {@link DefaultTextProducer}.
|
||||
*/
|
||||
public Builder addText() {
|
||||
return addText(new DefaultTextProducer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the answer to the CAPTCHA using the given
|
||||
* {@link TextProducer}.
|
||||
*/
|
||||
public Builder addText(TextProducer txtProd) {
|
||||
return addText(txtProd, new DefaultWordRenderer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the answer to the CAPTCHA using the default
|
||||
* {@link TextProducer}, and render it to the image using the given
|
||||
* {@link WordRenderer}.
|
||||
*/
|
||||
public Builder addText(WordRenderer wRenderer) {
|
||||
return addText(new DefaultTextProducer(), wRenderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the answer to the CAPTCHA using the given
|
||||
* {@link TextProducer}, and render it to the image using the given
|
||||
* {@link WordRenderer}.
|
||||
*/
|
||||
public Builder addText(TextProducer txtProd, WordRenderer wRenderer) {
|
||||
answer += txtProd.getText();
|
||||
wRenderer.render(answer, img);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add noise using the default {@link NoiseProducer} (a {@link CurvedLineNoiseProducer}).
|
||||
*/
|
||||
public Builder addNoise() {
|
||||
return this.addNoise(new CurvedLineNoiseProducer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add noise using the given NoiseProducer.
|
||||
*/
|
||||
public Builder addNoise(NoiseProducer nProd) {
|
||||
nProd.makeNoise(img);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a single-pixel wide black border around the image.
|
||||
*/
|
||||
public Builder addBorder() {
|
||||
addBorder = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CAPTCHA. This method should always be called, and should always
|
||||
* be called last.
|
||||
*
|
||||
* @return The constructed CAPTCHA.
|
||||
*/
|
||||
public Captcha build() {
|
||||
if (backGround == null) {
|
||||
backGround = new TransparentBackgroundProducer().getBackground(img.getWidth(), img.getHeight());
|
||||
}
|
||||
|
||||
// Paint the main image over the background
|
||||
final Graphics2D g = backGround.createGraphics();
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
|
||||
g.drawImage(img, null, null);
|
||||
|
||||
if (addBorder) {
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
|
||||
g.setColor(Color.BLACK);
|
||||
g.drawLine(0, 0, 0, width);
|
||||
g.drawLine(0, 0, width, 0);
|
||||
g.drawLine(0, height - 1, width, height - 1);
|
||||
g.drawLine(width - 1, height - 1, width - 1, 0);
|
||||
}
|
||||
|
||||
img = backGround;
|
||||
|
||||
timeStamp = LocalDateTime.now();
|
||||
|
||||
return new Captcha(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package dev.struchkov.haiti.util.captcha.background;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Used to add a captcha background.
|
||||
*
|
||||
* @author upagge 10.07.2022
|
||||
*/
|
||||
public interface BackgroundProducer {
|
||||
|
||||
/**
|
||||
* Add the background to the given image.
|
||||
*
|
||||
* @param image The image onto which the background will be rendered.
|
||||
* @return The image with the background rendered.
|
||||
*/
|
||||
BufferedImage addBackground(BufferedImage image);
|
||||
|
||||
/**
|
||||
* Returns a gradient background.
|
||||
*/
|
||||
BufferedImage getBackground(int width, int height);
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package dev.struchkov.haiti.util.captcha.background;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import static java.awt.Color.DARK_GRAY;
|
||||
import static java.awt.Color.WHITE;
|
||||
import static java.awt.RenderingHints.KEY_ANTIALIASING;
|
||||
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
|
||||
|
||||
/**
|
||||
* Creates a gradiated background with the given <i>from</i> and <i>to</i>
|
||||
* Color values. If none are specified they default to light gray and white
|
||||
* respectively.
|
||||
*/
|
||||
public class GradiatedBackgroundProducer implements BackgroundProducer {
|
||||
|
||||
private Color fromColor;
|
||||
private Color toColor;
|
||||
|
||||
public GradiatedBackgroundProducer() {
|
||||
this(DARK_GRAY, WHITE);
|
||||
}
|
||||
|
||||
public GradiatedBackgroundProducer(Color from, Color to) {
|
||||
fromColor = from;
|
||||
toColor = to;
|
||||
}
|
||||
|
||||
public void setFromColor(Color fromColor) {
|
||||
this.fromColor = fromColor;
|
||||
}
|
||||
|
||||
public void setToColor(Color toColor) {
|
||||
this.toColor = toColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getBackground(int width, int height) {
|
||||
// create an opaque image
|
||||
final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
final Graphics2D g = img.createGraphics();
|
||||
final RenderingHints hints = new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
|
||||
|
||||
g.setRenderingHints(hints);
|
||||
|
||||
// create the gradient color
|
||||
final GradientPaint ytow = new GradientPaint(0, 0, fromColor, width, height, toColor);
|
||||
|
||||
g.setPaint(ytow);
|
||||
// draw gradient color
|
||||
g.fill(new Rectangle2D.Double(0, 0, width, height));
|
||||
|
||||
// draw the transparent image over the background
|
||||
g.drawImage(img, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage addBackground(BufferedImage image) {
|
||||
return getBackground(image.getWidth(), image.getHeight());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dev.struchkov.haiti.util.captcha.background;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Generates a transparent background.
|
||||
*/
|
||||
public class TransparentBackgroundProducer implements BackgroundProducer {
|
||||
|
||||
@Override
|
||||
public BufferedImage addBackground(BufferedImage image) {
|
||||
return getBackground(image.getWidth(), image.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getBackground(int width, int height) {
|
||||
final BufferedImage bg = new BufferedImage(width, height, Transparency.TRANSLUCENT);
|
||||
final Graphics2D g = bg.createGraphics();
|
||||
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
|
||||
g.fillRect(0, 0, width, height);
|
||||
|
||||
return bg;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package dev.struchkov.haiti.util.captcha.noise;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.awt.geom.PathIterator.SEG_LINETO;
|
||||
import static java.awt.geom.PathIterator.SEG_MOVETO;
|
||||
|
||||
/**
|
||||
* Adds a randomly curved line to the image.
|
||||
*/
|
||||
public class CurvedLineNoiseProducer implements NoiseProducer {
|
||||
|
||||
private static final Random RAND = new SecureRandom();
|
||||
|
||||
private final Color color;
|
||||
private final float width;
|
||||
|
||||
public CurvedLineNoiseProducer() {
|
||||
this(Color.BLACK, 3.0f);
|
||||
}
|
||||
|
||||
public CurvedLineNoiseProducer(Color color, float width) {
|
||||
this.color = color;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeNoise(BufferedImage image) {
|
||||
final int imgWidth = image.getWidth();
|
||||
final int imgHeight = image.getHeight();
|
||||
|
||||
// the curve from where the points are taken
|
||||
final CubicCurve2D cc = new CubicCurve2D.Float(
|
||||
imgWidth * .1f, imgHeight * RAND.nextFloat(),
|
||||
imgWidth * .1f, imgHeight * RAND.nextFloat(),
|
||||
imgWidth * .25f, imgHeight * RAND.nextFloat(),
|
||||
imgWidth * .9f, imgHeight * RAND.nextFloat()
|
||||
);
|
||||
|
||||
// creates an iterator to define the boundary of the flattened curve
|
||||
final PathIterator pi = cc.getPathIterator(null, 2);
|
||||
final Point2D[] tmp = new Point2D[200];
|
||||
int i = 0;
|
||||
|
||||
// while pi is iterating the curve, adds points to tmp array
|
||||
while (!pi.isDone()) {
|
||||
float[] coords = new float[6];
|
||||
int currentSegment = pi.currentSegment(coords);
|
||||
if (currentSegment == SEG_MOVETO || currentSegment == SEG_LINETO) {
|
||||
tmp[i] = new Point2D.Float(coords[0], coords[1]);
|
||||
}
|
||||
i++;
|
||||
pi.next();
|
||||
}
|
||||
|
||||
// the points where the line changes the stroke and direction
|
||||
final Point2D[] pts = new Point2D[i];
|
||||
// copies points from tmp to pts
|
||||
System.arraycopy(tmp, 0, pts, 0, i);
|
||||
|
||||
final Graphics2D graph = (Graphics2D) image.getGraphics();
|
||||
graph.setRenderingHints(new RenderingHints(
|
||||
RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON));
|
||||
|
||||
graph.setColor(color);
|
||||
|
||||
// for the maximum 3 point change the stroke and direction
|
||||
for (i = 0; i < pts.length - 1; i++) {
|
||||
if (i < 3) {
|
||||
graph.setStroke(new BasicStroke(this.width));
|
||||
}
|
||||
graph.drawLine(
|
||||
(int) pts[i].getX(),
|
||||
(int) pts[i].getY(),
|
||||
(int) pts[i + 1].getX(),
|
||||
(int) pts[i + 1].getY()
|
||||
);
|
||||
}
|
||||
graph.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package dev.struchkov.haiti.util.captcha.noise;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface NoiseProducer {
|
||||
|
||||
void makeNoise(BufferedImage image);
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package dev.struchkov.haiti.util.captcha.text.producer;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Produces text of a given length from a given array of characters.
|
||||
*/
|
||||
public class DefaultTextProducer implements TextProducer {
|
||||
|
||||
private static final Random RAND = new SecureRandom();
|
||||
private static final int DEFAULT_LENGTH = 5;
|
||||
private static final char[] DEFAULT_CHARS = new char[]{
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'k', 'm', 'n', 'p', 'r', 'w', 'x', 'y',
|
||||
'2', '3', '4', '5', '6', '7', '8'
|
||||
};
|
||||
|
||||
private final int length;
|
||||
private final char[] srcChars;
|
||||
|
||||
public DefaultTextProducer() {
|
||||
this(DEFAULT_LENGTH, DEFAULT_CHARS);
|
||||
}
|
||||
|
||||
public DefaultTextProducer(int length, char[] srcChars) {
|
||||
this.length = length;
|
||||
this.srcChars = copyOf(srcChars, srcChars.length);
|
||||
}
|
||||
|
||||
private static char[] copyOf(char[] original, int newLength) {
|
||||
char[] copy = new char[newLength];
|
||||
System.arraycopy(
|
||||
original, 0, copy, 0,
|
||||
Math.min(original.length, newLength)
|
||||
);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(srcChars[RAND.nextInt(srcChars.length)]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dev.struchkov.haiti.util.captcha.text.producer;
|
||||
|
||||
/**
|
||||
* Generate an answer for the CAPTCHA.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TextProducer {
|
||||
|
||||
/**
|
||||
* Generate a series of characters to be used as the answer for the CAPTCHA.
|
||||
*/
|
||||
String getText();
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package dev.struchkov.haiti.util.captcha.text.renderer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.awt.RenderingHints.KEY_ANTIALIASING;
|
||||
import static java.awt.RenderingHints.KEY_RENDERING;
|
||||
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
|
||||
import static java.awt.RenderingHints.VALUE_RENDER_QUALITY;
|
||||
|
||||
/**
|
||||
* Renders the answer onto the image.
|
||||
*/
|
||||
public class DefaultWordRenderer implements WordRenderer {
|
||||
|
||||
private static final Random RAND = new SecureRandom();
|
||||
private static final List<Color> DEFAULT_COLORS = new ArrayList<>();
|
||||
private static final List<Font> DEFAULT_FONTS = new ArrayList<>();
|
||||
|
||||
// The text will be rendered 25%/5% of the image height/width from the X and Y axes
|
||||
private static final double YOFFSET = 0.25;
|
||||
private static final double XOFFSET = 0.05;
|
||||
|
||||
static {
|
||||
DEFAULT_COLORS.add(Color.BLACK);
|
||||
DEFAULT_FONTS.add(new Font("Arial", Font.BOLD, 40));
|
||||
DEFAULT_FONTS.add(new Font("Courier", Font.BOLD, 40));
|
||||
}
|
||||
|
||||
private final List<Color> colors = new ArrayList<>();
|
||||
private final List<Font> fonts = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Use the default color (black) and fonts (Arial and Courier).
|
||||
*/
|
||||
public DefaultWordRenderer() {
|
||||
this(DEFAULT_COLORS, DEFAULT_FONTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a <code>WordRenderer</code> using the given <code>Color</code>s and
|
||||
* <code>Font</code>s.
|
||||
*
|
||||
* @param colors
|
||||
* @param fonts
|
||||
*/
|
||||
public DefaultWordRenderer(List<Color> colors, List<Font> fonts) {
|
||||
this.colors.addAll(colors);
|
||||
this.fonts.addAll(fonts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a word onto a BufferedImage.
|
||||
*
|
||||
* @param word The word to be rendered.
|
||||
* @param image The BufferedImage onto which the word will be painted.
|
||||
*/
|
||||
@Override
|
||||
public void render(final String word, BufferedImage image) {
|
||||
final Graphics2D g = image.createGraphics();
|
||||
|
||||
final RenderingHints hints = new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
|
||||
hints.add(new RenderingHints(KEY_RENDERING, VALUE_RENDER_QUALITY));
|
||||
g.setRenderingHints(hints);
|
||||
|
||||
final FontRenderContext frc = g.getFontRenderContext();
|
||||
int xBaseline = (int) Math.round(image.getWidth() * XOFFSET);
|
||||
final int yBaseline = image.getHeight() - (int) Math.round(image.getHeight() * YOFFSET);
|
||||
|
||||
final char[] chars = new char[1];
|
||||
for (char c : word.toCharArray()) {
|
||||
chars[0] = c;
|
||||
|
||||
g.setColor(colors.get(RAND.nextInt(colors.size())));
|
||||
|
||||
final int choiceFont = RAND.nextInt(fonts.size());
|
||||
Font font = fonts.get(choiceFont);
|
||||
g.setFont(font);
|
||||
|
||||
GlyphVector gv = font.createGlyphVector(frc, chars);
|
||||
g.drawChars(chars, 0, chars.length, xBaseline, yBaseline);
|
||||
|
||||
final int width = (int) gv.getVisualBounds().getWidth();
|
||||
xBaseline = xBaseline + width;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package dev.struchkov.haiti.util.captcha.text.renderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Render the answer for the CAPTCHA onto the image.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface WordRenderer {
|
||||
|
||||
/**
|
||||
* Render a word to a BufferedImage.
|
||||
*
|
||||
* @param word The sequence of characters to be rendered.
|
||||
* @param image The image onto which the word will be rendered.
|
||||
*/
|
||||
void render(String word, BufferedImage image);
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user