1+ """
2+ Functions for building movie grid image.
3+ To build call build(list[MovieCell], username: str)
4+ """
5+
6+ from PIL import Image , ImageDraw , ImageFont
7+ from grid_shape import get_grid_size
8+ import json
9+ from functools import partial
10+ import datetime
11+
12+ def trans_paste (fg_img , bg_img , alpha = 1.0 , box = (0 , 0 )):
13+ fg_img_trans = Image .new ("RGBA" , fg_img .size )
14+ fg_img_trans = Image .blend (fg_img_trans ,fg_img , alpha )
15+ bg_img .paste (fg_img_trans ,box ,fg_img_trans )
16+ return bg_img
17+
18+ def resize_image (im : Image , w_factor : float , h_factor : float ) -> Image :
19+ new_size = (int (im .size [0 ] * w_factor ), int (im .size [1 ] * h_factor ))
20+ return im .resize (size = new_size )
21+
22+ def build_thumbnail (cell : "MovieCell" , resize_factor : float ) -> Image :
23+ return resize_image (Image .open (cell .im_path ), resize_factor , resize_factor )
24+
25+ def build_background (thumbnail_width : int , thumbnail_height : int ,
26+ grid_width : int , grid_height : int , text_width : int ,
27+ username_box_height : int , image_gap : int ) -> Image :
28+ return Image .new (
29+ mode = 'RGBA' ,
30+ size = (thumbnail_width * grid_width + image_gap * (grid_width + 1 ) + text_width ,
31+ thumbnail_height * grid_height + image_gap * (grid_height + 1 ) + username_box_height ),
32+ color = (50 , 50 , 50 ))
33+
34+ def get_max_text_size (text_drawer : ImageDraw , font : ImageFont , text_list : list ) -> int :
35+ WIDTH_LIMIT = 500
36+ max_width = - 1
37+ for text in text_list :
38+ max_width = max (text_drawer .textsize (text , font ))
39+ return min (max_width , WIDTH_LIMIT )
40+
41+ def build_movie_text (movie_cell : "MovieCell" ) -> str :
42+ mv_text = f'{ movie_cell .title } - { movie_cell .director } '
43+ if len (mv_text ) > 68 :
44+ director = movie_cell .director .split (' ' )
45+ director [- 1 ] = director [- 1 ][0 ]
46+ mv_text = f'{ movie_cell .title } - { ' ' .join (map (str , director )) } '
47+ return mv_text
48+
49+ def get_text_dimensions (text_string : str , font : ImageFont .truetype ):
50+ # https://stackoverflow.com/a/46220683/9263761
51+ _ , descent = font .getmetrics ()
52+
53+ text_width = font .getmask (text_string ).getbbox ()[2 ]
54+ text_height = font .getmask (text_string ).getbbox ()[3 ] + descent
55+
56+ return (text_width , text_height )
57+
58+ def load_config (path : str ) -> list :
59+ config : dict
60+ with open (path , 'r' ) as f :
61+ config = json .load (f )
62+ return (
63+ config ['resize_factor' ],
64+ config ['image_gap' ],
65+ config ['info_box_width' ],
66+ config ['username_box_height' ],
67+ config ['movie_info_font_size' ],
68+ config ['username_font_size' ],
69+ config ['font_color' ]
70+ )
71+
72+
73+ def build (movie_cells : list ["MovieCell" ], username : str , config_path : str ) -> Image .Image :
74+
75+ # loading config file
76+ resize_factor , image_gap , info_box_width , \
77+ username_box_height , movie_info_font_size , \
78+ username_font_size , font_color = load_config (config_path )
79+
80+ # create dynamically sized grid
81+ grid_width , grid_height = get_grid_size (len (movie_cells ))
82+
83+ # creating thumbnails
84+ thumbnails = list (map (partial (build_thumbnail , resize_factor = resize_factor ), movie_cells ))
85+ thumb_width , thumb_height = thumbnails [0 ].size
86+
87+ # create background
88+ bg = build_background (thumb_width , thumb_height , grid_width , grid_height ,
89+ info_box_width , username_box_height , image_gap )
90+
91+ # defining fonts
92+ info_font = ImageFont .truetype ('./font/JuliaMono-Bold.ttf' , movie_info_font_size , encoding = 'utf-8' )
93+ username_font = ImageFont .truetype ('./font/JuliaMono-Bold.ttf' , username_font_size , encoding = 'utf-8' )
94+ text_drawer = ImageDraw .Draw (bg )
95+
96+ # writing username and date to image
97+ my_date = datetime .datetime .now ()
98+ username_str = f'{ username } - { my_date .strftime ("%B" )} { my_date .strftime ("%Y" )} '
99+ username_width , username_height = get_text_dimensions (username_str , username_font )
100+ username_x = bg ._size [0 ]// 2 - username_width // 2
101+ username_y = username_box_height // 2 - username_height // 2
102+
103+ text_drawer .text ((username_x , username_y ), username_str , font = username_font ,fill = tuple (font_color ))
104+
105+ cell_index = 0
106+
107+ # paste thumbnails, text, and stars to background
108+ for i in range (grid_width ):
109+ for j in range (grid_height ):
110+ if cell_index >= len (movie_cells ): break
111+
112+ # thumbnails
113+ im_x = i * thumb_width + image_gap * (i + 1 )
114+ im_y = j * thumb_height + image_gap * (j + 1 ) + username_box_height
115+
116+ bg .paste (thumbnails [cell_index ], (im_x , im_y ))
117+
118+ # text
119+ txt_x = grid_width * thumb_width + image_gap * (grid_width + 1 )
120+ txt_y = (j % grid_width ) * thumb_height + image_gap * ((j % grid_width ) + 1 ) + (i * 20 ) + username_box_height
121+
122+ txt_str = build_movie_text (movie_cells [cell_index ])
123+ print (f'{ txt_str } -> { len (txt_str )} ' )
124+
125+ text_drawer .text ((txt_x , txt_y ), txt_str , font = info_font , fill = tuple (font_color ))
126+
127+ # bg.paste(star, (im_x, im_y + star.size[1] - star.size[1]))
128+
129+ # bg = trans_paste(star, bg, alpha=1.0, box=(im_x, im_y + star.size[1] - star.size[1]))
130+ cell_index += 1
131+
132+ return bg
0 commit comments