Sunday, March 3, 2019

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.

captcha.php

<?php
    class captcha{

        private $image;
        private $color;
        public $width;
        public $height;

        function __construct($width = 120,$height = 60,$background = "#4F5B93",$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->decimal_color($color); // Get decimal color value from hex color value
            $this->color = $this->true_color($color); // Set true color from decimal color

            $background = $this->decimal_color($background);
            $background = $this->true_color($background);

            // Fill background color to image
            imagefill($this->image, 0, 0, $background);
        }

        /*****************************************************************
        | Function to add noise to captcha image
        ** @param : (hex) Noise color
        ******************************************************************/
        public function captcha_noise($color = "#8892BF"){
            $font_color = $this->decimal_color($color);
            $font_color = $this->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);
                }
            }
        }

        /*****************************************************************
        | Function to blur captcha image
        * @param : (int) Number of times image should be blurred
        ******************************************************************/
        public function captcha_blur($count = 3){
            $count = intval($count) > 10 ? 10 : $count;
            for($i = 1; $i <= $count; $i++){
                imagefilter($this->image, IMG_FILTER_GAUSSIAN_BLUR);
            }
        }

        /*****************************************************************
        | Function to return created captcha image
        ******************************************************************/
        public function captcha_image(){
            $this->captcha_text();
            ob_start();
            header("Content-Type: image/jpeg");
            imagejpeg($this->image,NULL,100);
            $captcha = ob_get_clean();

            imagedestroy($this->image);
            return $captcha;
        }
        /*****************************************************************
        | Function to blur captcha image
        * @param : (hex) Hexadecimal color value
        ******************************************************************/
        private function decimal_color($color){
            $color = strpos($color, "#") !== FALSE ? substr($color, 1) : $color;

            if(strlen($color) === 6){
                $red = substr($color, 0,2);
                $green = substr($color, 2,2);
                $blue = substr($color, 4,2);
            }else{
                $red = substr($color, 0,1).substr($color, 0,1);
                $green = substr($color, 1,1).substr($color, 1,1);
                $blue = substr($color, 2,1).substr($color, 1,1);
            }
            $red = hexdec($red);
            $green = hexdec($green);
            $blue = hexdec($blue);

            return ["red" => $red, "green" => $green, "blue" => $blue];
        }

        /*****************************************************************
        | Function to convert decimal color to true color
        * @param : (array) Array of decimal color value for red,green,blue
        ******************************************************************/
        private function true_color($color){
            return imagecolorallocate($this->image, $color["red"], $color["green"], $color["blue"]);
        }

        /*****************************************************************
        | Function to generate random string for captcha
        * @param : (int)Length of characters
        ******************************************************************/
        private function random_string($length){
            $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            $random_string = "";

            for($i = 0; $i < $length; $i++){
                $random_string .= $characters[mt_rand(0,strlen($characters) - 1)];
            }

            return $random_string;
        }

        /*****************************************************************
        | Function to add random string to captcha image
        ******************************************************************/
        private function captcha_text(){
            $font_file = realpath(dirname(__FILE__)."/sketch_block.ttf");
            $font_size = $this->height * 0.6;
            $font_color = $this->color;
            $random_string = $this->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 = ( $this->width - $xr ) / 2;
            $y = ( $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->captcha_noise(); // Add noise to captcha image
$captcha->captcha_blur(); // Blur captcha image

echo $captcha->captcha_image();
?>

index.php

<?php
session_start();
if(!empty($_POST)){
    if($_POST['captcha'] === $_SESSION["captcha"]){
        $success = 1;
    }else{
        $success = 0;
    }
    $_SESSION["captcha_status"] = $success;
    header("Location: ".$_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="main-container">
            <div class="section">
                <div class="captcha-box">
                    <?php
                    if(isset($_SESSION["captcha_status"])){
                        $success = $_SESSION["captcha_status"];
                        if($success){?>
                            <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;
}