Lagom.nl > Linux > HKCaptcha > source code

HKCaptcha: PHP Source code

random letters random letters

Image generation algorithm

Contents of captcha-image.php.
<?php

/***************************************

 Han-Kwang Nienhuys' PHP captcha
 Copyright June 2007

 This copyright message and attribution must be preserved upon
 modification. Redistribution under other licenses is expressly allowed.
 Other licenses include GPL 2 or higher, BSD, and non-free licenses.
 The original, unrestricted version can be obtained from
 http://www.lagom.nl/linux/hkcaptcha/ .
 
 **************************************
 
 Yet another captcha implementation in PHP.  This one is written with
 the current state (as of 2007) of captcha-defeating research in
 mind. Apart from a letter distortion that is more advanced than just
 rotating the letters, the clutter is designed to make segmentation of
 the image into separate letter glyphs hard to do automatically.

 The 5-letter code is stored into the PHP session variable
 $_SESSION['captcha_string']; see the examples example.html and verify.php.
 
***************************************/


// CONFIG
$font = "include/VeraSeBd.ttf";
$signature = "www.lagom.nl";
$perturbation = 1.0; // bigger numbers give more distortion; 1 is standard
$imgwid = 200; // image width, pixels
$imghgt = 100; // image height, pixels
$numcirc = 4; // number of wobbly circles
$numlines = 3; // number of lines
// END CONFIG

// global vars
$ncols = 20; // foreground or background cols
// end global vars


function frand()
{
  return 0.0001*rand(0,9999);
}

// wiggly random line centered at specified coordinates
function randomline($img, $col, $x, $y)
{
  $theta = (frand()-0.5)*M_PI*0.7;
  global $imgwid;
  $len = rand($imgwid*0.4,$imgwid*0.7);
  $lwid = rand(0,2);

  $k = frand()*0.6+0.2; $k = $k*$k*0.5;
  $phi = frand()*6.28;
  $step = 0.5;
  $dx = $step*cos($theta);
  $dy = $step*sin($theta);
  $n = $len/$step;
  $amp = 1.5*frand()/($k+5.0/$len);
  $x0 = $x - 0.5*$len*cos($theta);
  $y0 = $y - 0.5*$len*sin($theta);

  $ldx = round(-$dy*$lwid);
  $ldy = round($dx*$lwid);
  for ($i = 0; $i < $n; ++$i) {
    $x = $x0+$i*$dx + $amp*$dy*sin($k*$i*$step+$phi);
    $y = $y0+$i*$dy - $amp*$dx*sin($k*$i*$step+$phi);
    imagefilledrectangle($img, $x, $y, $x+$lwid, $y+$lwid, $col);
  }
}

// amp = amplitude (<1), num=numwobb (<1)
function imagewobblecircle($img, $xc, $yc, $r, $wid, $amp, $num, $col)
{
  $dphi = 1;
  if ($r > 0)
    $dphi = 1/(6.28*$r);
  $woffs = rand(0,100)*0.06283;
  for ($phi = 0; $phi < 6.3; $phi += $dphi) {
    $r1 = $r * (1-$amp*(0.5+0.5*sin($phi*$num+$woffs)));
    $x = $xc + $r1*cos($phi);
    $y = $yc + $r1*sin($phi);
    imagefilledrectangle($img, $x, $y, $x+$wid, $y+$wid, $col);
  }
}

// make a distorted copy from $tmpimg to $img. $wid,$height apply to $img,
// $tmpimg is a factor $iscale bigger.
function distorted_copy($tmpimg, $img, $width, $height, $iscale)
{
  $numpoles = 3;

  // make an array of poles AKA attractor points
  global $perturbation;
  for ($i = 0; $i < $numpoles; ++$i) {
    do {
      $px[$i] = rand(0, $width);
    } while ($px[$i] >= $width*0.3 && $px[$i] <= $width*0.7);
    do {
      $py[$i] = rand(0, $height);
    } while ($py[$i] >= $height*0.3 && $py[$i] <= $height*0.7);
    $rad[$i] = rand($width*0.4, $width*0.8);
    $tmp = -frand()*0.15-0.15;
    $amp[$i] = $perturbation * $tmp;
  }

  // get img properties bgcolor
  $bgcol = imagecolorat($tmpimg, 1, 1);
  $width2 = $iscale*$width;
  $height2 = $iscale*$height;
  
  // loop over $img pixels, take pixels from $tmpimg with distortion field
  for ($ix = 0; $ix < $width; ++$ix)
    for ($iy = 0; $iy < $height; ++$iy) {
      $x = $ix;
      $y = $iy;
      for ($i = 0; $i < $numpoles; ++$i) {
	$dx = $ix - $px[$i];
	$dy = $iy - $py[$i];
	if ($dx == 0 && $dy == 0)
	  continue;
	$r = sqrt($dx*$dx + $dy*$dy);
	if ($r > $rad[$i])
	  continue;
	$rscale = $amp[$i] * sin(3.14*$r/$rad[$i]);
	$x += $dx*$rscale;
	$y += $dy*$rscale;
      }
      $c = $bgcol;
      $x *= $iscale;
      $y *= $iscale;
      if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2)
	$c = imagecolorat($tmpimg, $x, $y);
      imagesetpixel($img, $ix, $iy, $c);
    }
}

