User:Eleanorg/1.3/Dissolute Image/Code1

From XPUB & Lens-Based wiki

==



August 2012

Code below is the prototype shown at WORM in July 2012. I'm now re-working how the pixels are embedded so that users will be given an image file to host, rather than a boring old string.
Image embedding: code experiments

Make database of pixels

Splits up image into pixels (and their associated info) with imageMagick, then inserts each one as a hash in a Mongo database. To be run once only.

#!/usr/bin/python
#-*- coding:utf-8 -*-

# this script grabs pixel data from imagemagick & creates the mongo database of pixels. to be run once.

import os, re
import pymongo  #the library that serves as python interface to mongo database system
from pymongo import Connection


#--------- create database --------------------------------#	

connection = Connection()
myDB = connection['pixelDbQuery1']
collection = myDB.collection  					#tables in Mongo are called 'collections'. variable name on the left can be anything.


#--------- determine image dimensions-----------------------#	

image = "thatwouldbetelling!"
sizeQuery = "identify -format '%w %h' " + image			# system command gets image dimensions with ImageMagick
size = os.popen(sizeQuery, 'r').read()				# captures output of system call
(width, height) = size.split()					# splits ImageMagick output into separate column & row no's
columns = int(width)
rows = int(height)

#print columns
#print rows


##--------- assign pixel details to database-----------------#		


def getcolors ():
    ID = 0
    for row in range (0, rows):					# for each row in the image...		
        for column in range (0, columns):			# for each column in the row...
            ID = ID + 1
            colorQuery = "convert " + image + " -format '%[pixel:p{" + str(column) + "," + str(row) + "}]' info:-"
            colorResult = os.popen(colorQuery, 'r').read()	# makes & captures output of system call
            rgbResult = re.sub("\n", "", colorResult)			# strips newline character from end of sys call result
            pat = r"(\d\d\d),(\d\d\d),(\d\d\d),"		# use dirty dirty regex to manually convert rgb result to a tuple
            for m in re.finditer(pat,rgbResult):		
                r = int((m.group(1)))
                g = int((m.group(2)))
                b = int((m.group(3)))
            RGB = r,g,b	
            hexa = "".join(map(chr, RGB)).encode('hex')		# converts tuple from rgb form to hex value	
            color = re.sub(hexa, "#"+hexa, hexa)		# replaces hex value with the same value plus a leading #
            pixelHash = {'ID': ID, 'xpos': str(column), 'ypos': str(row), 'color': color, 'url':"" }
            collection.insert(pixelHash)			# insert this pixel into the db
            

getcolors()

Give user an unadopted pixel

Finds all the pixels in the db with no url associated. Picks one at random and gives it to the user, with an input form for them to enter the url where they have put it.

#!/usr/bin/python
#-*- coding:utf-8 -*-

import cgi
import cgitb; cgitb.enable()
import pymongo, random  
from pymongo import Connection

## grab a random unadopted pixel from the database and give user a unique string for it

connection = Connection()
myDB = connection['pixelDbQuery1']

#--------- generate pixel string ------------#

# find all entries where url is empty:
unadopted = []
for entry in myDB.collection.find({"url": ""}):
    unadopted.append(entry)			# add this pixel's hash as an item to the 'unadopted' dictionary
#print unadopted

howMany = len(unadopted)			# find out how many items in 'unadopted' list
#print howMany
random = random.randint(0,howMany)		# pick a random number in this range
#print random
pixelHash = unadopted[random]			# ...and choose the pixel at this index
#print pixelHash

pixelString = "ID=" + str(pixelHash['ID']) + ";x=" + pixelHash['xpos'] + ";y=" + pixelHash['ypos'] + ";col=" + pixelHash['color']
#print pixelString


#--------- print input form with pixel string ------------#

htmlHeader = """<!DOCTYPE html>
<html>
  <head>
    <title>Adopt a Pixel</title>
        <link <link rel="stylesheet" href="../../pixels.css">
        <link href='http://fonts.googleapis.com/css?family=Josefin+Sans' rel='stylesheet' type='text/css'>
  </head>
  <body>"""
 
 
