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.

Generate Captcha Image in PHP

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.

The captcha class will hold following properties:
  • $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;
}