Advertisement
Guest User

image mosaic script

a guest
May 5th, 2016
122
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.52 KB | None | 0 0
  1. #This script takes a directory as its only argument and creates a mosaic image out of all the images in that folder, subfolders not included.
  2. #Its output is a 'mosaic.jpg' in that same folder.
  3. #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.
  4. #Read the description of variables below.
  5.  
  6. import os
  7. import sys
  8. import subprocess as sub
  9. import random
  10. from PIL import Image
  11. from math import floor
  12.  
  13. #give path as command line argument or just change it below
  14. if (len(sys.argv) > 1) and os.path.isdir(sys.argv[1]):
  15.     path = sys.argv[1]
  16. else:
  17.     path = "E:/Pictures/anime/Anne Happy/Hanakoizumi Anne/smiles"
  18.  
  19. #force_aspect_ratio = 0: allow row heights to exceed row_height
  20. #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
  21. force_aspect_ratio = 0
  22.  
  23. 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
  24. padding_left = 2    #only on the left end of each row
  25. padding_right = 2   #only on the right end of each row
  26. padding_center = 3  #between images in a row
  27. padding_top = 2     #top, bottom and between rows
  28.  
  29. row_height = 480 - padding_top
  30. row_width = 3840 - padding_left - padding_right
  31. output_quality = 100    #jpg quality of the output image
  32.  
  33. weight_accuracy = 1000
  34.  
  35. ############
  36. #http://stackoverflow.com/questions/7938809/dynamic-programming-linear-partitioning-please-help-grok/7942946#7942946
  37. ############
  38. from operator import itemgetter
  39.  
  40. def linear_partition(seq, k):
  41.     if k <= 0:
  42.         return []
  43.     n = len(seq) - 1
  44.     if k > n:
  45.         return map(lambda x: [x], seq)
  46.     table, solution = linear_partition_table(seq, k)
  47.     k, ans = k-2, []
  48.     while k >= 0:
  49.         ans = [[seq[i] for i in range(solution[n-1][k]+1, n+1)]] + ans
  50.         n, k = solution[n-1][k], k-1
  51.     return [[seq[i] for i in range(0, n+1)]] + ans
  52.  
  53. def linear_partition_table(seq, k):
  54.     n = len(seq)
  55.     table = [[0] * k for x in range(n)]
  56.     solution = [[0] * (k-1) for x in range(n-1)]
  57.     for i in range(n):
  58.         table[i][0] = seq[i] + (table[i-1][0] if i else 0)
  59.     for j in range(k):
  60.         table[0][j] = seq[0]
  61.     for i in range(1, n):
  62.         for j in range(1, k):
  63.             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)),
  64.                                                key=itemgetter(0))
  65.     return (table, solution)
  66. ############
  67.  
  68.  
  69. S = {}
  70. scaled_sum = 0
  71. random.seed()
  72.  
  73. #info[f] = [width, height, scale]
  74. info = {}
  75.  
  76. #calculate image S
  77. print('calculating image weights')
  78. root, dirs, files = next(os.walk(path))
  79. for f in files:
  80.     if os.path.splitext(f)[1].lower() in [".jpeg", ".jpg", ".png", ".gif", ".tiff", ".webp"]:
  81.         if f.lower() == "mosaic.jpg": continue
  82.         try:
  83.             image = Image.open(os.path.join(root, f))
  84.             width, height = image.size
  85.             info[f] = [width, height, 1]
  86.         except:
  87.             print('Error while opening "'+f+'"')
  88.     else:
  89.         # print("unknown: ", f)
  90.         continue
  91.  
  92.     # print(f, str(width), str(height))
  93.     asp = round(weight_accuracy * float(width + padding_left) / float(height))
  94.     info[f][2] = row_height / (height + padding_top)
  95.     scaled_sum += info[f][2] * width
  96.  
  97.     if asp in S:
  98.         S[asp].append(f)
  99.     else:
  100.         S[asp] = [f]
  101.  
  102.  
  103. #calculate number of rows
  104. #images scaled to row height -> sum of scaled widths / row_height
  105. k = round(scaled_sum / row_width)
  106.  
  107. # print(scaled_sum)
  108. # print(k)
  109. # print(S)
  110.  
  111. seq = []
  112. for w in S:
  113.     for i in range(len(S[w])):
  114.         seq.append(w)
  115. print('fitting '+str(len(seq))+' images/videos into '+str(k)+' rows')
  116. solution = linear_partition(seq, k)
  117.  
  118.  
  119.  
  120. print('calculating final image size')
  121. # {row: [(file1, sizex, sizey, posx, posy), (file2, sizex, sizey, posx, posy), ...]}
  122. mosaic_solution = {}
  123. mosaic_fname = os.path.join(path, "mosaic.jpg")
  124. mosaic_width, mosaic_height = (row_width + padding_left + padding_right, row_height + padding_top)
  125.  
  126. #current "cursor" position
  127. pos = [padding_left, padding_top]
  128. for row in range(len(solution)):
  129.     mosaic_solution[row] = []
  130.  
  131.     #rescale the row to fit target row_width
  132.     row_scale = 1
  133.     solution_width = padding_left
  134.     temp_S = {}
  135.     for col in range(len(solution[row])):
  136.         f = S[solution[row][col]].pop(0)
  137.         if solution[row][col] in temp_S:
  138.             temp_S[solution[row][col]].append(f)
  139.         else:
  140.             temp_S[solution[row][col]] = [f]
  141.  
  142.         width, height, scale = info[f][0:3]
  143.         solution_width += floor(scale*width) + padding_center
  144.  
  145.     row_scale = row_width / solution_width
  146.     # print(solution_width, row_scale)
  147.  
  148.     for w in temp_S:
  149.         for f in temp_S[w]:
  150.             S[w].insert(0, f)
  151.     # print('S',S)
  152.     # print('solution',solution)
  153.  
  154.     for col in range(len(solution[row])):
  155.         #desired weight = solution[row][col]
  156.         if random_pick:
  157.             # index = random.randint(0, len(S[solution[row][col]]) - 1)
  158.             random.shuffle(solution[row])
  159.             f = S[solution[row][0]].pop(0)
  160.             # print(solution[row])
  161.             # print(S[solution[row][0]])
  162.            
  163.             #if last image with that weight was taken
  164.             if len(S[solution[row][0]]) == 0:
  165.                 solution[row].pop(0)
  166.         else:
  167.             f = S[solution[row][col]].pop(0)
  168.             if len(S[solution[row][col]]) == 0: S.pop(solution[row][col])
  169.  
  170.         width, height, scale = info[f][0:3]
  171.        
  172.         mosaic_solution[row].append([f, floor(row_scale*scale*width), floor(row_scale*scale*height), pos[0], pos[1]])
  173.         pos[0] += floor(row_scale*scale*width) + padding_center
  174.         if pos[0] > mosaic_width: mosaic_width = pos[0]
  175.    
  176.     pos[0] = padding_center
  177.     pos[1] += floor(row_scale*row_height) + padding_top
  178.     if pos[1] > mosaic_height: mosaic_height = pos[1]
  179.  
  180. if force_aspect_ratio > 0:
  181.     #mosaic height is not guaranteed to be the desired height
  182.     #calculate new image width from desired aspect ratio
  183.     print('refitting into aspect ratio: '+str(force_aspect_ratio))
  184.  
  185.     #if mosaic is too high -> more padding between images
  186.     if force_aspect_ratio > float(mosaic_width / mosaic_height):
  187.         new_mosaic_width = round(mosaic_height * force_aspect_ratio)
  188.         new_padding = new_mosaic_width - mosaic_width - padding_left - padding_right
  189.         mosaic_width = new_mosaic_width
  190.        
  191.         #reposition images, distribute padding equally *between* the pictures, leave outer padding
  192.         print("new padding: "+str(new_padding))
  193.         for row in range(len(mosaic_solution)):
  194.             if len(mosaic_solution[row]) == 0: next
  195.             new_row_padding = round(new_padding / (len(mosaic_solution[row]) - 1))
  196.             print("new row padding: "+str(new_row_padding))
  197.             for col in range(len(mosaic_solution[row])):
  198.                 mosaic_solution[row][col][3] += new_row_padding * col
  199.  
  200.     #if mosaic is too wide -> more padding between rows
  201.     else:
  202.         new_mosaic_height = round(mosaic_width / force_aspect_ratio)
  203.         new_padding = new_mosaic_height - mosaic_height - padding_top - padding_top
  204.         mosaic_height = new_mosaic_height
  205.        
  206.         #reposition images, distribute padding equally between the rows, leave top and bottom padding
  207.         print("new padding: "+str(new_padding))
  208.         for row in range(len(mosaic_solution)):
  209.             if len(mosaic_solution[row]) == 0: next
  210.             new_row_padding = round(new_padding / (len(mosaic_solution) - 1))
  211.             print("new row padding: "+str(new_row_padding))
  212.             for col in range(len(mosaic_solution[row])):
  213.                 mosaic_solution[row][col][4] += new_row_padding * row
  214.  
  215.  
  216. print('creating mosaic.jpg\n')
  217. mosaic = Image.new("RGB", (mosaic_width, mosaic_height), color=0)
  218. for row in range(len(mosaic_solution)):
  219.     for col in range(len(mosaic_solution[row])):
  220.         f, sizex, sizey, posx, posy = mosaic_solution[row][col]
  221.        
  222.         temp_image = Image.open(os.path.join(root, f))
  223.         #rescale if necessary
  224.         if sizex != temp_image.size[0]:
  225.             temp_image = temp_image.resize((sizex, sizey), resample = Image.BICUBIC)
  226.  
  227.         #place the image into the mosaic
  228.         mosaic.paste(temp_image, (posx, posy))
  229.        
  230. mosaic.save(mosaic_fname, quality=output_quality)
  231. # mosaic.show()
  232. sub.Popen(mosaic_fname, shell=True)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement