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); } }