/** * Generate noisy background * * @param int $width: width of the image in pixels * @param int $height: width of the image in pixels * @param string $word: the captcha word * @param string $backgroundType (see constants) * @param array $backgroundImages: array of background image file names * @param boolean $morphBackground: if TRUE, the background will be morphed * @param boolean $blurBackground: if TRUE, the background will be blurred * @return string GD image identifier of noisy background */ public static function generateNoisyBackground($width, $height, $word, $backgroundType, $backgroundImages = array(), $morphBackground = TRUE, $blurBackground = TRUE) { $image = ImageCreateTrueColor($width, $height); if ($backgroundType != self::BACKGROUND_TYPE_TRANSPARENT) { // Generate noisy background, to be merged with CAPTCHA later // any suggestions on how best to do this much appreciated // sample code would be even better! // I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint) // so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog // ideally, the character obfuscation would be strong enough not to need additional background noise // in any case, I hope at least one of the options given here provide some extra security! $tempBackground = ImageCreateTrueColor($width * 1.5, $height * 1.5); $background = ImageColorAllocate($image, 255, 255, 255); ImageFill($image, 0, 0, $background); $tempBackgroundColor = ImageColorAllocate($tempBackground, 255, 255, 255); ImageFill($tempBackground, 0, 0, $tempBackgroundColor); // We draw all noise onto tempBackground // then if we're morphing, merge from tempBackground to image // or if not, just copy a $width x $height portion of $tempBackground to $image // tempBackground is much larger so that when morphing, the edges retain the noise. switch ($backgroundType) { case self::BACKGROUND_TYPE_WHITE_WITH_GRID: // Draw grid on x for ($i = RandomContentUtility::getRandomNumberInRange(6, 20); $i < $width * 2; $i += RandomContentUtility::getRandomNumberInRange(10, 25)) { ImageSetThickness($tempBackground, RandomContentUtility::getRandomNumberInRange(2, 6)); $textColor = RandomContentUtility::getRandomColor(100, 150); $textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]); ImageLine($tempBackground, $i, 0, $i, $height * 2, $textColor2); } // Draw grid on y for ($i = RandomContentUtility::getRandomNumberInRange(6, 20); $i < $height * 2; $i += RandomContentUtility::getRandomNumberInRange(10, 25)) { ImageSetThickness($tempBackground, RandomContentUtility::getRandomNumberInRange(2, 6)); $textColor = RandomContentUtility::getRandomColor(100, 150); $textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]); ImageLine($tempBackground, 0, $i, $width * 2, $i, $textColor2); } break; case self::BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES: ImageSetThickness($tempBackground, 4); for ($i = 0; $i < strlen($word) + 1; $i++) { $textColor = RandomContentUtility::getRandomColor(100, 150); $textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]); $points = array(); // Draw random squiggle for each character // the longer the loop, the more complex the squiggle // keep random so OCR can't say "if found shape has 10 points, ignore it" // each squiggle will, however, be a closed shape, so OCR could try to find // line terminations and start from there. (I don't think they're that advanced yet..) for ($j = 1; $j < RandomContentUtility::getRandomNumberInRange(5, 10); $j++) { $points[] = RandomContentUtility::getRandomNumberInRange(1 * (20 * ($i + 1)), 1 * (50 * ($i + 1))); $points[] = RandomContentUtility::getRandomNumberInRange(30, $height + 30); } ImagePolygon($tempBackground, $points, intval(sizeof($points) / 2), $textColor2); } break; case self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS: // Take random chunks of $backgroundImages and paste them onto the background for ($i = 0; $i < sizeof($backgroundImages); $i++) { // Read each image and its size $tempImages[$i] = ImageCreateFromJPEG(GeneralUtility::getFileAbsFileName($backgroundImages[$i])); $tempWidths[$i] = imagesx($tempImages[$i]); $tempHeights[$i] = imagesy($tempImages[$i]); } $blocksize = RandomContentUtility::getRandomNumberInRange(20, 60); for ($i = 0; $i < $width * 2; $i += $blocksize) { // Could randomise blocksize here... hardly matters for ($j = 0; $j < $height * 2; $j += $blocksize) { $imageIndex = RandomContentUtility::getRandomNumberInRange(0, sizeof($tempImages) - 1); $cut_x = RandomContentUtility::getRandomNumberInRange(0, $tempWidths[$imageIndex] - $blocksize); $cut_y = RandomContentUtility::getRandomNumberInRange(0, $tempHeights[$imageIndex] - $blocksize); ImageCopy($tempBackground, $tempImages[$imageIndex], $i, $j, $cut_x, $cut_y, $blocksize, $blocksize); } } // Cleanup for ($i = 0; $i < sizeof($tempImages); $i++) { ImageDestroy($tempImages[$i]); } break; } if ($morphBackground) { // Morph background // We do this separately to the main text morph because: // a) the main text morph is done char-by-char, this is done across whole image // b) if an attacker could un-morph the background, it would un-morph the CAPTCHA // hence background is morphed differently to text // why do we morph it at all? it might make it harder for an attacker to remove the background // morph_chunk 1 looks better but takes longer // this is a different and less perfect morph than the one we do on the CAPTCHA // occasonally you get some dark background showing through around the edges // it doesn't need to be perfect as it's only the background. $morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5); $morph_y = 0; for ($x = 0; $x < $width; $x += $morph_chunk) { $morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5); $morph_y += RandomContentUtility::getRandomNumberInRange(-1, 1); ImageCopy($image, $tempBackground, $x, 0, $x + 30, 30 + $morph_y, $morph_chunk, $height * 2); } ImageCopy($tempBackground, $image, 0, 0, 0, 0, $width, $height); $morph_x = 0; for ($y = 0; $y <= $height; $y += $morph_chunk) { $morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5); $morph_x += RandomContentUtility::getRandomNumberInRange(-1, 1); ImageCopy($image, $tempBackground, $morph_x, $y, 0, $y, $width, $morph_chunk); } } else { // Just copy tempBackground onto $image ImageCopy($image, $tempBackground, 0, 0, 30, 30, $width, $height); } // Cleanup ImageDestroy($tempBackground); if ($blurBackground) { $image = self::blurImage($image); } } return $image; }