diff --git a/graphmath_captcha/README b/graphmath_captcha/README
new file mode 100644
index 0000000..479478a
--- /dev/null
+++ b/graphmath_captcha/README
@@ -0,0 +1,15 @@
+GraphMath CAPTCHA is a graphical version of the well-known Math CAPTCHA, which is part of the CAPTCHA module.
+In this case the mathematical equation is hidden in an obfuscated image. The amount and types of distortion
+and noise can be configured along with the image type, the types and sizes of the fonts used, and color settings.
+This module is intended as a submodule of the CAPTCHA module. This module was built using the Image CAPTCHA 
+submodule of the CAPTCHA module.
+ 
+Installation
+Installation is the same as for all modules: copy the graphmath_captcha dir into the directory of the CAPTCHA 
+module, or to the modules dir. Enable the module under the category Spam Control, then go to 
+admin/config/people/captcha/graphmath_captcha to configure it.
+ 
+Requirements
+ •The main CAPTCHA module
+ •The GD graphical library
+ •Truetype font support
diff --git a/graphmath_captcha/graphmath_captcha.admin.inc b/graphmath_captcha/graphmath_captcha.admin.inc
new file mode 100644
index 0000000..642f8a6
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.admin.inc
@@ -0,0 +1,402 @@
+<?php
+
+/**
+ * @file
+ * Functions for administration/settings interface.
+ *
+ */
+
+
+/**
+ * Configuration form for graphmath_captcha.
+ */
+function graphmath_captcha_settings_form() {
+
+  $form = array();
+
+  // Add CSS for theming of admin form.
+  $form['#attached']['css'] = array(drupal_get_path('module', 'graphmath_captcha') . '/graphmath_captcha.css');
+  // Use javascript for some added usability on admin form.
+  $form['#attached']['js'] = array(drupal_get_path('module', 'graphmath_captcha') . '/graphmath_captcha.js');
+
+
+  // First some error checking.
+  $setup_status = _graphmath_captcha_check_setup(FALSE);
+  if ($setup_status & graphmath_captcha_ERROR_NO_GDLIB) {
+    drupal_set_message(t(
+      'The GraphMath CAPTCHA module can not generate images because your PHP setup does not support it (no <a href="!gdlib">GD library</a> with JPEG support).',
+      array('!gdlib' => 'http://php.net/manual/en/book.image.php')
+    ), 'error');
+    // It is no use to continue building the rest of the settings form.
+    return $form;
+  }
+
+  $form['graphmath_captcha_example'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Example'),
+    '#description' => t('Presolved GraphMath CAPTCHA example, generated with the current settings.'),
+  );
+  $form['graphmath_captcha_example']['image'] = array(
+    '#type' => 'captcha',
+    '#captcha_type' => 'graphmath_captcha/Image',
+    '#captcha_admin_mode' => TRUE,
+  );
+
+  // Font related stuff.
+  $form['graphmath_captcha_font_settings'] = _graphmath_captcha_settings_form_font_section();
+
+    // Color and file format settings.
+  $form['graphmath_captcha_color_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Color and image settings'),
+    '#description' => t('Configuration of the background, text colors and file format of the GraphMath CAPTCHA.'),
+  );
+  $form['graphmath_captcha_color_settings']['graphmath_captcha_background_color'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Background color'),
+    '#description' => t('Enter the hexadecimal code for the background color (e.g. #FFF or #FFCE90). When using the PNG file format with transparent background, it is recommended to set this close to the underlying background color.'),
+    '#default_value' => variable_get('graphmath_captcha_background_color', '#ffffff'),
+    '#maxlength' => 7,
+    '#size' => 8,
+  );
+  $form['graphmath_captcha_color_settings']['graphmath_captcha_foreground_color'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Text color'),
+    '#description' => t('Enter the hexadecimal code for the text color (e.g. #000 or #004283).'),
+    '#default_value' => variable_get('graphmath_captcha_foreground_color', '#000000'),
+    '#maxlength' => 7,
+    '#size' => 8,
+  );
+  $form['graphmath_captcha_color_settings']['graphmath_captcha_foreground_color_randomness'] = array(
+    '#type' => 'select',
+    '#title' => t('Additional variation of text color'),
+    '#options' => array(
+      0 => t('none'),
+      50 => t('small'),
+      100 => t('moderate'),
+      150 => t('high'),
+      200 => t('very high'),
+    ),
+    '#default_value' => (int) variable_get('graphmath_captcha_foreground_color_randomness', 100),
+    '#description' => t('The different characters will have randomized colors in the specified range around the text color.'),
+  );
+  $form['graphmath_captcha_color_settings']['graphmath_captcha_file_format'] = array(
+    '#type' => 'select',
+    '#title' => t('File format'),
+    '#description' => t('Select the file format for the image. JPEG usually results in smaller files, PNG allows tranparency.'),
+    '#default_value' => variable_get('graphmath_captcha_file_format', graphmath_captcha_FILE_FORMAT_JPG),
+    '#options' => array(
+      graphmath_captcha_FILE_FORMAT_JPG => t('JPEG'),
+      graphmath_captcha_FILE_FORMAT_PNG => t('PNG'),
+      graphmath_captcha_FILE_FORMAT_TRANSPARENT_PNG => t('PNG with transparent background'),
+    ),
+  );
+
+  // distortion and noise settings
+  $form['graphmath_captcha_distortion_and_noise'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Distortion and noise'),
+    '#description' => t('With these settings you can control the degree of obfuscation by distortion and added noise. Do not exaggerate the obfuscation and assure that the code in the image is reasonably readable. For example, do not combine high levels of distortion and noise.'),
+  );
+  // distortion
+  $form['graphmath_captcha_distortion_and_noise']['graphmath_captcha_distortion_amplitude'] = array(
+    '#type' => 'select',
+    '#title' => t('Distortion level'),
+    '#options' => array(
+      0 => t('@level - no distortion', array('@level' => '0')),
+      1 => t('@level - low', array('@level' => '1')),
+      2 => '2',
+      3 => '3',
+      4 => '4',
+      5 => t('@level - medium', array('@level' => '5')),
+      6 => '6',
+      7 => '7',
+      8 => '8',
+      9 => '9',
+      10 => t('@level - high', array('@level' => '10')),
+    ),
+    '#default_value' => (int) variable_get('graphmath_captcha_distortion_amplitude', 0),
+    '#description' => t('Set the degree of wave distortion in the image.'),
+  );
+  $form['graphmath_captcha_distortion_and_noise']['graphmath_captcha_bilinear_interpolation'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Smooth distortion'),
+    '#default_value' => variable_get('graphmath_captcha_bilinear_interpolation', FALSE),
+    '#description' => t('This option enables bilinear interpolation of the distortion which makes the image look smoother, but it is more CPU intensive.'),
+  );
+  // noise
+  $form['graphmath_captcha_distortion_and_noise']['graphmath_captcha_dot_noise'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Add salt and pepper noise'),
+    '#default_value' => variable_get('graphmath_captcha_dot_noise', 0),
+    '#description' => t('This option adds randomly colored point noise.'),
+  );
+  $form['graphmath_captcha_distortion_and_noise']['graphmath_captcha_line_noise'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Add line noise'),
+    '#default_value' => variable_get('graphmath_captcha_line_noise', 0),
+    '#description' => t('This option enables lines randomly drawn on top of the text code.'),
+  );
+  $form['graphmath_captcha_distortion_and_noise']['graphmath_captcha_noise_level'] = array(
+    '#type' => 'select',
+    '#title' => t('Noise level'),
+    '#options' => array(
+      1 => '1 - ' . t('low'),
+      2 => '2',
+      3 => '3 - ' . t('medium'),
+      4 => '4',
+      5 => '5 - ' . t('high'),
+      7 => '7',
+      10 => '10 - ' . t('severe'),
+    ),
+    '#default_value' => (int) variable_get('graphmath_captcha_noise_level', 5),
+  );
+
+  // Add a validation handler.
+  $form['#validate'] = array('graphmath_captcha_settings_form_validate');
+
+  // Make it a settings form.
+  $form = system_settings_form($form);
+  // But also do some custom submission handling.
+  $form['#submit'][] = 'graphmath_captcha_settings_form_submit';
+
+  return $form;
+}
+
+
+/**
+ * Form elements for the font specific setting.
+ *
+ * This is refactored to a separate function to avoid poluting the
+ * general form function graphmath_captcha_settings_form with some
+ * specific logic.
+ *
+ * @return $form, the font settings specific form elements.
+ */
+function _graphmath_captcha_settings_form_font_section() {
+  // Put it all in a fieldset.
+  $form = array(
+    '#type' => 'fieldset',
+    '#title' => t('Font settings'),
+  );
+
+  // First check if there is TrueType support.
+  $setup_status = _graphmath_captcha_check_setup(FALSE);
+  if ($setup_status & graphmath_captcha_ERROR_NO_TTF_SUPPORT) {
+    // Show a warning that there is no TrueType support
+    $form['no_ttf_support'] = array(
+      '#type' => 'item',
+      '#title' => t('No TrueType support'),
+      '#markup' => t('The GraphMath CAPTCHA module can not use TrueType fonts because your PHP setup does not support it. You can only use a PHP built-in bitmap font of fixed size.'),
+    );
+
+  }
+  else {
+
+    // Build a list of  all available fonts.
+    $available_fonts = array();
+
+    // List of folders to search through for TrueType fonts.
+    $fonts = _graphmath_captcha_get_available_fonts_from_directories();
+    // Cache the list of previewable fonts. All the previews are done
+    // in separate requests, and we don't want to rescan the filesystem
+    // every time, so we cache the result.
+    variable_set('graphmath_captcha_fonts_preview_map_cache', $fonts);
+    // Put these fonts with preview image in the list
+    foreach ($fonts as $token => $font) {
+      $img_src = check_url(url('admin/config/people/captcha/graphmath_captcha/font_preview/' . $token));
+      $title = t('Font preview of @font (@file)', array('@font' => $font->name, '@file' => $font->uri));
+      $available_fonts[$font->uri] = '<img src="' . $img_src . '" alt="' . $title . '" title="' . $title . '" />';
+    }
+
+    // Append the PHP built-in font at the end.
+    $img_src = check_url(url('admin/config/people/captcha/graphmath_captcha/font_preview/BUILTIN'));
+    $title = t('Preview of built-in font');
+    $available_fonts['BUILTIN'] = t('PHP built-in font: !font_preview',
+      array('!font_preview' => '<img src="' . $img_src . '" alt="' . $title . '" title="' . $title . '" />')
+    );
+
+    $default_fonts = _graphmath_captcha_get_enabled_fonts();
+    $form['graphmath_captcha_fonts'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Fonts'),
+      '#default_value' => $default_fonts,
+      '#description' => t('Select the fonts to use for the text in the GraphMath CAPTCHA. Apart from the provided defaults, you can also use your own TrueType fonts (filename extension .ttf) by putting them in %fonts_library_general or %fonts_library_specific.',
+        array(
+          '%fonts_library_general' => 'sites/all/libraries/fonts',
+          '%fonts_library_specific' => conf_path() . '/libraries/fonts',
+        )
+      ),
+      '#options' => $available_fonts,
+      '#attributes' => array('class' => array('graphmath_captcha_admin_fonts_selection')),
+      '#process' => array('form_process_checkboxes'),
+    );
+
+
+    // Font size.
+    $form['graphmath_captcha_font_size'] = array(
+      '#type' => 'select',
+      '#title' => t('Font size'),
+      '#options' => array(
+        9 => '9 pt - ' . t('tiny'),
+        12 => '12 pt - ' . t('small'),
+        18 => '18 pt',
+        24 => '24 pt - ' . t('normal'),
+        30 => '30 pt',
+        36 => '36 pt - ' . t('large'),
+        48 => '48 pt',
+        64 => '64 pt - ' . t('extra large'),
+      ),
+      '#default_value' => (int) variable_get('graphmath_captcha_font_size', 30),
+      '#description' => t('The font size influences the size of the image. Note that larger values make the image generation more CPU intensive.'),
+    );
+
+  }
+
+  // Character spacing (available for both the TrueType fonts and the builtin font.
+  $form['graphmath_captcha_font_settings']['graphmath_captcha_character_spacing'] = array(
+    '#type' => 'select',
+    '#title' => t('Character spacing'),
+    '#description' => t('Define the average spacing between characters. Note that larger values make the image generation more CPU intensive.'),
+    '#default_value' => variable_get('graphmath_captcha_character_spacing', '1.2'),
+    '#options' => array(
+      '0.75' => t('tight'),
+      '1' => t('normal'),
+      '1.2' => t('wide'),
+      '1.5' => t('extra wide'),
+    ),
+  );
+
+  return $form;
+}
+
+/**
+ * Helper function to get fonts from the given directories.
+ *
+ * @param $directories (optional) an array of directories
+ *   to recursively search through, if not given, the default
+ *   directories will be used.
+ *
+ * @return an array of fonts file objects (with fields 'name',
+ *   'basename' and 'filename'), keyed on the md5 hash of the font
+ *   path (to have an easy token that can be used in an url
+ *   without en/decoding issues).
+ */
+function _graphmath_captcha_get_available_fonts_from_directories($directories=NULL) {
+  // If no fonts directories are given: use the default.
+  if ($directories === NULL) {
+    $directories = array(
+      drupal_get_path('module', 'graphmath_captcha') . '/fonts',
+      'sites/all/libraries/fonts',
+      conf_path() . '/libraries/fonts',
+    );
+  }
+  // Collect the font information.
+  $fonts = array();
+  foreach ($directories as $directory) {
+    foreach (file_scan_directory($directory, '/\.[tT][tT][fF]$/') as $filename => $font) {
+      $fonts[md5($filename)] = $font;
+    }
+  }
+
+  return $fonts;
+}
+
+
+/**
+ * Validation function for graphmath_captcha configuration form
+ */
+function graphmath_captcha_settings_form_validate($form, &$form_state) {
+  if (!isset($form['graphmath_captcha_font_settings']['no_ttf_support'])) {
+    // Check the selected fonts.
+    // Filter the graphmath_captcha fonts array to pick out the selected ones.
+    $fonts = array_filter($form_state['values']['graphmath_captcha_fonts']);
+    if (count($fonts) < 1) {
+      form_set_error('graphmath_captcha_fonts', t('You need to select at least one font.'));
+    }
+    list($readable_fonts, $problem_fonts) = _graphmath_captcha_check_fonts($fonts);
+    if (count($problem_fonts) > 0) {
+      form_set_error('graphmath_captcha_fonts', t('The following fonts are not readable: %fonts.', array('%fonts' => implode(', ', $problem_fonts))));
+    }
+  }
+
+  // check color settings
+  if (!preg_match('/^#([0-9a-fA-F]{3}){1,2}$/', $form_state['values']['graphmath_captcha_background_color'])) {
+    form_set_error('graphmath_captcha_background_color', t('Background color is not a valid hexadecimal color value.'));
+  }
+  if (!preg_match('/^#([0-9a-fA-F]{3}){1,2}$/', $form_state['values']['graphmath_captcha_foreground_color'])) {
+    form_set_error('graphmath_captcha_foreground_color', t('Text color is not a valid hexadecimal color value.'));
+  }
+}
+
+/**
+ * Submit function for graphmath_captcha configuration form.
+ */
+function graphmath_captcha_settings_form_submit($form, &$form_state) {
+  if (!isset($form['graphmath_captcha_font_settings']['no_ttf_support'])) {
+    // Filter the graphmath_captcha fonts array to pick out the selected ones.
+    $fonts = array_filter($form_state['values']['graphmath_captcha_fonts']);
+    variable_set('graphmath_captcha_fonts', $fonts);
+  }
+}
+
+/**
+ * Menu handler for font preview request.
+ *
+ */
+function graphmath_captcha_font_preview($font_token) {
+
+  // Get the font from the given font token.
+  if ($font_token == 'BUILTIN') {
+    $font = 'BUILTIN';
+  }
+  else {
+    // Get the mapping of font tokens to font file objects.
+    $fonts = variable_get('graphmath_captcha_fonts_preview_map_cache', array());
+    if (!isset($fonts[$font_token])) {
+      echo('bad token');
+      exit();
+    }
+    // Get the font path.
+    $font = $fonts[$font_token]->uri;
+    // Some sanity checks if the given font is valid.
+    if (!is_file($font) || !is_readable($font)) {
+      echo('bad font');
+      exit();
+    }
+  }
+
+  // Settings of the font preview.
+  $width = 120;
+  $text = 'AaBbCc123';
+  $font_size = 14;
+  $height = 2 * $font_size;
+
+  // Allocate image resource.
+  $image = imagecreatetruecolor($width, $height);
+  if (!$image) {
+    exit();
+  }
+  // White background and black foreground.
+  $background_color = imagecolorallocate($image, 255, 255, 255);
+  $color = imagecolorallocate($image, 0, 0, 0);
+  imagefilledrectangle($image, 0, 0, $width, $height, $background_color);
+
+  // Draw preview text
+  if ($font == 'BUILTIN') {
+    imagestring($image, 5, 1, .5*$height-10, $text, $color);
+  }
+  else {
+    imagettftext($image, $font_size, 0, 1, 1.5*$font_size, $color, realpath($font), $text);
+  }
+
+  // Set content type.
+  drupal_add_http_header('Content-Type', 'image/png');
+  // Dump image data to client.
+  imagepng($image);
+  // Release image memory.
+  imagedestroy($image);
+
+  // Close connection.
+  exit();
+}
diff --git a/graphmath_captcha/graphmath_captcha.css b/graphmath_captcha/graphmath_captcha.css
new file mode 100644
index 0000000..d6e2f76
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.css
@@ -0,0 +1,30 @@
+/**
+ * Styling of the font selection list (with previews)
+ * on the Image CAPTCHA settings page.
+ */
+
+/**
+ * Float the fonts with preview (with a fixed width)
+ * to create a multi-column layout.
+ */
+.image_captcha_admin_fonts_selection .form-item {
+  float: left;
+  width: 160px;
+}
+
+/**
+ * Stop floating with the item for the built in font.
+ */
+.image_captcha_admin_fonts_selection .form-item-image-captcha-fonts-BUILTIN {
+  clear: both;
+  float: none;
+  width: 100%;
+}
+
+/**
+ * Center the font previews vertically to the text.
+ */
+.image_captcha_admin_fonts_selection img {
+  vertical-align: middle;
+}
+
diff --git a/graphmath_captcha/graphmath_captcha.info b/graphmath_captcha/graphmath_captcha.info
new file mode 100644
index 0000000..2492668
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.info
@@ -0,0 +1,12 @@
+name = "GraphMath CAPTCHA"
+description = "Provides an image based Math CAPTCHA."
+package = "Spam control"
+dependencies[] = captcha
+core = 7.x
+configure = admin/config/people/captcha/graphmath_captcha
+
+files[] = graphmath_captcha.install
+files[] = graphmath_captcha.module
+files[] = graphmath_captcha.admin.inc
+files[] = graphmath_captcha.user.inc
+
diff --git a/graphmath_captcha/graphmath_captcha.install b/graphmath_captcha/graphmath_captcha.install
new file mode 100644
index 0000000..27f5dae
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.install
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Installation/uninstallation related functions for the graphmath_captcha module.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function graphmath_captcha_requirements($phase) {
+  $requirements = array();
+  $t = get_t();
+  if ($phase == 'install') {
+    // _graphmath_captcha_check_setup() is defined in graphmath_captcha.module.
+    module_load_include('module', 'graphmath_captcha');
+    // Check if the GD library is available and raise an error when not.
+    if (_graphmath_captcha_check_setup(FALSE) & graphmath_captcha_ERROR_NO_GDLIB) {
+      $requirements['graphmath_captcha_requires_gd'] = array(
+        'title' => $t('GraphMath CAPTCHA requires GD library'),
+        'description' =>
+          $t('The GraphMath CAPTCHA module can not be installed because your PHP setup does not provide the <a href="!gddoc">GD library</a>, which is required to generate images.',
+            array('!gddoc' => 'http://www.php.net/manual/en/book.image.php', )
+          ),
+        'severity' => REQUIREMENT_ERROR,
+      );
+    }
+  }
+  return $requirements;
+}
+
+/**
+ * On uninstall: remove module variables and clear variable cache
+ */
+function graphmath_captcha_uninstall() {
+  db_delete('variable')
+    ->condition('name', db_like('graphmath_captcha_') . '%', 'LIKE')
+    ->execute();
+  cache_clear_all('variables', 'cache');
+}
diff --git a/graphmath_captcha/graphmath_captcha.js b/graphmath_captcha/graphmath_captcha.js
new file mode 100644
index 0000000..b20facc
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.js
@@ -0,0 +1,40 @@
+(function($) {
+
+  Drupal.behaviors.captchaAdmin = {
+    attach : function(context) {
+
+      // Helper function to show/hide noise level widget.
+      var noise_level_shower = function(speed) {
+        speed = (typeof speed == 'undefined') ? 'slow' : speed;
+        if ($("#edit-image-captcha-dot-noise").is(":checked")
+            || $("#edit-image-captcha-line-noise").is(":checked")) {
+          $(".form-item-image-captcha-noise-level").show(speed);
+        } else {
+          $(".form-item-image-captcha-noise-level").hide(speed);
+        }
+      }
+      // Add onclick handler to the dot and line noise check boxes.
+      $("#edit-image-captcha-dot-noise").click(noise_level_shower);
+      $("#edit-image-captcha-line-noise").click(noise_level_shower);
+      // Show or hide appropriately on page load.
+      noise_level_shower(0);
+
+      // Helper function to show/hide smooth distortion widget.
+      var smooth_distortion_shower = function(speed) {
+        speed = (typeof speed == 'undefined') ? 'slow' : speed;
+        if ($("#edit-image-captcha-distortion-amplitude").val() > 0) {
+          $(".form-item-image-captcha-bilinear-interpolation").show(speed);
+        } else {
+          $(".form-item-image-captcha-bilinear-interpolation").hide(speed);
+        }
+      }
+      // Add onchange handler to the distortion level select widget.
+      $("#edit-image-captcha-distortion-amplitude").change(
+          smooth_distortion_shower);
+      // Show or hide appropriately on page load.
+      smooth_distortion_shower(0)
+
+    }
+  };
+
+})(jQuery);
diff --git a/graphmath_captcha/graphmath_captcha.module b/graphmath_captcha/graphmath_captcha.module
new file mode 100644
index 0000000..01005a6
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.module
@@ -0,0 +1,245 @@
+<?php
+
+/**
+ * @file
+ * Implements GraphMath CAPTCHA for use with the CAPTCHA module
+ */
+
+// Setup status flags.
+define('graphmath_captcha_ERROR_NO_GDLIB', 1);
+define('graphmath_captcha_ERROR_NO_TTF_SUPPORT', 2);
+define('graphmath_captcha_ERROR_TTF_FILE_READ_PROBLEM', 4);
+
+define('graphmath_captcha_FILE_FORMAT_JPG', 1);
+define('graphmath_captcha_FILE_FORMAT_PNG', 2);
+define('graphmath_captcha_FILE_FORMAT_TRANSPARENT_PNG', 3);
+
+
+/**
+ * Implements hook_help().
+ */
+function graphmath_captcha_help($path, $arg) {
+  switch ($path) {
+    case 'admin/config/people/captcha/graphmath_captcha':
+      $output = '<p>' . t('The GraphMath CAPTCHA is an image version of the  popular challenge where a simple math equation is obfuscated in an image. The image is generated on the fly for each request, which is rather CPU intensive for the server. Be careful with the size and computation related settings.') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function graphmath_captcha_menu() {
+  $items = array();
+  // add an administration tab for graphmath_captcha
+  $items['admin/config/people/captcha/graphmath_captcha'] = array(
+    'title' => 'GraphMath CAPTCHA',
+    'file' => 'graphmath_captcha.admin.inc',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('graphmath_captcha_settings_form'),
+    'access arguments' => array('administer CAPTCHA settings'),
+    'type' => MENU_LOCAL_TASK,
+  );
+  // Menu path for generating font example.
+  $items['admin/config/people/captcha/graphmath_captcha/font_preview'] = array(
+    'title' => 'Font example',
+    'file' => 'graphmath_captcha.admin.inc',
+    'page callback' => 'graphmath_captcha_font_preview',
+    'access arguments' => array('administer CAPTCHA settings'),
+    'type' => MENU_CALLBACK,
+  );
+  // callback for generating an image
+  $items['graphmath_captcha'] = array(
+    'file' => 'graphmath_captcha.user.inc',
+    'page callback' => 'graphmath_captcha_image',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+/**
+ * Helper function for getting the fonts to use in the GraphMath CAPTCHA.
+ *
+ * @return a list of font paths.
+ */
+function _graphmath_captcha_get_enabled_fonts() {
+  if (graphmath_captcha_ERROR_NO_TTF_SUPPORT & _graphmath_captcha_check_setup(FALSE)) {
+    return array('BUILTIN');
+  }
+  else {
+    $default = array(
+      drupal_get_path('module', 'graphmath_captcha') . '/fonts/Tesox/tesox.ttf',
+      drupal_get_path('module', 'graphmath_captcha') . '/fonts/Tuffy/Tuffy.ttf',
+    );
+    return variable_get('graphmath_captcha_fonts', $default);
+  }
+}
+
+/**
+ * Helper function for checking if the specified fonts are available.
+ *
+ * @param $fonts paths of fonts to check.
+ * @return list($readable_fonts, $problem_fonts)
+ */
+function _graphmath_captcha_check_fonts($fonts) {
+  $readable_fonts = array();
+  $problem_fonts = array();
+  foreach ($fonts as $font) {
+    if ($font != 'BUILTIN' && (!is_file($font) || !is_readable($font))) {
+      $problem_fonts[] = $font;
+    }
+    else {
+      $readable_fonts[] = $font;
+    }
+  }
+  return array($readable_fonts, $problem_fonts);
+}
+
+/**
+ * Helper function for checking the setup of the GraphMath CAPTCHA.
+ *
+ * The GraphMath CAPTCHA requires at least the GD PHP library.
+ * Support for TTF is recommended and the enabled
+ * font files should be readable.
+ * This functions checks these things.
+ *
+ * @param $check_fonts whether or not the enabled fonts should be checked.
+ *
+ * @return status code: bitwise 'OR' of status flags like
+ *   graphmath_captcha_ERROR_NO_GDLIB, graphmath_captcha_ERROR_NO_TTF_SUPPORT,
+ *   graphmath_captcha_ERROR_TTF_FILE_READ_PROBLEM.
+ */
+function _graphmath_captcha_check_setup($check_fonts=TRUE) {
+  // Start clean.
+  $status = 0;
+  // Check if we can use the GD library.
+  // We need at least the imagepng function (for font previews on the settings page).
+  // Note that the imagejpg function is optionally also used, but not required.
+  if (!function_exists('imagepng')) {
+    $status = $status | graphmath_captcha_ERROR_NO_GDLIB;
+  }
+  if (!function_exists('imagettftext')) {
+    $status = $status | graphmath_captcha_ERROR_NO_TTF_SUPPORT;
+  }
+  if ($check_fonts) {
+    // Check availability of enabled fonts.
+    $fonts = _graphmath_captcha_get_enabled_fonts();
+    list($readable_fonts, $problem_fonts) = _graphmath_captcha_check_fonts($fonts);
+    if (count($problem_fonts) != 0) {
+      $status = $status | graphmath_captcha_ERROR_TTF_FILE_READ_PROBLEM;
+    }
+  }
+  return $status;
+}
+
+/**
+ * Helper function for calculating image height and width
+ * based on given code and current font/spacing settings.
+ *
+ * @return array($width, $heigh)
+ */
+function _graphmath_captcha_image_size($code) {
+  $characters = array();
+  for ($i=0; $i<strlen($code); $i++) {
+    $characters[$i] = substr($code, $i, 1);
+  }
+  // Get settings
+  $font_size = (int) variable_get('graphmath_captcha_font_size', 30);
+  $character_spacing = (float) variable_get('graphmath_captcha_character_spacing', '1.2');
+  $character_quantity = count($characters);
+
+  // Calculate height and width
+  $width = $character_spacing * $font_size * $character_quantity;
+  $height = 2 * $font_size;
+
+  return array($width, $height);
+}
+
+function graphmath_captcha_validate($solution, $answer) {
+//Strict validation
+  return $solution===$answer;
+}
+
+/**
+ * Implements hook_captcha().
+ */
+function graphmath_captcha_captcha($op, $captcha_type='', $captcha_sid=NULL) {
+  switch ($op) {
+    case 'list':
+      // Only offer the GraphMath CAPTCHA if it is possible to generate an image on this setup.
+      if (!(_graphmath_captcha_check_setup() & graphmath_captcha_ERROR_NO_GDLIB)) {
+        return array('Image');
+      }
+      else {
+        return array();
+      }
+      break;
+
+    case 'generate':
+      if ($captcha_type == 'Image') {
+        // In maintenance mode, the GraphMath CAPTCHA does not work because the request
+        // for the image itself won't succeed (only ?q=user is permitted for
+        // unauthenticated users). We fall back to the Math CAPTCHA in that case.
+        global $user;
+        if (variable_get('maintenance_mode', 0) && $user->uid == 0) {
+          return captcha_captcha('generate', 'Math');
+        }
+        // generate a CAPTCHA code
+        $code = '';
+        //Select operation
+        $myrand = mt_rand(1, 4);
+        //Select operands for the operation
+        if ($myrand==1) {
+          $n1 = mt_rand(1, 10); $n2 = mt_rand(1, 10); $answer = $n1+$n2; $op = '+'; }
+        elseif ($myrand==2) {
+          $n2 = mt_rand(1, 10); $n1 = mt_rand($n2, 20); $answer = $n1-$n2; $op = '-'; }
+        elseif ($myrand==3) {
+          $n1 = mt_rand(1, 6); $n2 = mt_rand(1, 6); $answer = $n1*$n2; $op = '*'; }
+        else {
+          $n2 = mt_rand(1, 6); $answer = mt_rand(1, 6); $n1 = $n2 * $answer; $op = '/';
+        }
+        $code = "$n1$op$n2=";
+        variable_set('graphmath_' . $captcha_sid, $code);
+        // build the result to return
+        $result = array();
+
+        $result['solution'] = strval($answer);
+        // Generate image source URL (add timestamp to avoid problems with
+        // client side caching: subsequent images of the same CAPTCHA session
+        // have the same URL, but should display a different code).
+        $img_src = check_url(url("graphmath_captcha/$captcha_sid/" . REQUEST_TIME));
+        list($width, $height) = _graphmath_captcha_image_size($code);
+        // TODO: start using a theming function for generating the image markup?
+        $result['form']['captcha_image'] = array(
+          '#type' => 'markup',
+          '#markup' => '<img src="' . $img_src
+            . '" width="' . $width . '" height="' . $height
+            . '" alt="' . t('GraphMath CAPTCHA') . '" title="' . t('GraphMath CAPTCHA') . '" />',
+          '#weight' => -2,
+        );
+        $result['form']['captcha_response'] = array(
+          '#type' => 'textfield',
+          '#title' => t('What is the result of this math equation?'),
+          '#description' => t('Solve the math equation pictured in the image, and enter the result.'),
+          '#weight' => 0,
+          '#required' => TRUE,
+          '#size' => 15,
+        );
+
+        // Handle the case insensitive validation option combined with ignoring spaces.
+        switch (variable_get('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE)) {
+          case CAPTCHA_DEFAULT_VALIDATION_CASE_SENSITIVE:
+            $result['captcha_validate'] = 'graphmath_captcha_validate';
+            break;
+          case CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE:
+            $result['captcha_validate'] = 'graphmath_captcha_validate';
+            break;
+        }
+
+        return $result;
+      }
+      break;
+
+  }
+}
diff --git a/graphmath_captcha/graphmath_captcha.user.inc b/graphmath_captcha/graphmath_captcha.user.inc
new file mode 100644
index 0000000..cc0d925
--- /dev/null
+++ b/graphmath_captcha/graphmath_captcha.user.inc
@@ -0,0 +1,335 @@
+<?php
+
+/**
+ * @file
+ * Functions for the generation of the CAPTCHA image.
+ *
+ * Loosely Based on MyCaptcha by Heine Deelstra
+ * (http://heine.familiedeelstra.com/mycaptcha-download)
+ */
+
+/**
+ * Menu callback function that generates the CAPTCHA image.
+ */
+function graphmath_captcha_image($captcha_sid=NULL) {
+  // If output buffering is on: discard current content and disable further buffering
+  if (ob_get_level()) {
+    ob_end_clean();
+  }
+  
+  if (!$captcha_sid) {
+    exit();
+  }
+
+  // Get the equation to show.
+  $code = variable_get('graphmath_' . $captcha_sid, FALSE); variable_del('graphmath_' . $captcha_sid);
+  // Only generate captcha if code exists in the session.
+  if ($code !== FALSE) {
+    // generate the image
+    $image = @_graphmath_captcha_generate_image($code);
+    // check of generation was successful
+    if (!$image) {
+      watchdog('CAPTCHA', 'Generation of GraphMath CAPTCHA failed. Check your GraphMath CAPTCHA configuration and especially the used font.', array(), WATCHDOG_ERROR);
+      exit();
+    }
+    // Send the image resource as an image file to the client.
+    $file_format = variable_get('graphmath_captcha_file_format', graphmath_captcha_FILE_FORMAT_JPG);
+    if ($file_format == graphmath_captcha_FILE_FORMAT_JPG) {
+      drupal_add_http_header('Content-Type', 'image/jpeg');
+      imagejpeg($image);
+    }
+    else {
+      drupal_add_http_header('Content-Type', 'image/png');
+      imagepng($image);
+    }
+    // Clean up the image resource.
+    imagedestroy($image);
+  }
+  exit();
+}
+
+
+/**
+ * Small helper function for parsing a hexadecimal color to a RGB tuple.
+ */
+function _graphmath_captcha_hex_to_rgb($hex) {
+  // handle #RGB format
+  if (strlen($hex) == 4) {
+    $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
+  }
+  $c = hexdec($hex);
+  $rgb = array();
+  for ($i = 16; $i >= 0; $i -= 8) {
+    $rgb[] = ($c >> $i) & 0xFF;
+  }
+  return $rgb;
+}
+
+
+/**
+ * Base function for generating a GraphMath CAPTCHA.
+ */
+function _graphmath_captcha_generate_image($code) {
+  // Get font.
+  $fonts = _graphmath_captcha_get_enabled_fonts();
+
+  // get other settings
+  $font_size = (int) variable_get('graphmath_captcha_font_size', 30);
+  list($width, $height) = _graphmath_captcha_image_size($code);
+
+  // create image resource
+  $image = imagecreatetruecolor($width, $height);
+  if (!$image) {
+    return FALSE;
+  }
+
+  // Get the background color and paint the background.
+  $background_rgb = _graphmath_captcha_hex_to_rgb(variable_get('graphmath_captcha_background_color', '#ffffff'));
+  $background_color = imagecolorallocate($image, $background_rgb[0], $background_rgb[1], $background_rgb[2]);
+  // Set transparency if needed.
+  $file_format = variable_get('graphmath_captcha_file_format', graphmath_captcha_FILE_FORMAT_JPG);
+  if ($file_format == graphmath_captcha_FILE_FORMAT_TRANSPARENT_PNG) {
+    imagecolortransparent($image, $background_color);
+  }
+  imagefilledrectangle($image, 0, 0, $width, $height, $background_color);
+
+  // draw text
+  $result = _graphmath_captcha_image_generator_print_string($image, $width, $height, $fonts, $font_size, $code);
+  if (!$result) {
+    return FALSE;
+  }
+
+  // add noise
+  $noise_colors = array();
+  for ($i = 0; $i < 20; $i++) {
+    $noise_colors[] = imagecolorallocate($image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
+  }
+  // Add additional noise.
+  if (variable_get('graphmath_captcha_dot_noise', 0)) {
+    _graphmath_captcha_image_generator_add_dots($image, $width, $height, $noise_colors);
+  }
+  if (variable_get('graphmath_captcha_line_noise', 0)) {
+    _graphmath_captcha_image_generator_add_lines($image, $width, $height, $noise_colors);
+  }
+
+  // Distort the image.
+  $distortion_amplitude = .25 * $font_size * variable_get('graphmath_captcha_distortion_amplitude', 0) / 10.0;
+  if ($distortion_amplitude > 1) {
+    // distortion parameters
+    $wavelength_xr = (2+3*lcg_value())*$font_size;
+    $wavelength_yr = (2+3*lcg_value())*$font_size;
+    $freq_xr = 2 * 3.141592 / $wavelength_xr;
+    $freq_yr = 2 * 3.141592 / $wavelength_yr;
+    $wavelength_xt = (2+3*lcg_value())*$font_size;
+    $wavelength_yt = (2+3*lcg_value())*$font_size;
+    $freq_xt = 2 * 3.141592 / $wavelength_xt;
+    $freq_yt = 2 * 3.141592 / $wavelength_yt;
+
+    $distorted_image = imagecreatetruecolor($width, $height);
+    if ($file_format == graphmath_captcha_FILE_FORMAT_TRANSPARENT_PNG) {
+      imagecolortransparent($distorted_image, $background_color);
+    }
+    if (!$distorted_image) {
+      return FALSE;
+    }
+
+    if (variable_get('graphmath_captcha_bilinear_interpolation', FALSE)) {
+      // distortion with bilinear interpolation
+      for ($x = 0; $x < $width; $x++) {
+        for ($y = 0; $y < $height; $y++) {
+          // get distorted sample point in source image
+          $r = $distortion_amplitude * sin($x * $freq_xr + $y * $freq_yr);
+          $theta = $x * $freq_xt + $y * $freq_yt;
+          $sx = $x + $r * cos($theta);
+          $sy = $y + $r * sin($theta);
+          $sxf = (int)floor($sx);
+          $syf = (int)floor($sy);
+          if ($sxf < 0 || $syf < 0 || $sxf >= $width - 1 || $syf >= $height - 1) {
+            $color = $background_color;
+          }
+          else {
+            // bilinear interpolation: sample at four corners
+            $color_00 = imagecolorat($image, $sxf  , $syf  );
+            $color_00_r = ($color_00 >> 16) & 0xFF;
+            $color_00_g = ($color_00 >> 8) & 0xFF;
+            $color_00_b = $color_00 & 0xFF;
+            $color_10 = imagecolorat($image, $sxf+1, $syf  );
+            $color_10_r = ($color_10 >> 16) & 0xFF;
+            $color_10_g = ($color_10 >> 8) & 0xFF;
+            $color_10_b = $color_10 & 0xFF;
+            $color_01 = imagecolorat($image, $sxf  , $syf+1);
+            $color_01_r = ($color_01 >> 16) & 0xFF;
+            $color_01_g = ($color_01 >> 8) & 0xFF;
+            $color_01_b = $color_01 & 0xFF;
+            $color_11 = imagecolorat($image, $sxf+1, $syf+1);
+            $color_11_r = ($color_11 >> 16) & 0xFF;
+            $color_11_g = ($color_11 >> 8) & 0xFF;
+            $color_11_b = $color_11 & 0xFF;
+            // interpolation factors
+            $u  = $sx - $sxf;
+            $v  = $sy - $syf;
+            // interpolate
+            $r = (int)((1-$v)*((1-$u)*$color_00_r + $u*$color_10_r) + $v*((1-$u)*$color_01_r + $u*$color_11_r));
+            $g = (int)((1-$v)*((1-$u)*$color_00_g + $u*$color_10_g) + $v*((1-$u)*$color_01_g + $u*$color_11_g));
+            $b = (int)((1-$v)*((1-$u)*$color_00_b + $u*$color_10_b) + $v*((1-$u)*$color_01_b + $u*$color_11_b));
+            // build color
+            $color = ($r<<16) + ($g<<8) + $b;
+          }
+          imagesetpixel($distorted_image, $x, $y, $color);
+        }
+      }
+    }
+    else {
+      // distortion with nearest neighbor interpolation
+      for ($x = 0; $x < $width; $x++) {
+        for ($y = 0; $y < $height; $y++) {
+          // get distorted sample point in source image
+          $r = $distortion_amplitude * sin($x * $freq_xr + $y * $freq_yr);
+          $theta = $x * $freq_xt + $y * $freq_yt;
+          $sx = $x + $r * cos($theta);
+          $sy = $y + $r * sin($theta);
+          $sxf = (int)floor($sx);
+          $syf = (int)floor($sy);
+          if ($sxf < 0 || $syf < 0 || $sxf >= $width - 1 || $syf >= $height - 1) {
+            $color = $background_color;
+          }
+          else {
+            $color = imagecolorat($image, $sxf, $syf);
+          }
+          imagesetpixel($distorted_image, $x, $y, $color);
+        }
+      }
+    }
+    // release undistorted image
+    imagedestroy($image);
+    // return distorted image
+    return $distorted_image;
+  }
+  else {
+    return $image;
+  }
+}
+
+function _graphmath_captcha_image_generator_add_lines(&$image, $width, $height, $colors) {
+  $line_quantity = $width * $height/200.0 * ((int) variable_get('graphmath_captcha_noise_level', 5)) / 10.0;
+  for ($i = 0; $i <  $line_quantity; $i++) {
+    imageline($image, mt_rand(0, $width), mt_rand(0, $height), mt_rand(0, $width), mt_rand(0, $height), $colors[array_rand($colors)]);
+  }
+}
+
+function _graphmath_captcha_image_generator_add_dots(&$image, $width, $height, $colors) {
+  $noise_quantity = $width * $height * ((int) variable_get('graphmath_captcha_noise_level', 5)) / 10.0;
+  for ($i = 0; $i < $noise_quantity; $i++ ) {
+    imagesetpixel($image, mt_rand(0, $width), mt_rand(0, $height), $colors[array_rand($colors)]);
+  }
+}
+
+/**
+ * Helper function for drawing text on the image.
+ */
+function _graphmath_captcha_image_generator_print_string(&$image, $width, $height, $fonts, $font_size, $text) {
+  // get characters
+  $characters = array();
+  for ($i=0; $i<strlen($text); $i++) {
+    $characters[$i] = substr($text, $i, 1);
+  }
+  $character_quantity = count($characters);
+
+  // get colors
+  $background_rgb = _graphmath_captcha_hex_to_rgb(variable_get('graphmath_captcha_background_color', '#ffffff'));
+  $foreground_rgb = _graphmath_captcha_hex_to_rgb(variable_get('graphmath_captcha_foreground_color', '#000000'));
+  $background_color = imagecolorallocate($image, $background_rgb[0], $background_rgb[1], $background_rgb[2]);
+  $foreground_color = imagecolorallocate($image, $foreground_rgb[0], $foreground_rgb[1], $foreground_rgb[2]);
+  // precalculate the value ranges for color randomness
+  $foreground_randomness = (int)(variable_get('graphmath_captcha_foreground_color_randomness', 100));
+  if ($foreground_randomness) {
+    $foreground_color_range = array();
+    for ($i=0; $i<3; $i++) {
+      $foreground_color_range[$i] = array(max(0, $foreground_rgb[$i] - $foreground_randomness), min(255, $foreground_rgb[$i] + $foreground_randomness));
+    }
+  }
+
+  // set default text color
+  $color = $foreground_color;
+
+  // the image is seperated in different character cages, one for each character
+  // each character will be somewhere inside that cage
+  $ccage_width = $width / $character_quantity;
+  $ccage_height = $height;
+
+  foreach ($characters as $c => $character) {
+    // initial position of character: in the center of its cage
+    $center_x = ($c + 0.5) * $ccage_width;
+    $center_y = 0.5 * $height;
+
+    // Pick a random font from the list.
+    $font = $fonts[array_rand($fonts)];
+
+    // Get character dimensions for TrueType fonts.
+    if ($font != 'BUILTIN') {
+      $bbox = imagettfbbox($font_size, 0, drupal_realpath($font), $character);
+      // In very rare cases with some versions of the GD library, the x-value
+      // of the left side of the bounding box as returned by the first call of
+      // imagettfbbox is corrupt (value -2147483648 = 0x80000000).
+      // The weird thing is that calling the function a second time
+      // can be used as workaround.
+      // This issue is discussed at http://drupal.org/node/349218.
+      if ($bbox[2] < 0) {
+        $bbox = imagettfbbox($font_size, 0, drupal_realpath($font), $character);
+      }
+    }
+    else {
+      $character_width = imagefontwidth(5);
+      $character_height = imagefontheight(5);
+      $bbox = array(0, $character_height, $character_width, $character_height, $character_width, 0, 0, 0);
+    }
+
+    // Random (but small) rotation of the character.
+    // TODO: add a setting for this?
+    $angle = mt_rand(-10, 10);
+
+    // Determine print position: at what coordinate should the character be
+    // printed so that the bounding box would be nicely centered in the cage?
+    $bb_center_x = .5 * ($bbox[0] + $bbox[2]);
+    $bb_center_y = .5 * ($bbox[1] + $bbox[7]);
+    $angle_cos = cos($angle*3.1415/180);
+    $angle_sin = sin($angle*3.1415/180);
+    $pos_x = $center_x - ($angle_cos * $bb_center_x + $angle_sin * $bb_center_y);
+    $pos_y = $center_y - (-$angle_sin * $bb_center_x + $angle_cos * $bb_center_y);
+
+    // Calculate available room to jitter: how much can the character be moved
+    // so that it stays inside its cage?
+    $bb_width = $bbox[2] - $bbox[0];
+    $bb_height = $bbox[1] - $bbox[7];
+    $dev_x = .5 * max(0, $ccage_width - abs($angle_cos) * $bb_width - abs($angle_sin) * $bb_height);
+    $dev_y = .5 * max(0, $ccage_height - abs($angle_cos) * $bb_height - abs($angle_sin) * $bb_width);
+
+    // add jitter to position
+    $pos_x = $pos_x + mt_rand(-$dev_x, $dev_x);
+    $pos_y = $pos_y + mt_rand(-$dev_y, $dev_y);
+
+    // calculate text color in case of randomness
+    if ($foreground_randomness) {
+      $color = imagecolorallocate($image,
+        mt_rand($foreground_color_range[0][0], $foreground_color_range[0][1]),
+        mt_rand($foreground_color_range[1][0], $foreground_color_range[1][1]),
+        mt_rand($foreground_color_range[2][0], $foreground_color_range[2][1])
+      );
+    }
+
+    // draw character
+    if ($font == 'BUILTIN') {
+      imagestring($image, 5, $pos_x, $pos_y, $character, $color);
+    }
+    else {
+      imagettftext($image, $font_size, $angle, $pos_x, $pos_y, $color, drupal_realpath($font), $character);
+    }
+
+    // For debugging purposes: draw character bounding box (only valid when rotation is disabled).
+    // imagerectangle($image, $pos_x + $bbox[0], $pos_y + $bbox[1], $pos_x + $bbox[2], $pos_y + $bbox[7], $color);
+
+  }
+
+  // return a sign of success
+  return TRUE;
+}
