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