Advertisement
Guest User

image mosaic script v3

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