User:Cristinac/TPFinal: Difference between revisions

From XPUB & Lens-Based wiki
No edit summary
No edit summary
 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Question: ==
How do we design in favour of the attentive thought?


== Printed Book ==
== Printed Book ==
<div style="width:800px;">
<div style="width:800px;">
<font size="2">
I would call the result of this trimester's thematic project an imagebook rather than a photobook, due to the fact that it relies on the distinction between image and information. The starting point for this work was the subject of computer vision, a field that deals with processing and analyzing images from the real world in order to produce numerical or symbolic information in the form of decisions. Computer vision is a way of training the algorithm to make sense of the bundle of pixels of a photograph or video and transform it into something executable.
CAPTCHAs are representations of words in such a form that they cannot be read by a computer and need the assistance of human intelligence. The original reCAPTCHAs were taken from fragments of scanned text that couldn't be processed with the help of OCR (Optical Character Recognition) software. The act of scanning a file and producing a .jpg, .png or .tiff image for compression is in a way equivalent to the act of pressing the shutter on a digital camera: it captures 3D objects indiscriminately and transforms them into data.
By generating my own CAPTCHAs, I am forcefully creating images consisting of the same building blocks of photographs, that are abstract to the computer, but can be read by humans. However, while this differentiation between the two types of intelligence is intrinsic to this form of verification, for this project the more relevant aspect of CAPTCHAs is that of sensemaking. Sensemaking relies on people's ability to give meaning to an experience; it is a prevalent term in human-computer interaction, information studies and organizational studies.
The making of the book meant running each line of The Open Society through a script that would distort the writing in a random fashion and add noise to it, then putting all of the .png files in the original order of the text. Reading the book is turned into a slow process of digesting each line and requires a mathematical understanding of the text: normal reading is fluid, it allows you to skip fragments and leaves the necessary space for your mind to fill that in, however, mathematical reading is more exhaustive, it builds itself up with each line. The conversion from image to text happens in the mind of the readers, so in a way it could be said that until they decipher the meaning behind each word, it remains an image. Running the pages of the book through OCR, for example, would prove this realization.
Taken from Geert Lovink's essay “Psychopathology in the age of information overload”, the question the book was trying to answer became:
How do we design in favour of the attentive thought?
reasons for choosing this particular text:


The printed book is a remake of The Open Society, by Karl Popper though CAPTCHAS. The reason this work was chosen is that at the time of producing the photobook, it was at the core of the research being done. The Open Society was a building block in the popularization of the "open thought" movement. In it, Popper  presents a defense of the open society and liberal democracy. His interpretation of Plato together with the dichotomy of open and closed systems have been widely criticized. For example, one of Popper's critics, Nathaniel Tkacz, argues that due to Popper's binary notion of openness, it inherently possesses closure.
The printed book is a remake of The Open Society, by Karl Popper though CAPTCHAS. The reason this work was chosen is that at the time of producing the photobook, it was at the core of the research being done. The Open Society was a building block in the popularization of the "open thought" movement. In it, Popper  presents a defense of the open society and liberal democracy. His interpretation of Plato together with the dichotomy of open and closed systems have been widely criticized. For example, one of Popper's critics, Nathaniel Tkacz, argues that due to Popper's binary notion of openness, it inherently possesses closure.
Line 7: Line 33:
‘Openness refers to the relative degree of freedom given to the dissemination of information or knowledge and involves assumptions concerning the nature and extent of the audience’
‘Openness refers to the relative degree of freedom given to the dissemination of information or knowledge and involves assumptions concerning the nature and extent of the audience’


However, as we would soon be able to discover, the term 'open' would soon reveal itself as an empty vessel. As Hall has argued in Digitize this book!, where he gives a very detailed and comprehensive overview of the differing but often also overlapping motivations that exist concerning open access and openness, there is nothing intrinsically political or democratic about open access. A constant wide flow of information can give rise to secrecy through the difficulty of gaining visibility.   
However, as we would soon be able to discover, the term 'open' would reveal itself as an empty vessel. As Hall has argued in Digitize this book!, where he gives a very detailed and comprehensive overview of the differing but often also overlapping motivations that exist concerning open access and openness, there is nothing intrinsically political or democratic about open access. A constant wide flow of information can give rise to secrecy through the difficulty of gaining visibility.   
 