print "Content-Type: text/html"
print 
print htmlHeader
print """
<h1>the DISSOLUTE IMAGE</h1>

<div class="main">
	<div class="menu">
		<ul class="menuList">
			<li><a href="pixelsShowImage.cgi">Home</a></li>
			<li><a href="../../forbiddenPixels/pixelsAbout.html">About</a></li>
			<li>Host a Pixel</li>
		</ul>
	</div>
</div>
<div class="heading">Host a Pixel</div>

<div class="rightBox" style="">

<div class="pixelInfo">
You'll be hosting pixel <strong>""" + str(pixelHash['ID']) + """</strong> out of a total 95600. Enlarged preview: 
<div id="pixelPreview" style="background-color: """ + pixelHash['color'] + """">
</div>

<div class="pixelDetails">
Here are the unique details for your pixel: <br />
</div>

<form action="pixelsScrapeAndVerify.cgi" name="inputForm">  
    <input name="pixel" class="pixelInput" value=""" + pixelString + """><br />   

<div class="inputBox">
    To host this pixel, just copy & paste the details above<br /> somewhere public on the web, exactly as they appear.<br />
    Then tell us the full URL of the page where you pasted them:<br />
</div>
    </span>
    URL: <input name="url" class="urlInput" value="">
    <input type="submit" value="OK">
</form>



  <div class="">
Your pixel will appear in the image for as long as the text remains at this URL.
  </div>
</div>

</body>
</html>"""

Verify the url submitted by user

Scrapes the url given by the user to see if the right pixel can be found there. If yes, that url gets associated with the appropriate pixel in the db so it no longer comes up for adoption. The pixel is then added to the image via a system call to Imagemagick.

#!/usr/bin/python
#-*- coding:utf-8 -*-
 
import cgi
import os, subprocess, re, urllib2
import cgitb; cgitb.enable()
import pymongo 
from pymongo import Connection

# recieves url from inputForm.html scrapes it to check if pixel has been put there correctly
# if yes = updates db entry for that pixel
# if no = throws error
 
 

#-------------- error / thank you messages ----------------#
 
htmlHeader = """<!DOCTYPE html>
<html>
  <head>
    <title>A form talking to a python script</title>
        <link <link rel="stylesheet" href="../../pixels.css">
        <link href='http://fonts.googleapis.com/css?family=Josefin+Sans' rel='stylesheet' type='text/css'>
  </head>
  <body>
  <h1>the DISSOLUTE IMAGE</h1>
  <div class="main">
	<div class="menu">
		<ul class="menuList">
			<li><a href="../mongo/pixelsShowImage.cgi">Home</a></li>
			<li><a href="../../forbiddenPixels/pixelsAbout.html">About</a></li>
			<li><a href="../cgi-bin/mongo/pixelsShowUnadoptedPixel.cgi">Host a Pixel</a></li>
		</ul>
	</div>
  <div class="rightBox">
     """
  
htmlFooter = """
  
  </div>
  </div>
   </body>
</html>"""

scrapingError = """Sorry, your pixel details couldn't be found at that URL! <br />
<ul>
<li>Did you paste them exactly as they appear?</li>
<li>Did you give the URL for the specific page or tweet where you pasted them?</li>
<li>Is the page where you pasted them publicly accessible?</li>
</li>
<br />
Use your browser's back button to try again, or <a href="pixelsShowUnadoptedPixel.cgi">try another pixel</a>.
       """
verifyError = """Sorry, the details you've put online for that pixel seem to be wrong. <br />
<ul>
<li>Did you paste the pixel details exactly as they appear?</li>
</ul>
<br />
Use your browser's back button to try again, or <a href="pixelsShowUnadoptedPixel.cgi">try another pixel</a>."""
               
thankyou = """<strong>Thanks, URL submitted.</strong><br /><br />
Your pixel has been added to the image,<br /> and will remain there as long as you keep your pixel details online."""
 
#------------- get URL from input form -------------------#
 
form = cgi.FieldStorage()			# Grabs whatever input comes from form
string = form.getvalue("pixel")
#TODO check if valid url/protocol given. urllib2 bit breaks if not.
url = form.getvalue("url", "http://ox4.org/~nor/trials/hostedString.html")  # assigns form's input to var 'url'. url on the right is a default value that will be printed for testing if nothing is recieved from the form 

#for testing:
#string = r"ID=65;x=4;y=3;col=red" 
#url = "http://ox4.org/~nor/trials/hostedString.html"

#------------- print html header--------------------------#

print "Content-Type: text/html"
print 
print htmlHeader


#------------ define splitting function-------------------#
# to split user's string into a hash which can be verified against the one in db

def splitString (stg) :
    pat = re.compile("(\w{1,4}=[^;]+);?") 	# captures each foo=blah pair in the string, separated by ;s
    cap = pat.findall(stg)                   	# cap variable contains all four captures - id, x, y, color (if valid)
	
    # put captures into a hash for this pixel
    myhash = {}
    for s in cap :
