4chan/twister/twister_captcha.php
2025-04-17 18:12:08 -05:00

2582 lines
71 KiB
PHP

<?php
/**
* Twister Captcha
*/
class TwisterCaptcha {
private
$font_id = 0,
$font_width = 0,
$font_height = 0,
$font_scale = 1,
$font_spacing = 0,
$difficulty = null,
$char_dx_ratio = 0,
$char_dy_ratio = 0,
$noise_dy_ratio_min = 0,
$noise_dy_ratio_max = 0,
$blotFilter = IMG_FILTER_EDGEDETECT,
$useScoreLines = false,
$useGridLines = false,
$useInkBlot = false,
$useEdgeBlock = false,
$useFakeCharPadding = false,
$useFenceNoise = false,
$useTopBottomY = false,
$useInvert = false,
$useStaticRot = false,
$useOverlayId = 0,
$useOverlayDark = true,
$useAltBlackWhite = false,
$useSimplexBg = false,
$useEmboss = false,
$useSpecialRot = false,
$useExtraSpaces = false,
$useEdgeDetect = false,
$useMeanRemoval = false
;
const CHAR_LIST = 'ADGHJKMNPRSTVWXY0248';
const CHAR_LIST_SPLIT = 'ADGHJKMNTVWXY4';
/**
* B0 = dots
* F7 = double ~
* 1A / 1B = arrow right / left
* A9 / AA = L rotated right / left
*/
const CHAR_NOISE = "\xF7\x1A\x1B\xA9\xAA+<";
const CHAR_NOISE_OVERLAY = "\xB0";
const IMG_WIDTH_MAX = 300;
const IMG_HEIGHT = 80;
const SHEAR_X_MIN = 3;
const SHEAR_X_MAX = 6;
const SHEAR_Y_MIN = 14;
const SHEAR_Y_MAX = 30;
const SCALE_X_MIN = 0;
const SCALE_X_MAX = 80;
const SCALE_Y_MIN = 0;
const SCALE_Y_MAX = 15;
const SCALE_MAX = 80;
const CHAR_DY_RATIO = 0.10;
const NOISE_DY_RATIO_MIN = 0.380;
const NOISE_DY_RATIO_MAX = 0.460;
const NOISE_OFS_X_RATIO_MIN = 1.30;
const NOISE_OFS_X_RATIO_MAX = 1.50;
const LEVEL_EASY = 1;
const LEVEL_NORMAL = 2;
const LEVEL_HARD = 3;
const LEVEL_LUNATIC = 4;
const CHALLENGE_ID_BYTES = 12;
// 32 bytes for SHA256
private static $hmac_secret = '2Lm9j17D2SKElS1/s5SQCzIH1E031gSfKarbLNycerA=';
public function __construct($font_file) {
$font_id = imageloadfont($font_file);
if ($font_id) {
$this->font_id = $font_id;
$this->font_width = imagefontwidth($font_id);
$this->font_height = imagefontheight($font_id);
$this->setFontScale(0.62);
$this->setDyRatios(self::CHAR_DY_RATIO, self::NOISE_DY_RATIO_MIN, self::NOISE_DY_RATIO_MAX);
$this->setDifficulty(self::LEVEL_NORMAL);
}
}
public function __call($name, $args) {
return false;
}
public function setDifficulty($val) {
if ($val === $this->difficulty) {
return true;
}
if ($val === self::LEVEL_EASY) {
$this->char_dx_ratio = 0.210;
}
else if ($val === self::LEVEL_NORMAL) {
$this->char_dx_ratio = 0.218;
}
else if ($val === self::LEVEL_HARD) {
$this->char_dx_ratio = 0.240;
}
else if ($val === self::LEVEL_LUNATIC) {
$this->char_dx_ratio = 0.280;
}
else {
return false;
}
$this->difficulty = $val;
return true;
}
public function setFontScale($scale) {
if (!$scale || $scale <= 0) {
$scale = 0.6;
}
$this->font_scale = self::IMG_HEIGHT / ($this->font_height * (100 + self::SHEAR_Y_MAX / 2) / 100) * $scale;
}
public function setDyRatios($char, $noise_min, $noise_max) {
$char = (float)$char;
$noise_min = (float)$noise_min;
$noise_max = (float)$noise_max;
if ($char <= 0.0 || $noise_min <= 0.0 || $noise_max <= 0.0) {
return false;
}
$this->char_dy_ratio = $char;
$this->noise_dy_ratio_min = $noise_min;
$this->noise_dy_ratio_max = $noise_max;
}
private function debugOutputImage($img) {
header('Content-Type: image/png');
imagesavealpha($img, true);
imagepng($img);
die();
}
public function useJumpyMode($flag) {
if ($flag) {
$this->setDyRatios(0.5, 0.42, 0.5);
}
else {
$this->setDyRatios(self::CHAR_DY_RATIO, self::NOISE_DY_RATIO_MIN, self::NOISE_DY_RATIO_MAX);
}
}
public function useTopBottomY($flag) {
$this->useTopBottomY = (bool)$flag;
}
public function useInvert($flag) {
$this->useInvert = (bool)$flag;
}
public function useStaticRot($flag) {
$this->useStaticRot = (bool)$flag;
}
public function useFenceNoise($flag) {
$this->useFenceNoise = (bool)$flag;
}
public function useNegateBlotFilter($flag) {
if ($flag === true) {
$this->blotFilter = IMG_FILTER_NEGATE;
}
else {
$this->blotFilter = IMG_FILTER_EDGEDETECT;
}
}
public function useFakeCharPadding($flag) {
$this->useFakeCharPadding = (bool)$flag;
}
public function useScoreLines($flag) {
$this->useScoreLines = (bool)$flag;
}
public function useGridLines($flag) {
$this->useGridLines = (bool)$flag;
}
public function useInkBlot($flag) {
$this->useInkBlot = (bool)$flag;
}
public function useOverlayId($id, $dark = true) {
$this->useOverlayId = (int)$id;
$this->useOverlayDark = (bool)$dark;
}
public function useAltBlackWhite($flag) {
$this->useAltBlackWhite = (bool)$flag;
}
public function useSimplexBg($flag) {
$this->useSimplexBg = (bool)$flag;
}
public function useEmboss($flag) {
$this->useEmboss = (bool)$flag;
}
public function useSpecialRot($flag) {
$this->useSpecialRot = (bool)$flag;
}
public function useExtraSpaces($flag) {
$this->useExtraSpaces = (bool)$flag;
}
public function useEdgeDetect($flag) {
$this->useEdgeDetect = (bool)$flag;
}
public function useMeanRemoval($flag) {
$this->useMeanRemoval = (bool)$flag;
}
public function useEdgeBlock($flag) {
$this->useEdgeBlock = (bool)$flag;
if ($this->useEdgeBlock) {
$this->char_dx_ratio = 0.180;
}
else {
$this->setDifficulty($this->difficulty);
}
}
private function getRandomString($len) {
$chars_lim = strlen(self::CHAR_LIST) - 1;
$str = [];
for ($i = 0; $i < $len; $i++) {
$str[] = self::CHAR_LIST[mt_rand(0, $chars_lim)];
}
return implode('', $str);
}
private function getRandomStringPair($len, $offset = 1) {
$chars_lim = strlen(self::CHAR_LIST) - 1;
$str = [];
$strike = [];
for ($i = 0; $i < $len; $i++) {
$k = mt_rand(0, $chars_lim);
$str[] = self::CHAR_LIST[$k];
$k += $offset;
if ($k > $chars_lim) {
$k = 0;
}
else if ($k < 0) {
$k = $char_lim;
}
$strike[] = self::CHAR_LIST[$k];
}
return [ implode('', $str), implode('', $strike) ];
}
private function scaleImage($img, $w, $h) {
$img_scaled = imagescale($img, $w, $h, IMG_NEAREST_NEIGHBOUR);
return $img_scaled;
}
private function getScaleMatrix($x, $y) {
return imageaffinematrixget(IMG_AFFINE_SCALE, [$x, $y]);
}
private function getShearHMatrix($a) {
return imageaffinematrixget(IMG_AFFINE_SHEAR_HORIZONTAL, $a);
}
private function getShearVMatrix($a) {
return imageaffinematrixget(IMG_AFFINE_SHEAR_VERTICAL, $a);
}
private function getRotMatrix($a) {
return imageaffinematrixget(IMG_AFFINE_ROTATE, $a);
}
private function transformImage($img, $mat, $mat2 = null, $mat3 = null, $mat4 = null) {
if ($mat2) {
$mat = imageaffinematrixconcat($mat, $mat2);
}
if ($mat3) {
$mat = imageaffinematrixconcat($mat, $mat3);
}
if ($mat4) {
$mat = imageaffinematrixconcat($mat, $mat4);
}
$w = imagesx($img);
$h = imagesy($img);
$clip = ['x' => 0, 'y' => 0, 'width' => $w, 'height' => $h];
imagesetinterpolation($img, IMG_NEAREST_NEIGHBOUR);
$img = imageaffine($img, $mat, $clip);
return $img;
}
private function warpImage($img, $scale_x, $rotate) {
$shear_x =
[ 1, 0, (self::SHEAR_X_MIN + mt_rand(0, self::SHEAR_X_MAX)) * (mt_rand(0, 1) ? 1 : -1) / 100,
1, 0, 0 ]
;
$shear_y =
[ 1, (self::SHEAR_Y_MIN + mt_rand(0, self::SHEAR_Y_MAX)) * (mt_rand(0, 1) ? 1 : -1) / 100, 0,
1, 0, 0 ]
;
$scale =
[ $scale_x, 0, 0,
1.0 + (mt_rand(self::SCALE_Y_MIN, self::SCALE_Y_MAX) / 100), 0, 0 ]
;
if ($rotate) {
if ($this->difficulty > TwisterCaptcha::LEVEL_HARD) {
$da = 15;
}
else if ($this->difficulty > TwisterCaptcha::LEVEL_NORMAL) {
$da = 11;
}
else {
$da = 6;
}
$angle = mt_rand(-$da, $da) * M_PI / 180;
$cos = cos($angle);
$sin = sin($angle);
$rot = [ $cos, -$sin, $sin, $cos, 0, 0 ];
$mat = imageaffinematrixconcat($scale, $rot);
$mat = imageaffinematrixconcat($mat, $shear_x);
}
else {
$mat = imageaffinematrixconcat($scale, $shear_x);
}
$mat = imageaffinematrixconcat($mat, $shear_y);
$img_warped = imageaffine($img, $mat);
return [$img_warped, $scale_x];
}
private function cleanImageAlpha($img, $keep_true_color = false) {
imagetruecolortopalette($img, false, 2);
if ($keep_true_color) {
imagepalettetotruecolor($img);
}
else {
imagecolorset($img, 0, 0, 0, 0);
imagecolorset($img, 1, 238, 238, 238);
}
}
private function mergeCharImage($dest_img, $char_img, $dest_x, $dest_y, $clr, $alpha) {
$w = imagesx($char_img);
$h = imagesy($char_img);
imagealphablending($dest_img, true);
$txt_clr = imagecolorallocatealpha($dest_img, $clr, $clr, $clr, $alpha);
for ($x = 0; $x < $w; ++$x) {
for ($y = 0; $y < $h; ++$y) {
$c = imagecolorat($char_img, $x, $y);
$a = ($c >> 24) & 0xFF;
$b = $c & 0xFF;
if ($a < 1 && $b < 200) {
imagesetpixel($dest_img, $dest_x + $x, $dest_y + $y, $txt_clr);
}
}
}
return true;
}
private function flattenImageAlpha($img) {
imagetruecolortopalette($img, false, 3);
for ($i = 0; $i < 3; $i++) {
$c = imagecolorsforindex($img, $i);
if ($c['red'] < 128 && $c['green'] > 200) {
imagecolortransparent($img, $i);
}
else if ($c['blue'] > 140) {
imagecolorset($img, $i, 238, 238, 238);
}
else {
imagecolorset($img, $i, 0, 0, 0);
}
}
}
private function prepareCharImage() {
$img_char = imagecreate($this->font_width, $this->font_height);
$img_char_clr_txt = imagecolorallocate($img_char, 0, 0, 0);
$img_char_clr_bg = imagecolorallocate($img_char, 0, 255, 0);
imagecolortransparent($img_char, $img_char_clr_bg);
return [$img_char, $img_char_clr_txt, $img_char_clr_bg];
}
private function getCharImage($char, $height) {
if ($height < 1) {
return false;
}
$w = $this->font_width;
$h = $this->font_height;
// Create image
$img = imagecreate($w, $h);
$clr_bg = imagecolorallocate($img, 255, 255, 255);
$clr_txt = imagecolorallocate($img, 0, 0, 0);
imagealphablending($img, false);
imagefilledrectangle($img, 0, 0, $w, $h, $clr_bg);
// Draw character
imagechar($img, $this->font_id, 1, 1, $char, $clr_txt);
// Crop
if ($char !== ' ') {
$cropped = imagecropauto($img, IMG_CROP_WHITE);
if ($cropped !== false) {
$img = $cropped;
}
}
// Resize
$_r = imagesx($img) / imagesy($img);
if ($_r <= 0) {
return false;
}
$width = round($height * $_r);
if ($width < 1) {
return false;
}
//$this->debugOutputImage($img);
$img = imagescale($img, $width, $height, IMG_NEAREST_NEIGHBOUR);
return [$img, $width, $height];
}
private function drawCharFake($img, $img_char, $x, $y, $w, $h, $bg_clr, $char_clr) {
$lim = strlen(self::CHAR_LIST_SPLIT) - 1;
$char = self::CHAR_LIST_SPLIT[mt_rand(0, $lim)];
$scale_x = 1.0 + (mt_rand(self::SCALE_X_MIN, self::SCALE_X_MAX) / 100);
imagefilledrectangle($img_char, 0, 0, $this->font_width, $this->font_height, $bg_clr);
imagechar($img_char, $this->font_id, 0, 0, $char, $char_clr);
$img_scaled = $this->scaleImage($img_char, $w, $h);
list($img_warped, $scale_x) = $this->warpImage($img_scaled, $scale_x, true);
imagedestroy($img_scaled);
$warped_width = imagesx($img_warped);
$warped_height = imagesy($img_warped);
$half_w_a = ceil($warped_width * 0.45);
imagecopy($img, $img_warped,
$x, $y,
0, 0, $half_w_a, $warped_height
);
return round($w * $scale_x);
}
private function drawChar($img, $img_char, $char, $x, $y, $w, $h, $bg_clr, $char_clr,
$scale_x = null, $rotate = false
) {
imagefilledrectangle($img_char, 0, 0, $this->font_width, $this->font_height, $bg_clr);
imagechar($img_char, $this->font_id, 0, 0, $char, $char_clr);
if (!$scale_x) {
$scale_x = 1.0 + (mt_rand(self::SCALE_X_MIN, self::SCALE_X_MAX) / 100);
}
else {
$scale_x += mt_rand(0, self::SCALE_MAX) / 100;
}
$img_scaled = $this->scaleImage($img_char, $w, $h);
list($img_warped, $scale_x) = $this->warpImage($img_scaled, $scale_x, $rotate);
imagedestroy($img_scaled);
$warped_width = imagesx($img_warped);
$warped_height = imagesy($img_warped);
$warped_delta_x = round(($warped_width - $w) / 2.0);
imagecopy($img, $img_warped,
$x, $y,
0, 0, $warped_width, $warped_height
);
imagedestroy($img_warped);
return round($w * $scale_x);
}
private function addNoise($img, $img_width, $img_height) {
list($img_char, $img_char_clr_txt, $img_char_clr_bg) = $this->prepareCharImage();
$char_width_scaled = round($this->font_width * $this->font_scale);
$char_height_scaled = round($this->font_height * $this->font_scale);
$noise_lim = strlen(self::CHAR_NOISE) - 1;
$noise_dx = ceil($char_width_scaled * $this->char_dx_ratio);
$noise_dy_min = floor($img_height * $this->noise_dy_ratio_min);
$noise_dy_max = ceil($img_height * $this->noise_dy_ratio_max);
$offset_x_min = round($char_width_scaled * self::NOISE_OFS_X_RATIO_MIN);
$offset_x_max = round($char_width_scaled * self::NOISE_OFS_X_RATIO_MAX);
$x = mt_rand($noise_dx, $char_width_scaled);
$y = floor(($img_height - $char_height_scaled) / 2.0);
$img_end = $img_width - $char_width_scaled;
$y_flag = mt_rand(0, 1) ? true : false;
while ($x < $img_end) {
$this->drawChar($img, $img_char,
self::CHAR_NOISE[mt_rand(0, $noise_lim)],
$x + mt_rand(0, $noise_dx),
$y + (mt_rand($noise_dy_min, $noise_dy_max)) * ($y_flag ? 1 : -1),
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
$y_flag = !$y_flag;
$x += mt_rand($offset_x_min, $offset_x_max);
}
}
private function addLinesV($img, $img_width, $img_height, $line_count, $color) {
$base_dx = (int)($img_width / ($line_count + 1));
$min_dx = (int)($base_dx * 0.75);
$max_dx = (int)($base_dx * 1.25);
$min_dy = (int)($img_height / 5);
$max_dy = (int)($img_height / 3);
$x = mt_rand($base_dx, $max_dx);
for ($i = 0; $i < $line_count; ++$i) {
$this_x = $x + mt_rand($min_dx, $max_dx);
$y1 = mt_rand($min_dy, $max_dy);
$y2 = $img_height - mt_rand($min_dy, $max_dy);
imageline($img,
$this_x, $y1,
$this_x, $y2,
$color
);
$x += $base_dx;
}
}
private function addPowder($img, $img_width, $img_height, $ratio, $color) {
if ($this->difficulty > self::LEVEL_HARD) {
$powder_max_width = 8;
}
else {
$powder_max_width = 4;
}
$step = ceil(1 / $ratio);
for ($x = 1; $x < $img_width; $x += $step) {
for ($y = 1; $y < $img_height; $y += $step) {
$x1 = mt_rand($x, $x + $step);
$y1 = mt_rand($y, $y + $step);
$size = mt_rand(1, $powder_max_width);
imagefilledarc($img, $x1, $y1, $size, $size, 0, mt_rand(180, 360), $color, IMG_ARC_PIE);
}
}
}
private function addLines($img, $img_width, $img_height, $color) {
$count = 5;
$spacing = (int)($img_width / $count);
$max_dx = (int)($img_width / 3);
$mid_dx = ceil($max_dx / 2);
$x = mt_rand(0, $max_dx);
for ($i = 0; $i < $count; ++$i) {
$dx = mt_rand(0, $max_dx);
$x2 = $x + $dx;
if ($dx >= $mid_dx) {
imageline($img, $x, 0, $x2, $img_height, $color);
}
else {
imageline($img, $x, $img_height, $x2, 0, $color);
}
$x += $spacing;
}
}
private function addArc($img, $img_width, $img_height, $thick, $color) {
$dx_min = 0;
$dx_max = (int)($img_width * 0.35);
$dy_min = $img_height;
$dy_max = (int)($img_height * 1.8);
$width_min = (int)($img_width) * 1.2;
$width_max = (int)($img_width * 2.2);
$start_x = (int)($img_width * 0.5);
$start_y = (int)($img_height * 0.5);
$alt = mt_rand(0, 1) === 1;
$dy = mt_rand($dy_min, $dy_max);
$x = $start_x + mt_rand($dx_min, $dx_max) * (mt_rand(0, 1) ? 1 : -1);
if ($alt) {
$y = $start_y - $dy;
}
else {
$y = $start_y + $dy;
}
$alt = !$alt;
$w = mt_rand($width_min, $width_max);
$h = mt_rand((int)($dy * 1.7), (int)($dy * 2.3));
imageellipse($img, $x, $y, $w, $h, $color);
if ($thick) {
imageellipse($img, $x, $y + 1, $w, $h, $color);
}
}
private function addEdgeBlock($img, $img_width, $img_height) {
$w = mt_rand(floor($img_width * 0.25), floor($img_width * 0.65));
$x = mt_rand(0, $img_width - $w);
//$w = $img_width;
//$x = 0;
$img_blot = imagecreatetruecolor($img_width, $img_height);
imagecopy($img_blot, $img, 0, 0, 0, 0, $img_width, $img_height);
imagefilter($img_blot, IMG_FILTER_EDGEDETECT);
imagefilter($img_blot, IMG_FILTER_GAUSSIAN_BLUR);
imagetruecolortopalette($img_blot, false, 8);
for ($i = 0; $i < 8; $i++) {
$_c = imagecolorsforindex($img_blot, $i);
if ($_c['green'] > 140) {
imagecolorset($img_blot, $i, 0, 0, 0);
}
else {
imagecolorset($img_blot, $i, 238, 238, 238);
}
}
imagecopy($img, $img_blot, $x, 0, $x, 0, $w, $img_height);
imagedestroy($img_blot);
return [$x, $w];
}
private function addInkBlot($img, $img_width, $img_height) {
$w = mt_rand(floor($img_width * 0.25), floor($img_width * 0.30));
$x = mt_rand(0, $img_width - $w);
$c_h = mt_rand(floor($img_height * 0.5), $img_height);
$img_blot = imagecreatetruecolor($w, $img_height);
imagecopy($img_blot, $img, 0, 0, $x, 0, $w, $img_height);
imagefilter($img_blot, $this->blotFilter);
$mask = imagecreatetruecolor($w, $img_height);
$mask_green = imagecolorallocate($mask, 0, 255, 0);
imagecolortransparent($mask, $mask_green);
imagecopy($mask, $img, 0, 0, $x, 0, $w, $img_height);
imagefilledellipse($mask, floor($w / 2), floor($img_height / 2), $w, $c_h, $mask_green);
imagecopy($img_blot, $mask, 0, 0, 0, 0, $w, $img_height);
imagecopy($img, $img_blot, $x, 0, 0, 0, $w, $img_height);
imagedestroy($mask);
imagedestroy($img_blot);
return [$x, $w];
}
private function addScoreLines($img, $img_width, $img_height, $color) {
$step = 10;
$count = 10 + mt_rand(-2, 2);
$x = mt_rand(floor($img_width * 0.10), floor($img_width * 0.20));
$y = mt_rand(floor($img_height * 0.05), floor($img_height * 0.15));
$l = mt_rand(floor($img_width * 0.60), floor($img_width * 0.70));
for ($i = 0; $i < $count; $i++) {
$j_x = mt_rand(-2, 2);
$j_l = mt_rand(-2, 2);
$y_j = mt_rand(-3, 3);
imageline($img, $x + $j_x, $y, $x + $l + $j_l, $y + $y_j, $color);
$y += $step;
}
}
private function addGridLines($img, $img_width, $img_height, $color, $grid_size = 15, $line_width = 1) {
if ($line_width > 1) {
imagesetthickness($img, $line_width);
}
for ($x = mt_rand(2, $grid_size); $x < $img_width; $x += $grid_size) {
imageline($img, $x + mt_rand(-1, 1), 0, $x + mt_rand(-1, 1), $img_height, $color);
}
for ($y = mt_rand(2, $grid_size); $y < $img_height; $y += $grid_size) {
imageline($img, 0, $y + mt_rand(-1, 1), $img_width, $y + mt_rand(-1, 1), $color);
}
if ($line_width > 1) {
imagesetthickness($img, 1);
}
}
private function generateImage($str, $str_len) {
if (!$this->font_id) {
return false;
}
$char_width_scaled = round($this->font_width * $this->font_scale);
$char_height_scaled = round($this->font_height * $this->font_scale);
$char_dx = ceil($char_width_scaled * $this->char_dx_ratio);
$char_dy = ceil($char_height_scaled * $this->char_dy_ratio);
$max_possible_chars = ceil(self::IMG_WIDTH_MAX /
($char_width_scaled * (self::SCALE_X_MAX / 100 + 1) + $char_dx)
);
$pad_pool = $max_possible_chars - $str_len;
/*
if ($str_len > $max_possible_chars) {
$ratio = $this->font_width / $this->font_height;
$char_width_scaled = round(self::IMG_WIDTH_MAX / (self::SCALE_X_MAX / 100 + 1) / $str_len);
$char_height_scaled = round($char_width_scaled / $ratio);
$pad_pool = 0;
}
else {
$pad_pool = $max_possible_chars - $str_len;
}
*/
$noise_lim = strlen(self::CHAR_NOISE) - 1;
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$x = mt_rand(0, $char_dx);
if ($this->useTopBottomY) {
$y = 0;
$y_bot = $img_height - $char_height_scaled;
}
else {
$y = $y_bot = ceil((($img_height - $char_height_scaled) / 2.0) - $char_height_scaled * 0.02);
}
$y_flag = (bool)mt_rand(0, 1);
// Main image
$img = imagecreatetruecolor($img_width, $img_height);
$img_clr_bg = imagecolorallocate($img, 255, 255, 255);
$img_clr_transp = imagecolorallocatealpha($img, 0, 255, 0, 127);
imagealphablending($img, true);
imagefill($img, 0, 0, $img_clr_bg);
// Character image
list($img_char, $img_char_clr_txt, $img_char_clr_bg) = $this->prepareCharImage();
// Left padding
$j = 0;
while ($j < $pad_pool) {
if (mt_rand(0, 1)) {
$pad_pool--;
$_y = $y + mt_rand(-$char_dy, $char_dy);
if ($this->useFakeCharPadding) {
$warped_width = $this->drawCharFake($img, $img_char,
$x,
$_y,
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
}
else {
$warped_width = $this->drawChar($img, $img_char,
self::CHAR_NOISE[mt_rand(0, $noise_lim)],
$x,
$_y,
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
}
$x += $warped_width;
}
else {
$j++;
}
}
// Characters
$scale_x = 0;
$scale_adjust = false;
$scale_x_mid = (int)(self::SCALE_X_MAX - abs(self::SCALE_X_MIN)) / 2;
$chars_start_x = $x;
if ($this->difficulty > self::LEVEL_EASY && $str_len > 1) {
$inter_pad_idx = mt_rand(1, $str_len - 1);
}
else {
$inter_pad_idx = -1;
}
$bot_slot = mt_rand(0, $str_len - 1);
for ($i = 0; $i < $str_len; $i++) {
if ($i === $inter_pad_idx) {
$x += $char_dx;
}
$this_y = $y;
if ($this->useTopBottomY && $bot_slot === $i) {
$this_y = $y_bot;
}
if ($scale_adjust) {
if ($scale_x >= $scale_x_mid) {
$scale_x = mt_rand(self::SCALE_X_MIN, $scale_x_mid);
$scale_adjust = false;
}
else {
$scale_x = mt_rand($scale_x_mid, self::SCALE_X_MAX);
$scale_adjust = false;
}
}
else {
$scale_x = mt_rand(self::SCALE_X_MIN, self::SCALE_X_MAX);
$scale_adjust = true;
}
$scale_x_f = 1.0 + ($scale_x / 100);
$warped_width = $this->drawChar($img, $img_char,
$str[$i],
$x,
$this_y + mt_rand(0, $char_dy) * ($y_flag ? 1 : -1),
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt, $scale_x_f, true
);
$y_flag = !$y_flag;
$x += ($warped_width - $char_dx);
}
// Right padding
$j = 0;
while ($j < $pad_pool) {
$pad_pool--;
$_y = $y + mt_rand(-$char_dy, $char_dy);
if ($this->useFakeCharPadding) {
$warped_width = $this->drawCharFake($img, $img_char,
$x,
$_y,
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
}
else {
$warped_width = $this->drawChar($img, $img_char,
self::CHAR_NOISE[mt_rand(0, $noise_lim)],
$x,
$_y,
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
}
$x += $warped_width;
}
return [ $img, $img_width, $img_height, $img_clr_bg ];
}
private function generateBgImage($img_width) {
if (!$this->font_id) {
return false;
}
$char_width_scaled = round($this->font_width * $this->font_scale);
$char_height_scaled = round($this->font_height * $this->font_scale);
$char_dy = ceil($char_height_scaled * $this->char_dy_ratio);
$img_height = self::IMG_HEIGHT;
$_dx = ceil($char_width_scaled * $this->char_dx_ratio);
$x = mt_rand($_dx, $char_width_scaled - $_dx);
$y = floor((($img_height - $char_height_scaled) / 2.0) + $char_height_scaled * 0.025);
$y_flag = (bool)mt_rand(0, 1);
// Main image
$img = imagecreatetruecolor($img_width, $img_height);
$img_clr_bg = imagecolorallocate($img, 255, 255, 255);
imagefilledrectangle($img, 0, 0, $img_width, $img_height, $img_clr_bg);
// Character image
list($img_char, $img_char_clr_txt, $img_char_clr_bg) = $this->prepareCharImage();
$char_lim = strlen(self::CHAR_LIST) - 1;
$x_lim = $img_width - $char_width_scaled;
while ($x < $x_lim) {
$warped_width = $this->drawChar($img, $img_char,
self::CHAR_LIST[mt_rand(0, $char_lim)],
$x,
$y + $char_dy * ($y_flag ? 1 : -1),
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
// ---
$y_flag = !$y_flag;
$x += $warped_width - mt_rand((int)($warped_width * 0.1), (int)($warped_width * 0.5));
}
return [ $img, $img_width, $img_height, $img_clr_bg ];
}
private function addFenceNoise($img, $img_width, $img_height, $img_clr_bg) {
//$this->addPowder($img, $img_width, $img_height, 0.15, $img_clr_bg);
$this->addGridLines($img, $img_width, $img_height, $img_clr_bg);
}
private function sliceVertical($img, $img_bg, $img_width, $img_height, $bg_width) {
$img_clr_bg = imagecolorallocatealpha($img, 0, 255, 0, 127);
$char_width = round($this->font_width * $this->font_scale);
$char_height = round($this->font_height * $this->font_scale);
$slice_width_min = floor($char_width * 0.8);
$slice_width_max = ceil($char_width * 1.0);
$slice_height_min = floor($img_height * 0.7);
$slice_height_max = ceil($img_height * 0.9);
$slice_dx = floor($slice_width_min / 10);
$slice_dy = floor($char_height / 10);
$slice_offset = $slice_width_min * 3;
$img_start = $char_width + (int)($slice_width_min / 2);
$img_end = $img_width - $char_width - 1;
$fence_size = $img_end - $img_start;
$bg_start = $img_start;
$bg_end = $bg_width - $char_width - 1;
$bg_fence_start = $bg_start + mt_rand(0, $bg_end - $fence_size - $bg_start);
$bg_fence_end = $bg_fence_start + $fence_size - 1;
$img_x = $img_start;
$bg_x = $bg_fence_start;
$slices = [];
while ($img_x < $img_end) {
$sw = mt_rand($slice_width_min, $slice_width_max);
$sh = mt_rand($slice_height_min, $slice_height_max);
$dx = mt_rand(-$slice_dx, $slice_dx);
$sy = $img_height - $sh;
imagecopy($img_bg, $img, $bg_x + $dx, $sy, $img_x + $dx, $sy, $sw, $sh);
$slices[] = [ $img_x + $dx, $sy, $sw, $sh, $img_x + $sw - 2, $sh - 2 ];
$img_x += $slice_offset;
$bg_x += $slice_offset;
}
// Dummy fence
$slice_lim = count($slices) - 1;
$dummy_end = $bg_fence_start - $slice_width_min - 1;
$dummy_x = $slice_width_min;
while ($dummy_x < $dummy_end) {
list($_x, $_y, $_w, $_h) = $slices[mt_rand(0, $slice_lim)];
imagecopy($img_bg, $img, $dummy_x, $_y, $_x, $_y, $_w, $_h);
$dummy_x += $slice_offset;
}
$dummy_end = $bg_end + $slice_width_min - 1;
$dummy_x = $bg_fence_end + $slice_width_min;
while ($dummy_x < $dummy_end) {
list($_x, $_y, $_w, $_h) = $slices[mt_rand(0, $slice_lim)];
imagecopy($img_bg, $img, $dummy_x, $_y, $_x, $_y, $_w, $_h);
$dummy_x += $slice_offset;
}
// Add noise
if ($this->difficulty > self::LEVEL_EASY && !$this->useTopBottomY) {
$this->addNoise($img, $img_width, $img_height);
}
// Clear fence
imagealphablending($img, false);
foreach ($slices as $rect) {
$width = $rect[4] - $rect[0];
$height = $rect[5] - $rect[1];
$x = $rect[0] + (int)($width / 2);
$y = $rect[1] + (int)($height / 2);
imagefilledrectangle($img,
$rect[0], $rect[1],
$rect[0] + $rect[2] - 1, $rect[1] + $rect[3] - 1,
$img_clr_bg);
}
// Add transparent noise
if ($this->useFenceNoise) {
$this->addFenceNoise($img_bg, $bg_width, $img_height, 1);
}
imagealphablending($img, true);
return $bg_fence_start - $bg_start;
}
private function sliceVerticalFog($img, $img_bg, $img_chars, $slide_range = 60) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$slice_width = ceil($this->font_width * $this->font_scale);
if ($slice_width <= 0) {
return false;
}
$this->removeBackground($img_chars);
// Temp image containing background chars
$img_chars_tmp = imagecreate($img_width, $img_height);
imagepalettecopy($img_chars_tmp, $img_chars);
imagecolortransparent($img_chars_tmp, 1);
imagealphablending($img_chars_tmp, false);
imagefilledrectangle($img_chars_tmp, 0, 0, $img_width, $img_height, 1);
imagealphablending($img_chars_tmp, true);
// Copy and clear 2 slices
$_qw = ceil($img_width * 0.25);
$slice_dx = mt_rand(0, $slide_range);
$slices_x = [ $_qw - $slice_width, $img_width - $_qw - $slice_width ];
$c = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagecolortransparent($img, $c);
imagesavealpha($img, true);
imagealphablending($img, false);
$slice_mini_width = ceil($slice_width * 0.33);
foreach ($slices_x as $x) {
imagecopy($img_chars_tmp, $img_chars, $x + $slice_dx, 0, $x, 0, $slice_width, $img_height);
for ($_i = 1; $_i < 4; $_i++) {
$_x = $x + $slice_dx - $slice_mini_width * $_i;
imagecopy($img_chars_tmp, $img_chars, $_x, 0, $x, 0, $slice_mini_width, $img_height);
$_x = $x + $slice_dx + $slice_width + $slice_mini_width * $_i;
imagecopy($img_chars_tmp, $img_chars, $_x, 0, $x + $slice_width - $slice_mini_width, 0, $slice_mini_width, $img_height);
}
imagefilledrectangle($img, $x, 0, $x + $slice_width - 1, $img_height - 1, $c);
}
//$this->addGridLines($img_chars_tmp, $img_width, $img_height, 0, mt_rand(15, 20));
//$this->addPowder($img_chars_tmp, $img_width, $img_height, 0.15, 0);
// Copy characters to background
imagecolorset($img_chars_tmp, 0, 0, 0, 0, mt_rand(64, 72));
imagealphablending($img_bg, true);
imagecopy($img_bg, $img_chars_tmp, 0, 0, 0, 0, $img_width, $img_height);
if ($this->useInvert) {
imagefilter($img_bg, IMG_FILTER_NEGATE);
}
imagealphablending($img, true);
return $slide_range;
}
private function sliceVerticalSimplex($img, $img_bg, $img_chars, $slide_range = 60) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$slice_width = ceil($this->font_width * $this->font_scale);
if ($slice_width <= 0) {
return false;
}
// Copy and clear 2 slices
$_qw = ceil($img_width * 0.25);
$slice_dx = mt_rand(0, $slide_range);
$slices_x = [ $_qw - $slice_width, $img_width - $_qw - $slice_width ];
$c = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagecolortransparent($img, $c);
imagesavealpha($img, true);
imagealphablending($img, false);
imagealphablending($img_bg, true);
$slice_mini_width = ceil($slice_width * 0.33);
foreach ($slices_x as $x) {
imagecopy($img_bg, $img_chars, $x + $slice_dx, 0, $x, 0, $slice_width, $img_height);
for ($_i = 1; $_i < 4; $_i++) {
$_x = $x + $slice_dx - $slice_mini_width * $_i;
imagecopy($img_bg, $img_chars, $_x, 0, $x, 0, $slice_mini_width, $img_height);
$_x = $x + $slice_dx + $slice_width + $slice_mini_width * $_i;
imagecopy($img_bg, $img_chars, $_x, 0, $x + $slice_width - $slice_mini_width, 0, $slice_mini_width, $img_height);
}
imagefilledrectangle($img, $x, 0, $x + $slice_width - 1, $img_height - 1, $c);
}
// Noise
$c = imagecolorallocatealpha($img, 255, 255, 255, mt_rand(85, 95));
//$this->addGridLines($img_bg, $img_width, $img_height, $c, 30, 2);
$this->addPowder($img_bg, $img_width, $img_height, 0.07, $c);
if ($this->useInvert) {
imagefilter($img_bg, IMG_FILTER_NEGATE);
}
imagealphablending($img, true);
return $slide_range;
}
public function generateTwisterV($char_count) {
if (!$this->font_id) {
return false;
}
if ($char_count > 5) {
$char_count = 5;
}
$challenge_str = $this->getRandomString($char_count);
$this->setFontScale(0.96);
list($img, $img_width, $img_height, $img_white) = $this->generateImage($challenge_str, $char_count);
list($img_bg, $bg_width, $bg_height, $img_bg_white) = $this->generateBgImage($img_width + 59);
$img_clr_bg = imagecolorallocatealpha($img, 0, 255, 0, 127);
$clear_x = mt_rand(0, $bg_width - $img_width);
$clear_h = floor($img_height * 0.4);
$clear_y = $img_height - $clear_h;
imagefilledrectangle($img_bg, 0, $clear_y, $bg_width, $img_height, $img_bg_white);
imagecopy($img_bg, $img, $clear_x, $clear_y, 0, $clear_y, $img_width, $clear_h);
imagefilledrectangle($img_bg, 0, $clear_y, $bg_width, $clear_y + 0, 0);
imagefilter($img, IMG_FILTER_EDGEDETECT);
// Rays
$ray_count = mt_rand(18, 20);
$spacing = ceil($img_width / ($ray_count + 1));
$dx_max = 5;
$x = mt_rand(0, $spacing);
imagesetthickness($img, 2);
for ($i = 0; $i < $ray_count; ++$i) {
$x1 = $x;
$y1 = 0;
$to_x = $x1 + mt_rand(-$dx_max, $dx_max);
for ($to_y = 0; $to_y < $clear_y; $to_y++) {
$_c1 = imagecolorat($img, $to_x, $to_y);
$_c2 = imagecolorat($img, $to_x + 1, $to_y);
$_c3 = imagecolorat($img, $to_x - 1, $to_y);
if ($_c1 === $img_white || $_c2 === $img_white || $_c3 === $img_white) {
imageline($img, $x1, $y1, $to_x, $to_y, $img_white);
$x += $spacing + mt_rand(0, 5);
continue 2;
}
}
imageline($img, $x1, $y1, $to_x, $to_y, $img_white);
$x += $spacing + mt_rand(0, 5);
}
imagesetthickness($img, 1);
// Grain
$this->addPowder($img_bg, $bg_width, $img_height, 0.1, 0);
$this->addPowder($img, $img_width, $img_height, 0.15, 0);
// Moon
$_x_min = ceil($img_width * 0.3);
$_x_max = ceil($img_width * 0.7);
imagefilledellipse($img,
mt_rand($_x_min, $_x_max),
mt_rand(-20, -15),
300, 50,
$img_white);
// Clear
imagealphablending($img, false);
imagefilledrectangle($img, 0, $clear_y, $img_width, $img_height, $img_clr_bg);
imagealphablending($img, true);
$this->flattenImageAlpha($img);
$this->cleanImageAlpha($img_bg);
if ($this->useInvert) {
imagecolorset($img, 0, 128, 128, 128);
imagecolorset($img_bg, 0, 215, 215, 215);
imagecolorset($img, 2, 255, 255, 255);
imagecolorset($img_bg, 1, 128, 128, 128);
}
else {
imagecolorset($img, 0, 108, 108, 108);
imagecolorset($img_bg, 0, 108, 108, 108);
}
return [ $challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width, $fence_start ];
}
public function generateTwisterH($char_count) {
if (!$this->font_id) {
return false;
}
$challenge_str = $this->getRandomString($char_count);
$background_str = $this->getRandomString($char_count + 2);
list($img, $img_width, $img_height, $img_white) = $this->generateImage($challenge_str, $char_count);
list($img_bg, $bg_width, $bg_height, $img_bg_white) = $this->generateBgImage($img_width + 49);
if ($this->useEdgeBlock) {
$this->addEdgeBlock($img, $img_width, $img_height);
}
if ($this->useInkBlot) {
$this->addInkBlot($img, $img_width, $img_height);
}
if ($this->difficulty > self::LEVEL_EASY) {
$this->addPowder($img, $img_width, $img_height, 0.08, 1);
}
if ($this->difficulty > self::LEVEL_EASY) {
$this->addArc($img, $img_width, $img_height, false, 1);
}
$fence_start = $this->sliceVertical($img, $img_bg, $img_width, $img_height, $bg_width);
if ($this->useScoreLines) {
$this->addScoreLines($img, $img_width, $img_height, $clr_txt);
}
if ($this->useGridLines) {
$this->addGridLines($img, $img_width, $img_height, $clr_txt);
}
if ($this->difficulty > self::LEVEL_EASY) {
$this->addLines($img, $img_width, $img_height, $img_white);
}
$this->flattenImageAlpha($img);
if ($this->difficulty > self::LEVEL_NORMAL) {
$this->addPowder($img_bg, $bg_width, $img_height, 0.1, 1);
$this->addPowder($img_bg, $bg_width, $img_height, 0.06, $img_bg_white);
$this->addArc($img_bg, $bg_width, $img_height, false, $img_bg_white);
$this->addArc($img_bg, $bg_width, $img_height, false, $img_bg_white);
}
else if ($this->difficulty > self::LEVEL_EASY) {
$this->addPowder($img_bg, $bg_width, $img_height, 0.08, 1);
$this->addPowder($img_bg, $bg_width, $img_height, 0.05, $img_bg_white);
$this->addArc($img_bg, $bg_width, $img_height, false, $img_bg_white);
}
$this->cleanImageAlpha($img_bg);
return [ $challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width, $fence_start ];
}
private function generateFogBg() {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$img = imagecreatetruecolor($img_width, $img_height);
$img_clr_bg = imagecolorallocate($img, 255, 255, 255);
imagefill($img, 0, 0, $img_clr_bg);
$c_width_min = ceil($img_height * 1.8);
$c_width_max = ceil($img_height * 2.5);
$x_step = ceil($img_width / 5);
if ($x_step <= 0) {
$x_step = 10;
}
$x_jitter = ceil($img_width * 0.05);
$x = 0;
$top = (bool)mt_rand(0, 1);
while ($x <= $img_width) {
if ($top) {
$_y = 0;
}
else {
$_y = $img_height;
}
$_w = mt_rand($c_width_min, $c_width_max);
$_h = mt_rand($c_width_min, $c_width_max);
$_x = $x + mt_rand(-$x_jitter, $x_jitter);
$gray = imagecolorallocatealpha($img, 0, 0, 0, mt_rand(95, 100));
imagefilledellipse($img, $_x, $_y, $_w, $_h, $gray);
$x += $x_step;
$top = !$top;
}
return $img;
}
private function generateSimplexBg($zoom_min = 62, $zoom_max = 68) {
$noise = new SimplexNoise();
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$img = imagecreatetruecolor($img_width, $img_height);
$color_count = 8;
$color_base = 64;
$color_range = round((255 - $color_base * 2) / 6);
$colors = [];
for ($i = 0; $i < $color_count; $i++) {
$c = $color_base + $i * $color_range + mt_rand(-8, 8);
$colors[] = imagecolorallocate($img, $c, $c, $c);
}
$zoom = mt_rand($zoom_min, $zoom_max);
$_cc = $color_count - 1;
for ($x = 0; $x < $img_width; ++$x) {
for ($y = 0; $y < $img_height; ++$y) {
$n = $noise->noise($x / $zoom, $y / $zoom);
$c = floor((1 + $n) / 2 * $_cc);
if ($c > $_cc) {
$c = $_cc;
}
$ci = $colors[$c];
imagesetpixel($img, $x, $y, $ci);
}
}
return $img;
}
private function removeBackground($img) {
$this->cleanImageAlpha($img);
imagecolortransparent($img, 1);
}
private function overlayPattern($img, $type, $dark = true) {
if ($type === 1) {
$this->overlayPatternRings($img, $dark);
}
else if ($type === 2) {
$this->overlayPatternRad($img, $dark);
}
else if ($type === 3) {
$this->overlayPatternMoz($img, $dark);
}
else if ($type === 4) {
$this->overlayPatternSimplexA($img, 16, 120);
}
else if ($type === 5) {
$this->overlayPatternHatchA($img, 10, 2);
}
else if ($type === 6) {
$this->overlayPatternSimplexA($img, 1, 110);
}
}
private function overlayPatternHatchA($img, $noise_width = 10, $zoom = 2) {
$noise = new SimplexNoise();
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$img_noise = imagecreatetruecolor($noise_width, $img_height);
$c1 = imagecolorallocatealpha($img_noise, 0, 0, 0, 110);
$c2 = imagecolorallocatealpha($img_noise, 255, 255, 255, 110);
imagealphablending($img_noise, false);
for ($x = 0; $x < $img_width; ++$x) {
for ($y = 0; $y < $img_height; ++$y) {
$n = $noise->noise($x / $zoom, $y / $zoom);
$c = floor((1 + $n) / 2 * 255);
if ($c < 128) {
$c = $c1;
}
else {
$c = $c2;
}
imagesetpixel($img_noise, $x, $y, $c);
}
}
$img_noise = imagescale($img_noise, $img_width, $img_height);
//$this->debugOutputImage($img_noise);
imagealphablending($img, true);
imagecopy($img, $img_noise, 0, 0, 0, 0, $img_width, $img_height);
}
private function overlayPatternSimplexA($img, $zoom = 16, $alpha = 120) {
$noise = new SimplexNoise();
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
if ($alpha < 0) {
$alpha = 0;
}
else if ($alpha > 127) {
$alpha = 127;
}
$img_noise = imagecreatetruecolor($img_width, $img_height);
$c1 = imagecolorallocatealpha($img_noise, 0, 0, 0, $alpha);
$c2 = imagecolorallocatealpha($img_noise, 255, 255, 255, $alpha);
imagealphablending($img_noise, false);
if ($zoom <= 0) {
$zoom = 1;
}
for ($x = 0; $x < $img_width; ++$x) {
for ($y = 0; $y < $img_height; ++$y) {
$n = $noise->noise($x / $zoom, $y / $zoom);
$c = floor((1 + $n) / 2 * 255);
if ($c < 128) {
$c = $c1;
}
else {
$c = $c2;
}
imagesetpixel($img_noise, $x, $y, $c);
}
}
//$this->debugOutputImage($img_noise);
imagealphablending($img, true);
imagecopy($img, $img_noise, 0, 0, 0, 0, $img_width, $img_height);
}
private function overlayPatternRings($img, $dark = true) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
if ($dark) {
$c_rings = imagecolorallocatealpha($img, 0, 0, 0, 110);
}
else {
$c_rings = imagecolorallocatealpha($img, 255, 255, 255, 110);
}
imagealphablending($img, true);
$ring_w = mt_rand(ceil($img_width * 0.06), ceil($img_width * 0.08));
$ring_d = ceil($ring_w * 0.5) + 1;
$x_max = $img_width + $ring_w;
$y_max = $img_height + $ring_w;
for ($x = mt_rand(1 - $ring_w, 0); $x < $x_max; $x += $ring_d) {
for ($y = mt_rand(1 - $ring_w, 0); $y < $y_max; $y += $ring_d) {
imageellipse($img, $x, $y, $ring_w, $ring_w, $c_rings);
}
}
}
private function overlayPatternRad($img, $dark = true) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
if ($dark) {
$c_rings = imagecolorallocatealpha($img, 0, 0, 0, 110);
}
else {
$c_rings = imagecolorallocatealpha($img, 255, 255, 255, 110);
}
imagealphablending($img, true);
$x = mt_rand(round($img_width * 0.4), round($img_width * 0.6));
$y = mt_rand(round($img_height * 0.4), round($img_height * 0.6));
$w_d = 16;
$w = $w_d;
while ($w < $img_width) {
imageellipse($img, $x, $y, $w, $w, $c_rings);
imageellipse($img, $x, $y, $w + 2, $w + 2, $c_rings);
$w += $w_d;
}
}
private function overlayPatternMoz($img, $dark = true) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
imagealphablending($img, true);
$color_map = [];
$sq_w = 6;
for ($x = 0; $x < $img_width; $x += $sq_w) {
for ($y = 0; $y < $img_height; $y += $sq_w) {
$_c = mt_rand(100, 115);
if (isset($color_map[$_c])) {
$c_rings = $color_map[$_c];
}
else {
if ($dark) {
$c_rings = imagecolorallocatealpha($img, 0, 0, 0, $_c);
}
else {
$c_rings = imagecolorallocatealpha($img, 255, 255, 255, $_c);
}
$color_map[$_c] = $c_rings;
}
imagefilledrectangle($img, $x, $y, $x + $sq_w - 1, $y + $sq_w - 1, $c_rings);
}
}
}
private function applyTextToFogBg($img_chars, $img_bg) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$this->cleanImageAlpha($img_chars);
imagealphablending($img_bg, true);
$color_map = [];
$c_text = imagecolorallocatealpha($img_bg, 0, 0, 0, mt_rand(70, 80));
$c_text_alt = imagecolorallocatealpha($img_bg, 255, 255, 255, mt_rand(40, 50));
if ($this->useInvert) {
$_c = $c_text;
$c_text = $c_text_alt;
$c_text_alt = $_c;
}
$anti_x1 = mt_rand(round($img_width * 0.30), round($img_width * 0.45));
$anti_x2 = mt_rand(round($img_width * 0.55), round($img_width * 0.60));
$anti_x1_jitter = $anti_x1 - 2;
$anti_x2_jitter = $anti_x2 + 2;
for ($x = 0; $x < $img_width; ++$x) {
for ($y = 0; $y < $img_height; ++$y) {
$c_chars = imagecolorat($img_chars, $x, $y);
// Character pixel, bright if inside the anti rect, dark otherwise
if ($c_chars === 0) {
$_anti = false;
if ($this->useInkBlot) {
if ($x >= $anti_x1 && $x <= $anti_x2) {
$_anti = true;
}
else if ($x >= $anti_x1_jitter && $x < $anti_x1) {
$_anti = !!mt_rand(0, 1);
}
else if ($x > $anti_x2 && $x <= $anti_x2_jitter) {
$_anti = !!mt_rand(0, 1);
}
}
if ($_anti) {
imagesetpixel($img_bg, $x, $y, $c_text_alt);
}
else {
imagesetpixel($img_bg, $x, $y, $c_text);
}
}
$c_delta = mt_rand(100, 120);
if (isset($color_map[$c_delta])) {
$c_noise = $color_map[$c_delta];
}
else {
$c_noise = imagecolorallocatealpha($img_bg, 0, 0, 0, $c_delta);
}
$color_map[$c_delta] = $c_noise;
imagesetpixel($img_bg, $x, $y, $c_noise);
}
}
}
private function applyTextToSimplexBg($img_chars, $img_bg, $dirx = 2, $diry = 2) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
imagealphablending($img_bg, true);
imagecolorset($img_chars, 0, 255, 255, 255, mt_rand(60, 70));
imagecopy($img_bg, $img_chars, 0, 0, 0, 0, $img_width, $img_height);
imagecolorset($img_chars, 0, 0, 0, 0, mt_rand(70, 80));
imagecopy($img_bg, $img_chars, $dirx, $diry, 0, 0, $img_width, $img_height);
}
public function generateStatic($char_count) {
if (!$this->font_id) {
return false;
}
if ($this->useStaticRot) {
$this->setFontScale(0.5);
}
$challenge_str = $this->getRandomString($char_count);
list($img, $img_width, $img_height, $clr_bg) = $this->generateImage($challenge_str, $char_count);
if (!$this->useTopBottomY) {
$this->addNoise($img, $img_width, $img_height);
}
if ($this->useStaticRot) {
$img = imagerotate($img, 15, $clr_bg);
$_w = imagesx($img);
$_h = imagesy($img);
$_x = round(abs($_w - $img_width) * 0.5);
$_y = round(abs($_h - $img_height) * 0.65);
$img = imagecrop($img, ['x' => $_x, 'y' => $_y, 'width' => $img_width, 'height' => $img_height]);
}
else {
// Overlay noise
if ($this->difficulty > self::LEVEL_EASY) {
list($img_char, $img_char_clr_txt, $img_char_clr_bg) = $this->prepareCharImage();
$char_width_scaled = $this->font_width * 2.4;
$char_height_scaled = $this->font_height * 2.4;
$noise_count = ceil($char_count / 2);
$dx = (int)($img_width / $noise_count);
$x = mt_rand($char_width_scaled, $dx);
for ($i = 0; $i < $noise_count; ++$i) {
$this->drawChar($img, $img_char,
self::CHAR_NOISE_OVERLAY[0],
$x,
(int)($img_height / 2) - (int)($char_height_scaled / 2),
$char_width_scaled, $char_height_scaled,
$img_char_clr_bg, $img_char_clr_txt
);
$x += mt_rand($char_width_scaled, $dx);
}
}
}
$this->cleanImageAlpha($img);
if ($this->difficulty > self::LEVEL_EASY) {
$this->addPowder($img, $img_width, $img_height, 0.08, 0);
if (!$this->useStaticRot) {
$this->addPowder($img, $img_width, $img_height, 0.06, 1);
}
}
return [ $challenge_str, $img, $img_width, $img_height ];
}
private function applyNoiseToFog($img) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$img_noise = imagecreatetruecolor($img_width, $img_height);
$img_noise_clr_bg = imagecolorallocate($img_noise, 255, 255, 255);
$img_noise_clr_transp = imagecolorallocatealpha($img_noise, 0, 255, 0, 127);
imagefill($img_noise, 0, 0, $img_noise_clr_bg);
$this->addNoise($img_noise, $img_width, $img_height);
$this->cleanImageAlpha($img_noise);
imagecolortransparent($img_noise, 1);
$_g = mt_rand(40, 80);
imagecolorset($img_noise, 0, $_g, $_g, $_g);
imagecopy($img, $img_noise, 0, 0, 0, 0, $img_width, $img_height);
}
public function generateSimpleTask($task_id = 0) {
if (!$this->font_id) {
return false;
}
$min_size = 24;
$max_size = 32;
$visual_count = 10;
$visual_str = $this->getRandomString($visual_count);
if ($task_id < 1 || $task_id > 2) {
$task_id = mt_rand(1, 2);
}
$char_count = mt_rand(4, 6);
$task_str_2 = '';
// Type first or last characters
if ($task_id == 1) {
$_ary = [
3 => 'three',
4 => 'four',
5 => 'five',
6 => 'six',
7 => 'seven'
];
if (isset($_ary[$char_count]) && mt_rand(0, 1)) {
$_char_count_str = $_ary[$char_count];
}
else {
$_char_count_str = $char_count;
}
if (mt_rand(0, 1)) {
$challenge_str = substr($visual_str, 0, $char_count);
$task_str_1 = ('only type the first ' . $_char_count_str . ' characters');
}
else {
$challenge_str = substr($visual_str, -$char_count);
$task_str_1 = ('only type the last ' . $_char_count_str . ' characters');
}
}
// Type characters with symbol above
else if ($task_id == 2) {
$task_str_1 = 'only type characters';
$task_str_2 = ' with a circle above them';
$picked_char_ids = array_rand(str_split($visual_str), $char_count);
$challenge_str = '';
foreach ($picked_char_ids as $key) {
$challenge_str .= $visual_str[$key];
}
}
// ---
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$img_half_height = round($img_height * 0.5);
if ($this->useSimplexBg) {
$img = $this->generateSimplexBg();
}
else {
$img = $this->generateFogBg();
}
// ---
$task_str_1_x = mt_rand(0, 20);
if ($task_str_2) {
$task_str_1_y = mt_rand(0, 2);
}
else {
$task_str_1_y = mt_rand(0, 8);
}
imagestring($img, $this->font_id, $task_str_1_x, $task_str_1_y, $task_str_1, 0);
if ($task_str_2) {
$task_str_2_x = $task_str_1_x + mt_rand(20, 40);
$task_str_2_y = $task_str_1_y + 12;
imagestring($img, $this->font_id, $task_str_2_x, $task_str_2_y, $task_str_2, 0);
}
// ---
$char_imgs = [];
$x = 0;
for ($i = 0; $i < $visual_count; $i++) {
$_c = $visual_str[$i];
list($char) = $this->getCharImage($_c, mt_rand($min_size, $max_size));
if ($this->useSpecialRot) {
$mat_rot = $this->getRotMatrix(-mt_rand(45, 65));
$mat_shear_h = null;
$mat_shear_v = null;
}
else {
$mat_rot = $this->getRotMatrix(mt_rand(-25, 25));
$mat_shear_h = $this->getShearHMatrix(mt_rand(-12, 12));
$mat_shear_v = $this->getShearVMatrix(mt_rand(-25, 25));
}
$char = $this->transformImage($char, $mat_rot, $mat_shear_v, $mat_shear_h);
$char_w = imagesx($char);
$char_h = imagesy($char);
$y = $img_half_height + 2;
if ($this->useAltBlackWhite && mt_rand(0, 1)) {
$c = 255;
$alpha = mt_rand(55, 65);
}
else {
$c = 0;
$alpha = mt_rand(55, 75);
}
$char_imgs[] = [$char, $char_w, $char_h, $x, $y, $c, $alpha];
$dx = mt_rand(-1, 2);
$x += $char_w + $dx;
}
$x0 = max(0, $img_width - $x);
$x0 = round($x0 * 0.5);
foreach ($char_imgs as $_char) {
list($char, $char_w, $char_h, $x, $y, $c, $alpha) = $_char;
$this->mergeCharImage($img, $char, $x0 + $x, $y, $c, $alpha);
if ($this->useEmboss) {
$this->mergeCharImage($img, $char, $x0 + $x + 2, $y - 2, $c ? 0 : 255, $alpha);
}
}
if ($this->useInvert) {
imagefilter($img, IMG_FILTER_NEGATE);
}
$img_bg = $this->generateFogBg();
$bg_chars = $this->generateSimplexBg();
foreach ($char_imgs as $_char) {
list($char, $char_w, $char_h, $x, $y, $c, $alpha) = $_char;
$this->mergeCharImage($bg_chars, $char, $x0 + $x, $y, $c, $alpha);
if ($this->useEmboss) {
$this->mergeCharImage($bg_chars, $char, $x0 + $x + 2, $y - 2, $c ? 0 : 255, $alpha);
}
}
imagestring($bg_chars, $this->font_id, $task_str_1_x, $task_str_1_y, $task_str_1, 0);
if ($task_str_2) {
imagestring($bg_chars, $this->font_id, $task_str_2_x, $task_str_2_y, $task_str_2, 0);
}
if ($this->useEdgeDetect && !$this->useEmboss) {
$_b = 25;
$_c = 20;
imagefilter($img, IMG_FILTER_EDGEDETECT);
imagefilter($bg_chars, IMG_FILTER_EDGEDETECT);
imagefilter($img, IMG_FILTER_BRIGHTNESS, mt_rand(-$_b, $_b));
imagefilter($img, IMG_FILTER_CONTRAST, mt_rand(-$_c, $_c));
imagefilter($bg_chars, IMG_FILTER_BRIGHTNESS, mt_rand(-$_b, $_b));
imagefilter($bg_chars, IMG_FILTER_CONTRAST, mt_rand(-$_c, $_c));
}
if ($this->useOverlayId) {
$this->overlayPattern($img, $this->useOverlayId, $this->useOverlayDark);
}
if ($this->useGridLines) {
$_c = imagecolorallocatealpha($img, 255, 255, 255, 105);
$this->addGridLines($img, $img_width, $img_height, $_c, mt_rand(15, 20), 2);
}
$slider_range = $this->sliceVerticalSimplex($img, $img_bg, $bg_chars);
// Print symbols for task 2
if ($task_id == 2) {
$_i = 0;
foreach ($picked_char_ids as $key) {
$_char = $char_imgs[$key];
list($char, $char_w, $char_h, $x, $y, $c, $alpha) = $_char;
$_dot_d = mt_rand(8, 10);
if (mt_rand(0, 1)) {
$_c = imagecolorallocatealpha($img, 255, 255, 255, 50);
}
else {
$_c = imagecolorallocatealpha($img, 0, 0, 0, 50);
}
imagefilledellipse($img, $x0 + $x + round($char_w * 0.5) - 4, $y - mt_rand(7, 9), $_dot_d, $_dot_d, $_c);
$_i++;
}
}
//$this->debugOutputImage($char);
imagetruecolortopalette($img, false, 50);
return [ $challenge_str, $img, $img_width, $img_height, $img_bg, $img_width + $slider_range, 0 ];
}
public function generateTwisterHFogNew($char_count, $min_size = 38, $max_size = 52) {
if (!$this->font_id) {
return false;
}
if (!$min_size || $min_size < 0) {
$min_size = 38;
}
if (!$max_size || $max_size < 0) {
$max_size = 52;
}
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$challenge_str = $this->getRandomString($char_count);
if ($this->useSimplexBg) {
$img = $this->generateSimplexBg();
}
else {
$img = $this->generateFogBg();
}
$bleach = imagecolorallocatealpha($img, 255, 255, 255, mt_rand(80, 100));
$this->addPowder($img, $img_width, $img_height, 0.08, $bleach);
$char_imgs = [];
$x = 0;
if ($this->useExtraSpaces) {
$_extra_space_i = (int)($char_count * 0.5) + mt_rand(-1, 1);
}
for ($i = 0; $i < $char_count; $i++) {
$_c = $challenge_str[$i];
list($char) = $this->getCharImage($_c, mt_rand($min_size, $max_size));
if ($this->useSpecialRot) {
$mat_rot = $this->getRotMatrix(-mt_rand(45, 65));
$mat_shear_h = null;
$mat_shear_v = null;
}
else {
$mat_rot = $this->getRotMatrix(mt_rand(-30, 30));
$mat_shear_h = $this->getShearHMatrix(mt_rand(-15, 15));
$mat_shear_v = $this->getShearVMatrix(mt_rand(-30, 30));
}
$char = $this->transformImage($char, $mat_rot, $mat_shear_v, $mat_shear_h);
$char_w = imagesx($char);
$char_h = imagesy($char);
if ($img_height >= $char_h) {
$y = mt_rand(0, $img_height - $char_h);
}
else {
$y = $img_height - $char_h;
}
if ($this->useAltBlackWhite && mt_rand(0, 1)) {
$c = 255;
$alpha = mt_rand(55, 65);
}
else {
$c = 0;
$alpha = mt_rand(55, 75);
}
$char_imgs[] = [$char, $char_w, $char_h, $x, $y, $c, $alpha];
$dx = mt_rand(-10, 2);
$x += $char_w + $dx;
if ($this->useExtraSpaces && $i == $_extra_space_i) {
$x += $char_w + mt_rand(0, $char_w);
}
}
$x0 = max(0, $img_width - $x);
$x0 = round($x0 * 0.5);
foreach ($char_imgs as $_char) {
list($char, $char_w, $char_h, $x, $y, $c, $alpha) = $_char;
$this->mergeCharImage($img, $char, $x0 + $x, $y, $c, $alpha);
if ($this->useEmboss) {
$this->mergeCharImage($img, $char, $x0 + $x + 2, $y - 2, $c ? 0 : 255, $alpha);
}
}
if ($this->useInvert) {
imagefilter($img, IMG_FILTER_NEGATE);
}
$img_bg = $this->generateFogBg();
$bg_chars = $this->generateSimplexBg();
foreach ($char_imgs as $_char) {
list($char, $char_w, $char_h, $x, $y, $c, $alpha) = $_char;
$this->mergeCharImage($bg_chars, $char, $x0 + $x, $y, $c, $alpha);
if ($this->useEmboss) {
$this->mergeCharImage($bg_chars, $char, $x0 + $x + 2, $y - 2, $c ? 0 : 255, $alpha);
}
}
if ($this->useEdgeDetect && !$this->useEmboss) {
$_b = 25;
$_c = 20;
imagefilter($img, IMG_FILTER_EDGEDETECT);
imagefilter($bg_chars, IMG_FILTER_EDGEDETECT);
imagefilter($img, IMG_FILTER_BRIGHTNESS, mt_rand(-$_b, $_b));
imagefilter($img, IMG_FILTER_CONTRAST, mt_rand(-$_c, $_c));
imagefilter($bg_chars, IMG_FILTER_BRIGHTNESS, mt_rand(-$_b, $_b));
imagefilter($bg_chars, IMG_FILTER_CONTRAST, mt_rand(-$_c, $_c));
}
if ($this->useMeanRemoval && !$this->useEdgeDetect) {
imagefilter($img, IMG_FILTER_MEAN_REMOVAL);
imagefilter($bg_chars, IMG_FILTER_MEAN_REMOVAL);
}
if ($this->useOverlayId) {
$this->overlayPattern($img, $this->useOverlayId, $this->useOverlayDark);
}
if ($this->useGridLines) {
$_c = imagecolorallocatealpha($img, 255, 255, 255, 105);
$this->addGridLines($img, $img_width, $img_height, $_c, mt_rand(15, 20), 2);
}
$slider_range = $this->sliceVerticalSimplex($img, $img_bg, $bg_chars);
//$this->debugOutputImage($char);
imagetruecolortopalette($img, false, 50);
return [ $challenge_str, $img, $img_width, $img_height, $img_bg, $img_width + $slider_range, 0 ];
}
public function generateStaticFog($char_count) {
if (!$this->font_id) {
return false;
}
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$challenge_str = $this->getRandomString($char_count);
// Background image
$img = $this->generateFogBg();
// Text image
list($img_chars) = $this->generateImage($challenge_str, $char_count);
// Transparent line
$this->addArc($img_chars, $img_width, $img_height, false, 1);
// Copy the text to the bg
$this->applyTextToFogBg($img_chars, $img);
if ($this->useOverlayId) {
$this->overlayPattern($img, $this->useOverlayId, $this->useOverlayDark);
}
if ($this->useScoreLines) {
$this->addScoreLines($img, $img_width, $img_height, 1);
}
if ($this->useGridLines) {
$this->addGridLines($img, $img_width, $img_height, 1);
}
// Top / Bottom symbol noise
$this->applyNoiseToFog($img);
// ---
imagetruecolortopalette($img, false, 50);
return [ $challenge_str, $img, self::IMG_WIDTH_MAX, self::IMG_HEIGHT ];
}
public function generateTwisterHFog($char_count) {
if (!$this->font_id) {
return false;
}
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$challenge_str = $this->getRandomString($char_count);
// Foreground fog
$img = $this->generateFogBg();
//$bleach = imagecolorallocatealpha($img, 255, 255, 255, mt_rand(80, 100));
//$grid_size = mt_rand(floor($img_height * 0.35), floor($img_height * 0.55));
//$this->addGridLines($img, $img_width, $img_height, $bleach, $grid_size, 2);
//$this->addPowder($img, $img_width, $img_height, 0.08, $bleach);
// Background fog
$img_bg = $this->generateFogBg();
// Text image
list($img_chars_orig) = $this->generateImage($challenge_str, $char_count);
$img_chars = $this->cloneTextImage($img_chars_orig);
$this->removeBackground($img_chars);
// Copy the text to the main fog image
$this->applyTextToFogBg($img_chars, $img);
if ($this->useOverlayId) {
$this->overlayPattern($img, $this->useOverlayId, $this->useOverlayDark);
}
// Slice
$slider_range = $this->sliceVerticalFog($img, $img_bg, $img_chars_orig);
// Perturbations for the character image
$this->addArc($img_chars, $img_width, $img_height, false, 1);
if ($this->useScoreLines) {
$this->addScoreLines($img, $img_width, $img_height, 1);
}
if ($this->useGridLines) {
$this->addGridLines($img, $img_width, $img_height, 1);
}
if (!$this->useTopBottomY) {
$this->applyNoiseToFog($img);
}
// ---
imagetruecolortopalette($img, false, 50);
return [ $challenge_str, $img, $img_width, $img_height, $img_bg, $img_width + $slider_range, 0 ];
}
public function generateStaticSimplex($char_count) {
if (!$this->font_id) {
return false;
}
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$challenge_str = $this->getRandomString($char_count);
// Background image
$img = $this->generateSimplexBg();
// Text image
list($img_chars) = $this->generateImage($challenge_str, $char_count);
$this->removeBackground($img_chars);
$this->applyTextToSimplexBg($img_chars, $img, -2, 2);
if ($this->useOverlayId) {
$this->overlayPattern($img, $this->useOverlayId, $this->useOverlayDark);
}
if ($this->useInvert) {
imagefilter($img, IMG_FILTER_NEGATE);
}
// ---
imagetruecolortopalette($img, false, 50);
return [ $challenge_str, $img, self::IMG_WIDTH_MAX, self::IMG_HEIGHT ];
}
public function generateTwisterHSimplex($char_count) {
if (!$this->font_id) {
return false;
}
$challenge_str = $this->getRandomString($char_count);
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
// Foreground fog
$img = $this->generateSimplexBg();
$bleach = imagecolorallocatealpha($img, 255, 255, 255, mt_rand(80, 100));
$grid_size = mt_rand(floor($img_height * 0.35), floor($img_height * 0.55));
$this->addGridLines($img, $img_width, $img_height, $bleach, $grid_size, 2);
$this->addPowder($img, $img_width, $img_height, 0.08, $bleach);
// Background fog
$img_bg = $this->generateFogBg();
// Text image
list($img_chars_orig) = $this->generateImage($challenge_str, $char_count);
$img_chars = $this->cloneTextImage($img_chars_orig);
$this->removeBackground($img_chars);
// Copy the text to the main fog image
$this->applyTextToSimplexBg($img_chars, $img, 2, -2);
// Prepare the text for the bg image
$bg_chars = imagecreatetruecolor($img_width, $img_height);
$_c = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagealphablending($bg_chars, false);
imagefilledrectangle($bg_chars, 0, 0, $img_width, $img_height, $_c);
$this->applyTextToSimplexBg($img_chars, $bg_chars, 2, -2);
if ($this->useOverlayId) {
$this->overlayPattern($img, $this->useOverlayId, $this->useOverlayDark);
}
if ($this->useInvert) {
imagefilter($img, IMG_FILTER_NEGATE);
}
if ($this->useScoreLines) {
$this->addScoreLines($img, $img_width, $img_height, 1);
}
if ($this->useGridLines) {
$this->addGridLines($img, $img_width, $img_height, 1);
}
// Slice
$slider_range = $this->sliceVerticalSimplex($img, $img_bg, $bg_chars);
// ---
imagetruecolortopalette($img, false, 50);
return [ $challenge_str, $img, $img_width, $img_height, $img_bg, $img_width + $slider_range, 0 ];
}
private function cloneTextImage($img) {
$img_width = self::IMG_WIDTH_MAX;
$img_height = self::IMG_HEIGHT;
$tmp = imagecreatetruecolor($img_width, $img_height);
imagealphablending($tmp, false);
imagecopy($tmp, $img, 0, 0, 0, 0, $img_width, $img_height);
imagealphablending($tmp, true);
return $tmp;
}
public static function getChallengeHash($solution, $params) {
$uniq_id = self::b64_encode_url(openssl_random_pseudo_bytes(self::CHALLENGE_ID_BYTES));
if (!$uniq_id) {
return false;
}
if (!is_array($params) || empty($params)) {
return false;
}
$data = "$uniq_id $solution " . implode(' ', $params);
$challenge_hash = hash_hmac('sha256', $data, base64_decode(self::$hmac_secret));
if (!$challenge_hash) {
return false;
}
return [ $uniq_id, $challenge_hash ];
}
public static function verifyChallengeHash($challenge_hash, $uniq_id, $solution, $params) {
if (!$challenge_hash || !$uniq_id || !$solution) {
return false;
}
if (!is_array($params) || empty($params)) {
return false;
}
$data = "$uniq_id $solution " . implode(' ', $params);
$this_hash = hash_hmac('sha256', $data, base64_decode(self::$hmac_secret));
if (!$this_hash) {
return false;
}
return $this_hash === $challenge_hash;
}
public static function getBase64Images($img, $img_bg = null) {
ob_start();
imagepng($img);
$url_img = base64_encode(ob_get_contents());
if ($img_bg) {
ob_clean();
imagepng($img_bg);
$url_img_bg = base64_encode(ob_get_contents());
}
else {
$url_img_bg = null;
}
ob_end_clean();
return [ $url_img, $url_img_bg ];
}
public static function normalizeReponseStr($str) {
return str_replace(
['F', 'B', 'O', 'Z', 'U', '5', '6', 'Q'],
['P', '8', '0', '2', 'V', 'S', 'G', '0'],
strtoupper(preg_replace('/[^a-zA-Z0-9]+/', '', $str))
);
}
private static function b64_encode_url($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
}
class SimplexNoise {
const G2 = (3.0 - M_SQRT3) / 6.0;
const Grad = [
[1, 1],
[-1, 1],
[1, -1],
[-1, -1],
[1, 0],
[-1, 0],
[1, 0],
[-1, 0],
[0, 1],
[0, -1],
[0, 1],
[0, -1],
];
private $perm = [];
private $permMod12 = [];
public function __construct() {
$max_rand = mt_getrandmax();
$p = [];
for ($i = 0; $i < 256; $i++) {
$p[$i] = $i;
}
for ($i = 255; $i > 0; $i--) {
$n = floor(($i + 1) * (mt_rand() / $max_rand));
$q = $p[$i];
$p[$i] = $p[$n];
$p[$n] = $q;
}
for ($i = 0; $i < 512; $i++) {
$this->perm[$i] = $p[$i & 255];
$this->permMod12[$i] = $this->perm[$i] % 12;
}
}
public function noise($x, $y) {
$s = ($x + $y) * 0.5 * (M_SQRT3 - 1.0);
$i = floor($x + $s);
$j = floor($y + $s);
$t = ($i + $j) * self::G2;
$X0 = $i - $t;
$Y0 = $j - $t;
$x0 = $x - $X0;
$y0 = $y - $Y0;
$i1 = $x0 > $y0 ? 1 : 0;
$j1 = $x0 > $y0 ? 0 : 1;
$xG = $x0 + self::G2;
$yG = $y0 + self::G2;
$x1 = $xG - $i1;
$y1 = $yG - $j1;
$x2 = $xG + self::G2 - 1.0;
$y2 = $yG + self::G2 - 1.0;
$ii = $i & 255;
$jj = $j & 255;
$g0 = self::Grad[$this->permMod12[$ii + $this->perm[$jj]]];
$g1 = self::Grad[$this->permMod12[$ii + $i1 + $this->perm[$jj + $j1]]];
$g2 = self::Grad[$this->permMod12[$ii + 1 + $this->perm[$jj + 1]]];
$t0 = 0.5 - $x0 * $x0 - $y0 * $y0;
$n0 = $t0 < 0 ? 0.0 : ($t0 * $t0 * $t0 * $t0) * ($g0[0] * $x0 + $g0[1] * $y0);
$t1 = 0.5 - $x1 * $x1 - $y1 * $y1;
$n1 = $t1 < 0 ? 0.0 : ($t1 * $t1 * $t1 * $t1) * ($g1[0] * $x1 + $g1[1] * $y1);
$t2 = 0.5 - $x2 * $x2 - $y2 * $y2;
$n2 = $t2 < 0 ? 0.0 : ($t2 * $t2 * $t2 * $t2) * ($g2[0] * $x2 + $g2[1] * $y2);
return 70.14805770653952 * ($n0 + $n1 + $n2);
}
}