@@ -26,7 +26,7 @@ class MeetupImageGenerator:
2626 - data (YYYY MM DD) w żółtym akcencie + poniżej "DZIEŃ, HH:MM"
2727 - prelegenci: okrągłe avatary z cienkim ringiem; nazwisko + tytuł POD avatarem
2828 - stopka bez paska:
29- * lewy dół: skośny blok CG tylko z adresem " pythonlodz.org"
29+ * lewy dół: skośny blok CG z "Meetup #<id>" i " pythonlodz.org" (z ikoną linku)
3030 * środek: lokalizacja (z pinem)
3131 """
3232
@@ -91,6 +91,7 @@ def generate_featured_image(
9191 "footer_font" : preset ["footer_font" ],
9292 "icon_link" : preset ["icon_link" ],
9393 "icon_pin" : preset ["icon_pin" ],
94+ "link_enabled" : aspect_ratio == "16x9" ,
9495 }
9596 if options :
9697 opt .update (options )
@@ -186,7 +187,7 @@ def generate_featured_image(
186187 # stopka
187188 site_text = getattr (meetup , "website" , None ) or "pythonlodz.org"
188189 self ._draw_corner_footer (
189- img , draw , site_text , meetup .location_name (language ), meetup_id , opt
190+ img , draw , site_text , meetup .location_name (language ), opt
190191 )
191192
192193 output_path .parent .mkdir (parents = True , exist_ok = True )
@@ -235,7 +236,7 @@ def _aspect_preset(self, aspect: str) -> dict:
235236 }
236237 if aspect == "1x1" :
237238 return {
238- "safe_margin" : 120 , # więcej miejsca od góry (nagłówek dalej od wstęgi)
239+ "safe_margin" : 90 , # więcej miejsca od góry i od dołu
239240 "max_content_width" : 980 ,
240241 "date_block_max_width_ratio" : 0.90 ,
241242 "duo_mode" : "stack" ,
@@ -291,14 +292,13 @@ def _draw_header_centered(
291292 shadow_color = (0 , 0 , 0 , 90 ),
292293 )
293294
294- # ---------------- ribbon ----------------
295295 def _draw_top_right_ribbon_en (
296296 self , img : Image .Image , opt : dict , label : str = "ENGLISH EDITION"
297297 ):
298298 """
299299 Ukośna wstęga w prawym górnym rogu:
300- - kąt zgodny z dolnym trójkątem (stałe nachylenie ~0.406 ),
301- - od krawędzi do krawędzi z nadmiarem,
300+ - równoległa do krawędzi trójkąta stopki (atan2(0.26*H, 0.36*W) ),
301+ - od krawędzi do krawędzi z nadmiarem (brak szczelin) ,
302302 - flaga + napis wycentrowane na osi wstęgi.
303303 """
304304 W , H = img .width , img .height
@@ -308,7 +308,7 @@ def _draw_top_right_ribbon_en(
308308 cos_t , sin_t = math .cos (theta ), math .sin (theta )
309309
310310 # odległości punktów końcowych na krawędziach
311- m = int (0.28 * W ) # po górnej krawędzi od prawej
311+ m = int (0.33 * W ) # po górnej krawędzi od prawej
312312 n = int (m * math .tan (theta )) # w dół po prawej krawędzi
313313
314314 thickness = 72
@@ -360,7 +360,7 @@ def add(p, dx, dy):
360360 cd = ImageDraw .Draw (content )
361361
362362 flag_img = None
363- flag_h = min (box_h - 10 , 40 )
363+ flag_h = min (box_h - 10 , 35 )
364364 flag_w = flag_h
365365 if self .icon_flag_gb and self .icon_flag_gb .exists ():
366366 try :
@@ -377,11 +377,11 @@ def add(p, dx, dy):
377377 gap = 12 if flag_img else 0
378378 total_w = label_w + (flag_w + gap if flag_img else 0 )
379379
380- x0 = max (0 , (box_w - total_w ) // 2 )
380+ x0 = max (0 , (box_w - total_w - 2 ) // 2 )
381381 y0 = (box_h - cd .textbbox ((0 , 0 ), label , font = font )[3 ]) // 2
382382
383383 if flag_img :
384- content .alpha_composite (flag_img , (x0 , (box_h - flag_h ) // 2 ))
384+ content .alpha_composite (flag_img , (x0 , (box_h - flag_h + 10 ) // 2 ))
385385 x0 += flag_w + gap
386386
387387 cd .text ((x0 , y0 + 2 ), label , font = font , fill = (255 , 255 , 255 , 90 ))
@@ -396,55 +396,63 @@ def add(p, dx, dy):
396396 img .alpha_composite (ribbon )
397397 img .alpha_composite (content_rot , (cx , cy ))
398398
399- # ---------------- footer ----------------
400399 def _draw_corner_footer (
401400 self ,
402401 img : Image .Image ,
403402 draw : ImageDraw .ImageDraw ,
404403 site_text : str ,
405404 place_text : str ,
406- meetup_id : str | int ,
407405 opt : dict ,
408406 ):
409407 """
410408 Skośny trójkąt w lewym dolnym rogu + lokalizacja na środku.
411- - trójkąt zawiera tylko adres strony (bez "Meetup #<id>" i bez ikony linku)
412- - kąt trójkąta stały, zgodny z wstęgą EN
409+ Kąt trójkąta jest zgodny z 16x9 niezależnie od proporcji.
413410 """
414411 W , H = img .width , img .height
415412 pad = opt ["safe_margin" ]
416413
417- tri_slope = 0.406 # spójny z wstęgą
414+ # bazowa szerokość, wysokość wynikająca ze stałego nachylenia ~0.406
415+ base_block_w = int (W * 0.36 * 0.85 )
416+ tri_slope = 0.406 # pochylanie dopasowane do projektu 16x9
417+ base_block_h = int (base_block_w * tri_slope )
418+
418419 font_site = self ._load_font (self .font_normal , opt ["footer_font" ])
419420
420421 site_bbox = draw .textbbox ((0 , 0 ), site_text , font = font_site )
422+
421423 site_w , site_h = site_bbox [2 ], site_bbox [3 ]
422424
423- text_margin_x = 74
425+ text_gap = 8
426+ text_margin_x = 54
424427 inner_pad = 30
428+ meet_w = 100
429+ meet_h = 30
430+ req_w = max (meet_w , site_w ) + text_margin_x * 2
431+ req_h = meet_h + text_gap + site_h + inner_pad * 2
425432
426- base_block_w = int (W * 0.36 * 0.85 )
427- req_w = site_w + text_margin_x * 2
428433 block_w = max (base_block_w , req_w )
429- block_h = int ( block_w * tri_slope )
434+ block_h = max ( base_block_h , req_h )
430435
431436 poly = [(0 , H ), (0 , H - block_h ), (block_w , H )]
432437 draw .polygon (poly , fill = self .CG )
433438
434439 m = block_h / float (block_w ) if block_w else 0.0
435-
436- total_text_h = site_h
440+ total_text_h = meet_h + text_gap + site_h
437441 bottom_anchor_y = H - inner_pad - total_text_h
438442 y_top_inside = (H - block_h ) + m * text_margin_x + inner_pad
439443 start_y = int (max (y_top_inside , bottom_anchor_y ))
440444 start_x = text_margin_x
441445
442- draw .text (
443- (start_x , start_y ),
444- site_text ,
445- fill = "#E8EEF5" ,
446- font = font_site ,
447- )
446+ # dolna linia: ikona linku + adres
447+ site_y = start_y + text_gap
448+ text_x = start_x
449+ if opt ["link_enabled" ]:
450+ draw .text (
451+ (text_x , site_y ),
452+ site_text ,
453+ fill = "#E8EEF5" ,
454+ font = font_site ,
455+ )
448456
449457 # środek – lokalizacja (pin + tekst)
450458 small_font = self ._load_font (self .font_normal , max (22 , opt ["footer_font" ]))
@@ -501,16 +509,19 @@ def _layout_single(
501509 name_h = draw .textbbox ((0 , 0 ), sp .name , font = name_f )[3 ]
502510 title_h = self ._multiline_height (draw , lines , title_f , gap = 8 )
503511
512+ # overlay pod całą sekcją (avatar + tekst poniżej) z nieco mniejszymi marginesami
504513 overlay_bottom = box_y + size + 32 + name_h + 8 + title_h + 24
505514 self ._overlay (
506515 img ,
507516 (box_x , box_y , box_x + box_w , overlay_bottom ),
508517 opt ["overlay_opacity" ],
509518 )
510519
520+ # avatar
511521 av_x = img .width // 2 - size // 2
512522 self ._paste_with_ring_thin (img , av , (av_x , box_y ))
513523
524+ # nazwisko i tytuł POD avatarem
514525 name_y = box_y + size + 24
515526 title_y = name_y + name_h + 8
516527
0 commit comments