Given the possibility to do it over, I would choose to write an original text.
 
</font>
 
''Code:''
 
<source lang="python">
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#NOTE!: the interpreter line above MUST run whichever python version
#your installation's gimp-python support is compiled with. ensure that this
#corresponds, or nothing here will work.
 
'''A Command Line Interface for Captcha Generation with Gimp
 
To use this script, place it in a gimp plug-ins directory and make it
executable. On Ubuntu Edgy this can be: $HOME/.gimp-2.2/plug-ins/
 
Invoke gimp like so:
 
    gimp --no-interface --no-data --console-messages \
        --batch '(python-fu-captcha-generate 1 "DIRECTORY" NUMBER) (gimp-quit 1)'
 
NUMBER is the number of captchas to put in the directory specified by
DIRECTORY. The directory will be "filled" to the goal level with pngs named
like ANSWER.jpg, where ANSWER is the string pictured in the image.
'''
 
from tempfile import mkstemp
 
import os
import random
import sys
 
import gimpfu
import gimpplugin


Given the possibility to do it over, I would choose a a book that has more to do with the information society than with the open society.
from gimpfu import gimp
from gimpfu import pdb as gpdb
from gimpfu import RGB_IMAGE
from gimpfu import GRAY_IMAGE
from gimpfu import GRAY
from gimpfu import NORMAL_MODE
from gimpfu import MULTIPLY_MODE
from gimpfu import WHITE_FILL
from gimpfu import TRANSPARENT_FILL
from gimpfu import FALSE
from gimpfu import PIXELS
from gimpfu import HISTOGRAM_VALUE
from gimpfu import CLIP_TO_IMAGE


from gimpenums import PLUGIN
from gimpenums import EXTENSION


Code:
# ==========
#  Settings
# ==========


# Units are generally in pixels:
CAPTCHA_LETTERS = 40
CAPTCHA_WIDTH  = 3000
CAPTCHA_HEIGHT  = 150
FONT_HEIGHT    = 80
FONT_HEIGHT2 = 10
LEFT_MARGIN    = 10


# Remove characters to reduce confusion. No l, or o, for instance, as running
# them together might look like b or d.
CAPTCHA_CHARS  = 'aAbdeEfFgGjJknNMpPqQstTuvxyYzZ1234567890- '
LETTER_SPACING  = -5
# Rotate individual letters, in radians:
ANGLE_RANGE    = (-0.30, 0.25)


FONTS = ['Sans', 'Serif', 'Monospace', 'Serif Bold', 'Century Schoolbook',
        'DejaVu Sans', 'DejaVu Sans Bold', 'FreeMono', 'FreeSerif']