// add grid for debugging purposes
function addgrid($tmpimg, $width2, $height2, $iscale, $color) {
  $lwid = floor($iscale*3/2);
  imagesetthickness($tmpimg, $lwid);
  for ($x = 4; $x < $width2-$lwid; $x+=$lwid*2)
    imageline($tmpimg, $x, 0, $x, $height2-1, $color);
  for ($y = 4; $y < $height2-$lwid; $y+=$lwid*2)
    imageline($tmpimg, 0, $y, $width2-1, $y, $color);
}


function warped_text_image($width, $height, $string)
{
  // internal variablesinternal scale factor for antialias
  $iscale = 3;

  // initialize temporary image
  $width2 = $iscale*$width;
  $height2 = $iscale*$height;
  $tmpimg = imagecreate($width2, $height2);
  $bgColor = imagecolorallocatealpha ($tmpimg, 192, 192, 192, 100);
  $col = imagecolorallocate($tmpimg, 0, 0, 0);

  // init final image
  $img = imagecreate($width, $height);
  imagepalettecopy($img, $tmpimg);    
  imagecopy($img, $tmpimg, 0,0 ,0,0, $width, $height);
  
  // put straight text into $tmpimage
  global $font;
  $fsize = $height2*0.25;
  $bb = imageftbbox($fsize, 0, $font, $string);
  $tx = $bb[4]-$bb[0];
  $ty = $bb[5]-$bb[1];
  $x = floor($width2/2 - $tx/2 - $bb[0]);
  $y = round($height2/2 - $ty/2 - $bb[1]);
  imagettftext($tmpimg, $fsize, 0, $x, $y, -$col, $font, $string);

  // addgrid($tmpimg, $width2, $height2, $iscale, $col); // debug

  // warp text from $tmpimg into $img
  distorted_copy($tmpimg, $img, $width, $height, $iscale);

  // add wobbly circles (spaced)
  global $numcirc;
  for ($i = 0; $i < $numcirc; ++$i) {
    $x = $width * (1+$i) / ($numcirc+1);
    $x += (0.5-frand())*$width/$numcirc;
    $y = rand($height*0.1, $height*0.9);
    $r = frand();
    $r = ($r*$r+0.2)*$height*0.2;
    $lwid = rand(0,2);
    $wobnum = rand(1,4);
    $wobamp = frand()*$height*0.01/($wobnum+1);
    imagewobblecircle($img, $x, $y, $r, $lwid, $wobamp, $wobnum, $col);
  }
  
  // add wiggly lines
  global $numlines;
  for ($i = 0; $i < $numlines; ++$i) {
    $x = $width * (1+$i) / ($numlines+1);
    $x += (0.5-frand())*$width/$numlines;
    $y = rand($height*0.1, $height*0.9);
    randomline($img, $col, $x, $y);
  }

  return $img;
}

function add_text($img, $string)
{
  $cmtcol = imagecolorallocatealpha ($img, 128, 0, 0, 64);
  imagestring($img, 5, 10, imagesy($img)-20, $string, $cmtcol);
}


// start main program
session_start(); 

// generate  5 letter random string
$rand = "";
// some easy-to-confuse letters taken out C/G I/l Q/O h/b 
$letters = "ABDEFHKLMNOPRSTUVWXZabdefghikmnopqrstuvwxyz";
for ($i = 0; $i < 5; ++$i) {
  $rand .= substr($letters, rand(0,strlen($letters)-1), 1);
}

// create the hash for the random number and put it in the session
$_SESSION['captcha_string'] = $rand;

$image = warped_text_image($imgwid, $imghgt, $rand);
add_text($image, $signature);

// send several headers to make sure the image is not cached     
// taken directly from the PHP Manual 
     
// Date in the past  
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");  
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");  
header("Cache-Control: no-store, no-cache, must-revalidate");  
header("Cache-Control: post-check=0, pre-check=0", false);  
header("Pragma: no-cache");      
header('Content-type: image/png'); 

// send the image to the browser 
imagepng($image); 

// destroy the image to free up the memory 
imagedestroy($image); 
?>

Verification script

Contents of example verify.php.
<?php

session_start();

// check captcha post form field, return 1 if OK
// it is case-insensitive.
function check_captcha()
{
  if (!isset($_POST['captcha']))
    return 0;
  $userletters = strtoupper($_POST['captcha']);
    if (!isset($_SESSION['captcha_string']))
    return 0;
  if ($userletters === strtoupper($_SESSION['captcha_string'])) {
    // make sure that the code is only used once
    unset($_SESSION['captcha_string']);
    return 1;
  }
  return 0;  
}

?>


<html>
<body>
Captcha test: <?php print check_captcha() ? "PASSED" : "FAILED"; ?>
</body>
<html>

Laatste wijziging: 09 Jul 2007   Copyright