captchaimage-1.3/0000755000175000017500000000000011333340677013630 5ustar fredrikfredrikcaptchaimage-1.3/setup.py0000755000175000017500000000245511333337026015345 0ustar fredrikfredrik#!/usr/bin/python # # python-captchaimage # # 2008 Fredrik Portstrom # # I, the copyright holder of this file, hereby release it into the # public domain. This applies worldwide. In case this is not legally # possible: I grant anyone the right to use this work for any # purpose, without any conditions, unless such conditions are # required by law. from distutils.core import setup, Extension setup( name = "captchaimage", version = "1.3", author = "Fredrik Portstrom", author_email = "fredrik@jemla.se", url = "http://fredrik.jemla.eu/captchaimage", description = "Create captcha image data", long_description = "python-captchaimage is a fast and easy to use Python " "extension for creating images with distorted text that are easy for " "humans and difficult for computers to read. Glyphs are loaded as bezier " "curves using Freetype, rotated and scaled randomly, and then distorted by " "adding Perlin noise to each point of the curve before rendering into a " "bitmap.", license = "Public Domain", classifiers = [ "License :: Public Domain", "Programming Language :: C"], ext_modules = [Extension( "captchaimage", ["captchaimage.c"], include_dirs = ["/usr/include/freetype2"], libraries = ["freetype"])]) captchaimage-1.3/README0000644000175000017500000000514311333336672014513 0ustar fredrikfredrikcaptchaimage 1.3 - Python extension to generate captcha images 2008 Fredrik Portstrom I, the copyright holder of this file, hereby release it into the public domain. This applies worldwide. In case this is not legally possible: I grant anyone the right to use this work for any purpose, without any conditions, unless such conditions are required by law. Introduction ============ python-captchaimage is a fast and easy to use Python extension for creating images with distorted text that are easy for humans and difficult for computers to read. Glyphs are loaded as bezier curves using Freetype, rotated and scaled randomly, and then distorted by adding Perlin noise to each point of the curve before rendering into a bitmap. python-captchaimage generates about 950 images a second on a 1800 MHz Intel Celeron. python-captchaimage: http://fredrik.jemla.eu/captchaimage Freetype: http://freetype.org Installation ============ python-captchaimage uses distutils. python-captchaimage requires the library Freetype 2. http://freetype.org To build: ./setup.py build To build and install: ./setup.py install Example ======= To get a string containing a JPEG formatted image: It as a number of lines, but it's just to copy and paste, and maybe change the font face. import captchaimage import cStringIO import Image # Black text on white background def get_captcha_image(code): size_y = 32 image_data = captchaimage.create_image( "/usr/share/fonts/truetype/freefont/FreeSerif.ttf", 28, size_y, code) file = cStringIO.StringIO() Image.fromstring( "L", (len(image_data) / size_y, size_y), image_data).save( file, "JPEG", quality = 30) return file.getvalue() # Arbitrary colors, in the formats PIL accepts def get_color_captcha_image(code, background_color, text_color): size_y = 32 image_data = captchaimage.create_image( "/usr/share/fonts/truetype/freefont/FreeSerif.ttf", 28, size_y, code) size_x = len(image_data) / size_y file = cStringIO.StringIO() mask_im = Image.fromstring("L", (size_x, size_y), image_data) target_im = Image.new("RGB", (size_x, size_y), text_color) target_im.paste(background_color, (0, 0), mask_im) target_im.save(file, "JPEG", quality = 30) return file.getvalue() # Example: Magenta text on yellow background # get_color_captcha_image("TESTING", (255, 255, 0), (255, 0, 255)) Change log ========== 1.3 (2010-02-06) - Added example with color to README file. 1.2 (2009-11-03) - Fixed reference to the wrong variable. Updated URL. 1.1 (2008-09-13) - Fixed bug causing python-captchaimage to crash. 1.0 (2008-07-30) - Initial release. captchaimage-1.3/PKG-INFO0000644000175000017500000000123511333340677014726 0ustar fredrikfredrikMetadata-Version: 1.0 Name: captchaimage Version: 1.3 Summary: Create captcha image data Home-page: http://fredrik.jemla.eu/captchaimage Author: Fredrik Portstrom Author-email: fredrik@jemla.se License: Public Domain Description: python-captchaimage is a fast and easy to use Python extension for creating images with distorted text that are easy for humans and difficult for computers to read. Glyphs are loaded as bezier curves using Freetype, rotated and scaled randomly, and then distorted by adding Perlin noise to each point of the curve before rendering into a bitmap. Platform: UNKNOWN Classifier: License :: Public Domain Classifier: Programming Language :: C captchaimage-1.3/benchmark.py0000755000175000017500000000167011275100774016140 0ustar fredrikfredrik#!/usr/bin/python # # Usage: ./benchmark.py # # Run this script to see how captchaimage performs on your computer. # # 2008 Fredrik Portstrom # # I, the copyright holder of this file, hereby release it into the # public domain. This applies worldwide. In case this is not legally # possible: I grant anyone the right to use this work for any # purpose, without any conditions, unless such conditions are # required by law. try: import captchaimage except ImportError: print "The captcha image module is not installed." else: import timeit number = 1000 timer = timeit.Timer( "create_image('/usr/share/fonts/truetype/freefont/FreeSerif.ttf', 30, " "32, 'EMACS')", "from captchaimage import create_image") for i in xrange(5): time = timer.timeit(number = number) print "Generated %d captcha images in %.2f seconds. That is %.2f " \ "images per second." % (number, time, number / time) captchaimage-1.3/captchaimage.c0000644000175000017500000001774011275100775016411 0ustar fredrikfredrik// captchaimage - Python extension to generate captcha images // // 2008 Fredrik Portstrom // // I, the copyright holder of this file, hereby release it into the // public domain. This applies worldwide. In case this is not legally // possible: I grant anyone the right to use this work for any // purpose, without any conditions, unless such conditions are // required by law. #include #include #include #include #include FT_FREETYPE_H #include FT_GLYPH_H #define RAISE(message) PyErr_SetString(PyExc_StandardError, message); \ return NULL; #define NOISE_DETAIL 3 #define NOISE_TABLE_SIZE 32 #define RAND_DOUBLE (rand() / (RAND_MAX + 1.)) // Used in plain_noise #define WEIGHT(t) ((2. * (t < 0 ? -t : t) - 3) * t * t + 1) // This is a special kind of Perlin noise that returns two-tuples // instead of scalars. Therefore there are four gradient tables // instead of two. struct noise { int permutation_table[NOISE_TABLE_SIZE]; double gradient_table_xx[NOISE_TABLE_SIZE]; double gradient_table_xy[NOISE_TABLE_SIZE]; double gradient_table_yx[NOISE_TABLE_SIZE]; double gradient_table_yy[NOISE_TABLE_SIZE]; }; // Used in get_noise. static inline void plain_noise(struct noise *noise, double x, double y, int s, double *result_x, double *result_y) { x *= s; y *= s; int a = x; int b = y; int i; int j; for(i = 0; i < 2; i++) { for(j = 0; j < 2; j++) { int n = noise->permutation_table[(a + i + noise->permutation_table[ (b + j) % NOISE_TABLE_SIZE]) % NOISE_TABLE_SIZE]; double x2 = x - a - i; double y2 = y - b - j; double w = WEIGHT(x2) * WEIGHT(y2); *result_x += w * (x2 * noise->gradient_table_xx[n] + y2 * noise->gradient_table_yx[n]); *result_y += w * (x2 * noise->gradient_table_xy[n] + y2 * noise->gradient_table_yy[n]); } } } // Used in create_image_internal. static inline void get_noise(struct noise *noise, double x, double y, double *result_x, double *result_y) { int i; for(i = 1; i <= NOISE_DETAIL; i++) { plain_noise(noise, x, y, i, result_x, result_y); } } // Used in captchaimage_create_image. static inline PyObject *create_image_internal(FT_Library library, const char *font_face_path, int font_size, int image_size_y, const char *text) { // Initialize font face FT_Face face; if(FT_New_Face(library, font_face_path, 0, &face)) { RAISE("failed to load font face"); } if(!FT_IS_SCALABLE(face)) { // Non scalable fonts require other operations. There's no // reason to use them anyway. RAISE("font face is not scalable"); } if(FT_Set_Pixel_Sizes(face, 0, font_size)) { RAISE("failed to set font size"); } // Allocate image int image_size_x = font_size * strlen(text); char *image = malloc(image_size_x * image_size_y); memset(image, 255, image_size_x * image_size_y); if(!image) { return PyErr_NoMemory(); } // Seed pseudorandom number generator, used for initializing noise // and scaling, rotating and positioning glyphs. { unsigned seed = 0; const char *p = text; while(*p) { seed += *p; p++; } srand(seed); } // Prepare the noise structure int i; struct noise noise; for(i = 0; i < NOISE_TABLE_SIZE; i++) { noise.permutation_table[i] = i; } for(i = 0; i < NOISE_TABLE_SIZE; i++) { double m1; double m2; int j = (int)(RAND_DOUBLE * NOISE_TABLE_SIZE); int tmp = noise.permutation_table[i]; noise.permutation_table[i] = noise.permutation_table[j]; noise.permutation_table[j] = tmp; for(;;) { noise.gradient_table_xx[i] = 2 * RAND_DOUBLE - 1; noise.gradient_table_yx[i] = 2 * RAND_DOUBLE - 1; m1 = noise.gradient_table_xx[i] * noise.gradient_table_xx[i] + noise.gradient_table_yx[i] * noise.gradient_table_yx[i]; if(m1 > 0 && m1 <= 1) { break; } } for(;;) { noise.gradient_table_xy[i] = 2 * RAND_DOUBLE - 1; noise.gradient_table_yy[i] = 2 * RAND_DOUBLE - 1; m2 = noise.gradient_table_xy[i] * noise.gradient_table_xy[i] + noise.gradient_table_yy[i] * noise.gradient_table_yy[i]; if(m2 > 0 && m2 <= 1) { break; } } m1 = sqrt(m1 + m2); noise.gradient_table_xx[i] /= m1; noise.gradient_table_xy[i] /= m1; noise.gradient_table_yx[i] /= m1; noise.gradient_table_yy[i] /= m1; } // Draw each letter int offset_x = 0; while(*text) { // Set rotation and scale { FT_Matrix matrix; double angle = (RAND_DOUBLE - .5) * M_PI / 4; double size = RAND_DOUBLE / 4 + 1; matrix.xx = (FT_Fixed)(size * cos(angle) * 0x10000); matrix.yx = (FT_Fixed)(size * sin(angle) * 0x10000); matrix.xy = -matrix.yx; matrix.yy = matrix.xx; FT_Set_Transform(face, &matrix, NULL); } if(FT_Load_Glyph(face, FT_Get_Char_Index(face, *text), FT_LOAD_DEFAULT)) { free(image); RAISE("failed to load glyph"); } // Distort outline { FT_Outline *outline = &face->glyph->outline; int i = outline->n_points; while(i) { i--; double noise_x = 0; double noise_y = 0; get_noise(&noise, (outline->points[i].x / 64. + offset_x) / font_size / 3, outline->points[i].y / 192. / font_size, &noise_x, &noise_y); outline->points[i].x += 16 * font_size * noise_x; outline->points[i].y += 16 * font_size * noise_y; } } if(FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) { free(image); RAISE("failed to render glyph"); } // Copy glyph to the image FT_Bitmap *bitmap = &face->glyph->bitmap; int offset_y = bitmap->rows < image_size_y ? RAND_DOUBLE * (image_size_y - bitmap->rows) : 0; int x; int x_max = bitmap->width < image_size_x - offset_x ? bitmap->width : image_size_x - offset_x; int y_max = bitmap->rows < image_size_y ? bitmap->rows : image_size_y; for(x = 0; x < x_max; x++) { int y; for(y = 0; y < y_max; y++) { int value = bitmap->buffer[y * bitmap->pitch + x]; if(value) { image[(y + offset_y) * image_size_x + x + offset_x] = 255 - value; } } } offset_x += bitmap->width; text++; if(*text) { offset_x -= font_size / 8; } } // Crop image char *cropped_image = malloc(offset_x * image_size_y); if(!cropped_image) { return PyErr_NoMemory(); } { int y; for(y = 0; y < image_size_y; y++) { memmove(cropped_image + offset_x * y, image + image_size_x * y, offset_x); } } free(image); PyObject *value = PyString_FromStringAndSize(cropped_image, offset_x * image_size_y); free(cropped_image); return value; } static PyObject *captchaimage_create_image(PyObject *self, PyObject *args) { const char *font_face_path; const char *text; int font_size; int image_size_y; FT_Library library; if(!PyArg_ParseTuple(args, "siis", &font_face_path, &font_size, &image_size_y, &text)) { return NULL; } if(font_size < 1 || image_size_y < 1) { PyErr_SetString(PyExc_ValueError, "arguments must be positive"); return NULL; } if(FT_Init_FreeType(&library)) { RAISE("failed to initialize Freetype"); } PyObject *value = create_image_internal(library, font_face_path, font_size, image_size_y, text); FT_Done_FreeType(library); return value; } PyDoc_STRVAR(captchaimage_create_image__doc__, "create_image(font_face_path, font_size, image_size_y, text) -> image_data" "\n" "Create captcha image. It's recommended that text is only uppercase " "letters and numbers. Returns a string with one byte per pixel. Image " "height is image_size_y. Image width is the length of the return value " "divided by image_size_y."); static PyMethodDef captchaimage_methods[] = { {"create_image", captchaimage_create_image, METH_VARARGS, captchaimage_create_image__doc__}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initcaptchaimage(void) { Py_InitModule("captchaimage", captchaimage_methods); } captchaimage-1.3/example.py0000755000175000017500000000166011275100774015640 0ustar fredrikfredrik#!/usr/bin/python # # Usage: ./example.py [word] # # Run this script to generate and display an image with any word of # your choice. # # 2008 Fredrik Portstrom # # I, the copyright holder of this file, hereby release it into the # public domain. This applies worldwide. In case this is not legally # possible: I grant anyone the right to use this work for any # purpose, without any conditions, unless such conditions are # required by law. try: import captchaimage except ImportError: print "The captcha image module is not installed." else: import random import Image import sys size_y = 32 image_data = captchaimage.create_image( "/usr/share/fonts/truetype/freefont/FreeSerif.ttf" if len(sys.argv) < 3 else sys.argv[2], 28, size_y, "EMACS" if len(sys.argv) < 2 else sys.argv[1]) image = Image.fromstring( "L", (len(image_data) / size_y, size_y), image_data) image.show()