# The extension of the file type to save generated CAPTCHAs as. Can be ".png"
# or ".jpg".
CAPTCHA_FILE_EXT = ".png"
# =============================================================================
#  End of settings (you probably don't want to touch anything further down)
# =============================================================================
PNG_INTERLACE  = 0
PNG_COMPRESSION = 9
PNG_BKGD        = 0
PNG_GAMA        = 0
PNG_OFFSET      = 0
PNG_PHYS        = 0
PNG_TIME        = 0
PNG_COMMENT    = 1
PNG_SVGTRANS    = 0
JPG_QUALITY    = float(1)
JPG_SMOOTHING  = float(0)
JPG_OPTIMIZE    = 0
JPG_PROGRESSIVE = 0
JPG_COMMENT    = ""
JPG_SUBSMP      = 0
JPG_BASELINE    = 1
JPG_MARKERS    = 0
JPG_DCTALGO    = 0
counter = 0
listfile = open('gimp-captcha/opensociety.csv', 'r')
def make_captcha(sx, sy, font_height, letter_spacing, left_margin,
                angle_range, fonts, answer):
    """Generate a captcha consisting of the letters in answer.
    :rtype: :class:`gimp.Image`
    :returns: The CAPTCHA as a gimp-python image object.
    """
    img = gimp.Image(sx, sy, RGB_IMAGE)
    img.disable_undo()
   
    print answer
    light_noise_layer = gimp.Layer(img, 'light noise', sx, sy,RGB_IMAGE, 100, NORMAL_MODE)
    img.add_layer(light_noise_layer, 0)
    gpdb.gimp_selection_none(img)
    gpdb.gimp_drawable_fill(light_noise_layer, WHITE_FILL)
    # plug_in_randomize_hurl at 1% 1 time is vastly superior to
    # scatter_rgb here, but has a bug where it creates an artifact at
    # the top of the image when invoked in a scripting context like
    # this.
    #
    # Future experiment: dial down the amount of noise generated by
    # scatter, then run it through levels to darken it, then
    # blur. This should be equivalent to hurl + blur.
    #gpdb.plug_in_randomize_hurl(img, light_noise_layer, 1, 1, 0, 0)
    gpdb.plug_in_scatter_hsv(img, light_noise_layer, 1, 25, 200, 180)
    gpdb.plug_in_gauss_iir(img, light_noise_layer, 1, 1, 1)
    gpdb.gimp_desaturate(light_noise_layer)
    # Next make pure black layer which we will copy repeatedly as a
    # place to cut out letters.
    blackLayer = gimp.Layer(img, 'black', sx, sy, RGB_IMAGE, 100, NORMAL_MODE)
    img.add_layer(blackLayer, 0)
    blackLayer.add_alpha()
    gpdb.gimp_layer_add_alpha(blackLayer)
    gpdb.gimp_drawable_fill(blackLayer, WHITE_FILL)
    gpdb.gimp_invert(blackLayer)
    # Loop through each letter, making it a separate black layer.
    right = left_margin
    last_substrate = None
    for letter in answer:
        font = random.choice(FONTS)
        substrate = blackLayer.copy()
        img.add_layer(substrate, 0)
        new_right = cookie_cutter_letter(img, substrate, right, font, letter)
        print new_right
        # look out for really narrow letters
        if new_right - right < 20:
            new_right += 5
        right = new_right
    img.remove_layer(blackLayer)
    # Hide the light noise layer, then collapse all the remaining
    # layers (all letters) into a single layer.
    light_noise_layer.visible = False
    textLayer = gpdb.gimp_image_merge_visible_layers(img, CLIP_TO_IMAGE)
    light_noise_layer.visible = False
    # Create a layer of dark noise which will display the letters.
    dark_noise_layer = gimp.Layer(img, 'dark noise', sx, sy, RGB_IMAGE, 100, MULTIPLY_MODE)
    img.add_layer(dark_noise_layer, 1)
    gpdb.gimp_drawable_fill(dark_noise_layer, WHITE_FILL)
    gpdb.plug_in_randomize_hurl(img, dark_noise_layer, 25, 1, 0, 0)
    gpdb.gimp_desaturate(dark_noise_layer)
    # These next operations are ordered carefully. Changing the order
    # dramatically affects how the output looks.
    # Here's where we do the cutout operation.
    gpdb.gimp_selection_layer_alpha(textLayer)
    gpdb.gimp_selection_invert(img)
    gpdb.gimp_edit_clear(dark_noise_layer)
    gpdb.gimp_selection_none(img)
    # After the cutout, blur the dark noise layer and then darken it:
    gpdb.plug_in_gauss_iir(img, dark_noise_layer, 1, 1, 1)
    gpdb.gimp_levels(dark_noise_layer, HISTOGRAM_VALUE, 127, 255, 0.25, 0, 255)
    textLayer.visible = False
    # If you start gimp without --no-interface with an X server, this
    # line will let you see the image looks like at this point in the
    # script, layers and all. It should be fine to move this line to
    # any problematic part of the script for debugging.
    #
    # gimp.Display(gpdb.gimp_image_duplicate(img))
    final = img.flatten()
    gpdb.gimp_image_clean_all(img)
    img.enable_undo()
    return img, final
