Advertisement
Guest User

image mosaic script v6

a guest
Jan 16th, 2017
196
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.64 KB | None | 0 0
  1. ## This script requires Python 3.
  2. ## PIL (Python Imaging Library) is required to produce an output image. I use Pillow 3.4.0. Get it here: https://pypi.python.org/pypi/Pillow
  3. ## It takes a directory as its only argument and creates a mosaic image out of all the images in that folder, subfolders NOT included.
  4. ## Its output is a 'mosaic.jpg' in that same folder.
  5. ## If there already is a 'mosaic.jpg' in that folder, it will not be included in the mosaic, but the file will be overwritten with the new one without a confirmation prompt.
  6. ## Read the description of variables below.
  7. ## Usually only the adjustment of row_height and row_width is required.
  8. ## output_quality can be adjusted down from 100 to lower file sizes.
  9.  
  10. # pastebin link: http://pastebin.com/gP3UgLBC
  11.  
  12. import os
  13. import sys
  14. import subprocess as sub
  15. import random
  16. from PIL import Image
  17. from math import floor
  18. from copy import deepcopy
  19.  
  20. #give path as command line argument or just change it below
  21. if (len(sys.argv) > 1) and os.path.isdir(sys.argv[1]):
  22.     path = sys.argv[1]
  23. else:
  24.     path = "E:/Pictures/anime/Flip Flappers/Papika/n"
  25.  
  26. if not os.path.isdir(path):
  27.     print('Path "'+path+'" not found.')
  28.     sys.exit(0)
  29.  
  30. #force_aspect_ratio = 0: allow row heights to exceed row_height
  31. #force_aspect_ratio > 0: add padding between images in a row or between rows until desired aspect ratio (e.g. force_aspect_ratio = 16/9) is reached
  32. force_aspect_ratio = 0
  33.  
  34. random_pick = True      #the images in each row will likely be the same, but arranged in a different order if you run the script twice
  35. outputs = 1             #if >1 and random_pick=True, then multiple different mosaics will be created in a subfolder "mosaics"
  36.  
  37. padding_left = 2        #only on the left end of each row
  38. padding_right = 2       #only on the right end of each row
  39. padding_center = 3      #between images in a row; should not be too different from padding_left
  40. padding_top = 2         #top, bottom and between rows
  41.  
  42. row_height = 540 - padding_top
  43. # row_height = 1080 - padding_top
  44. row_width = 2*1920 + 960 - padding_left - padding_right   #final image width without padding
  45.  
  46. weight_accuracy = 1000  #determines with how much precision the aspect ratios of images will be handled
  47. output_quality = 95     #jpg quality of the output image
  48.  
  49. ############
  50. #http://stackoverflow.com/questions/7938809/dynamic-programming-linear-partitioning-please-help-grok/7942946#7942946
  51. ############
  52. from operator import itemgetter
  53.  
  54. def linear_partition(seq, k):
  55.     if k <= 0:
  56.         return []
  57.     n = len(seq) - 1
  58.     if k > n:
  59.         return map(lambda x: [x], seq)
  60.     table, solution = linear_partition_table(seq, k)
  61.     k, ans = k-2, []
  62.     while k >= 0:
  63.         ans = [[seq[i] for i in range(solution[n-1][k]+1, n+1)]] + ans
  64.         n, k = solution[n-1][k], k-1
  65.     return [[seq[i] for i in range(0, n+1)]] + ans
  66.  
  67. def linear_partition_table(seq, k):
  68.     n = len(seq)
  69.     table = [[0] * k for x in range(n)]
  70.     solution = [[0] * (k-1) for x in range(n-1)]
  71.     for i in range(n):
  72.         table[i][0] = seq[i] + (table[i-1][0] if i else 0)
  73.     for j in range(k):
  74.         table[0][j] = seq[0]
  75.     for i in range(1, n):
  76.         for j in range(1, k):
  77.             table[i][j], solution[i-1][j-1] = min(((max(table[x][j-1], table[i][0]-table[x][0]), x) for x in range(i)), key=itemgetter(0))
  78.     return (table, solution)
  79. ############
  80.  
  81.  
  82. Sol = {}
  83. scaled_sum = 0
  84. random.seed()
  85.  
  86. #info[f] = [width, height, scale]
  87. info = {}
  88.  
  89. #calculate image Sol
  90. print('calculating image weights')
  91. root, dirs, files = next(os.walk(path))
  92. for f in files:
  93.     if f.lower() == "mosaic.jpg": continue
  94.     if os.path.splitext(f)[1].lower() in [".jpeg", ".jpg", ".png", ".gif", ".tiff", ".webp"]:
  95.         try:
  96.             image = Image.open(os.path.join(root, f))
  97.             width, height = image.size
  98.             info[f] = [width, height, 1]
  99.         except:
  100.             print('Error while opening "'+f+'"')
  101.     else:
  102.         # print("unknown: ", f)
  103.         continue
  104.  
  105.     #padding_center is included in the aspect ratios here, because the linear partition solution has to consider the sum of all pixels in a row.
  106.     #Otherwise the rows with more images would receive more padding, resulting in wider rows.
  107.     #Since it's added for every image, it's added once too much for every row. In order to do it right, the final placement would have to be known beforehand.
  108.     #I'm assuming here that padding_left and padding_right aren't too different from padding_center, so the error compared to the entire row_width should be hardly noticeable.
  109.     asp = round(weight_accuracy * float(width + padding_center) / float(height))
  110.     info[f][2] = row_height / (height + padding_top)
  111.     scaled_sum += info[f][2] * width
  112.  
  113.     if asp in Sol:
  114.         Sol[asp].append(f)
  115.     else:
  116.         Sol[asp] = [f]
  117.  
  118.  
  119. #calculate number of rows
  120. #images scaled to row height -> sum of scaled widths / row_height
  121. k = round(scaled_sum / row_width)
  122.  
  123. seq = []
  124. for w in Sol:
  125.     for i in range(len(Sol[w])):
  126.         seq.append(w)
  127. print('fitting '+str(len(seq))+' images into '+str(k)+' rows')
  128. base_solution = linear_partition(seq, k)
  129.  
  130.  
  131. if not random_pick: outputs = 1
  132. mosaic_solutions = []
  133.  
  134. while outputs > 0:
  135.     outputs -= 1
  136.  
  137.     print('calculating final image size')
  138.     # {row: [(file1, sizex, sizey, posx, posy), (file2, sizex, sizey, posx, posy), ...]}
  139.     mosaic_solution = {}
  140.     mosaic_width, mosaic_height = (row_width + padding_left + padding_right, row_height + padding_top)
  141.     random.seed()
  142.     S = deepcopy(Sol)
  143.     solution = deepcopy(base_solution)
  144.  
  145.     #current "cursor" position
  146.     pos = [padding_left, padding_top]
  147.     for row in range(len(solution)):
  148.         random.seed()
  149.         random.shuffle(solution[row])
  150.  
  151.         mosaic_solution[row] = []
  152.  
  153.         #rescale the row to fit target row_width
  154.         row_scale = 1
  155.         solution_width = padding_left
  156.         temp_S = {}
  157.         for col in range(len(solution[row])):
  158.             f = S[solution[row][col]].pop(0)
  159.             if solution[row][col] in temp_S:
  160.                 temp_S[solution[row][col]].append(f)
  161.             else:
  162.                 temp_S[solution[row][col]] = [f]
  163.  
  164.             width, height, scale = info[f][0:3]
  165.             solution_width += floor(scale*width) + padding_center
  166.  
  167.         row_scale = row_width / solution_width
  168.  
  169.         for w in temp_S:
  170.             for f in temp_S[w]:
  171.                 S[w].insert(0, f)
  172.  
  173.         for col in range(len(solution[row])):
  174.             #desired weight = solution[row][col]
  175.             if random_pick:
  176.                 random.seed()
  177.                 random.shuffle(S[solution[row][col]])
  178.                 f = S[solution[row][col]].pop(0)
  179.             else:
  180.                 f = S[solution[row][col]].pop(0)
  181.                 if len(S[solution[row][col]]) == 0: S.pop(solution[row][col])
  182.  
  183.             width, height, scale = info[f][0:3]
  184.            
  185.             mosaic_solution[row].append([f, floor(row_scale*scale*width), floor(row_scale*scale*height), pos[0], pos[1]])
  186.             pos[0] += floor(row_scale*scale*width) + padding_center
  187.             if pos[0] > mosaic_width: mosaic_width = pos[0]
  188.        
  189.         pos[0] = padding_left
  190.         pos[1] += floor(row_scale*row_height) + padding_top
  191.         if pos[1] > mosaic_height: mosaic_height = pos[1]
  192.  
  193.     if force_aspect_ratio > 0:
  194.         #mosaic height is not guaranteed to be the desired height
  195.         #calculate new image width from desired aspect ratio
  196.         print('refitting into aspect ratio: '+str(force_aspect_ratio))
  197.  
  198.         #if mosaic is too high -> more padding between images
  199.         if force_aspect_ratio > float(mosaic_width / mosaic_height):
  200.             new_mosaic_width = round(mosaic_height * force_aspect_ratio)
  201.             new_padding = new_mosaic_width - mosaic_width - padding_left - padding_right
  202.             mosaic_width = new_mosaic_width
  203.            
  204.             #reposition images, distribute padding equally *between* the pictures, leave outer padding
  205.             print("new padding: "+str(new_padding))
  206.             for row in range(len(mosaic_solution)):
  207.                 if len(mosaic_solution[row]) == 0: next
  208.                 new_row_padding = round(new_padding / (len(mosaic_solution[row]) - 1))
  209.                 print("new row padding: "+str(new_row_padding))
  210.                 for col in range(len(mosaic_solution[row])):
  211.                     mosaic_solution[row][col][3] += new_row_padding * col
  212.  
  213.         #if mosaic is too wide -> more padding between rows
  214.         else:
  215.             new_mosaic_height = round(mosaic_width / force_aspect_ratio)
  216.             new_padding = new_mosaic_height - mosaic_height - padding_top - padding_top
  217.             mosaic_height = new_mosaic_height
  218.            
  219.             #reposition images, distribute padding equally between the rows, leave top and bottom padding
  220.             print("new padding: "+str(new_padding))
  221.             for row in range(len(mosaic_solution)):
  222.                 if len(mosaic_solution[row]) == 0: next
  223.                 new_row_padding = round(new_padding / (len(mosaic_solution) - 1))
  224.                 print("new row padding: "+str(new_row_padding))
  225.                 for col in range(len(mosaic_solution[row])):
  226.                     mosaic_solution[row][col][4] += new_row_padding * row
  227.  
  228.     mosaic_solutions.append(mosaic_solution)
  229.  
  230. if len(mosaic_solutions) > 1 and not os.path.isdir(os.path.join(path, 'mosaics')):
  231.     os.mkdir(os.path.join(path, 'mosaics'))
  232.  
  233. for s in range(len(mosaic_solutions)):
  234.     if len(mosaic_solutions) > 1:
  235.         print('\ncreating mosaics/mosaic'+str(s+1)+'.jpg')
  236.         mosaic_fname = os.path.join(path, 'mosaics', 'mosaic'+str(s+1)+'.jpg')
  237.     else:
  238.         print('creating mosaic.jpg\n')
  239.         mosaic_fname = os.path.join(path, 'mosaic.jpg')
  240.  
  241.     mosaic = Image.new("RGB", (mosaic_width, mosaic_height), color=0)
  242.     mosaic_solution = mosaic_solutions[s]
  243.     for row in range(len(mosaic_solution)):
  244.         for col in range(len(mosaic_solution[row])):
  245.             f, sizex, sizey, posx, posy = mosaic_solution[row][col]
  246.            
  247.             temp_image = Image.open(os.path.join(root, f))
  248.             #rescale if necessary
  249.             if sizex != temp_image.size[0]:
  250.                 temp_image = temp_image.resize((sizex, sizey), resample = Image.ANTIALIAS)
  251.  
  252.             #place the image into the mosaic
  253.             mosaic.paste(temp_image, (posx, posy))
  254.  
  255.     print('mosaic dimensions:', str(mosaic.size))
  256.     mosaic.save(mosaic_fname, quality=output_quality)
  257.  
  258.     print('mosaic file size:', "{:.2f}".format(os.path.getsize(mosaic_fname)/1024/1024) + "MB")
  259.    
  260.     if len(mosaic_solutions) == 1:
  261.         # mosaic.show()
  262.         sub.Popen('"'+mosaic_fname+'"', shell=True)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement