User:Eleanorg/1.3/Dissolute Image/Code1
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"