Generate CAPTCHA Image in PHP
CAPTCHA images are used to prevent automated form submission. In this post I am going to demonstrate how you can add an extra layer to your HTML forms using custom CAPTCHA images to prevent automated form submission.
So we will first create a php class captcha.php, which will contain the methods to create a captcha image, add random text to image, add noise to image and blur captcha image. image-captcha.php will be used to use methods from captcha class and will this file be used a source attribute for html image element. index.php will hold the html form and style.css will apply styling to form.
- $image: Holds the image resource created by the GD library.
- $color: Holds the color used for the captcha text.
- $width: Holds the width of the captcha image.
- $height: Holds the height of the captcha image
And following methods will be used:
add_noise: Adds noise to the image by setting random pixels to a specified color.
add_blur: Applies a Gaussian blur filter to the image multiple times.
get_image: Generates the catpcha text, renders the image as a JPEG binary string, and destroys the image resource.
get_decimal_color: Converts a hexadecimal color string to its RGB components and returns an array of colors containing "red", "green" and "blue" colors.
get_true_color: Allocates a color for the image using RGB values and returns integer color.
get_random_string: Generates and returns a random string of the specified length using alphanumeric characters.
captcha.php
<?php
class captcha {
/**
*
* @var GdImage|false
*/
private $image;
/**
*
* @var int
*/
private $color;
/**
*
* @var int
*/
public $width;
/**
*
* @var int
*/
public $height;
/**
*
* @param int $width
* @param int $height
* @param string $background
* @param string $color
*/
function __construct(int $width = 120, int $height = 60, string $background = '#4F5B93', string $color = '#ffffff')
{
// Create captcha image, Set height & width, text color when object is initialized
$this->image = imagecreatetruecolor($width, $height);
$this->width = intval($width);
$this->height = intval($height);
$color = $this->get_decimal_color($color); // Get decimal color value from hex color value
$this->color = $this->get_true_color($color); // Set true color from decimal color
$background = $this->get_decimal_color($background);
$background = $this->get_true_color($background);
// Fill background color to image
imagefill($this->image, 0, 0, $background);
}
/**
* Function to add noise to image
* @param string $color
* @return void
*/
public function add_noise(string $color = '#8892BF'): void
{
$font_color = $this->get_decimal_color($color);
$font_color = $this->get_true_color($font_color);
// Loop x co-ordinates of image
for($r = 0; $r < $this->width; $r++) {
// Loop y co-ordinates of image
for($c = 0; $c < $this->height; $c++) {
// Add noise on step of 1 pixel, skipping one pixel and noise to next pixel
if (mt_rand(0,1) == 1)
imagesetpixel($this->image, $r, $c, $font_color);
}
}
}
/**
* Adds blur filter to image
* @param int $count
* @return void
*/
public function add_blur(int $count = 3): void
{
$count = intval($count) > 10 ? 10 : $count;
for($i = 1; $i <= $count; $i++){
imagefilter($this->image, IMG_FILTER_GAUSSIAN_BLUR);
}
}
/**
* Returns the image as binary string
* @return string
*/
public function get_image(): string
{
$this->add_captcha_text();
$image = imagejpeg($this->image, NULL, 100);
imagedestroy($this->image);
return $image;
}
/**
* Returns decimal equivalent of hex color string
* @param string $color
* @return array
*/
private function get_decimal_color(string $color): array
{
$color = str_starts_with($color, '#') !== false ? substr($color, 1) : $color;
if(strlen($color) === 6){
$red = hexdec(substr($color, 0, 2));
$green = hexdec(substr($color, 2, 2));
$blue = hexdec(substr($color, 4, 2));
}else{
$red = hexdec(str_repeate(substr($color, 0, 1), 2));
$green = hexdec(str_repeate(substr($color, 1, 1), 2));
$blue = hexdec(str_repeate(substr($color, 2, 1), 2));
}
return ['red' => $red, 'green' => $green, 'blue' => $blue];
}
/**
*
* @param array $color
* @return int
*/
private function get_true_color(array $color): int
{
return imagecolorallocate($this->image, $color['red'], $color['green'], $color['blue']);
}
/**
* Generates and returns random string
* @param int $length
* @return string
*/
private function get_random_string(int $length): string
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$random_string = '';
for($i = 0; $i < $length; $i++){
$random_string .= $characters[mt_rand(0,strlen($characters) - 1)];
}
return $random_string;
}
/**
* Adds random string as text to captcha image
* @return void
*/
private function add_captcha_text(): void
{
$font_file = realpath(dirname(__FILE__) . '/fonts/sketch_block.ttf');
$font_size = $this->height * 0.6;
$font_color = $this->color;
$random_string = $this->get_random_string(5);
$ttf_box = imagettfbbox($font_size, 0, $font_file, $random_string);
$xr = abs(max( $ttf_box[2], $ttf_box[4] ));
$xy = abs(max( $ttf_box[5], $ttf_box[7] ));
$x = intval(($this->width - $xr) / 2);
$y = intval(($this->height + $xy) / 2);
imagettftext($this->image, $font_size, 0, $x, $y, $font_color, $font_file, $random_string);
$_SESSION['captcha'] = $random_string;
}
}
captcha-image.php
<?php
session_start();
include_once 'captcha.php';
$captcha = new captcha(280, 60);
$captcha->add_noise(); // Add noise to captcha image
$captcha->add_blur(); // Blur captcha image
header('Content-Type: image/jpeg');
$captcha_image = $captcha->get_image();
die($captcha_image);
index.php
<?php
session_start();
if(!empty($_POST)){
$_SESSION['captcha_status'] = intval($_POST['captcha'] === $_SESSION['captcha']);
header(sprintf('Location: %s', $_SERVER['REQUEST_URI']));
exit();
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Generate Captcha Image in PHP - Demo</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="css/style.css" />
<script type="text/javascript">
function refresh_captcha(){
var random = Math.random();
document.querySelector(".captcha-image").src = "captcha-image.php?" + random;
}
</script>
</head>
<body>
<div class="container">
<div class="my-4">
<div class="captcha-box">
<?php
if(isset($_SESSION['captcha_status'])){
if($_SESSION['captcha_status']){?>
<div class="captcha-message">Captcha verified</div>
<?php
}else{?>
<div class="captcha-message error">Captcha verification failed</div>
<?php
}
unset($_SESSION['captcha_status']);
}?>
<form name="captcha_form" method="POST">
<img class="captcha-image" src="captcha-image.php" />
<div><input type="text" name="captcha" class="form-control" /></div>
<p>Can't read? Click <a href="javascript:refresh_captcha();">here</a> to refresh</p>
<button type="submit" class="btn btn-green btn-block">Verify</button>
</form>
</div>
</div>
</div>
</body>
</html>
style.css
*{ box-sizing: border-box; } html,body{ margin: 0px; padding: 0px; } body{ background: #f0f0f0; font: normal normal 14px Open Sans,Verdana, Arial; } .main-container{ max-width: 1024px; margin: 0px auto; } .section { padding: 15px; } .form-control { padding: 10px; border: 1px solid #ddd; width: 100%; margin-bottom: 5px; color: #444; } .btn-green{ display: inline-block; padding: 5px 10px; cursor: pointer; background: #00a65a; border: 1px solid #009549; color: #fff; width: 100%; } .captcha-box{ max-width: 300px; background: #ddd; padding: 10px; } .captcha-message{ background: #fff; padding: 5px; text-align: center; color: #009549; border: 1px solid #009549; margin-bottom: 10px; } .captcha-message.error{ color: #DD1A16; border: 1px solid #DD1A16; }