OxCaptcha.java
//BSD 2-Clause "Simplified" License
//https://github.com/gbaydin/OxCaptcha/blob/master/LICENSE
// Copyright (c) 2016, Atılım Güneş Baydin
package com.nonononoki.alovoa.lib;
import java.awt.BasicStroke;
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.geom.AffineTransform;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
//import java.security.SecureRandom;
import java.util.Random;
import javax.imageio.ImageIO;
public class OxCaptcha {
private static final Random RAND = new SecureRandom();
// private static final Random RAND = new Random();
private BufferedImage _img;
private Graphics2D _img_g;
private int _width;
private int _height;
// private BufferedImage _bg;
private Color _bg_color;
private Color _fg_color;
private char[] _chars = new char[] {};
private int _length = 0;
private boolean _hollow;
private Font _font;
private FontRenderContext _fontRenderContext;
private static char[] _charSet = 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' };
public OxCaptcha(int width, int height) {
_img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
_img_g = _img.createGraphics();
_hollow = false;
_font = new Font("Arial", Font.PLAIN, 40);
_img_g.setFont(_font);
_fontRenderContext = _img_g.getFontRenderContext();
_bg_color = Color.WHITE;
_fg_color = Color.BLACK;
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
_img_g.setRenderingHints(hints);
_width = width;
_height = height;
}
public void setCharSet(char[] charSet) {
_charSet = charSet;
}
public void setFont(String name) {
setFont(name, Font.PLAIN, 40);
}
public void setFont(String name, int style, int size) {
_font = new Font(name, style, size);
_img_g.setFont(_font);
_fontRenderContext = _img_g.getFontRenderContext();
}
public void setHollow() {
_hollow = true;
}
public void background() {
background(_bg_color);
}
public void background(Color color) {
_bg_color = color;
_img_g.setPaint(color);
_img_g.fillRect(0, 0, _width, _height);
}
public void foreground(Color color) {
_fg_color = color;
}
public void text() {
text(5);
}
public void text(int length) {
text(genText(length));
}
public static char[] genText(int length) {
char[] t = new char[length];
for (int i = 0; i < length; i++) {
t[i] = _charSet[RAND.nextInt(_charSet.length)];
}
return t;
}
public void text(String chars) {
text(chars, (int) (0.05 * _width), (int) (0.75 * _height), 0);
}
public void text(String chars, int kerning) {
text(chars, (int) (0.05 * _width), (int) (0.75 * _height), kerning);
}
public void text(char[] chars) {
text(chars, (int) (0.05 * _width), (int) (0.75 * _height), 0);
}
public void text(String chars, int xOffset, int yOffset, int kerning) {
int l = chars.length();
char[] t = new char[l];
chars.getChars(0, l, t, 0);
text(t, xOffset, yOffset, kerning);
}
public void text(char[] chars, int xOffset, int yOffset, int kerning) {
text(chars, xOffset, yOffset, Font.PLAIN, kerning);
}
public void text(String chars, int xOffset, int yOffset, int style, int kerning) {
int l = chars.length();
char[] t = new char[l];
chars.getChars(0, l, t, 0);
int styles[] = new int[t.length];
for (int i = 0; i < t.length; i++) {
styles[i] = style;
}
text(t, styles, xOffset, yOffset, kerning);
}
public void text(char[] chars, int xOffset, int yOffset, int style, int kerning) {
int styles[] = new int[chars.length];
for (int i = 0; i < chars.length; i++) {
styles[i] = style;
}
text(chars, styles, xOffset, yOffset, kerning);
}
public void text(char[] chars, int[] styles, int xOffset, int yOffset, int kerning) {
int xn[] = new int[chars.length];
for (int i = 0; i < chars.length; i++) {
xn[i] = kerning;
}
int yn[] = new int[chars.length];
xn[0] = xOffset;
yn[0] = yOffset;
textRelative(chars, styles, xn, yn);
}
public void textRelative(char[] chars, int[] xOffsets, int[] yOffsets) {
int styles[] = new int[chars.length];
for (int i = 0; i < chars.length; i++) {
styles[i] = Font.PLAIN;
}
textRelative(chars, styles, xOffsets, yOffsets);
}
public void textCentered(String chars, int kerning) {
int l = chars.length();
char[] t = new char[l];
chars.getChars(0, l, t, 0);
int styles[] = new int[l];
for (int i = 0; i < l; i++) {
styles[i] = Font.PLAIN;
}
textCentered(t, styles, kerning);
}
public void textCentered(String chars, int style, int kerning) {
int l = chars.length();
char[] t = new char[l];
chars.getChars(0, l, t, 0);
int styles[] = new int[l];
for (int i = 0; i < l; i++) {
styles[i] = style;
}
textCentered(t, styles, kerning);
}
// Add letters with relative per letter positioning
// Offsets give the position of each letter relative to the top right of the
// previous letter
// The offsets of the first letter are relative to the top left of the image
// (For an image 50 pixels high, it's a good idea to start the first y offset
// around 30, so that the text is inside the image)
// x increases from left to right
// y increases from top to bottom
public void textRelative(char[] chars, int[] styles, int[] xOffsets, int[] yOffsets) {
_chars = chars;
_length = _chars.length;
_img_g.setColor(_fg_color);
int x = 0;
int y = 0;
char[] cc = new char[1];
GlyphVector gv;
int gvWidth;
for (int i = 0; i < _length; i++) {
x = x + xOffsets[i];
y = y + yOffsets[i];
cc[0] = _chars[i];
_font = _font.deriveFont(styles[i]);
_img_g.setFont(_font);
_fontRenderContext = _img_g.getFontRenderContext();
renderChar(cc, x, y);
gv = _font.createGlyphVector(_fontRenderContext, cc);
gvWidth = (int) gv.getVisualBounds().getWidth();
x = x + gvWidth + 1;
}
_font = _font.deriveFont(Font.PLAIN);
}
// Add letters with absolute per letter positioning.
// xs and ys are absolute positions of letters in chars
// The offsets of the first letter are relative to the top left of the image
// (For an image 50 pixels high, it's a good idea to start the first y offset
// around 30, so that the text is inside the image)
// x increases from left to right
// y increases from top to bottom
public void textAbsolute(char[] chars, int[] styles, int[] xs, int[] ys) {
_chars = chars;
_length = _chars.length;
_img_g.setColor(_fg_color);
char[] cc = new char[1];
for (int i = 0; i < _length; i++) {
cc[0] = _chars[i];
_font = _font.deriveFont(styles[i]);
_img_g.setFont(_font);
_fontRenderContext = _img_g.getFontRenderContext();
renderChar(cc, xs[i], ys[i]);
}
_font = _font.deriveFont(Font.PLAIN);
}
public void textCentered(char[] chars, int[] styles, int kerning) {
_chars = chars;
_length = _chars.length;
char[] cc = new char[1];
GlyphVector gv;
int[] gvWidths = new int[_length];
int[] gvHeights = new int[_length];
int width = 0;
int height = 0;
for (int i = 0; i < _length; i++) {
cc[0] = _chars[i];
_font = _font.deriveFont(styles[i]);
_img_g.setFont(_font);
_fontRenderContext = _img_g.getFontRenderContext();
gv = _font.createGlyphVector(_fontRenderContext, cc);
gvWidths[i] = (int) gv.getVisualBounds().getWidth();
gvHeights[i] = (int) gv.getVisualBounds().getHeight();
if (gvHeights[i] > height) {
height = gvHeights[i];
}
width = width + gvWidths[i] + kerning + 1;
}
int x0 = (_width - width) / 2;
int y0 = height + (_height - height) / 2;
int x = x0;
_img_g.setColor(_fg_color);
for (int i = 0; i < _length; i++) {
cc[0] = _chars[i];
_font = _font.deriveFont(styles[i]);
_img_g.setFont(_font);
renderChar(cc, x, y0);
x = x + gvWidths[i] + kerning + 1;
}
_font = _font.deriveFont(Font.PLAIN);
}
public void renderString(String s, int x, int y) {
_img_g.setColor(_fg_color);
_img_g.drawString(s, x, y);
}
private void renderChar(char[] cc, int x, int y) {
if (_hollow) {
_img_g.drawChars(cc, 0, 1, x - 1, y - 1);
_img_g.drawChars(cc, 0, 1, x - 1, y + 1);
_img_g.drawChars(cc, 0, 1, x + 1, y - 1);
_img_g.drawChars(cc, 0, 1, x + 1, y + 1);
_img_g.setColor(_bg_color);
_img_g.drawChars(cc, 0, 1, x, y);
_img_g.setColor(_fg_color);
} else {
_img_g.drawChars(cc, 0, 1, x, y);
}
}
public void blur() {
blur(3);
}
public void blur(int kernelSize) {
float[] k = new float[kernelSize * kernelSize];
for (int i = 0; i < kernelSize; i++) {
k[i] = 1f / ((float) (kernelSize));
}
Kernel kernel = new Kernel(kernelSize, kernelSize, k);
BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
_img = op.filter(_img, null);
_img_g = _img.createGraphics();
_img_g.setFont(_font);
}
private ConvolveOp gbConvolve(int radius, float sigma, boolean horizontal) {
int size = radius * 2 + 1;
float[] vals = new float[size];
float twoSigmaSq = 2.0f * sigma * sigma;
float sqrtPiTwoSigmaSq = (float) Math.sqrt(twoSigmaSq * Math.PI);
float sum = 0.0f;
for (int i = -radius; i <= radius; i++) {
float distance = (float) i * i;
int index = i + radius;
vals[index] = (float) Math.exp(-distance / twoSigmaSq) / sqrtPiTwoSigmaSq;
sum += vals[index];
}
if (sum != 0) {
for (int i = 0; i < size; i++) {
vals[i] /= sum;
}
}
Kernel kernel = null;
if (horizontal) {
kernel = new Kernel(size, 1, vals);
} else {
kernel = new Kernel(1, size, vals);
}
return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
}
public void blurGaussian(double sigma) {
blurGaussian(2, sigma);
}
public void blurGaussian(int radius, double sigma) {
if (radius < 1) {
throw new IllegalArgumentException("radius must be greater than 1");
}
BufferedImageOp op = gbConvolve(radius, (float) sigma, true);
_img = op.filter(_img, null);
op = gbConvolve(radius, (float) sigma, false);
_img = op.filter(_img, null);
_img_g = _img.createGraphics();
_img_g.setFont(_font);
}
public void blurGaussian3x3() {
float[] k = new float[] { 1f / 16f, 1f / 8f, 1f / 16f, 1f / 8f, 1f / 4f, 1f / 8f, 1f / 16f, 1f / 8f, 1f / 16f };
Kernel kernel = new Kernel(3, 3, k);
BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
_img = op.filter(_img, null);
_img_g = _img.createGraphics();
_img_g.setFont(_font);
}
public void blurGaussian5x5s1() {
float[] k = new float[] { 1f / 273f, 4f / 273f, 7f / 273f, 4f / 273f, 1f / 273f, 4f / 273f, 16f / 273f,
26f / 273f, 16f / 273f, 4f / 273f, 7f / 273f, 26f / 273f, 41f / 273f, 26f / 273f, 7f / 273f, 4f / 273f,
16f / 273f, 26f / 273f, 16f / 273f, 4f / 273f, 1f / 273f, 4f / 273f, 7f / 273f, 4f / 273f, 1f / 273f };
Kernel kernel = new Kernel(5, 5, k);
BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
_img = op.filter(_img, null);
_img_g = _img.createGraphics();
_img_g.setFont(_font);
}
public void blurGaussian5x5s2() {
float[] k = new float[] { 0.023528f, 0.033969f, 0.038393f, 0.033969f, 0.023528f, 0.033969f, 0.049045f,
0.055432f, 0.049045f, 0.033969f, 0.038393f, 0.055432f, 0.062651f, 0.055432f, 0.038393f, 0.033969f,
0.049045f, 0.055432f, 0.049045f, 0.033969f, 0.023528f, 0.033969f, 0.038393f, 0.033969f, 0.023528f };
Kernel kernel = new Kernel(5, 5, k);
BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
_img = op.filter(_img, null);
_img_g = _img.createGraphics();
_img_g.setFont(_font);
}
public void noiseCurvedLine() {
noiseCurvedLine(5, _width, 2.0f, _fg_color);
}
public void noiseCurvedLine(float thickness) {
noiseCurvedLine(5, _width, thickness, _fg_color);
}
public void noiseCurvedLine(int xOffset, int xRange) {
noiseCurvedLine(xOffset, xRange, 2.0f, _fg_color);
}
public void noiseCurvedLine(int xOffset, int xRange, float thickness) {
noiseCurvedLine(xOffset, xRange, thickness, _fg_color);
}
public void noiseCurvedLine(int xOffset, int xRange, float thickness, Color color) {
// the curve from where the points are taken
CubicCurve2D cc = new CubicCurve2D.Float(xOffset + RAND.nextFloat() * _width * 0.25f,
_height * RAND.nextFloat(), xOffset + RAND.nextFloat() * _width * 0.25f, _height * RAND.nextFloat(),
xOffset + xRange * 0.4f, _height * RAND.nextFloat(),
xOffset + xRange * (0.8f + RAND.nextFloat() * 0.2f), _height * RAND.nextFloat());
// creates an iterator to define the boundary of the flattened curve
PathIterator pi = cc.getPathIterator(null, 0.1);
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];
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
tmp[i] = new Point2D.Float(coords[0], coords[1]);
}
i++;
pi.next();
}
// the points where the line changes the stroke and direction
Point2D[] pts = new Point2D[i];
// copies points from tmp to pts
System.arraycopy(tmp, 0, pts, 0, i);
_img_g.setColor(color);
for (i = 0; i < pts.length - 1; i++) {
// for the maximum 3 point change the stroke and direction
if (i < 3) {
_img_g.setStroke(new BasicStroke(thickness));
}
_img_g.drawLine((int) pts[i].getX(), (int) pts[i].getY(), (int) pts[i + 1].getX(), (int) pts[i + 1].getY());
}
}
public void noiseStrokes() {
noiseStrokes(8, 1.5f);
}
public void noiseStrokes(int strokes, float width) {
_img_g.setStroke(new BasicStroke(width));
_img_g.setColor(_fg_color);
for (int i = 0; i < strokes; i++) {
Path2D.Double path = new Path2D.Double();
path.moveTo(RAND.nextInt(_width), RAND.nextInt(_height));
path.curveTo(RAND.nextInt(_width), RAND.nextInt(_height), RAND.nextInt(_width), RAND.nextInt(_height),
RAND.nextInt(_width), RAND.nextInt(_height));
_img_g.draw(path);
}
}
public void noiseEllipses(int ellipses, float width) {
_img_g.setStroke(new BasicStroke(width));
_img_g.setColor(_bg_color);
for (int i = 0; i < ellipses; i++) {
Ellipse2D.Double ellipse = new Ellipse2D.Double(RAND.nextInt(_width), RAND.nextInt(_height),
RAND.nextInt(_width), RAND.nextInt(_height));
_img_g.draw(ellipse);
}
}
public void noiseStraightLine() {
noiseStraightLine(_fg_color, 3.0f);
}
public void noiseStraightLine(Color color, float thickness) {
int y1 = RAND.nextInt(_height) + 1;
int y2 = RAND.nextInt(_height) + 1;
int x1 = 0;
int x2 = _width;
// The thick line is in fact a filled polygon
_img_g.setColor(color);
int dX = x2 - x1;
int dY = y2 - y1;
// line length
double lineLength = Math.sqrt((double) dX * dX + dY * dY);
double scale = thickness / (2 * lineLength);
// The x and y increments from an endpoint needed to create a
// rectangle...
double ddx = -scale * dY;
double ddy = scale * dX;
ddx += ddx > 0 ? 0.5 : -0.5;
ddy += ddy > 0 ? 0.5 : -0.5;
int dx = (int) ddx;
int dy = (int) ddy;
// Now we can compute the corner points...
int xPoints[] = new int[4];
int yPoints[] = new int[4];
xPoints[0] = x1 + dx;
yPoints[0] = y1 + dy;
xPoints[1] = x1 - dx;
yPoints[1] = y1 - dy;
xPoints[2] = x2 - dx;
yPoints[2] = y2 - dy;
xPoints[3] = x2 + dx;
yPoints[3] = y2 + dy;
_img_g.fillPolygon(xPoints, yPoints, 4);
}
public void noiseSaltPepper() {
noiseSaltPepper(0.01f, 0.01f);
}
public void noiseSaltPepper(float salt, float pepper) {
int s = (int) (_height * _width * salt);
int p = (int) (_height * _width * pepper);
_img_g.setStroke(new BasicStroke(1));
_img_g.setColor(Color.WHITE);
for (int i = 0; i < s; i++) {
int x = (int) (RAND.nextFloat() * _width);
int y = (int) (RAND.nextFloat() * _height);
_img_g.drawLine(x, y, x, y);
}
_img_g.setColor(Color.BLACK);
for (int i = 0; i < p; i++) {
int x = (int) (RAND.nextFloat() * _width);
int y = (int) (RAND.nextFloat() * _height);
_img_g.drawLine(x, y, x, y);
}
}
public void noiseWhiteGaussian() {
noiseWhiteGaussian(1.0);
}
public void noiseWhiteGaussian(double sigma) {
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
int p = _img.getRGB(x, y) & 0xFF;
p = (int) (((double) p) + sigma * RAND.nextGaussian());
p = Math.max(0, Math.min(255, p));
_img.setRGB(x, y, new Color(p, p, p).getRGB());
}
}
}
public void noiseWhiteUniform() {
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
int p = _img.getRGB(x, y) & 0xFF;
p = (int) (((double) p) + RAND.nextInt(256));
p = Math.max(0, Math.min(255, p));
_img.setRGB(x, y, new Color(p, p, p).getRGB());
}
}
}
public void distortion() {
distortionShear();
}
public void distortionFishEye() {
// Color hColor = Color.BLACK;
// Color vColor = Color.BLACK;
float thickness = 1.0f;
_img_g.setStroke(new BasicStroke(thickness));
// int hstripes = _height / 7;
// int vstripes = _width / 7;
//
// // Calculate space between lines
// int hspace = _height / (hstripes + 1);
// int vspace = _width / (vstripes + 1);
//
// // Draw the horizontal stripes
// for (int i = hspace; i < _height; i = i + hspace) {
// _img_g.setColor(hColor);
// _img_g.drawLine(0, i, _width, i);
// }
//
// // Draw the vertical stripes
// for (int i = vspace; i < _width; i = i + vspace) {
// _img_g.setColor(vColor);
// _img_g.drawLine(i, 0, i, _height);
// }
// Create a pixel array of the original image.
// we need this later to do the operations on..
int pix[] = new int[_height * _width];
int j = 0;
for (int j1 = 0; j1 < _width; j1++) {
for (int k1 = 0; k1 < _height; k1++) {
pix[j] = _img.getRGB(j1, k1);
j++;
}
}
double distance = ranInt(_width / 4, _width / 3);
// put the distortion in the (dead) middle
int wMid = _width / 2;
int hMid = _height / 2;
// again iterate over all pixels..
for (int x = 0; x < _width; x++) {
for (int y = 0; y < _height; y++) {
int relX = x - wMid;
int relY = y - hMid;
double d1 = Math.sqrt((double) relX * relX + relY * relY);
if (d1 < distance) {
int j2 = wMid + (int) (((fishEyeFormula(d1 / distance) * distance) / d1) * (x - wMid));
int k2 = hMid + (int) (((fishEyeFormula(d1 / distance) * distance) / d1) * (y - hMid));
_img.setRGB(x, y, pix[j2 * _height + k2]);
}
}
}
}
public void distortionStretch() {
double xScale = RAND.nextDouble() * 2;
double yScale = RAND.nextDouble() * 2;
distortionStretch(xScale, yScale);
}
public void distortionStretch(double xScale, double yScale) {
AffineTransform at = new AffineTransform();
at.scale(xScale, yScale);
_img_g.drawRenderedImage(_img, at);
}
public void distortionElectric() {
distortionElectric(4000, 2, 3);
}
public void distortionElectric(int amount, int shift, int size) {
for (int i = 0; i < amount / 2; i++) {
int x = RAND.nextInt(_width);
int y = RAND.nextInt(_height);
int s = RAND.nextInt(shift);
_img_g.copyArea(x + s, y, size, 1, s, 0);
_img_g.copyArea(x, y, size, 1, s, 0);
}
for (int i = 0; i < amount / 2; i++) {
int x = RAND.nextInt(_width);
int y = RAND.nextInt(_height);
int s = RAND.nextInt(shift);
_img_g.copyArea(x, y + s, 1, size, 0, s);
_img_g.copyArea(x, y, 1, size, 0, s);
}
}
public void distortionElectric2() {
distortionElectric2(24);
}
public void distortionElectric2(double alpha) {
int s[][] = getImageArray2D();
double source[][] = new double[_height][_width];
double dxField[][] = new double[_height][_width];
double dyField[][] = new double[_height][_width];
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
dxField[y][x] = 2 * (RAND.nextDouble() - 0.5);
dyField[y][x] = 2 * (RAND.nextDouble() - 0.5);
source[y][x] = (double) s[y][x];
}
}
dxField = OxCaptcha.gaussian(dxField, 2, 2.2);
dyField = OxCaptcha.gaussian(dyField, 2, 2.2);
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
double dx = dxField[y][x] * alpha;
double dy = dyField[y][x] * alpha;
double sx = (double) x + dx;
double sy = (double) y + dy;
if (sx < 0 || sx > _width - 2 || sy < 0 || sy > _height - 2) {
_img.setRGB(x, y, _bg_color.getRGB());
} else {
int sxleft = (int) Math.floor(sx);
int sxright = sxleft + 1;
double sxdist = sx % 1;
int sytop = (int) Math.floor(sy);
int sybottom = sytop + 1;
double sydist = sy % 1;
double top = (1. - sxdist) * source[sytop][sxleft] + sxdist * source[sytop][sxright];
double bottom = (1. - sxdist) * source[sybottom][sxleft] + sxdist * source[sybottom][sxright];
double target = (1. - sydist) * top + sydist * bottom;
int t = Math.max(Math.min((int) target, 255), 0);
// double target = (dyField[y][x] + 1) * 128;
// System.out.println(target);
_img.setRGB(x, y, new Color(t, t, t).getRGB());
}
}
}
}
public void distortionShear2() {
int xPhase = -_width + 2 * RAND.nextInt(_width);
int xPeriod = 6 + RAND.nextInt(30);
int xAmplitude = 2 + RAND.nextInt(7);
int yPhase = -_height + 2 * RAND.nextInt(_height);
int yPeriod = 10 + RAND.nextInt(20);
int yAmplitude = 2 + RAND.nextInt(15);
distortionShear2(xPhase, xPeriod, xAmplitude, yPhase, yPeriod, yAmplitude);
}
public void distortionShear2(int xPhase, int xPeriod, int xAmplitude, int yPhase, int yPeriod, int yAmplitude) {
for (int i = 0; i < _width; i++) {
int dst_x = i - 1;
int dst_y = (int) (Math.sin((double) (xPhase + i) / (double) xPeriod) * xAmplitude);
int src_x = i;
int src_y = 0;
int src_w = 1;
int src_h = _height;
int dx = dst_x - src_x;
int dy = dst_y - src_y;
_img_g.copyArea(src_x, src_y, src_w, src_h, dx, dy);
_img_g.setColor(_bg_color);
if (dy >= 0) {
_img_g.drawLine(i, 0, i, dy);
} else {
_img_g.drawLine(i, _height + dy, i, _height);
}
_img_g.setColor(_fg_color);
}
for (int i = 0; i < _height; i++) {
int dst_x = (int) (Math.sin((double) (yPhase + i) / (double) yPeriod) * yAmplitude);
int dst_y = i - 1;
int src_x = 0;
int src_y = i;
int src_w = _width;
int src_h = 1;
int dx = dst_x - src_x;
int dy = dst_y - src_y;
_img_g.copyArea(src_x, src_y, src_w, src_h, dx, dy);
_img_g.setColor(_bg_color);
if (dx >= 0) {
_img_g.drawLine(0, i, dx, i);
} else {
_img_g.drawLine(_width + dx, i, _width, i);
}
_img_g.setColor(_fg_color);
}
}
public void distortionShear() {
int xPeriod = RAND.nextInt(10) + 8;
int xPhase = RAND.nextInt(8) + 8;
int yPeriod = RAND.nextInt(10) + 8;
int yPhase = RAND.nextInt(8) + 8;
distortionShear(xPeriod, xPhase, yPeriod, yPhase);
}
public void distortionShear(int xPeriod, int xPhase, int yPeriod, int yPhase) {
shearX(_img_g, xPeriod, xPhase, _width, _height);
shearY(_img_g, yPeriod, yPhase, _width, _height);
}
public void distortionElastic() {
distortionElastic(38);
}
public void distortionElastic(double alpha) {
distortionElastic(alpha, 11, 8);
}
public void distortionElastic(double alpha, int kernelSize, double sigma) {
int s[][] = getImageArray2D();
double source[][] = new double[_height][_width];
double dxField[][] = new double[_height][_width];
double dyField[][] = new double[_height][_width];
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
dxField[y][x] = 2 * (RAND.nextDouble() - 0.5);
dyField[y][x] = 2 * (RAND.nextDouble() - 0.5);
if (RAND.nextDouble() < 0.1) {
dxField[y][x] = dxField[y][x] * 5;
}
if (RAND.nextDouble() < 0.1) {
dyField[y][x] = dyField[y][x] * 5;
}
source[y][x] = (double) s[y][x];
}
}
dxField = OxCaptcha.gaussian(dxField, kernelSize, sigma);
dyField = OxCaptcha.gaussian(dyField, kernelSize, sigma);
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
double dx = dxField[y][x] * alpha;
double dy = dyField[y][x] * alpha;
double sx = (double) x + dx;
double sy = (double) y + dy;
if (sx < 0 || sx > _width - 2 || sy < 0 || sy > _height - 2) {
_img.setRGB(x, y, _bg_color.getRGB());
} else {
int sxleft = (int) Math.floor(sx);
int sxright = sxleft + 1;
double sxdist = sx % 1;
int sytop = (int) Math.floor(sy);
int sybottom = sytop + 1;
double sydist = sy % 1;
double top = (1. - sxdist) * source[sytop][sxleft] + sxdist * source[sytop][sxright];
double bottom = (1. - sxdist) * source[sybottom][sxleft] + sxdist * source[sybottom][sxright];
double target = (1. - sydist) * top + sydist * bottom;
int t = Math.max(Math.min((int) target, 255), 0);
// double target = (dyField[y][x] + 1) * 128;
// System.out.println(target);
_img.setRGB(x, y, new Color(t, t, t).getRGB());
}
}
}
}
public void normalize() {
int p[] = getImageArray1D();
int pmin = p[0];
int pmax = p[0];
for (int i = 0; i < p.length; i++) {
if (p[i] < pmin) {
pmin = p[i];
} else if (p[i] > pmax) {
pmax = p[i];
}
}
int prange = pmax - pmin;
if (prange > 2) {
int i = 0;
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
int pp = 255 * (p[i++] - pmin) / prange;
_img.setRGB(x, y, new Color(pp, pp, pp).getRGB());
}
}
}
}
public void recenter() {
int p[][] = getImageArray2D();
int pb = p[0][0];
int xmin = _width - 1;
int xmax = 0;
int ymin = _height - 1;
int ymax = 0;
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
if (p[y][x] != pb) {
if (x < xmin) {
xmin = x;
}
if (x > xmax) {
xmax = x;
}
if (y < ymin) {
ymin = y;
}
if (y > ymax) {
ymax = y;
}
}
}
}
int w = xmax - xmin + 1;
int h = ymax - ymin + 1;
if (w > 0 && h > 0) {
int xt = (_width - w) / 2;
int yt = (_height - h) / 2;
BufferedImage b = _img.getSubimage(xmin, ymin, w, h);
BufferedImage b2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
b2.createGraphics().drawImage(b, 0, 0, null);
_img_g.setColor(_bg_color);
_img_g.fillRect(0, 0, _width, _height);
_img_g.drawImage(b2, xt, yt, null);
}
}
public int[][] load(String fileName) throws IOException {
BufferedImage i = ImageIO.read(new File(fileName));
int height = i.getHeight();
int width = i.getWidth();
int ret[][] = new int[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
ret[y][x] = i.getRGB(x, y) & 0xFF;
}
}
return ret;
}
public void save(int[] pixels, int width, int height, String fileName) throws IOException {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int i = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
img.setRGB(x, y, new Color(pixels[i], pixels[i], pixels[i]).getRGB());
i++;
}
}
ImageIO.write(img, "webp", new File(fileName));
}
public void save(int[][] pixels, String fileName) throws IOException {
int height = pixels.length;
int width = pixels[0].length;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
img.setRGB(x, y, new Color(pixels[x][y], pixels[x][y], pixels[x][y]).getRGB());
}
}
ImageIO.write(img, "webp", new File(fileName));
}
public String getText() {
return new String(_chars);
}
public BufferedImage getImage() {
return _img;
}
public int[][] getImageArray2D() {
int ret[][] = new int[_height][_width];
for (int x = 0; x < _width; x++) {
for (int y = 0; y < _height; y++) {
int p = _img.getRGB(x, y);
int red = (p >> 16) & 0xff;
ret[y][x] = red;
}
}
return ret;
}
public int[] getImageArray1D() {
int ret[] = new int[_height * _width];
int i = 0;
for (int y = 0; y < _height; y++)
for (int x = 0; x < _width; x++) {
{
int p = _img.getRGB(x, y);
int red = (p >> 16) & 0xff;
ret[i++] = red;
}
}
return ret;
}
public void save(String fileName) throws IOException {
ImageIO.write(_img, "webp", new File(fileName));
}
private int ranInt(int i, int j) {
double d = RAND.nextDouble();
return (int) (i + ((j - i) + 1) * d);
}
private double fishEyeFormula(double s) {
// implementation of:
// g(s) = - (3/4)s3 + (3/2)s2 + (1/4)s, with s from 0 to 1.
if (s < 0.0D) {
return 0.0D;
}
if (s > 1.0D) {
return s;
}
return -0.75D * s * s * s + 1.5D * s * s + 0.25D * s;
}
/*
* private static final void applyFilter(BufferedImage img, ImageFilter filter)
* { FilteredImageSource src = new FilteredImageSource(img.getSource(), filter);
* Image fImg = Toolkit.getDefaultToolkit().createImage(src); Graphics2D g =
* img.createGraphics(); g.drawImage(fImg, 0, 0, null, null); //g.dispose(); }
*/
private void shearX(Graphics2D g, int period, int phase, int width, int height) {
int frames = 15;
for (int i = 0; i < height; i++) {
double d = (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * phase) / frames);
g.copyArea(0, i, width, 1, (int) d, 0);
g.setColor(_bg_color);
if (d >= 0) {
g.drawLine(0, i, (int) d, i);
} else {
g.drawLine(width + (int) d, i, width, i);
}
g.setColor(_fg_color);
}
}
private void shearY(Graphics2D g, int period, int phase, int width, int height) {
int frames = 15;
for (int i = 0; i < width; i++) {
double d = (period >> 1) * Math.sin((float) i / period + (6.2831853071795862D * phase) / frames);
g.copyArea(i, 0, 1, height, 0, (int) d);
g.setColor(_bg_color);
if (d >= 0) {
g.drawLine(i, 0, i, (int) d);
} else {
g.drawLine(i, height + (int) d, i, height);
}
g.setColor(_fg_color);
}
}
public static double singlePixelConvolution(double[][] input, int x, int y, double[][] k, int kernelWidth,
int kernelHeight) {
double output = 0;
for (int i = 0; i < kernelWidth; ++i) {
for (int j = 0; j < kernelHeight; ++j) {
output = output + (input[x + i][y + j] * k[i][j]);
}
}
return output;
}
public static double[][] convolution2D(double[][] input, int width, int height, double[][] kernel, int kernelWidth,
int kernelHeight) {
int smallWidth = width - kernelWidth + 1;
int smallHeight = height - kernelHeight + 1;
double[][] output = new double[smallWidth][smallHeight];
for (int i = 0; i < smallWidth; ++i) {
for (int j = 0; j < smallHeight; ++j) {
output[i][j] = 0;
}
}
for (int i = 0; i < smallWidth; ++i) {
for (int j = 0; j < smallHeight; ++j) {
output[i][j] = singlePixelConvolution(input, i, j, kernel, kernelWidth, kernelHeight);
// if (i==32- kernelWidth + 1 && j==100- kernelHeight + 1)
// System.out.println("Convolve2D: "+output[i][j]);
}
}
return output;
}
public static double[][] convolution2DPadded(double[][] input, int width, int height, double[][] kernel,
int kernelWidth, int kernelHeight) {
int smallWidth = width - kernelWidth + 1;
int smallHeight = height - kernelHeight + 1;
int top = kernelHeight / 2;
int left = kernelWidth / 2;
double small[][] = new double[smallWidth][smallHeight];
small = convolution2D(input, width, height, kernel, kernelWidth, kernelHeight);
double large[][] = new double[width][height];
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
large[i][j] = 0;
}
}
for (int j = 0; j < smallHeight; ++j) {
for (int i = 0; i < smallWidth; ++i) {
// if (i+left==32 && j+top==100) System.out.println("Convolve2DP:
// "+small[i][j]);
large[i + left][j + top] = small[i][j];
}
}
return large;
}
public static double gaussianDiscrete2D(double theta, int x, int y) {
double g = 0;
for (double ySubPixel = y - 0.5; ySubPixel < y + 0.55; ySubPixel += 0.1) {
for (double xSubPixel = x - 0.5; xSubPixel < x + 0.55; xSubPixel += 0.1) {
g = g + ((1 / (2 * Math.PI * theta * theta))
* Math.pow(Math.E, -(xSubPixel * xSubPixel + ySubPixel * ySubPixel) / (2 * theta * theta)));
}
}
g = g / 121;
return g;
}
public static double[][] gaussian2D(double theta, int size) {
double[][] kernel = new double[size][size];
for (int j = 0; j < size; ++j) {
for (int i = 0; i < size; ++i) {
kernel[i][j] = gaussianDiscrete2D(theta, i - (size / 2), j - (size / 2));
}
}
double sum = 0;
for (int j = 0; j < size; ++j) {
for (int i = 0; i < size; ++i) {
sum = sum + kernel[i][j];
}
}
return kernel;
}
public static double[][] gaussian(double[][] input, int ks, double sigma) {
int width = input.length;
int height = input[0].length;
double[][] gaussianKernel = new double[ks][ks];
double[][] output = new double[width][height];
gaussianKernel = gaussian2D(sigma, ks);
output = convolution2DPadded(input, width, height, gaussianKernel, ks, ks);
return output;
}
}