def cookie_cutter_letter(img, substrate, right, font, letter):
    '''Cut text shaped like letter out of the given layer.'''
    filler = False
    if letter is ' ':
        filler = True
        temp_layer = gpdb.gimp_text_fontname(img, substrate, right, 0, '_', 1, False, FONT_HEIGHT2, PIXELS, font)
    else:
        temp_layer = gpdb.gimp_text_fontname(img, substrate, right, 0, letter, 1, False, FONT_HEIGHT, PIXELS, font)   
    gpdb.gimp_selection_layer_alpha(temp_layer)
    angle = random.uniform(*ANGLE_RANGE)
    xaxis = right
    yaxis = 15
    # srcX = float(xaxis)
    # dstX = float(srcX + random.uniform(0, 25))
    # srcY = float(yaxis)
    # dstY = float(srcY + random.uniform(0, 25))
    # scaleX = scaleY = float(100)
    # We need to save the selection as a channel so we can mess with
    # the letter form.
    shape = gpdb.gimp_selection_save(img)
    gpdb.gimp_selection_none(img)
    gpdb.gimp_floating_sel_remove(temp_layer)
    # Distort the form of the individual letter:
    shape = gpdb.gimp_item_transform_rotate(shape, angle, 0, xaxis, yaxis)
    # We aren't doing any letter warping now, but if we were, this is the
    # point where it should be done. We want to warp the shape of textLayer,
    # which later serves as a cutout for the dark noise layer. If we warp the
    # dark noise layer directly we will end up with warped dots.
    #
    # gpdb.gimp_context_set_transform_resize(TRANSFORM_RESIZE_CROP)
    # shape = gpdb.gimp_item_transform_2d(shape, srcX, srcY, angle,
    #                                    scaleX, scaleY, dstX, dstY)
    gpdb.gimp_selection_load(shape)
    img.remove_channel(shape)
    # Note the bounding box of the letter form so we can figure out
    # where the next one should go.
    bounds = gpdb.gimp_selection_bounds(img)
    new_right = bounds[3] + LETTER_SPACING
   
    gpdb.gimp_selection_invert(img)
    gpdb.gimp_edit_clear(substrate)
    gpdb.gimp_selection_none(img)
    return new_right
def selectAnswer(length):
    """Select **length** charaters to form a CAPTCHA answer string.
    The alphabet is chosen to contain fewew similar letter shapes in roman
    fonts.
    :param int length: The number of letters to use for each CAPTCHA answer.
    :rtype: str
    :returns: A randomish string which can be made into a CAPTCHA.
    """
    answerLetters = []
    for letter in random.sample(CAPTCHA_CHARS, length):
        answerLetters.append(letter)
    answer = ''.join(answerLetters)
   
    answerletter = listfile.readline()
    answer = ''.join(answerletter)
    answer =  answer.replace('\n', '')
    return answer
def countImages(imageDir):
    """Count the images with the given file extension in a directory."""
    return len([f for f in os.listdir(imageDir)
                if f.endswith(CAPTCHA_FILE_EXT)])
def captcha_generate(imageDir, goal):
    """Make sure there are as many catchas in image_dir as goal."""
    needed = goal - countImages(imageDir)
    if needed < 1:
        return
    answers = [selectAnswer(CAPTCHA_LETTERS) for i in xrange(0, needed)]
    for answer in answers:
        imageFile = '{0}{1}'.format(answer, CAPTCHA_FILE_EXT)
        imagePath = os.path.join(imageDir, imageFile)
        imageTmp  = '%s.tmp' % imagePath
        img, drawable = make_captcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT,
                                    FONT_HEIGHT, LETTER_SPACING, LEFT_MARGIN,
                                    ANGLE_RANGE, FONTS, answer)
        try:
            if CAPTCHA_FILE_EXT == ".jpg":
                gpdb.file_jpeg_save(img, drawable,
                                    imageTmp, imageTmp,
                                    JPG_QUALITY,
                                    JPG_SMOOTHING,
                                    JPG_OPTIMIZE,
                                    JPG_PROGRESSIVE,
                                    JPG_COMMENT,
                                    JPG_SUBSMP,
                                    JPG_BASELINE,
                                    JPG_MARKERS,
                                    JPG_DCTALGO)
            elif CAPTCHA_FILE_EXT == ".png":
                gpdb.file_png_save2(img, drawable,
                                    imageTmp, imageTmp,
                                    PNG_INTERLACE,
                                    PNG_COMPRESSION,
                                    PNG_BKGD,
                                    PNG_GAMA,
                                    PNG_OFFSET,
                                    PNG_PHYS,
                                    PNG_TIME,
                                    PNG_COMMENT,
                                    PNG_SVGTRANS)
            else:
                return SystemExit("Image extension %r is not supported!"
                                  % CAPTCHA_FILE_EXT)
        except Exception as err:
            print(err)
            if os.path.isfile(imageTmp):
                os.unlink(imageTmp)
        else:
            os.rename(imageTmp, imagePath)
