Lucky's Captcha v4

Another captcha by me :)

This captcha consists of three completely random generated drawings, and each of the drawing contains has a code behind it. The drawings are painted on the image on the right side. One of the three drawings is drawings is painted on the left side of the image. The visitor has to enter te code which matches the drawing on the right.

Download Download script as zip

Tag Tags: gd php

Source

  • captcha.class.php
  • captcha.php
  1. <?php
  2. /**
  3.  * Lucky's Framework
  4.  * A highly extendable MVC PHP framework
  5.  *
  6.  * Created by Lucas van Dijk (http://www.return1.net)
  7.  * Copyright 2007 by Lucas van Dijk
  8.  *
  9.  * $Id$
  10.  *
  11.  * ---------------------------------------------------------------------
  12.  *
  13.  * This program is free software: you can redistribute it and/or modify
  14.  * it under the terms of the GNU General Public License as published by
  15.  * the Free Software Foundation, either version 3 of the License, or
  16.  * (at your option) any later version.
  17.  *
  18.  * This program is distributed in the hope that it will be useful,
  19.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21.  * GNU General Public License for more details.
  22.  *
  23.  * You should have received a copy of the GNU General Public License
  24.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  25.  */
  26.  
  27. /**
  28.  * Represents an image.
  29.  *
  30.  * @package Images
  31.  * @author Lucas van Dijk
  32.  */
  33. class Image
  34. {
  35.    
  36.     /**
  37.      * Holds the image GD resource
  38.      *
  39.      * @var resource
  40.      */
  41.     protected $image_resource;
  42.    
  43.     /**
  44.      * Holds the width of the image
  45.      *
  46.      * @var int
  47.      */
  48.     protected $width;
  49.    
  50.     /**
  51.      * Holds the height of the image
  52.      *
  53.      * @var int
  54.      */
  55.     protected $height;
  56.    
  57.     /**
  58.      * Constructor, creates the image resource
  59.      * @param mixed $param1 The filename to open, or the width of the image
  60.      * @param int $param2 The height of the image, when creating a new image (not from a file)
  61.      */
  62.     public function __construct($param1, $param2 = null)
  63.     {
  64.         if(empty($param2))
  65.         {
  66.             // Just one parameter given,
  67.             // Param1 should be a filename
  68.             $this -> image_resource = $this -> create_image($param1);
  69.         }
  70.         else
  71.         {
  72.             // Param 2 is given
  73.             // So we create a new image with the width and height
  74.             $this -> image_resource = imagecreatetruecolor(intval($param1), intval($param2));
  75.         }
  76.  
  77.         if(!$this -> image_resource)
  78.         {
  79.             throw new Exception('Could not create image', 100);
  80.         }
  81.  
  82.         $this -> update_size();
  83.     }
  84.  
  85.     /**
  86.      * Creates an GD image resource based on image type
  87.      * @param string $file The file to open
  88.      * @return resource
  89.      */
  90.     protected function create_image($file)
  91.     {
  92.         list(, , $image_type) = getimagesize($file);
  93.         $img = false;
  94.  
  95.         switch($image_type)
  96.         {
  97.             case 3:
  98.                 $img = imagecreatefrompng($file);
  99.             break;
  100.             case 2:
  101.                 $img = imagecreatefromjpeg($file);
  102.             break;
  103.             case 1:
  104.                 $img = imagecreatefromgif($file);
  105.             break;
  106.             case 6:
  107.                 $img = imagecreatefromwbmp($file);
  108.             break;
  109.             default:
  110.                 throw new Exception('File is not a valid Image', 101);
  111.         }
  112.  
  113.         return $img;
  114.     }
  115.    
  116.     /**
  117.      * Allocates a color, based on a hex color
  118.      * @param string $color The hex color to allocate
  119.      * @param int The background color
  120.      */
  121.     protected function allocate_color($color)
  122.     {
  123.         if(substr($color, 0, 1) == "#")
  124.         {
  125.             $color = substr($color, 1);
  126.         }
  127.        
  128.         $parts = str_split($color, 2);
  129.        
  130.         return eval('return imagecolorallocate($this -> image_resource, 0x'.$parts[0].', 0x'.$parts[1].', 0x'.$parts[2].');');
  131.     }
  132.    
  133.     /**
  134.      * Generates a random hex color
  135.      * @param bool $return_array return an array with three elememts containing each a part of the color, default false
  136.      * @return string|array The random hex color
  137.      */
  138.     protected function random_hex_color($return_array = false)
  139.     {
  140.         $chars = array_merge(range('A', 'F'), range('0', '9 '));
  141.  
  142.         $max_chars = count($chars) - 1;
  143.         srand( (double) microtime()*1000000);
  144.  
  145.         $rand_color = '#';
  146.         for($i = 0; $i < 6; $i++)
  147.         {
  148.             $rand_color = ( empty($rand_color)) ? $chars[rand(0, $max_chars)] : $rand_color . $chars[rand(0, $max_chars)];
  149.         }
  150.  
  151.         if($return_array)
  152.         {
  153.             $rand_color_array = array();
  154.             $rand_color_array[0] = substr($rand_color, 1, 2);
  155.             $rand_color_array[1] = substr($rand_color, 3, 2);
  156.             $rand_color_array[2] = substr($rand_color, 5, 2);
  157.  
  158.             return $rand_color_array;
  159.         }
  160.  
  161.         return $rand_color;
  162.     }
  163.    
  164.     /**
  165.      * Generates a random RGB Color
  166.      * @return array with key 0 as red, key 1 as green, and key 2 as blue
  167.      */
  168.     protected function random_rgb_color()
  169.     {
  170.         $color = array();
  171.         for ($c = 0; $c < 3; $c++)
  172.         {
  173.             $color[] = rand(1, 255);
  174.         }
  175.  
  176.         return $color;
  177.     }
  178.    
  179.     /**
  180.      * Updates the width and height of the image
  181.      */
  182.     protected function update_size()
  183.     {
  184.         $this -> width = imagesx($this -> image_resource);
  185.         $this -> height = imagesy($this -> image_resource);
  186.     }
  187.    
  188.     /**
  189.      * Returns the image resource
  190.      * @return resource
  191.      */
  192.     public function get_resource()
  193.     {
  194.         return $this -> image_resource;
  195.     }
  196.    
  197.     /**
  198.      * Copies an Image object into the current image
  199.      * @param Image $image the image object to copy
  200.      * @param int $x The X position
  201.      * @param int $y The Y psoition
  202.      */
  203.     public function copy(Image $image, $x, $y)
  204.     {
  205.         imagecopy($this -> image_resource, $image -> get_resource(), $x, $y, 0, 0, $image -> get_width() - 1, $image -> get_height() - 1);
  206.     }
  207.    
  208.     /**
  209.      * Resizes the image
  210.      * @param int $max_width max width of the image
  211.      * @param int $max_height max height of the image
  212.      * @param bool $keep_aspect_ratio True if you wnat to keep the aspect ratio
  213.      */
  214.     public function resize($max_width, $max_height, $keep_aspect_ratio = true)
  215.     {      
  216.         if($keep_aspect_ratio)
  217.         {
  218.             // Find resize scale
  219.             $resize_scale = min(min($max_width / $this -> width, $max_height / $this -> height), 1.0);
  220.    
  221.             $new_width = $resize_scale * $this -> width;
  222.             $new_height = $resize_scale * $this -> height;
  223.         }
  224.         else
  225.         {
  226.             $new_width = $max_width;
  227.             $new_height = $max_height;
  228.         }
  229.  
  230.         $new_img = imagecreatetruecolor($new_width, $new_height);
  231.  
  232.         // Resize it
  233.         imagecopyresampled($new_img, $this -> image_resource, 0, 0, 0, 0, $new_width, $new_height, $this -> width, $this -> height);
  234.        
  235.         $this -> image_resource = $new_img;
  236.         $this -> update_size();
  237.     }
  238.    
  239.    
  240.     /**
  241.      * Outputs the image to the screen
  242.      *
  243.      * This method outputs the current image to the screen.
  244.      * Note: This function does <b>not</b> send the right Content-Type
  245.      * for the image, you'll have to do that by yourself
  246.      * @param string $type The image type to output
  247.      * @param int $quality The quality of the image (only needed for JPEG images)
  248.      */
  249.     public function show_image($type = "png", $quality = 70)
  250.     {
  251.         switch($type)
  252.         {
  253.             case "png":
  254.                 imagepng($this -> image_resource);
  255.             break;
  256.             case "jpg":
  257.             case "jpeg":
  258.                 imagejpeg($this -> image_resource, null, $quality);
  259.             break;
  260.             case "gif":
  261.                 imagegif($this -> image_resource);
  262.             break;
  263.             default:
  264.                 imagepng($this -> image_resource);
  265.         }
  266.     }
  267.  
  268.     /**
  269.      * Saves the image to a specific path
  270.      * @param string $file The path where to save to
  271.      * @param string $type The image type
  272.      * @param int $quality The quality of the image (only needed for JPEG images)
  273.      */
  274.     public function save_to_file($file, $type = "png", $quality = 70)
  275.     {
  276.         switch($type)
  277.         {
  278.             case "png":
  279.                 imagepng($this -> image_resource, $file);
  280.             break;
  281.             case "jpg":
  282.             case "jpeg":
  283.                 imagejpeg($this -> image_resource, $file, $quality);
  284.             break;
  285.             case "gif":
  286.                 imagegif($this -> image_resource, $file);
  287.             break;
  288.             default:
  289.                 imagepng($this -> image_resource, $file);
  290.         }
  291.     }
  292.    
  293.     /**
  294.      * Returns the width of the image
  295.      *
  296.      * @return int
  297.      */
  298.     public function get_width()
  299.     {
  300.         return $this -> width;
  301.     }
  302.    
  303.     /**
  304.      * Returns the height of the image
  305.      *
  306.      * @return int
  307.      */
  308.     public function get_height()
  309.     {
  310.         return $this -> height;
  311.     }
  312.    
  313.     public function __toString()
  314.     {
  315.         return imagepng($this -> image_resource);
  316.     }
  317. }
  318.  
  319. /**
  320.  * Represents the captcha image
  321.  * It generates a number of drawings,
  322.  * paints it on the main image,
  323.  * and sets the captcha code
  324.  * @package Images
  325.  * @author Lucas van Dijk
  326.  */
  327. class Captcha extends Image
  328. {
  329.     protected $drawings = array();
  330.     protected $font;
  331.     protected $number;
  332.     protected $code_length;
  333.     protected $code;
  334.    
  335.     /**
  336.      * Constructor, sets the captcha parameters, and inits the image
  337.      * @param string $font The TTF font file used in the image, use ./font.ttf (with ./) ti use a font in the current directory
  338.      * @param int $number_of_drawings The number of drawings you want to generate
  339.      * @param int $code_length The length of the catcha codes
  340.      */
  341.     public function __construct($number_of_drawings = 2, $code_length = 5)
  342.     {
  343.         if(!(ctype_digit((string) $number_of_drawings) && ctype_digit((string) $code_length)))
  344.         {
  345.             throw new InvalidArgumentException('Invalid parameters');
  346.         }      
  347.        
  348.         $this -> code_length = $code_length;
  349.         $this -> number = $number_of_drawings;
  350.        
  351.         $height = 50 + ($number_of_drawings * 35);     
  352.         $width = 200 + (imagefontwidth(4) * $code_length) + 50;
  353.        
  354.         parent::__construct($width, $height);
  355.         $this -> create();
  356.         imagecolortransparent($this -> image_resource, $this -> allocate_color('FFFFFF'));
  357.     }
  358.    
  359.     /**
  360.      * Generates a random string
  361.      * @param int $max_length The length of the generated string
  362.      * @param bool $hash Hash the string with md5?
  363.      * @return string The generated string
  364.      */
  365.     public function generate_random_string($max_length = 8, $hash = false)
  366.     {
  367.         $chars = array_merge(range('a', 'z'), range('A', 'Z'), range('0', '9'));
  368.  
  369.         $max_chars = count($chars) - 1;
  370.  
  371.         $rand_str = '';
  372.         for($i = 0; $i < $max_length; $i++)
  373.         {
  374.             $rand_str = ($i == 0) ? $chars[mt_rand(0, $max_chars)] : $rand_str . $chars[mt_rand(0, $max_chars)];
  375.         }
  376.  
  377.         return ($hash) ? md5($rand_str) : $rand_str;
  378.     }
  379.    
  380.     /**
  381.      * Returns the 'main' captcha code
  382.      * @return string
  383.      */
  384.     public function get_code()
  385.     {
  386.         return $this -> code;
  387.     }
  388.    
  389.     /**
  390.      * Creates the image
  391.      *
  392.      * Adds a border to the image, the seperation line, and paints the drawings
  393.      * on the image
  394.      */
  395.     protected function create()
  396.     {
  397.         imagefilledrectangle($this -> image_resource, 0, 0, $this -> get_width() - 1, $this -> get_height() - 1, $this -> allocate_color('FFFFFF'));
  398.         imagerectangle($this -> image_resource, 0, 0, $this -> get_width() - 1, $this -> get_height() - 1, $this -> allocate_color('000000'));
  399.         imageline($this -> image_resource, 150, 0, 150, $this -> get_height(), $this -> allocate_color('000000'));
  400.        
  401.         for($i = 0; $i < $this -> number; $i++)
  402.         {
  403.             $this -> add_drawing(new CaptchaDrawing());
  404.         }
  405.        
  406.         // Determine the 'main' drawing
  407.         $main = mt_rand(0, count($this -> drawings) - 1);
  408.         $this -> code = $this -> drawings[$main][1];
  409.         $this -> paint_drawings($main);
  410.     }
  411.    
  412.     /**
  413.      * This function paints the drawings on the main image
  414.      * @param int $main The index of the 'main' image, which will be painted on the left
  415.      */
  416.     protected function paint_drawings($main)
  417.     {
  418.         // paint the main drawing on the image
  419.         $this -> copy($this -> drawings[$main][0], 25, ($this -> get_height() / 2) - 50);
  420.        
  421.         // Paint all drawings on the right of the image, with the code
  422.         $i = 0;
  423.         $x = 175;
  424.         foreach($this -> drawings as $drawing)
  425.         {
  426.             $drawing[0] -> resize(30, 30);
  427.             $this -> copy($drawing[0], $x, 25 + (50 * $i) - 15);
  428.            
  429.             imagestring($this -> image_resource, 4, $x + 40, 15 + (50 * $i), $drawing[1], $this -> allocate_color('000000'));
  430.             $i++;
  431.         }
  432.     }          
  433.    
  434.     /**
  435.      * Adds a drawing to the $drawings member, and generates a code for the drawing
  436.      * @param CaptchaDrawing $drawing The drawing to add
  437.      */
  438.     protected function add_drawing(CaptchaDrawing $drawing)
  439.     {
  440.         $this -> drawings[] = array($drawing, $this -> generate_random_string($this -> code_length));
  441.     }
  442. }
  443.  
  444. /**
  445.  * A simple drawing on the captcha images
  446.  *
  447.  * The drawing is generated completely random
  448.  * @package Images
  449.  * @author Lucas van Dijk
  450.  */
  451. class CaptchaDrawing extends Image
  452. {
  453.     /**
  454.      * Constructor, generates the image immediatly
  455.      * @param int $width The width of the drawing
  456.      * @param int $height The height of the drawing
  457.      */
  458.     public function __construct($width = 100, $height = 100)
  459.     {
  460.         parent::__construct($width, $height);
  461.         $this -> generate_random_drawing();
  462.     }
  463.    
  464.     /**
  465.      * Generates a completely random drawing
  466.      */
  467.     protected function generate_random_drawing()
  468.     {      
  469.         imagefilledrectangle($this -> image_resource, 0, 0, $this -> get_width() - 1, $this -> get_height() - 1, $this -> allocate_color('FFFFFF'));
  470.        
  471.         $lines_drawed = 0;
  472.         $max_lines = rand(2, 5);
  473.        
  474.         $min_width = $this -> get_width() / 100 * 15;
  475.         $max_width = $this -> get_width() / 100 * 75;
  476.         $min_height = $this -> get_height() / 100 * 15;
  477.         $max_height = $this -> get_height() / 100 * 75;
  478.        
  479.         $rectangle_drawed = false;
  480.         $circle_drawed = false;
  481.        
  482.         while(($rectangle_drawed == false || $circle_drawed == false) && $lines_drawed < $max_lines)
  483.         {
  484.             $what_to_draw = mt_rand(1, 4);
  485.            
  486.             // generate random width/height
  487.             $width = mt_rand($min_width, $max_width);
  488.             $height = $width * (mt_rand(75, 120) / 100);
  489.            
  490.             // Generate the X and Y positions
  491.             $max_x = $this -> get_width() - $width;
  492.             $max_y = $this -> get_height() - $height;
  493.            
  494.             $x = mt_rand(0, $max_x);
  495.             $y = mt_rand(0, $max_y);
  496.            
  497.             $filled = mt_rand(0, 2) != 0;
  498.            
  499.             switch($what_to_draw)
  500.             {
  501.                 case 1:        
  502.                     if($filled)
  503.                     {
  504.                         imagefilledellipse($this -> image_resource, $x, $y, $width, $height, $this -> allocate_color($this -> random_hex_color()));
  505.                     }
  506.                     else
  507.                     {
  508.                         imageellipse($this -> image_resource, $x, $y, $width, $height, $this -> allocate_color($this -> random_hex_color()));
  509.                     }
  510.                    
  511.                     $circle_drawed = true;
  512.                 break;
  513.                 case 2:        
  514.                     if($filled)
  515.                     {
  516.                         imagefilledrectangle($this -> image_resource, $x, $y, $x + $width, $y + $height, $this -> allocate_color($this -> random_hex_color()));
  517.                     }
  518.                     else
  519.                     {
  520.                         imagerectangle($this -> image_resource, $x, $y, $x + $width, $y + $height, $this -> allocate_color($this -> random_hex_color()));
  521.                     }
  522.                    
  523.                     $rectangle_drawed = true;
  524.                 break;
  525.                 case 3:
  526.                 case 4:        
  527.                     // Draw a random line
  528.                     if($lines_drawed <= $max_lines)
  529.                     {
  530.                         $x2 = mt_rand($min_width, $max_width);
  531.                         $y2 = mt_rand($min_height, $max_height);
  532.                        
  533.                         imageline($this -> image_resource, $x, $y, $x2, $y2, $this -> allocate_color($this -> random_hex_color()));
  534.                         $lines_drawed++;
  535.                     }
  536.                 break;
  537.             }
  538.         }
  539.     }
  540. }
  541.  
  1. <?php
  2. include_once 'captcha.class.php';
  3.  
  4. $captcha = new Captcha(3);
  5. $_SESSION['captcha_code'] = strtolower($captcha -> get_code());
  6.  
  7. header("Content-Type: image/png");
  8. echo (string) $captcha;
  9.