annotate sites/all/modules/captcha/image_captcha/image_captcha.user.inc @ 2:b74b41bb73f0

-- Google analytics module
author danieleb <danielebarchiesi@me.com>
date Thu, 22 Aug 2013 17:22:54 +0100
parents
children
rev   line source
danielebarchiesi@2 1 <?php
danielebarchiesi@2 2
danielebarchiesi@2 3 /**
danielebarchiesi@2 4 * @file
danielebarchiesi@2 5 * Functions for the generation of the CAPTCHA image.
danielebarchiesi@2 6 *
danielebarchiesi@2 7 * Loosely Based on MyCaptcha by Heine Deelstra
danielebarchiesi@2 8 * (http://heine.familiedeelstra.com/mycaptcha-download)
danielebarchiesi@2 9 */
danielebarchiesi@2 10
danielebarchiesi@2 11 /**
danielebarchiesi@2 12 * Menu callback function that generates the CAPTCHA image.
danielebarchiesi@2 13 */
danielebarchiesi@2 14 function image_captcha_image() {
danielebarchiesi@2 15 // If output buffering is on: discard current content and disable further buffering
danielebarchiesi@2 16 if (ob_get_level()) {
danielebarchiesi@2 17 ob_end_clean();
danielebarchiesi@2 18 }
danielebarchiesi@2 19
danielebarchiesi@2 20 if (!isset($_GET['sid'])) {
danielebarchiesi@2 21 exit();
danielebarchiesi@2 22 }
danielebarchiesi@2 23 $captcha_sid = $_GET['sid'];
danielebarchiesi@2 24
danielebarchiesi@2 25 // Get solution (the code to show).
danielebarchiesi@2 26 $code = db_query("SELECT solution FROM {captcha_sessions} WHERE csid = :csid",
danielebarchiesi@2 27 array(':csid' => $captcha_sid)
danielebarchiesi@2 28 )->fetchField();
danielebarchiesi@2 29
danielebarchiesi@2 30 // Only generate captcha if code exists in the session.
danielebarchiesi@2 31 if ($code !== FALSE) {
danielebarchiesi@2 32 // generate the image
danielebarchiesi@2 33 $image = @_image_captcha_generate_image($code);
danielebarchiesi@2 34 // check of generation was successful
danielebarchiesi@2 35 if (!$image) {
danielebarchiesi@2 36 watchdog('CAPTCHA', 'Generation of image CAPTCHA failed. Check your image CAPTCHA configuration and especially the used font.', array(), WATCHDOG_ERROR);
danielebarchiesi@2 37 exit();
danielebarchiesi@2 38 }
danielebarchiesi@2 39 // Send the image resource as an image file to the client.
danielebarchiesi@2 40 $file_format = variable_get('image_captcha_file_format', IMAGE_CAPTCHA_FILE_FORMAT_JPG);
danielebarchiesi@2 41 if ($file_format == IMAGE_CAPTCHA_FILE_FORMAT_JPG) {
danielebarchiesi@2 42 drupal_add_http_header('Content-Type', 'image/jpeg');
danielebarchiesi@2 43 imagejpeg($image);
danielebarchiesi@2 44 }
danielebarchiesi@2 45 else {
danielebarchiesi@2 46 drupal_add_http_header('Content-Type', 'image/png');
danielebarchiesi@2 47 imagepng($image);
danielebarchiesi@2 48 }
danielebarchiesi@2 49 // Clean up the image resource.
danielebarchiesi@2 50 imagedestroy($image);
danielebarchiesi@2 51 }
danielebarchiesi@2 52 exit();
danielebarchiesi@2 53 }
danielebarchiesi@2 54
danielebarchiesi@2 55
danielebarchiesi@2 56 /**
danielebarchiesi@2 57 * Small helper function for parsing a hexadecimal color to a RGB tuple.
danielebarchiesi@2 58 */
danielebarchiesi@2 59 function _image_captcha_hex_to_rgb($hex) {
danielebarchiesi@2 60 // handle #RGB format
danielebarchiesi@2 61 if (strlen($hex) == 4) {
danielebarchiesi@2 62 $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
danielebarchiesi@2 63 }
danielebarchiesi@2 64 $c = hexdec($hex);
danielebarchiesi@2 65 $rgb = array();
danielebarchiesi@2 66 for ($i = 16; $i >= 0; $i -= 8) {
danielebarchiesi@2 67 $rgb[] = ($c >> $i) & 0xFF;
danielebarchiesi@2 68 }
danielebarchiesi@2 69 return $rgb;
danielebarchiesi@2 70 }
danielebarchiesi@2 71
danielebarchiesi@2 72
danielebarchiesi@2 73 /**
danielebarchiesi@2 74 * Base function for generating a image CAPTCHA.
danielebarchiesi@2 75 */
danielebarchiesi@2 76 function _image_captcha_generate_image($code) {
danielebarchiesi@2 77 // Get font.
danielebarchiesi@2 78 $fonts = _image_captcha_get_enabled_fonts();
danielebarchiesi@2 79
danielebarchiesi@2 80 // get other settings
danielebarchiesi@2 81 $font_size = (int) variable_get('image_captcha_font_size', 30);
danielebarchiesi@2 82 list($width, $height) = _image_captcha_image_size($code);
danielebarchiesi@2 83
danielebarchiesi@2 84 // create image resource
danielebarchiesi@2 85 $image = imagecreatetruecolor($width, $height);
danielebarchiesi@2 86 if (!$image) {
danielebarchiesi@2 87 return FALSE;
danielebarchiesi@2 88 }
danielebarchiesi@2 89
danielebarchiesi@2 90 // Get the background color and paint the background.
danielebarchiesi@2 91 $background_rgb = _image_captcha_hex_to_rgb(variable_get('image_captcha_background_color', '#ffffff'));
danielebarchiesi@2 92 $background_color = imagecolorallocate($image, $background_rgb[0], $background_rgb[1], $background_rgb[2]);
danielebarchiesi@2 93 // Set transparency if needed.
danielebarchiesi@2 94 $file_format = variable_get('image_captcha_file_format', IMAGE_CAPTCHA_FILE_FORMAT_JPG);
danielebarchiesi@2 95 if ($file_format == IMAGE_CAPTCHA_FILE_FORMAT_TRANSPARENT_PNG) {
danielebarchiesi@2 96 imagecolortransparent($image, $background_color);
danielebarchiesi@2 97 }
danielebarchiesi@2 98 imagefilledrectangle($image, 0, 0, $width, $height, $background_color);
danielebarchiesi@2 99
danielebarchiesi@2 100 // Do we need to draw in RTL mode?
danielebarchiesi@2 101 global $language;
danielebarchiesi@2 102 $rtl = $language->direction && ((bool) variable_get('image_captcha_rtl_support', 0));
danielebarchiesi@2 103
danielebarchiesi@2 104 // draw text
danielebarchiesi@2 105 $result = _image_captcha_image_generator_print_string($image, $width, $height, $fonts, $font_size, $code, $rtl);
danielebarchiesi@2 106 if (!$result) {
danielebarchiesi@2 107 return FALSE;
danielebarchiesi@2 108 }
danielebarchiesi@2 109
danielebarchiesi@2 110 // add noise
danielebarchiesi@2 111 $noise_colors = array();
danielebarchiesi@2 112 for ($i = 0; $i < 20; $i++) {
danielebarchiesi@2 113 $noise_colors[] = imagecolorallocate($image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
danielebarchiesi@2 114 }
danielebarchiesi@2 115 // Add additional noise.
danielebarchiesi@2 116 if (variable_get('image_captcha_dot_noise', 0)) {
danielebarchiesi@2 117 _image_captcha_image_generator_add_dots($image, $width, $height, $noise_colors);
danielebarchiesi@2 118 }
danielebarchiesi@2 119 if (variable_get('image_captcha_line_noise', 0)) {
danielebarchiesi@2 120 _image_captcha_image_generator_add_lines($image, $width, $height, $noise_colors);
danielebarchiesi@2 121 }
danielebarchiesi@2 122
danielebarchiesi@2 123 // Distort the image.
danielebarchiesi@2 124 $distortion_amplitude = .25 * $font_size * variable_get('image_captcha_distortion_amplitude', 0) / 10.0;
danielebarchiesi@2 125 if ($distortion_amplitude > 1) {
danielebarchiesi@2 126 // distortion parameters
danielebarchiesi@2 127 $wavelength_xr = (2+3*lcg_value())*$font_size;
danielebarchiesi@2 128 $wavelength_yr = (2+3*lcg_value())*$font_size;
danielebarchiesi@2 129 $freq_xr = 2 * 3.141592 / $wavelength_xr;
danielebarchiesi@2 130 $freq_yr = 2 * 3.141592 / $wavelength_yr;
danielebarchiesi@2 131 $wavelength_xt = (2+3*lcg_value())*$font_size;
danielebarchiesi@2 132 $wavelength_yt = (2+3*lcg_value())*$font_size;
danielebarchiesi@2 133 $freq_xt = 2 * 3.141592 / $wavelength_xt;
danielebarchiesi@2 134 $freq_yt = 2 * 3.141592 / $wavelength_yt;
danielebarchiesi@2 135
danielebarchiesi@2 136 $distorted_image = imagecreatetruecolor($width, $height);
danielebarchiesi@2 137 if ($file_format == IMAGE_CAPTCHA_FILE_FORMAT_TRANSPARENT_PNG) {
danielebarchiesi@2 138 imagecolortransparent($distorted_image, $background_color);
danielebarchiesi@2 139 }
danielebarchiesi@2 140 if (!$distorted_image) {
danielebarchiesi@2 141 return FALSE;
danielebarchiesi@2 142 }
danielebarchiesi@2 143
danielebarchiesi@2 144 if (variable_get('image_captcha_bilinear_interpolation', FALSE)) {
danielebarchiesi@2 145 // distortion with bilinear interpolation
danielebarchiesi@2 146 for ($x = 0; $x < $width; $x++) {
danielebarchiesi@2 147 for ($y = 0; $y < $height; $y++) {
danielebarchiesi@2 148 // get distorted sample point in source image
danielebarchiesi@2 149 $r = $distortion_amplitude * sin($x * $freq_xr + $y * $freq_yr);
danielebarchiesi@2 150 $theta = $x * $freq_xt + $y * $freq_yt;
danielebarchiesi@2 151 $sx = $x + $r * cos($theta);
danielebarchiesi@2 152 $sy = $y + $r * sin($theta);
danielebarchiesi@2 153 $sxf = (int)floor($sx);
danielebarchiesi@2 154 $syf = (int)floor($sy);
danielebarchiesi@2 155 if ($sxf < 0 || $syf < 0 || $sxf >= $width - 1 || $syf >= $height - 1) {
danielebarchiesi@2 156 $color = $background_color;
danielebarchiesi@2 157 }
danielebarchiesi@2 158 else {
danielebarchiesi@2 159 // bilinear interpolation: sample at four corners
danielebarchiesi@2 160 $color_00 = imagecolorat($image, $sxf , $syf );
danielebarchiesi@2 161 $color_00_r = ($color_00 >> 16) & 0xFF;
danielebarchiesi@2 162 $color_00_g = ($color_00 >> 8) & 0xFF;
danielebarchiesi@2 163 $color_00_b = $color_00 & 0xFF;
danielebarchiesi@2 164 $color_10 = imagecolorat($image, $sxf+1, $syf );
danielebarchiesi@2 165 $color_10_r = ($color_10 >> 16) & 0xFF;
danielebarchiesi@2 166 $color_10_g = ($color_10 >> 8) & 0xFF;
danielebarchiesi@2 167 $color_10_b = $color_10 & 0xFF;
danielebarchiesi@2 168 $color_01 = imagecolorat($image, $sxf , $syf+1);
danielebarchiesi@2 169 $color_01_r = ($color_01 >> 16) & 0xFF;
danielebarchiesi@2 170 $color_01_g = ($color_01 >> 8) & 0xFF;
danielebarchiesi@2 171 $color_01_b = $color_01 & 0xFF;
danielebarchiesi@2 172 $color_11 = imagecolorat($image, $sxf+1, $syf+1);
danielebarchiesi@2 173 $color_11_r = ($color_11 >> 16) & 0xFF;
danielebarchiesi@2 174 $color_11_g = ($color_11 >> 8) & 0xFF;
danielebarchiesi@2 175 $color_11_b = $color_11 & 0xFF;
danielebarchiesi@2 176 // interpolation factors
danielebarchiesi@2 177 $u = $sx - $sxf;
danielebarchiesi@2 178 $v = $sy - $syf;
danielebarchiesi@2 179 // interpolate
danielebarchiesi@2 180 $r = (int)((1-$v)*((1-$u)*$color_00_r + $u*$color_10_r) + $v*((1-$u)*$color_01_r + $u*$color_11_r));
danielebarchiesi@2 181 $g = (int)((1-$v)*((1-$u)*$color_00_g + $u*$color_10_g) + $v*((1-$u)*$color_01_g + $u*$color_11_g));
danielebarchiesi@2 182 $b = (int)((1-$v)*((1-$u)*$color_00_b + $u*$color_10_b) + $v*((1-$u)*$color_01_b + $u*$color_11_b));
danielebarchiesi@2 183 // build color
danielebarchiesi@2 184 $color = ($r<<16) + ($g<<8) + $b;
danielebarchiesi@2 185 }
danielebarchiesi@2 186 imagesetpixel($distorted_image, $x, $y, $color);
danielebarchiesi@2 187 }
danielebarchiesi@2 188 }
danielebarchiesi@2 189 }
danielebarchiesi@2 190 else {
danielebarchiesi@2 191 // distortion with nearest neighbor interpolation
danielebarchiesi@2 192 for ($x = 0; $x < $width; $x++) {
danielebarchiesi@2 193 for ($y = 0; $y < $height; $y++) {
danielebarchiesi@2 194 // get distorted sample point in source image
danielebarchiesi@2 195 $r = $distortion_amplitude * sin($x * $freq_xr + $y * $freq_yr);
danielebarchiesi@2 196 $theta = $x * $freq_xt + $y * $freq_yt;
danielebarchiesi@2 197 $sx = $x + $r * cos($theta);
danielebarchiesi@2 198 $sy = $y + $r * sin($theta);
danielebarchiesi@2 199 $sxf = (int)floor($sx);
danielebarchiesi@2 200 $syf = (int)floor($sy);
danielebarchiesi@2 201 if ($sxf < 0 || $syf < 0 || $sxf >= $width - 1 || $syf >= $height - 1) {
danielebarchiesi@2 202 $color = $background_color;
danielebarchiesi@2 203 }
danielebarchiesi@2 204 else {
danielebarchiesi@2 205 $color = imagecolorat($image, $sxf, $syf);
danielebarchiesi@2 206 }
danielebarchiesi@2 207 imagesetpixel($distorted_image, $x, $y, $color);
danielebarchiesi@2 208 }
danielebarchiesi@2 209 }
danielebarchiesi@2 210 }
danielebarchiesi@2 211 // release undistorted image
danielebarchiesi@2 212 imagedestroy($image);
danielebarchiesi@2 213 // return distorted image
danielebarchiesi@2 214 return $distorted_image;
danielebarchiesi@2 215 }
danielebarchiesi@2 216 else {
danielebarchiesi@2 217 return $image;
danielebarchiesi@2 218 }
danielebarchiesi@2 219 }
danielebarchiesi@2 220
danielebarchiesi@2 221 function _image_captcha_image_generator_add_lines(&$image, $width, $height, $colors) {
danielebarchiesi@2 222 $line_quantity = $width * $height/200.0 * ((int) variable_get('image_captcha_noise_level', 5)) / 10.0;
danielebarchiesi@2 223 for ($i = 0; $i < $line_quantity; $i++) {
danielebarchiesi@2 224 imageline($image, mt_rand(0, $width), mt_rand(0, $height), mt_rand(0, $width), mt_rand(0, $height), $colors[array_rand($colors)]);
danielebarchiesi@2 225 }
danielebarchiesi@2 226 }
danielebarchiesi@2 227
danielebarchiesi@2 228 function _image_captcha_image_generator_add_dots(&$image, $width, $height, $colors) {
danielebarchiesi@2 229 $noise_quantity = $width * $height * ((int) variable_get('image_captcha_noise_level', 5)) / 10.0;
danielebarchiesi@2 230 for ($i = 0; $i < $noise_quantity; $i++ ) {
danielebarchiesi@2 231 imagesetpixel($image, mt_rand(0, $width), mt_rand(0, $height), $colors[array_rand($colors)]);
danielebarchiesi@2 232 }
danielebarchiesi@2 233 }
danielebarchiesi@2 234
danielebarchiesi@2 235 /**
danielebarchiesi@2 236 * Helper function for drawing text on the image.
danielebarchiesi@2 237 */
danielebarchiesi@2 238 function _image_captcha_image_generator_print_string(&$image, $width, $height, $fonts, $font_size, $text, $rtl=FALSE) {
danielebarchiesi@2 239 // get characters
danielebarchiesi@2 240 $characters = _image_captcha_utf8_split($text);
danielebarchiesi@2 241 $character_quantity = count($characters);
danielebarchiesi@2 242
danielebarchiesi@2 243 // get colors
danielebarchiesi@2 244 $background_rgb = _image_captcha_hex_to_rgb(variable_get('image_captcha_background_color', '#ffffff'));
danielebarchiesi@2 245 $foreground_rgb = _image_captcha_hex_to_rgb(variable_get('image_captcha_foreground_color', '#000000'));
danielebarchiesi@2 246 $background_color = imagecolorallocate($image, $background_rgb[0], $background_rgb[1], $background_rgb[2]);
danielebarchiesi@2 247 $foreground_color = imagecolorallocate($image, $foreground_rgb[0], $foreground_rgb[1], $foreground_rgb[2]);
danielebarchiesi@2 248 // precalculate the value ranges for color randomness
danielebarchiesi@2 249 $foreground_randomness = (int)(variable_get('image_captcha_foreground_color_randomness', 100));
danielebarchiesi@2 250 if ($foreground_randomness) {
danielebarchiesi@2 251 $foreground_color_range = array();
danielebarchiesi@2 252 for ($i=0; $i<3; $i++) {
danielebarchiesi@2 253 $foreground_color_range[$i] = array(max(0, $foreground_rgb[$i] - $foreground_randomness), min(255, $foreground_rgb[$i] + $foreground_randomness));
danielebarchiesi@2 254 }
danielebarchiesi@2 255 }
danielebarchiesi@2 256
danielebarchiesi@2 257 // set default text color
danielebarchiesi@2 258 $color = $foreground_color;
danielebarchiesi@2 259
danielebarchiesi@2 260 // the image is seperated in different character cages, one for each character
danielebarchiesi@2 261 // each character will be somewhere inside that cage
danielebarchiesi@2 262 $ccage_width = $width / $character_quantity;
danielebarchiesi@2 263 $ccage_height = $height;
danielebarchiesi@2 264
danielebarchiesi@2 265 foreach ($characters as $c => $character) {
danielebarchiesi@2 266 // initial position of character: in the center of its cage
danielebarchiesi@2 267 $center_x = ($c + 0.5) * $ccage_width;
danielebarchiesi@2 268 if ($rtl) {
danielebarchiesi@2 269 $center_x = $width - $center_x;
danielebarchiesi@2 270 }
danielebarchiesi@2 271 $center_y = 0.5 * $height;
danielebarchiesi@2 272
danielebarchiesi@2 273 // Pick a random font from the list.
danielebarchiesi@2 274 $font = $fonts[array_rand($fonts)];
danielebarchiesi@2 275
danielebarchiesi@2 276 // Get character dimensions for TrueType fonts.
danielebarchiesi@2 277 if ($font != 'BUILTIN') {
danielebarchiesi@2 278 $bbox = imagettfbbox($font_size, 0, drupal_realpath($font), $character);
danielebarchiesi@2 279 // In very rare cases with some versions of the GD library, the x-value
danielebarchiesi@2 280 // of the left side of the bounding box as returned by the first call of
danielebarchiesi@2 281 // imagettfbbox is corrupt (value -2147483648 = 0x80000000).
danielebarchiesi@2 282 // The weird thing is that calling the function a second time
danielebarchiesi@2 283 // can be used as workaround.
danielebarchiesi@2 284 // This issue is discussed at http://drupal.org/node/349218.
danielebarchiesi@2 285 if ($bbox[2] < 0) {
danielebarchiesi@2 286 $bbox = imagettfbbox($font_size, 0, drupal_realpath($font), $character);
danielebarchiesi@2 287 }
danielebarchiesi@2 288 }
danielebarchiesi@2 289 else {
danielebarchiesi@2 290 $character_width = imagefontwidth(5);
danielebarchiesi@2 291 $character_height = imagefontheight(5);
danielebarchiesi@2 292 $bbox = array(0, $character_height, $character_width, $character_height, $character_width, 0, 0, 0);
danielebarchiesi@2 293 }
danielebarchiesi@2 294
danielebarchiesi@2 295 // Random (but small) rotation of the character.
danielebarchiesi@2 296 // TODO: add a setting for this?
danielebarchiesi@2 297 $angle = mt_rand(-10, 10);
danielebarchiesi@2 298
danielebarchiesi@2 299 // Determine print position: at what coordinate should the character be
danielebarchiesi@2 300 // printed so that the bounding box would be nicely centered in the cage?
danielebarchiesi@2 301 $bb_center_x = .5 * ($bbox[0] + $bbox[2]);
danielebarchiesi@2 302 $bb_center_y = .5 * ($bbox[1] + $bbox[7]);
danielebarchiesi@2 303 $angle_cos = cos($angle*3.1415/180);
danielebarchiesi@2 304 $angle_sin = sin($angle*3.1415/180);
danielebarchiesi@2 305 $pos_x = $center_x - ($angle_cos * $bb_center_x + $angle_sin * $bb_center_y);
danielebarchiesi@2 306 $pos_y = $center_y - (-$angle_sin * $bb_center_x + $angle_cos * $bb_center_y);
danielebarchiesi@2 307
danielebarchiesi@2 308 // Calculate available room to jitter: how much can the character be moved
danielebarchiesi@2 309 // so that it stays inside its cage?
danielebarchiesi@2 310 $bb_width = $bbox[2] - $bbox[0];
danielebarchiesi@2 311 $bb_height = $bbox[1] - $bbox[7];
danielebarchiesi@2 312 $dev_x = .5 * max(0, $ccage_width - abs($angle_cos) * $bb_width - abs($angle_sin) * $bb_height);
danielebarchiesi@2 313 $dev_y = .5 * max(0, $ccage_height - abs($angle_cos) * $bb_height - abs($angle_sin) * $bb_width);
danielebarchiesi@2 314
danielebarchiesi@2 315 // add jitter to position
danielebarchiesi@2 316 $pos_x = $pos_x + mt_rand(-$dev_x, $dev_x);
danielebarchiesi@2 317 $pos_y = $pos_y + mt_rand(-$dev_y, $dev_y);
danielebarchiesi@2 318
danielebarchiesi@2 319 // calculate text color in case of randomness
danielebarchiesi@2 320 if ($foreground_randomness) {
danielebarchiesi@2 321 $color = imagecolorallocate($image,
danielebarchiesi@2 322 mt_rand($foreground_color_range[0][0], $foreground_color_range[0][1]),
danielebarchiesi@2 323 mt_rand($foreground_color_range[1][0], $foreground_color_range[1][1]),
danielebarchiesi@2 324 mt_rand($foreground_color_range[2][0], $foreground_color_range[2][1])
danielebarchiesi@2 325 );
danielebarchiesi@2 326 }
danielebarchiesi@2 327
danielebarchiesi@2 328 // draw character
danielebarchiesi@2 329 if ($font == 'BUILTIN') {
danielebarchiesi@2 330 imagestring($image, 5, $pos_x, $pos_y, $character, $color);
danielebarchiesi@2 331 }
danielebarchiesi@2 332 else {
danielebarchiesi@2 333 imagettftext($image, $font_size, $angle, $pos_x, $pos_y, $color, drupal_realpath($font), $character);
danielebarchiesi@2 334 }
danielebarchiesi@2 335
danielebarchiesi@2 336 // For debugging purposes: draw character bounding box (only valid when rotation is disabled).
danielebarchiesi@2 337 // imagerectangle($image, $pos_x + $bbox[0], $pos_y + $bbox[1], $pos_x + $bbox[2], $pos_y + $bbox[7], $color);
danielebarchiesi@2 338
danielebarchiesi@2 339 }
danielebarchiesi@2 340
danielebarchiesi@2 341 // return a sign of success
danielebarchiesi@2 342 return TRUE;
danielebarchiesi@2 343 }