# Gimp-python boilerplate.
gimpfu.register('captcha_generate',
                'Generate CAPTCHAs',
                'Generate CAPTCHAs',
                'Isis Lovecruft',
                'Isis Lovecruft',
                '2014',
                '<Toolbox>/Xtns/Make-Captcha', '',
                [(gimpfu.PF_STRING, 'basedir', 'base directory for images', ''),
                (gimpfu.PF_INT, 'count', 'number of images to add', 0)],
                [], captcha_generate)
gimpfu.main()
</source>
</div>
</div>
== Digital Book ==
== Digital Book ==
Line 24: Line 391:
<div style="width:800px;">
<div style="width:800px;">
For the digital book, I created a website that would display one CAPTCHA at a time and would allow you to move further only by passing these verification tests.
For the digital book, I created a website that would display one CAPTCHA at a time and would allow you to move further only by passing these verification tests.
[[File:opens.png|400px]]
</div>
</div>

Latest revision as of 16:40, 1 April 2015

Question:

How do we design in favour of the attentive thought?


Printed Book

I would call the result of this trimester's thematic project an imagebook rather than a photobook, due to the fact that it relies on the distinction between image and information. The starting point for this work was the subject of computer vision, a field that deals with processing and analyzing images from the real world in order to produce numerical or symbolic information in the form of decisions. Computer vision is a way of training the algorithm to make sense of the bundle of pixels of a photograph or video and transform it into something executable.


CAPTCHAs are representations of words in such a form that they cannot be read by a computer and need the assistance of human intelligence. The original reCAPTCHAs were taken from fragments of scanned text that couldn't be processed with the help of OCR (Optical Character Recognition) software. The act of scanning a file and producing a .jpg, .png or .tiff image for compression is in a way equivalent to the act of pressing the shutter on a digital camera: it captures 3D objects indiscriminately and transforms them into data.


By generating my own CAPTCHAs, I am forcefully creating images consisting of the same building blocks of photographs, that are abstract to the computer, but can be read by humans. However, while this differentiation between the two types of intelligence is intrinsic to this form of verification, for this project the more relevant aspect of CAPTCHAs is that of sensemaking. Sensemaking relies on people's ability to give meaning to an experience; it is a prevalent term in human-computer interaction, information studies and organizational studies.


The making of the book meant running each line of The Open Society through a script that would distort the writing in a random fashion and add noise to it, then putting all of the .png files in the original order of the text. Reading the book is turned into a slow process of digesting each line and requires a mathematical understanding of the text: normal reading is fluid, it allows you to skip fragments and leaves the necessary space for your mind to fill that in, however, mathematical reading is more exhaustive, it builds itself up with each line. The conversion from image to text happens in the mind of the readers, so in a way it could be said that until they decipher the meaning behind each word, it remains an image. Running the pages of the book through OCR, for example, would prove this realization.


Taken from Geert Lovink's essay “Psychopathology in the age of information overload”, the question the book was trying to answer became:

How do we design in favour of the attentive thought?


reasons for choosing this particular text:

The printed book is a remake of The Open Society, by Karl Popper though CAPTCHAS. The reason this work was chosen is that at the time of producing the photobook, it was at the core of the research being done. The Open Society was a building block in the popularization of the "open thought" movement. In it, Popper presents a defense of the open society and liberal democracy. His interpretation of Plato together with the dichotomy of open and closed systems have been widely criticized. For example, one of Popper's critics, Nathaniel Tkacz, argues that due to Popper's binary notion of openness, it inherently possesses closure.

‘Openness refers to the relative degree of freedom given to the dissemination of information or knowledge and involves assumptions concerning the nature and extent of the audience’

However, as we would soon be able to discover, the term 'open' would reveal itself as an empty vessel. As Hall has argued in Digitize this book!, where he gives a very detailed and comprehensive overview of the differing but often also overlapping motivations that exist concerning open access and openness, there is nothing intrinsically political or democratic about open access. A constant wide flow of information can give rise to secrecy through the difficulty of gaining visibility.

Given the possibility to do it over, I would choose to write an original text.

Code:

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#NOTE!: the interpreter line above MUST run whichever python version
#your installation's gimp-python support is compiled with. ensure that this
#corresponds, or nothing here will work.

'''A Command Line Interface for Captcha Generation with Gimp

To use this script, place it in a gimp plug-ins directory and make it
executable. On Ubuntu Edgy this can be: $HOME/.gimp-2.2/plug-ins/

Invoke gimp like so:

    gimp --no-interface --no-data --console-messages \
        --batch '(python-fu-captcha-generate 1 "DIRECTORY" NUMBER) (gimp-quit 1)'

NUMBER is the number of captchas to put in the directory specified by
DIRECTORY. The directory will be "filled" to the goal level with pngs named
like ANSWER.jpg, where ANSWER is the string pictured in the image.
'''

from tempfile import mkstemp

import os
import random
import sys

import gimpfu
import gimpplugin

from gimpfu import gimp
from gimpfu import pdb as gpdb
from gimpfu import RGB_IMAGE
from gimpfu import GRAY_IMAGE
from gimpfu import GRAY
from gimpfu import NORMAL_MODE
from gimpfu import MULTIPLY_MODE
from gimpfu import WHITE_FILL
from gimpfu import TRANSPARENT_FILL
from gimpfu import FALSE
from gimpfu import PIXELS
from gimpfu import HISTOGRAM_VALUE
from gimpfu import CLIP_TO_IMAGE

from gimpenums import PLUGIN
from gimpenums import EXTENSION

# ==========
#  Settings
# ==========

# Units are generally in pixels:
CAPTCHA_LETTERS = 40
CAPTCHA_WIDTH   = 3000
CAPTCHA_HEIGHT  = 150
FONT_HEIGHT     = 80
FONT_HEIGHT2 = 10
LEFT_MARGIN     = 10

# Remove characters to reduce confusion. No l, or o, for instance, as running
# them together might look like b or d.
CAPTCHA_CHARS   = 'aAbdeEfFgGjJknNMpPqQstTuvxyYzZ1234567890- '
LETTER_SPACING  = -5
# Rotate individual letters, in radians:
ANGLE_RANGE     = (-0.30, 0.25)

FONTS = ['Sans', 'Serif', 'Monospace', 'Serif Bold', 'Century Schoolbook',
         'DejaVu Sans', 'DejaVu Sans Bold', 'FreeMono', 'FreeSerif']

# The extension of the file type to save generated CAPTCHAs as. Can be ".png"
# or ".jpg".
CAPTCHA_FILE_EXT = ".png"

# =============================================================================
#  End of settings (you probably don't want to touch anything further down)
# =============================================================================

PNG_INTERLACE   = 0
PNG_COMPRESSION = 9
PNG_BKGD        = 0
PNG_GAMA        = 0
PNG_OFFSET      = 0
PNG_PHYS        = 0
PNG_TIME        = 0
PNG_COMMENT     = 1
PNG_SVGTRANS    = 0

JPG_QUALITY     = float(1)
JPG_SMOOTHING   = float(0)
JPG_OPTIMIZE    = 0
JPG_PROGRESSIVE = 0
JPG_COMMENT     = ""
JPG_SUBSMP      = 0
JPG_BASELINE    = 1
JPG_MARKERS     = 0
JPG_DCTALGO     = 0

counter = 0
listfile = open('gimp-captcha/opensociety.csv', 'r')


