Generate A CAPTCHA Image in PHP (GD Library)

CAPTCHA images are used to prevent automated form submission. This post demonstrates how to add a 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 that 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 that are 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 the 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 the captcha image.
  2. captcha-image.php: The PHP script to create an instance of the captcha class and generate a captcha image.
  3. index.php: The main index page that will contain a form with a CAPTCHA field.
  4. style.css: The CSS styles for the HTML page and CAPTCHA form.

How to Generate and Validate CAPTCHA Image in PHP

Generating a CAPTCHA image in PHP can be achieved in a few simple steps. The steps to generate a CAPTCHA image in PHP are:

  • Create a PHP image CAPTCHA class for image manipulation. This file will generate a CAPTCHA image with a random string.
  • Create another PHP file to receive the submitted form for processing and compare the CAPTCHA string passed in the form against the string generated by the CAPTCHA class.
  • Create a user interface with an HTML form for submission, along with the CAPTCHA string.
  • Add CSS styles for the user interface and test the CAPTCHA image implementation. 
 

Step 1: Create Image Captcha Class in PHP (GD Library)

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

  • $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 the 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 equivalent 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 an 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;
}
}

Step 2: Create a Captcha Image in PHP using CAPTCHA Class

Our captcha-image.php file will create a CAPTCHA image and return the image as the response to the page.

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

captcha-image.php

<?php
if (!session_id()) {
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);
 

Step 3: Create an HTML Form with a CAPTCHA Field

Create an HTML page with a form containing the CAPTCHA image and an input field to enter the CAPTCHA. Initially, load the image returned from the captcha-image.php file. Then refresh the image with a JavaScript click event. We also validate the CAPTCHA in this file.

index.php

<?php
if (!session_id()) {
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"/>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"/>
<link rel="stylesheet" href="css/style.css"/>

<script type="text/javascript">
function refresh_captcha() {
let random = Math.random();
document.querySelector(".captcha-image").src = "captcha-image.php?" + random;
}
</script>
</head>
<body>
<section class="section py-4">
<div class="container">
<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>
</section>
</body>
</html>

Step 4: Add CSS CAPTCHA Interface Styles

Add CSS styles for the entire HTML page and CAPTCHA form.

style.css

* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
body {
background-color: #f6f6f6;
font-family: "Segoe UI", "Roboto", "Helvetica", sans-serif;
font-size: 15px;
font-weight: normal;
font-style: normal;
line-height: 1.5;
}
.container {
width: 100%;
max-width: 1140px;
margin-right: auto;
margin-left: auto;
padding-right: 15px;
padding-left: 15px;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.form-control {
padding: 10px;
border: 1px solid #ddd;
width: 100%;
margin-bottom: 5px;
color: #444;
}
.btn {
display: inline-block;
padding: 5px 10px;
cursor: pointer;
font: inherit;
}
.btn-block {
width: 100%;
}
.btn-green {
background-color: #00a65a;
border: 1px solid #00a65a;
color: #ffffff;
}
.captcha-box {
max-width: 300px;
background: #dddddd;
padding: 0.5rem;
}
.captcha-message {
background: #ffffff;
padding: 0.25rem;
text-align: center;
color: #009549;
border: 1px solid #009549;
margin-bottom: 1rem;
}
.captcha-message.error {
color: #dd1a16;
border: 1px solid #dd1a16;
}