-
Notifications
You must be signed in to change notification settings - Fork 0
/
images.py
423 lines (387 loc) · 13.7 KB
/
images.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
import os
from google.appengine.ext.webapp import template
import cgi
import re
import logging
import StringIO
import struct
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.api import datastore
from google.appengine.api import memcache
from google.appengine.api import images
from django.utils import simplejson
import adventureModel
import main
def resizeImage(imageOBJ, width, height, maxW, maxH):
logging.info("resizeImage %dx%d to %dx%d" % (width, height, maxH, maxW))
#only resize if we have to
if width <= maxW and height <= maxH:
return(0, 0, False)
#we first have to try to do both and then we will see which results in a smaller ratio, then we do that one
#get the ratio of the change
widthRatio = float(maxW) / float(width)
heightRatio = float(maxH) / float(height)
ratio = 1.0
if widthRatio <= heightRatio:
ratio = widthRatio;
else:
ratio = heightRatio;
logging.info("resizing to ratio %f, new size = %dx%d" % (ratio, int(ratio*width), int(ratio*height)))
return(int(ratio*width), int(ratio*height), True)
def getImageInfo(data):
data = str(data)
size = len(data)
height = -1
width = -1
content_type = ''
# handle GIFs
if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
# Check to see if content_type is correct
content_type = 'image/gif'
w, h = struct.unpack("<HH", data[6:10])
width = int(w)
height = int(h)
# See PNG 2. Edition spec (http://www.w3.org/TR/PNG/)
# Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
# and finally the 4-byte width, height
elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n')
and (data[12:16] == 'IHDR')):
content_type = 'image/png'
w, h = struct.unpack(">LL", data[16:24])
width = int(w)
height = int(h)
# Maybe this is for an older PNG version.
elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'):
# Check to see if we have the right content type
content_type = 'image/png'
w, h = struct.unpack(">LL", data[8:16])
width = int(w)
height = int(h)
# handle JPEGs
elif (size >= 2) and data.startswith('\377\330'):
content_type = 'image/jpeg'
jpeg = StringIO.StringIO(data)
jpeg.read(2)
b = jpeg.read(1)
try:
while (b and ord(b) != 0xDA):
while (ord(b) != 0xFF): b = jpeg.read
while (ord(b) == 0xFF): b = jpeg.read(1)
if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
jpeg.read(3)
h, w = struct.unpack(">HH", jpeg.read(4))
break
else:
jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
b = jpeg.read(1)
width = int(w)
height = int(h)
except struct.error:
pass
except ValueError:
pass
return content_type, width, height
class ImageCropper(webapp.RequestHandler):
def post(self):
image = None
imageOBJ = None
adventure = None
left = None
top = None
right = None
bottom = None
newWidth = 0
newHeight = 0
imageKey = self.request.get('imageKey')
try:
left = float(self.request.get('left'))
top = float(self.request.get('top'))
newWidth = float(self.request.get('width'))
newHeight = float(self.request.get('height'))
right = newWidth + left
bottom = newHeight + top
except Exception, e:
logging.info('%s: %s' % (e.__class__.__name__, e))
self.error(404)
return
if imageKey:
logging.info("attempting to resize image with key " + imageKey)
try:
image = db.get(imageKey)
except Exception, e:
logging.info('%s: %s' % (e.__class__.__name__, e))
if image.imageData:
logging.info("got image data.")
adventure = image.adventure
imageOBJ = images.Image(image.imageData)
else:
self.error(404)
return
#make sure the user is logged in and owns this adventure and page
if users.get_current_user():
if not main.isUserAuthor(users.get_current_user(), adventure):
self.error(404)
return
else:
self.error(404)
return
#get the image dimensions
contentType, width, height = getImageInfo(image.imageData)
logging.info("left(" + str(left) + ") right(" + str(right) + ") top(" + str(top) + ") bottom(" + str(bottom) + ") width(" + str(width) + ") height(" + str(height) + ")")
#get the borders of the bounding box, as a proportion
left_x = left / width;
top_y = top / height;
right_x = right / width;
bottom_y = bottom / height;
logging.info("left_x(" + str(left_x) + ") right_x(" + str(right_x) + ") top_y(" + str(top_y) + ") bottom_y(" + str(bottom_y) + ")")
#now do the crop
imageOBJ.crop(left_x, top_y, right_x, bottom_y)
#if new width is over 300, resize it down to 300
#if new height is over 500, resize it down to 500
resizeW, resizeH, resizeBool = resizeImage(imageOBJ, newWidth, newHeight, 300, 500)
if resizeBool:
imageOBJ.resize(resizeW, resizeH)
#now execute the transforms
image.imageData = imageOBJ.execute_transforms()
image.put()
memcache.delete("img" + str(image.key()))
memcache.delete("images" + users.get_current_user().email())
self.response.out.write(simplejson.dumps('success'))
logging.info(simplejson.dumps('success'))
class ImageManager(webapp.RequestHandler):
def get(self):
#THIS CLASS IS CURRENTLY NOT BEING USED
adventure = None
error = None
if users.get_current_user():
pass
else:
url = users.create_login_url(self.request.uri)
url_linktext = 'Please login to use the StoryForge'
template_values = {
'url': url,
'url_linktext': url_linktext,
}
main.printHeader(self)
path = os.path.join(os.path.dirname(__file__), 'pleaseLogin.html')
self.response.out.write(template.render(path, template_values))
main.printFooter(self, None)
return
myAdventureKey = self.request.get('myAdventureKey')
if myAdventureKey:
adventure = main.getAdventure(myAdventureKey)
else:
error = 'error: no adventure key passed in'
if adventure == None:
error = 'error: could not find Adventure ' + myAdventureKey + ' in the database'
elif not main.isUserAuthor(users.get_current_user(), adventure):
logging.warning('ImageManager get: you are not an author of this adventure')
error = 'Error: You are not an author of this adventure'
adventure = None
template_values = {
'adventure': adventure,
'error': error
}
main.printHeader(self, 'Image Manager')
path = os.path.join(os.path.dirname(__file__), 'images.html')
self.response.out.write(template.render(path, template_values))
main.printFooter(self, template_values)
class ImageServer(webapp.RequestHandler):
def get(self, imageKey=None):
image = None
if imageKey == None:
imageKey = self.request.get('imageKey')
if imageKey:
if imageKey == 'aglpc3Rvcnl3ZWJyDAsSBUltYWdlGJATDA':
pass
else:
image = memcache.get("img" + imageKey)
if image:
logging.info("serving image from cache with key " + imageKey)
else:
hadException = False
try:
image = db.Model.get(imageKey)
except Exception, e:
hadException = True
logging.error('ImageServer db get exception: %s: %s' % (e.__class__.__name__, e))
#need to put an image in here that we can use for missing shit
image = self.getErrorImage()
if not hadException:
logging.info("serving image from db with key " + imageKey)
try:
memcache.add("img" + imageKey, image, 3600)
except Exception, e:
logging.error('ImageServer memcache add exception: %s: %s' % (e.__class__.__name__, e))
if not image:
image = self.getErrorImage()
if image.imageData:
self.response.headers['Content-Type'] = 'image/png'
self.response.out.write(image.imageData)
else:
self.error(404)
return
def getErrorImage(self):
image = memcache.get("img" + "unknown")
if image:
logging.info("serving unknown image from cache")
else:
image = db.Model.get('aglpc3Rvcnl3ZWJyDAsSBUltYWdlGNYUDA')
memcache.add("img" + "unknown", image, 86400)
logging.info("serving unknown image from db")
return image
class ImagesByUser(webapp.RequestHandler):
def getOrPost(self):
jsonArray = None
if users.get_current_user():
pass
else:
self.error(404)
return
#get all the images that they own
jsonArray = memcache.get("images" + users.get_current_user().email())
if jsonArray:
logging.info("ImagesByUser: serving %d images from cache" % len(jsonArray))
else:
imgQuery = adventureModel.Image.all()
imgQuery.filter('realAuthor = ', users.get_current_user())
imgQuery.order('imageName')
images = imgQuery.fetch(9999)
jsonArray = []
for image in images:
jsonArray.append(image.toDict())
logging.info("ImagesByUser: serving %d images from db" % len(jsonArray))
memcache.add("images" + users.get_current_user().email(), jsonArray, 3600)
self.response.out.write(simplejson.dumps(jsonArray))
#logging.info(simplejson.dumps(jsonArray))
def get(self):
self.getOrPost()
def post(self):
self.getOrPost()
class Uploader(webapp.RequestHandler):
def post(self):
logging.info("Uploader post start")
pageElement = None
adventure = None
page = None
newImage = None
myImageKey = self.request.get('imageRef')
myImageData = self.request.get('imageData')
myImageName = self.request.get('imageName') or 'No Name'
myPageElKey = self.request.get('myPageElKey')
myPageKey = self.request.get('myPageKey')
myPageOrder = int(self.request.get('myPageOrder') or -1)
logging.info("Uploader: myImageKey(" + myImageKey + ") myPageElKey(" + myPageElKey + ") myPageKey(" + myPageKey + ") order(" + str(myPageOrder) + ")")
if myImageData:
myImageSizeBytes = len(myImageData)
logging.info("GOT IMAGE DATA!! " + str(myImageSizeBytes) + ' bytes.')
if myImageSizeBytes > 1048576:
logging.info("ERROR: Image was too large(%d bytes). 1 megabyte is the max size." % (myImageSizeBytes))
self.response.out.write("ERROR: Image was too large. 1 megabyte is the max size.")
return
if not myImageData and not myPageElKey and not myImageKey:
self.error(404)
return
#try to get the existing page element, it might not exist
if myPageElKey:
pageElement = db.Model.get(myPageElKey)
if pageElement:
adventure = pageElement.adventure
page = pageElement.page
else:
self.error(404)
return
else:
#we create the page element that will reference the new image
logging.info('Uploader: creating new pageElement')
pageElement = adventureModel.PageElement()
pageElement.dataType = 2
pageElement.enabled = 1
pageElement.pageOrder = myPageOrder
if (not myPageKey):
logging.info("Uploader: expected myPageKey but it is null")
if myImageData:
self.response.out.write(simplejson.dumps(len(myImageData)))
#self.error(404)
return
page = db.Model.get(myPageKey)
adventure = page.adventure
if not page or not adventure:
self.error(404)
return
pageElement.adventure = adventure.key()
pageElement.page = page.key()
pageElement.put()
#figure out which image to use
#we might have a new page element and be trying to create it with an existing image (case 1)
#we might have an existing page element with an image already there (case 2)
#we need a new image (case 3)
#in cases 1 and 2, we're also renaming the image (if a new name was given)
if myImageKey:
newImage = db.Model.get(myImageKey)
elif pageElement.imageRef:
newImage = pageElement.imageRef
else:
newImage = adventureModel.Image()
if newImage.imageData and len(myImageData) > 100:
#if the existing image data is different from the new image data, we need to create a new image
#then set the image data, we dont need to set the image data if the old and new images are the same
if newImage.imageData != myImageData:
logging.info("the existing image data is different(" + str(len(myImageData)) + ").. lets create a new image")
newImage = adventureModel.Image()
newImage.imageData = db.Blob(myImageData)
elif len(myImageData) > 100:
#else its a new image, so save the image data from the form
newImage.imageData = db.Blob(myImageData)
if not page or not adventure:
self.error(404)
return
#make sure the user is logged in and owns this adventure and page
if users.get_current_user():
if not main.isUserAuthor(users.get_current_user(), adventure):
logging.warning('Uploader post: you are not an author of this adventure')
self.error(404)
return
else:
self.error(404)
return
#assign the pageElement reference
newImage.pageElement = str(pageElement.key())
#now we read the image data from the form and save the image into the DB
adventureStatus = main.getAdventure(adventure.adventureStatus)
if adventureStatus:
newImage.adventureStatus = adventureStatus
newImage.adventure = adventure.key()
newImage.realAuthor = users.get_current_user()
newImage.imageName = myImageName
logging.info("imageName(" + newImage.imageName + ")")
#last step- if the image is greater than 900 pixels in either dimension, resize it
if newImage.imageData:
imageOBJ = images.Image(newImage.imageData)
contentType, width, height = getImageInfo(newImage.imageData)
#keep resizing the image until it is below a megabyte
startResize = 900
counter = 1
while ((counter == 1 and (width > 900 or height > 900)) or len(newImage.imageData) > 999999) and counter < 10:
resizeW, resizeH, resizeBool = resizeImage(imageOBJ, width, height, startResize, startResize)
logging.info("resize try #%d: resizing image to %dx%d" % (counter, resizeW, resizeH))
imageOBJ.resize(resizeW, resizeH)
#now execute the transforms
newImage.imageData = imageOBJ.execute_transforms()
startResize = startResize - 50
counter = counter + 1
#finally save this image to the db
logging.info("saving image to db, size is %d bytes" % (len(newImage.imageData)))
newImage.put()
memcache.delete("img" + str(newImage.key()))
memcache.delete("images" + users.get_current_user().email())
#now link the image to the new page element
pageElement.imageRef = newImage.key()
pageElement.dataA = newImage.imageName
pageElement.put()
#send the json response, it includes the page element key
self.response.out.write(simplejson.dumps(newImage.toDict()))
#logging.info(simplejson.dumps(newImage.toDict()))