def make_captcha(sx, sy, font_height, letter_spacing, left_margin,
                 angle_range, fonts, answer):
    """Generate a captcha consisting of the letters in answer.

    :rtype: :class:`gimp.Image`
    :returns: The CAPTCHA as a gimp-python image object.
    """
    img = gimp.Image(sx, sy, RGB_IMAGE)
    img.disable_undo()
    
    print answer

    light_noise_layer = gimp.Layer(img, 'light noise', sx, sy,RGB_IMAGE, 100, NORMAL_MODE)
    img.add_layer(light_noise_layer, 0)

    gpdb.gimp_selection_none(img)
    gpdb.gimp_drawable_fill(light_noise_layer, WHITE_FILL)

    # plug_in_randomize_hurl at 1% 1 time is vastly superior to
    # scatter_rgb here, but has a bug where it creates an artifact at
    # the top of the image when invoked in a scripting context like
    # this.
    #
    # Future experiment: dial down the amount of noise generated by
    # scatter, then run it through levels to darken it, then
    # blur. This should be equivalent to hurl + blur.
    #gpdb.plug_in_randomize_hurl(img, light_noise_layer, 1, 1, 0, 0)
    gpdb.plug_in_scatter_hsv(img, light_noise_layer, 1, 25, 200, 180)
    gpdb.plug_in_gauss_iir(img, light_noise_layer, 1, 1, 1)
    gpdb.gimp_desaturate(light_noise_layer)

    # Next make pure black layer which we will copy repeatedly as a
    # place to cut out letters.
    blackLayer = gimp.Layer(img, 'black', sx, sy, RGB_IMAGE, 100, NORMAL_MODE)
    img.add_layer(blackLayer, 0)
    blackLayer.add_alpha()
    gpdb.gimp_layer_add_alpha(blackLayer)
    gpdb.gimp_drawable_fill(blackLayer, WHITE_FILL)
    gpdb.gimp_invert(blackLayer)

    # Loop through each letter, making it a separate black layer.
    right = left_margin
    last_substrate = None
    for letter in answer:
        font = random.choice(FONTS)
        substrate = blackLayer.copy()
        img.add_layer(substrate, 0)
        new_right = cookie_cutter_letter(img, substrate, right, font, letter)
        print new_right
        # look out for really narrow letters
        if new_right - right < 20:
            new_right += 5
        right = new_right
    img.remove_layer(blackLayer)

    # Hide the light noise layer, then collapse all the remaining
    # layers (all letters) into a single layer.
    light_noise_layer.visible = False
    textLayer = gpdb.gimp_image_merge_visible_layers(img, CLIP_TO_IMAGE)
    light_noise_layer.visible = False

    # Create a layer of dark noise which will display the letters.
    dark_noise_layer = gimp.Layer(img, 'dark noise', sx, sy, RGB_IMAGE, 100, MULTIPLY_MODE)
    img.add_layer(dark_noise_layer, 1)
    gpdb.gimp_drawable_fill(dark_noise_layer, WHITE_FILL)
    gpdb.plug_in_randomize_hurl(img, dark_noise_layer, 25, 1, 0, 0)
    gpdb.gimp_desaturate(dark_noise_layer)

    # These next operations are ordered carefully. Changing the order
    # dramatically affects how the output looks.

    # Here's where we do the cutout operation.
    gpdb.gimp_selection_layer_alpha(textLayer)
    gpdb.gimp_selection_invert(img)
    gpdb.gimp_edit_clear(dark_noise_layer)
    gpdb.gimp_selection_none(img)

    # After the cutout, blur the dark noise layer and then darken it:
    gpdb.plug_in_gauss_iir(img, dark_noise_layer, 1, 1, 1)
    gpdb.gimp_levels(dark_noise_layer, HISTOGRAM_VALUE, 127, 255, 0.25, 0, 255)
    textLayer.visible = False

    # If you start gimp without --no-interface with an X server, this
    # line will let you see the image looks like at this point in the
    # script, layers and all. It should be fine to move this line to
    # any problematic part of the script for debugging.
    #
    # gimp.Display(gpdb.gimp_image_duplicate(img))

    final = img.flatten()
    gpdb.gimp_image_clean_all(img)
    img.enable_undo()
    return img, final

def cookie_cutter_letter(img, substrate, right, font, letter):
    '''Cut text shaped like letter out of the given layer.'''
    filler = False
    if letter is ' ':
        filler = True
        temp_layer = gpdb.gimp_text_fontname(img, substrate, right, 0, '_', 1, False, FONT_HEIGHT2, PIXELS, font)
    else:
        temp_layer = gpdb.gimp_text_fontname(img, substrate, right, 0, letter, 1, False, FONT_HEIGHT, PIXELS, font)    
    gpdb.gimp_selection_layer_alpha(temp_layer)

    angle = random.uniform(*ANGLE_RANGE)
    xaxis = right
    yaxis = 15
    # srcX = float(xaxis)
    # dstX = float(srcX + random.uniform(0, 25))
    # srcY = float(yaxis)
    # dstY = float(srcY + random.uniform(0, 25))
    # scaleX = scaleY = float(100)

    # We need to save the selection as a channel so we can mess with
    # the letter form.
    shape = gpdb.gimp_selection_save(img)
    gpdb.gimp_selection_none(img)
    gpdb.gimp_floating_sel_remove(temp_layer)

    # Distort the form of the individual letter:

    shape = gpdb.gimp_item_transform_rotate(shape, angle, 0, xaxis, yaxis)


    # We aren't doing any letter warping now, but if we were, this is the
    # point where it should be done. We want to warp the shape of textLayer,
    # which later serves as a cutout for the dark noise layer. If we warp the
    # dark noise layer directly we will end up with warped dots.
    #
    # gpdb.gimp_context_set_transform_resize(TRANSFORM_RESIZE_CROP)
    # shape = gpdb.gimp_item_transform_2d(shape, srcX, srcY, angle,
    #                                     scaleX, scaleY, dstX, dstY)
    gpdb.gimp_selection_load(shape)
    img.remove_channel(shape)

    # Note the bounding box of the letter form so we can figure out
    # where the next one should go.
    bounds = gpdb.gimp_selection_bounds(img)
    new_right = bounds[3] + LETTER_SPACING
    
    gpdb.gimp_selection_invert(img)
    gpdb.gimp_edit_clear(substrate)
    gpdb.gimp_selection_none(img)
    return new_right

