Generate CAPTCHA Image in PHP

CAPTCHA images are used to prevent automated form submission. This post demonstrates how to add CAPTCHA image in PHP as an extra layer to your HTML forms and  prevent automated form submission.

Generate Captcha Image in PHP

CAPTCHA images are used in online forms to confirm the form is submitted by a real human and not some robot. CAPTCHA images contain some random characters with some distortion, blur and some random lines which is not readable by a robot. Thus ensuring the form is submitted by a human. 

In this post we will create a PHP CAPTCHA image class with some image manipulation functions. We will then use this class to generate CAPTCHA image in PHP. The main functions to mention in this class are add_noise(), add_blur() and get_random_string(). 

Files we are going to create for this post are:

  1. captcha.php: The PHP class to create captcha image.
  2. captcha-image.php: The PHP script to create instance of captcha class and generate a captcha image.
  3. index.php: The main index page which will contain a form with CAPTCHA field.
  4. style.css: The CSS styles for HTML page and CAPTCHA form.

Create Image Captcha Class in PHP

Our captcha class will be using GD Library to generate CAPTCHA image. This class will house methods for generating GD image resource, Adding noise to image, Adding blur to image and return image to browser. The constructor method of this class will generate CAPTCHA image with the provided background color for image. The class definition is as below:

  • $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 captcha 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;
}
}

Create Captcha Image in PHP

Our captcha-image.php file will create CAPTCHA image and return image as response of page.

  • Create an instance of captcha class providing the width and height for CAPTCHA image.
  • Add noise to CAPTCHA image.
  • Add blur to CAPTCHA image.
  • Set Content Type of page to image/jpeg.
  • Return image as response.

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

Create HTML Form with CAPTCHA Field

Create an HTML page with form containing the CAPTCHA image and input field to enter captcha. Initially load the image returned from captcha-image.php file. Then the refresh the image with javascript click event.

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> 

Add CSS styles

Add CSS style for whole HTML page and CAPTCHA form.

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