#        print s
        k,v = s.split('=') 	# splits each capture on the = into key and value
	myhash[k] = v		# puts new k/v pair into hash with these values
 
    # return hash so it can be compared with that in the db
    try :
        myhash['ID'] and myhash['x'] and myhash['y'] and myhash['col']
    except:
        return # missing parameter! return nothing
    return myhash   # returns a hash as above, accessed thus: split['x'] etc
    
    
#print splitString(string)

#    
##------------- scrape and check the url -----------------#

## if correct string not found, throw error
## if found, print thankyou and update db

#connect to db for verification, and possible updating
connection = Connection()
myDB = connection['pixelDbQuery1']
collection = myDB.collection  



text = urllib2.urlopen(url).read()		# reads page at the specified URL

# TODO change regex method to only scrape the first instance of string on a page
if not re.search(string, text):			# if string isn't matched within 'text'				
    print scrapingError			        # print error message to user
else:   

    split = splitString(string)			# calls split string function, passing it the string recieved from the input form, and assigns it to var 'split'
    if split:
        idNo = int(split['ID'])		        # get idNo according to user's string; cast as int to compare with db
        pixelHash = myDB.collection.find_one({'ID': idNo})   # this grabs a hash with the right id number (if valid)
        if pixelHash:			
        # now check if attributes from string recieved (xpos, ypos) match attributes for that ID in DB?  
            if split['x'] == pixelHash['xpos'] and split['y'] == pixelHash['ypos']:
                # print "correct x and y value given"
                collection.update( {'ID': idNo}, {"$set":{'url': url}}) # set pixel with string matching 'pat' to url given by user
                # draw a new pixel onto the img with a syscall
                color = split['col']
                x = str(split['x'])
                y = str(split['y'])
                
                command = """convert ../../forbiddenPixels/images/dissolute1.png -fill '""" + color + """' -draw 'point """ + x + "," + y + """' ../../forbiddenPixels/images/dissolute2.png"""             	           	
                #print command             
                os.popen(command, 'r').read()
                print thankyou
            else:
                print verifyError
        else: print verifyError
             
    else:
          print verifyError

print htmlFooter

Periodic check and re-draw to flush invalid urls

This script checks the database to see if urls can still be scraped for a valid string. If not, they are removed and their pixel disappears from the image.

Pipe to bash to redraw the image with Imagemagick.

#!/usr/bin/python
#-*- coding:utf-8 -*-

import pymongo, urllib2, glob
from pymongo import Connection
import re


## loops thru db. finds all entries (ie pixels) with an associated url. all that have a valid url are sent to imagemagick command to re-draw the image; invalid ones are removed.

##---------------------- connect to db:

connection = Connection()
myDB = connection['pixelDbQuery1']
collection = myDB.collection  

 
##---------------------- find all entries with a url:

valid = []							# adopted will be a list of hashes. 
for entry in myDB.collection.find( {"url" : { "$ne" : "" } }):		# $ne returns all where url is not equal to empty
	url = entry["url"]
	string = "ID=" + str(entry["ID"]) + ";x=" + str(entry['xpos']) + ";y=" + str(entry['ypos']) + ";col=" + entry['color']
	text = urllib2.urlopen(url).read()		# reads page at the specified URL
	if not re.search(string, text):			# TODO change regex method to only scrape the first instance of string on a page			
   		collection.update( {'url': url}, {"$set":{'url': ""}})	     	# if the exact string, matching db values, not found, the url is removed from db
	else:   
		valid.append(entry)
										# if all is good, add this pixel's hash to the 'adopted' list

    
    
##---------------------- loop through valid values, drawing onto image       			
# prints bash commands; pipe this script to bash
# [0]['url'] << this accesses the url of the first pixel in 'valid'

images = glob.glob('../../forbiddenPixels/images/*')
current = '%05d' % len(images)						# counts number of files in images folder to find out latest img filename
new = '%05d' % (int(current) + 1)					# converts to 5 digits. note: this changes type from int to str

print "convert ../../forbiddenPixels/images/00001.png \\"	# starts from original black image, making the image from scratch
for entry in valid:
                print """-fill '""" + entry["color"] + """' -draw 'point """ + entry["xpos"] + "," + entry["ypos"] + "' \\"             	           	  
print "../../forbiddenPixels/images/" + new + ".png"