def selectAnswer(length):
    """Select **length** charaters to form a CAPTCHA answer string.

    The alphabet is chosen to contain fewew similar letter shapes in roman
    fonts.

    :param int length: The number of letters to use for each CAPTCHA answer.
    :rtype: str
    :returns: A randomish string which can be made into a CAPTCHA.
    """
    answerLetters = []
    for letter in random.sample(CAPTCHA_CHARS, length):
        answerLetters.append(letter)
    answer = ''.join(answerLetters)
    
    answerletter = listfile.readline()
    answer = ''.join(answerletter)
    answer =  answer.replace('\n', '')
    return answer

def countImages(imageDir):
    """Count the images with the given file extension in a directory."""
    return len([f for f in os.listdir(imageDir)
                if f.endswith(CAPTCHA_FILE_EXT)])

def captcha_generate(imageDir, goal):
    """Make sure there are as many catchas in image_dir as goal."""
    needed = goal - countImages(imageDir)
    if needed < 1:
        return

    answers = [selectAnswer(CAPTCHA_LETTERS) for i in xrange(0, needed)]
    for answer in answers:
        imageFile = '{0}{1}'.format(answer, CAPTCHA_FILE_EXT)
        imagePath = os.path.join(imageDir, imageFile)
        imageTmp  = '%s.tmp' % imagePath

        img, drawable = make_captcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT,
                                     FONT_HEIGHT, LETTER_SPACING, LEFT_MARGIN,
                                     ANGLE_RANGE, FONTS, answer)

        try:
            if CAPTCHA_FILE_EXT == ".jpg":
                gpdb.file_jpeg_save(img, drawable,
                                    imageTmp, imageTmp,
                                    JPG_QUALITY,
                                    JPG_SMOOTHING,
                                    JPG_OPTIMIZE,
                                    JPG_PROGRESSIVE,
                                    JPG_COMMENT,
                                    JPG_SUBSMP,
                                    JPG_BASELINE,
                                    JPG_MARKERS,
                                    JPG_DCTALGO)

            elif CAPTCHA_FILE_EXT == ".png":
                gpdb.file_png_save2(img, drawable,
                                    imageTmp, imageTmp,
                                    PNG_INTERLACE,
                                    PNG_COMPRESSION,
                                    PNG_BKGD,
                                    PNG_GAMA,
                                    PNG_OFFSET,
                                    PNG_PHYS,
                                    PNG_TIME,
                                    PNG_COMMENT,
                                    PNG_SVGTRANS)

            else:
                return SystemExit("Image extension %r is not supported!"
                                  % CAPTCHA_FILE_EXT)
        except Exception as err:
            print(err)
            if os.path.isfile(imageTmp):
                os.unlink(imageTmp)
        else:
            os.rename(imageTmp, imagePath)


# Gimp-python boilerplate.
gimpfu.register('captcha_generate',
                'Generate CAPTCHAs',
                'Generate CAPTCHAs',
                'Isis Lovecruft',
                'Isis Lovecruft',
                '2014',
                '<Toolbox>/Xtns/Make-Captcha', '',
                [(gimpfu.PF_STRING, 'basedir', 'base directory for images', ''),
                 (gimpfu.PF_INT, 'count', 'number of images to add', 0)],
                [], captcha_generate)
gimpfu.main()

Digital Book

For the digital book, I created a website that would display one CAPTCHA at a time and would allow you to move further only by passing these verification tests.

Opens.png