/
remoji.py
396 lines (364 loc) · 15.5 KB
/
remoji.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
#!/usr/bin/python
import os
import sys
import getopt
import math
from PIL import Image, ImageDraw
#import ImageDraw
import movieMaker
from multiprocessing import Process, Pool
import time
import preProc
import instructionWriter
#THIS FUNCITON RETURNS THE AVERAGE INTENSITY OF THE SUPPLIED COLOR STRING WITHIN THE IMAGE
def getAvgColor(imageFile, colorStr, skip = 5):
imgPixels = imageFile.load()
xi, yi = imageFile.size
intensityList = []
if colorStr is 'r':
for x in range(0, xi / skip):
for y in range(0, yi / skip):
if len(imgPixels[x,y]) == 2:
break
(r,g,b) = imgPixels[x*skip, y*skip]
intensityList.append(r)
if colorStr is 'g':
for x in range(0, xi / skip):
for y in range(0, yi / skip):
if len(imgPixels[x*skip,y*skip]) == 2:
break
(r,g,b) = imgPixels[x*skip, y*skip]
intensityList.append(g)
if colorStr is 'b':
for x in range(0, xi / skip):
for y in range(0, yi / skip):
if len(imgPixels[x*skip,y*skip]) == 2:
break
(r,g,b) = imgPixels[x*skip, y*skip]
intensityList.append(b)
avg = sum(intensityList) / sum( [1 for n in intensityList] )
return avg
#THIS FUNCTION RETURNS THE AVERAGE RED, GREEN, AND BLUE VALUES OF AN IMAGE IN A LIST
def getAvgRGB(imageFile, skip = 5):
r = getAvgColor(imageFile, 'r', skip)
g = getAvgColor(imageFile, 'g', skip)
b = getAvgColor(imageFile, 'b', skip)
return (r, g, b)
#THIS FUNCTION REMOVES TRANSPARENCY IN THE IMG LIBRARY BY TAKING EVERY IMAGE IN THE GIVEN DIRECTORY, FINDS PIXELS WITH AN ALPHA VALUE OF 0, AND CONVERTS THE RGBA VALUES TO (255,255,255) FOR EVERY ONE OF THOSE PIXELS, AND SAVES THE IMAGES TO THEIR ORIGINAL NAMES AS RGB PNGS RATHER THAN RGBA PNGS
def changeLittleImgs(dbDir):
littleImgNames = os.listdir(dbDir)
i = 0
for imgName in littleImgNames:
i += 1
print i, imgName
tempImgFile = Image.open(dbDir + imgName).convert('RGBA')
x, y = tempImgFile.size
tempPix = tempImgFile.load()
for xi in range(0, x):
for yi in range(0,y):
(r,g,b,a) = tempPix[xi,yi]
if a == 0:
tempPix[xi,yi] = (255,255,255,1)
tempImgFile.convert('RGB')
# THIS CHANGES THE NAME TO PNG BEFORE SAVING
split = imgName.split('.')
if split[-1] != 'png':
os.system('rm ' + dbDir + imgName.replace(' ', '\ '))
split[-1] = 'png'
imgName = '.'.join(split)
print 'resaving as ' + imgName
tempImgFile.save(dbDir + imgName)
#THIS CLASS HOLDS INFORMATION ABOUT AN IMAGE WITH A GIVEN NAME WITHOUT LEAVING THE IMAGE FILE OPEN
class ImageInfo:
def __init__(self, imgName, imageFile, skip = 5):
self.name = imgName
self.width, self.height = imageFile.size
self.avgRGB = getAvgRGB(imageFile, skip)
self.mult = 1
def disp(self):
print '----------------------'
print 'name: ', self.name
print 'width: ', self.width
print 'height: ', self.height
print 'avgRGB: ', self.avgRGB
print 'mult: ', self.mult
print '----------------------'
#MAKES AND RETURNS A LIST OF IMAGEINFO CLASSES OF EVERY EMOJI IN THE 'IMAGES/' DIRECTORY
def getLittleImgs(directory, skip = 5):
littleImgs = []
littleImgNames = os.listdir(directory)
for imgName in littleImgNames:
imgFile = Image.open(directory + imgName).convert('RGB')
try:
imgInfo = ImageInfo(imgName, imgFile, skip)
littleImgs.append(imgInfo)
except:
print 'skipping image: ' + imgName
return littleImgs
#DO LEAST SQUARES APPROXIMATION TO FIND THE IMAGE THAT HAS THE CLOSEST COLOR TO ORIGTILE
def getClosestImg(origTile, littleImgs, colorMap, lilImgDir, oneColor = False):
ogAvgRGB = ''
if oneColor == True:
ogAvgRGB = preProc.compressColor(origTile)
else:
ogAvgRGB = preProc.compressColor(origTile.avgRGB)
#SET MINSQUAREDIS TO JUST ABOVE THE MAXIMUM SQUARE DISTANCE OF ANY TWO IMAGES
minSquareDis = 3*(266*266)
closestImg = None
if colorMap != None:
if (ogAvgRGB, lilImgDir) in colorMap:
return colorMap[(ogAvgRGB, lilImgDir)]
else:
for littleImg in littleImgs:
if oneColor == False:
dis = (littleImg.avgRGB[0] - origTile.avgRGB[0])* (littleImg.avgRGB[0] - origTile.avgRGB[0]) + (littleImg.avgRGB[1] - origTile.avgRGB[1])*(littleImg.avgRGB[1] - origTile.avgRGB[1]) + (littleImg.avgRGB[2] - origTile.avgRGB[2])* (littleImg.avgRGB[2] - origTile.avgRGB[2])
else:
dis = (littleImg.avgRGB[0] - ogAvgRGB[0])* (littleImg.avgRGB[0] - ogAvgRGB[0]) + (littleImg.avgRGB[1] - ogAvgRGB[1])*(littleImg.avgRGB[1] - ogAvgRGB[1]) + (littleImg.avgRGB[2] - ogAvgRGB[2])* (littleImg.avgRGB[2] - ogAvgRGB[2])
if dis < minSquareDis:
minSquareDis = dis
closestImg = littleImg.name
colorMap[(ogAvgRGB, lilImgDir)] = closestImg
for c in ogAvgRGB:
if c > 255:
print 'Error: color value ', c, 'out of range. This will cause closestImg to be None. Check to make sure all values in the the instructions file are under 255.'
return closestImg
# IF COLORMAP == NONE, DONT KEEP A COLOR MAP DURING PIXEL ITERATION
else:
for littleImg in littleImgs:
if oneColor == False:
dis = (littleImg.avgRGB[0] - origTile.avgRGB[0])* (littleImg.avgRGB[0] - origTile.avgRGB[0]) + (littleImg.avgRGB[1] - origTile.avgRGB[1])*(littleImg.avgRGB[1] - origTile.avgRGB[1]) + (littleImg.avgRGB[2] - origTile.avgRGB[2])* (littleImg.avgRGB[2] - origTile.avgRGB[2])
else:
dis = (littleImg.avgRGB[0] - ogAvgRGB[0])* (littleImg.avgRGB[0] - ogAvgRGB[0]) + (littleImg.avgRGB[1] - ogAvgRGB[1])*(littleImg.avgRGB[1] - ogAvgRGB[1]) + (littleImg.avgRGB[2] - ogAvgRGB[2])* (littleImg.avgRGB[2] - ogAvgRGB[2])
if dis < minSquareDis:
minSquareDis = dis
closestImg = littleImg.name
return closestImg
#THIS FUNCTION FINDS THE SCALE AND IMAGE OF THE TILES IN NEWTILES
def getNewTilesGen(totalXSideImgs, totalYSideImgs, origTiles, littleImgs, colorMap, lilImgDir):
for y in range(totalYSideImgs):
for x in range(totalXSideImgs):
#newTile = getClosestImg(origTiles[y][x], littleImgs, colorMap, lilImgDir)
newTile = getClosestImg(next(origTiles), littleImgs, colorMap, lilImgDir)
yield newTile
#THIS FUNCTION CONSTRUCTS AND RETURNS THE FINAL MOSAIC IMAGE
def getFinalImg(totalXSideImgs, totalYSideImgs, xt, yt, xBuf, yBuf, newTiles, directory):
finalImg = Image.new('RGB', (int(xt), int(yt)), (0,0,0))
tileWidth = int(xt / totalXSideImgs)
tileHeight = int(yt / totalYSideImgs)
for y in xrange(totalYSideImgs):
for x in xrange(totalXSideImgs):
newImg = Image.open(directory + next(newTiles)).convert('RGB')
newImg = newImg.resize((tileWidth, tileHeight), Image.ANTIALIAS)
finalImg.paste(newImg, (x*tileWidth + xBuf, y*tileHeight + yBuf))
return finalImg
#GIVEN THE ORIGINAL IMAGE, SCALE, AND DEPTH (PIXEL WIDTH OF SINGLE MOSAIC TILE), THIS FUNCTION BUILDS AND SAVES THE FINAL IMAGE FROM START TO FINISH
def makeMosaic(targetImgName, scale, depth, littleImgs, outputName, colorMap, lilImgDir, origSkip = 5):
start = time.time()
print 'started: ' + outputName
targetImg = Image.open(targetImgName).convert('RGB')
if scale == 'autoScale':
[xt, yt] = [1920, 1080]
else:
[xt, yt] = [ l * scale for l in targetImg.size ]
[xi, yi] = targetImg.size
xt = int(xt)
yt = int(yt)
xn = depth
totalXSideImgs = int(xt) / int(xn)
yn = xn
totalYSideImgs = int(yt) / int(yn)
extraXPix = xt % totalXSideImgs
extraYPix = yt % totalYSideImgs
#XBUF AND YBUF ARE THE NUMBER OF PIXELS NECESSARY ON EITHER SIDE FOR CENTERING THE IMAGE
xBuf = extraXPix / 2
yBuf = extraYPix / 2
#INFLATE TARGET IMG TO FINAL SIZE
if scale == 'autoScale':
bigTargetImg = targetImg.resize((1920,1080), Image.ANTIALIAS)
else:
bigTargetImg = targetImg.resize(((int(xi*scale)), int(yi*scale)), Image.ANTIALIAS)
#BREAK TARGET IMG INTO SMALLER PIECES THAT WILL BE REPLACED BY ONE EMOJI AND PUT THEM INTO A LIST OF LISTS CORRESPONDING TO TILE POSITION
# origTiles = []
# for y in range(0, totalYSideImgs):
# origTiles.append([])
# for x in range(0, totalXSideImgs):
# tempImg = bigTargetImg.crop((x*xn + xBuf, y*yn + yBuf, (x+1)*xn - 1 + xBuf, (y+1)*yn - 1 + yBuf))
# origTiles[y].append(ImageInfo('tempTile', tempImg, origSkip))
origTiles = origTileGen(totalXSideImgs, totalYSideImgs, xn, yn, xBuf, yBuf, bigTargetImg, origSkip)
newTiles = getNewTilesGen(totalXSideImgs, totalYSideImgs, origTiles, littleImgs, colorMap, lilImgDir)
#MAKE NEW FRAME AND FILL IT OUT WITH TILES FORM NEWTILES. THIS FUNCTION RETURNS THE FINAL IMAGE
finalImg = getFinalImg(totalXSideImgs, totalYSideImgs, xt, yt, xBuf, yBuf, newTiles, lilImgDir)
finalImg.save(outputName)
print 'finished: ' + outputName
end = time.time()
print 'total time: ' + str(end - start)
return
# GENERATOR FOR BREAKING IMG INTO SMALLER PIECES THAT WILL BE REPLACED BY ONE EMOJI AND PUT INTO A LIST OF LISTS CORRESPONDING TO TILE POSITION
def origTileGen(totalXSideImgs, totalYSideImgs, xn, yn, xBuf, yBuf, bigTargetImg, origSkip):
for y in xrange(totalYSideImgs):
for x in range(totalXSideImgs):
tempImg = bigTargetImg.crop((x*xn + xBuf, y*yn + yBuf, (x+1)*xn - 1 + xBuf, (y+1)*yn - 1 + yBuf))
yield ImageInfo('tempTile', tempImg, origSkip)
#CONVERTS GIFFRAMES TO IMAGE FILES OF THE NAME <NAMESTR>0.PNG, <NAMESTR>1.PNG IN THE RELATIVE 'FRAMES/' DIRECTORY. IF AUTO IS TRUE, THIS FUNCTION AUTOMATICALLY FLOPS THE IMAGE TO FIT THE 16:9 ASPECT RATIO
def convertGif(inputGifName, framesDB, auto = False):
longInputGifName = 'gifs/' + inputGifName
totFrames = movieMaker.getTotFrames(longInputGifName)
if auto is False:
for i in range(0, totFrames):
os.system('convert ' + longInputGifName + '.gif[' + str(i) + '] ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + '.png')
else:
for i in range(0, totFrames):
os.system('convert ' + longInputGifName + '.gif[' + str(i) + '] ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'l.png')
os.system('convert -flop ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'l.png ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'r.png')
os.system('convert +append ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'l.png ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'r.png ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + '.png')
os.system('rm ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'l.png')
os.system('rm ' + framesDB + inputGifName + movieMaker.getFrameStr(i,3) + 'r.png')
unCroppedImg = Image.open(framesDB + inputGifName + movieMaker.getFrameStr(i,3) + '.png').convert('RGB')
[xi, yi] = unCroppedImg.size
xn = int( (yi*16) / 9.0 )
xBuf = (xi - xn) / 2
croppedImg = unCroppedImg.crop((xBuf, 0, xi - xBuf - 1, yi))
croppedImg.save(framesDB + inputGifName + movieMaker.getFrameStr(i,3) + '.png')
return totFrames
# Given a time in seconds, outputs a str in 'HH:MM:SS.00' format
def secondsToMinuteStr(seconds):
minutes = seconds / 60
extraSeconds = seconds % 60
minuteStr = movieMaker.getFrameStr(minutes, 2)
secondStr = movieMaker.getFrameStr(extraSeconds, 2)
outStr = '00:' + minuteStr + ':' + secondStr + '.00'
return outStr
#JUST LIKE CONVERT GIFS, BUT FOR MP4'S INSTEAD
#ffmpeg -ss 00:00:00 -t 00:00:05.00 -i "GOPR0621.MP4" -r 30.0 "stripes%4d.png"
def convertMp4(inputMp4Name, framesDB, secondsRange, auto = False):
longInputMp4Name = 'mp4s/' + inputMp4Name
totFrames = (secondsRange[1] - secondsRange[0]) * 30
# split video into frames if auto is false
if auto is False:
startStr = secondsToMinuteStr(secondsRange[0])
endStr = secondsToMinuteStr(secondsRange[1])
os.system('ffmpeg -ss ' + startStr + ' -t ' + endStr + ' -i "' + longInputMp4Name + '.MP4" -r 30.0 "' + framesDB + inputMp4Name + '%4d.png"')
return totFrames
#THIS FUNCTION EXTRACTS THE GIF FRAMES, MAKES, AND SAVES MOSAICS OF EACH FRAME AT THE GIVEN SCALE AND DEPTH INTO THE OUTPUTNAMESTR PATH
def makeLoopsFromFrames(inputDirectory, scale, littleImgs, outputNameStr):
inputFrameNames = os.listdir(inputDirectory)
inputFrameNames.sort()
pool = Pool(processes = 4)
for i in range(0, len(inputFrameNames)):
d = 0
for depthPix in range(6,46,2):
dStr = movieMaker.getFrameStr(d, 2) + '_'
if scale is 'autoScale':
pool.apply_async(makeMosaic, [inputDirectory + '/' + inputFrameNames[i], scale, depthPix, littleImgs, outputNameStr + dStr + movieMaker.getFrameStr(i,3) + '.png', 'arbitraryDir/'])
d += 1
pool.close()
pool.join()
def usage():
print ''
print ' $ python remoji.py -c <little image directory>'
print ''
print ' $ python remoji.py -s <starting image name> <little image directory> <output name> <scale (ratio)> <depth (pixels)>'
print ''
print ' $ python remoji.py -i <starting gif name> <little image directory> <frame directory> <mosaic directory> <autoScale (optional)>'
print ''
print ' $ python -a <movie directory> <instructions file> <output name>'
print ''
#MAIN FUNCTION
def main(argv = None):
if argv is None:
argv = sys.argv
try:
singleBool = False
changeColorBool = False
preProcBool = False
loadBool = False
mapBool = False
joinBool = False
mp4Bool = False
writeBool = False
opts, args = getopt.getopt(sys.argv[1:], 'hcsiapmljvw')
for opt, arg in opts:
if opt == '-h':
usage()
if opt == '-c':
print 'Adjusting colors of database directory images...'
changeColorBool = True
if opt == '-s':
singleBool = True
print 'Making mosaic of single image...'
if opt == '-m':
mapBool = True
print 'Making complete colorMap and writing it to a text file...'
if opt == '-p':
preProcBool = True
print 'Making directory of frames by preprocessing instructions file...'
if opt == '-v':
mp4Bool = True
print 'Using mp4 input'
#if opt == '-l':
# loadBool = True
# print 'Making directory of frames by preprocessing instructions file (colorMap file provided)...'
if opt == '-j':
joinBool = True
print 'Concatenating movie directories...'
if opt == '-w':
writeBool = True
print 'Writing instructions file from writer file'
#ARGS FOR INITIATING ANIMATION FRAMES
if changeColorBool is True:
dbDir = args[0]
changeLittleImgs(dbDir)
#ARGS FOR SINGLE IMAGE
if singleBool is True:
targetImgName = args[0]
littleImgDir = args[1]
outputName = args[2]
scale = float(args[3])
depth = int(args[4])
colorMap = {}
littleImgs = getLittleImgs(littleImgDir, skip = 1)
makeMosaic(targetImgName, scale, depth, littleImgs, outputName, colorMap, littleImgDir, origSkip = 1)
print outputName + ' saved'
if mapBool is True:
startTime = time.time()
lilImgDir = args[0]
mapFile = args[1]
a = ''
while((a != 'y') and (a != 'n')):
a = raw_input('Overwrite file at ' + mapFile + ' if it exists? [y/n]')
if a == 'y':
print 'Directory size: ', str(len(os.listdir(lilImgDir))) , 'images'
preProc.buildColorMap(lilImgDir, mapFile)
endTime = time.time()
print time.time() - startTime, 'seconds'
if preProcBool is True:
startTime = time.time()
movDir = args[0] #i.e. 'mov/'
instructionsFile = args[1] #i.e. 'instruct.txt'
outputName = 'anim' #i.e. 'animation'
startSec = int(args[2])
endSec = int(args[3])
secondsRange = (startSec, endSec)
movieMaker.wipeDir(movDir)
#if loadBool is True:
# mapFile = args[3]
# colorMap = preProc.loadMapFile(mapFile)
#TODO IMPLEMENT MP4BOOL NEW ARGS
# preProc.readFile(instructionsFile, movDir, outputName, colorMap, mp4Bool, secondsRange = secondsRange)
#else:
preProc.readFile(instructionsFile, movDir, outputName, secondsRange = secondsRange)
print time.time() - startTime, 'seconds'
if joinBool is True:
movDirs = args[:-1]
opMovDir = args[-1]
print movDirs, opMovDir
movieMaker.joinMovDirs(movDirs, opMovDir)
if writeBool is True:
writerFile = args[0]
insFile = args[1]
instructionWriter.writeInsFile(writerFile, insFile)
except getopt.error as err:
print str(err)
sys.exit(-1)
if __name__ == '__main__':
sys.exit(main())