AhmadMustafa commited on
Commit
fd51374
·
1 Parent(s): 5f29e78

add: 9:16 layouts

Browse files
Files changed (1) hide show
  1. crop_utils.py +985 -226
crop_utils.py CHANGED
@@ -79,7 +79,7 @@ def add_top_numbers(
79
  input_image,
80
  num_divisions=20,
81
  margin=90,
82
- font_size=120,
83
  dot_spacing=20,
84
  ):
85
  """
@@ -384,8 +384,8 @@ def find_persons_center(image):
384
 
385
  def create_layouts(image, left_division, right_division):
386
  """
387
- Create different layout variations of the image using half, one-third, and two-thirds width.
388
- All layout variations will be centered on detected persons, including 16:9 and 9:16 crops.
389
 
390
  Args:
391
  image: PIL Image
@@ -393,7 +393,7 @@ def create_layouts(image, left_division, right_division):
393
  right_division: Right division index (1-20)
394
 
395
  Returns:
396
- tuple: (list of layout variations, cutout_image, cutout_16_9, cutout_9_16)
397
  """
398
  # Convert PIL Image to cv2 format
399
  if isinstance(image, Image.Image):
@@ -476,8 +476,12 @@ def create_layouts(image, left_division, right_division):
476
  x_end = min(cutout_width, x_start + new_width)
477
  cutout_16_9 = cutout_image[:, x_start:x_end].copy()
478
 
479
- # For 9:16 version (centered on person)
480
  target_width_9_16 = int(cutout_height * aspect_9_16)
 
 
 
 
481
  if target_width_9_16 <= cutout_width:
482
  # Center horizontally around person
483
  x_start = int(
@@ -490,56 +494,463 @@ def create_layouts(image, left_division, right_division):
490
  )
491
  )
492
  x_end = int(min(cutout_width, x_start + target_width_9_16))
493
- cutout_9_16 = cutout_image[:, x_start:x_end].copy()
 
 
 
 
 
 
 
 
 
 
 
494
  else:
495
  # Handle rare case where we need to adjust height
496
  new_height = int(cutout_width / aspect_9_16)
 
 
497
  y_start = int(
498
- max(0, min(cutout_height - new_height, cutout_center_y - new_height // 2))
499
  )
500
  y_end = int(min(cutout_height, y_start + new_height))
501
  cutout_9_16 = cutout_image[y_start:y_end, :].copy()
502
 
503
  # 4. Scale the center back to original image coordinates
504
  original_center_x = left_boundary + cutout_center_x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
 
506
- # 5. Create layout variations on the original image centered on persons
507
- # Half width layout
508
- half_width = width // 2
509
- half_left_x = max(0, min(width - half_width, original_center_x - half_width // 2))
510
- half_right_x = half_left_x + half_width
511
- half_width_crop = image_cv[:, half_left_x:half_right_x].copy()
512
 
513
- # Third width layout
514
- third_width = width // 3
515
- third_left_x = max(
516
- 0, min(width - third_width, original_center_x - third_width // 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  )
518
- third_right_x = third_left_x + third_width
519
- third_width_crop = image_cv[:, third_left_x:third_right_x].copy()
520
 
521
- # Two-thirds width layout
522
- two_thirds_width = (width * 2) // 3
523
- two_thirds_left_x = max(
524
- 0, min(width - two_thirds_width, original_center_x - two_thirds_width // 2)
 
 
 
 
 
 
525
  )
526
- two_thirds_right_x = two_thirds_left_x + two_thirds_width
527
- two_thirds_crop = image_cv[:, two_thirds_left_x:two_thirds_right_x].copy()
528
 
529
- # Add labels to all crops
530
- font = cv2.FONT_HERSHEY_SIMPLEX
531
- label_settings = {
532
- "fontScale": 1.0,
533
- "fontFace": 1,
534
- "thickness": 2,
535
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
- # Draw label backgrounds for better visibility
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  def add_label(img, label):
 
 
 
 
 
 
 
 
 
 
539
  # Draw background for text
540
  text_size = cv2.getTextSize(
541
- label, **{k: v for k, v in label_settings.items() if k != "color"}
 
 
 
542
  )
 
543
  cv2.rectangle(
544
  img,
545
  (10, 10),
@@ -547,55 +958,108 @@ def create_layouts(image, left_division, right_division):
547
  (0, 0, 0),
548
  -1,
549
  ) # Black background
 
550
  # Draw text
551
  cv2.putText(
552
  img,
553
  label,
554
  (15, 15 + text_size[0][1]),
555
- **label_settings,
 
 
556
  color=(255, 255, 255),
557
  lineType=cv2.LINE_AA,
558
  )
559
  return img
560
 
561
- cutout_image = add_label(cutout_image, "Cutout")
562
- cutout_16_9 = add_label(cutout_16_9, "16:9")
563
- cutout_9_16 = add_label(cutout_9_16, "9:16")
564
- half_width_crop = add_label(half_width_crop, "Half Width")
565
- third_width_crop = add_label(third_width_crop, "Third Width")
566
- two_thirds_crop = add_label(two_thirds_crop, "Two-Thirds Width")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
 
568
  # Convert all output images to PIL format
569
- layout_crops = []
570
- for layout, label in [
571
- (half_width_crop, "Half Width"),
572
- (third_width_crop, "Third Width"),
573
- (two_thirds_crop, "Two-Thirds Width"),
574
- ]:
575
- pil_layout = Image.fromarray(cv2.cvtColor(layout, cv2.COLOR_BGR2RGB))
576
- layout_crops.append(pil_layout)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
- cutout_pil = Image.fromarray(cv2.cvtColor(cutout_image, cv2.COLOR_BGR2RGB))
579
- cutout_16_9_pil = Image.fromarray(cv2.cvtColor(cutout_16_9, cv2.COLOR_BGR2RGB))
580
- cutout_9_16_pil = Image.fromarray(cv2.cvtColor(cutout_9_16, cv2.COLOR_BGR2RGB))
 
 
581
 
582
- return layout_crops, cutout_pil, cutout_16_9_pil, cutout_9_16_pil
 
 
 
 
 
 
583
 
584
 
585
- def draw_crops_on_original(image, left_division, right_division, crop_types):
 
 
586
  """
587
- Create a visualization showing selected crop regions overlaid on the original image.
588
- Each crop region is outlined with a different color and labeled.
589
- All crops are centered on the person's center point.
590
 
591
  Args:
592
  image: PIL Image
593
  left_division: Left division index (1-20)
594
  right_division: Right division index (1-20)
595
- crop_types: List of crop types to include in visualization (e.g., ["16:9", "9:16"])
 
596
 
597
  Returns:
598
- PIL Image: Original image with selected crop regions visualized
599
  """
600
  # Convert PIL Image to cv2 format
601
  if isinstance(image, Image.Image):
@@ -609,62 +1073,30 @@ def draw_crops_on_original(image, left_division, right_division, crop_types):
609
  # Get image dimensions
610
  height, width = image_cv.shape[:2]
611
 
612
- # Calculate division width and crop boundaries
613
- division_width = width / 20 # Assuming 20 divisions
614
- left_boundary = int((left_division - 1) * division_width)
615
- right_boundary = int(right_division * division_width)
616
-
617
- # Find person bounding box and center in cutout
618
- cutout_image = image_cv[:, left_boundary:right_boundary].copy()
619
-
620
- # Get YOLO detections for person bounding box
621
- results = model(cutout_image, classes=[0], conf=0.6)
622
-
623
- # Default values
624
- cutout_center_x = cutout_image.shape[1] // 2
625
- cutout_center_y = cutout_image.shape[0] // 2
626
- person_top = 0.0
627
- person_height = float(cutout_image.shape[0])
628
-
629
- if results and len(results[0].boxes) > 0:
630
- # Get person detection
631
- boxes = results[0].boxes.xyxy.cpu().numpy()
632
-
633
- if len(boxes) == 1:
634
- # Single person
635
- x1, y1, x2, y2 = boxes[0]
636
- cutout_center_x = int((x1 + x2) // 2)
637
- cutout_center_y = int((y1 + y2) // 2)
638
- person_top = y1
639
- person_height = y2 - y1
640
- else:
641
- # Multiple persons - merge bounding boxes
642
- left_x = min(box[0] for box in boxes)
643
- right_x = max(box[2] for box in boxes)
644
- top_y = min(box[1] for box in boxes) # Top of highest person
645
- bottom_y = max(box[3] for box in boxes) # Bottom of lowest person
646
-
647
- cutout_center_x = int((left_x + right_x) // 2)
648
- cutout_center_y = int((top_y + bottom_y) // 2)
649
- person_top = top_y
650
- person_height = bottom_y - top_y
651
-
652
- # Scale back to original image
653
- original_center_x = left_boundary + cutout_center_x
654
- original_center_y = cutout_center_y
655
- original_person_top = (
656
- person_top # Already in original image space since we didn't crop vertically
657
- )
658
- original_person_height = person_height # Same in original space
659
 
660
- # Define colors for different crops (BGR format)
661
  colors = {
662
- "cutout": (0, 165, 255), # Orange
663
- "16:9": (0, 255, 0), # Green
664
- "9:16": (255, 0, 0), # Blue
665
- "half": (255, 255, 0), # Cyan
666
- "third": (255, 0, 255), # Magenta
667
- "two_thirds": (0, 255, 255), # Yellow
 
 
 
 
 
 
 
 
668
  }
669
 
670
  # Define line thickness and font
@@ -673,161 +1105,454 @@ def draw_crops_on_original(image, left_division, right_division, crop_types):
673
  font_scale = 0.8
674
  font_thickness = 2
675
 
676
- # Draw cutout region (original divisions) if requested
677
- if "cutout" in crop_types:
 
 
 
 
 
 
 
 
 
 
 
678
  cv2.rectangle(
679
  visualization,
680
- (left_boundary, 0),
681
- (right_boundary, height),
682
- colors["cutout"],
683
  thickness,
684
  )
685
  cv2.putText(
686
  visualization,
687
- "Cutout",
688
- (left_boundary + 5, 30),
689
  font,
690
  font_scale,
691
- colors["cutout"],
692
  font_thickness,
693
  )
694
 
695
- # Create 16:9 version of the cutout if requested
696
- if "16:9" in crop_types:
697
- cutout_width = right_boundary - left_boundary
698
- cutout_height = height
699
- aspect_16_9 = 16 / 9
700
- target_height_16_9 = int(cutout_width / aspect_16_9)
701
-
702
- if target_height_16_9 <= height:
703
- # Calculate 20% of person height for top margin
704
- top_margin = int(original_person_height * 0.2)
705
 
706
- # Start 20% above the person's top
707
- y_start = int(max(0, original_person_top - top_margin))
 
 
 
708
 
709
- # If this would make the crop exceed the bottom, adjust y_start
710
- if y_start + target_height_16_9 > height:
711
- y_start = int(max(0, height - target_height_16_9))
 
 
 
 
 
 
 
 
 
 
 
 
 
712
 
713
- y_end = int(min(height, y_start + target_height_16_9))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
 
 
715
  cv2.rectangle(
716
  visualization,
717
- (left_boundary, y_start),
718
- (right_boundary, y_end),
719
- colors["16:9"],
720
  thickness,
721
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
  cv2.putText(
723
  visualization,
724
- "16:9",
725
- (left_boundary + 5, y_start + 30),
726
  font,
727
  font_scale,
728
- colors["16:9"],
729
  font_thickness,
730
  )
731
 
732
- # Create 9:16 version if requested
733
- if "9:16" in crop_types:
734
- cutout_width = right_boundary - left_boundary
735
- cutout_height = height
736
  aspect_9_16 = 9 / 16
737
- target_width_9_16 = int(cutout_height * aspect_9_16)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
 
739
- if target_width_9_16 <= cutout_width:
740
- # Center horizontally around person
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
  x_start = max(
742
  0,
743
  min(
744
- left_boundary + cutout_width - target_width_9_16,
745
  original_center_x - target_width_9_16 // 2,
746
  ),
747
  )
748
  x_end = x_start + target_width_9_16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
  cv2.rectangle(
750
- visualization, (x_start, 0), (x_end, height), colors["9:16"], thickness
 
 
 
 
751
  )
752
  cv2.putText(
753
  visualization,
754
- "9:16",
755
- (x_start + 5, 60),
756
  font,
757
  font_scale,
758
- colors["9:16"],
759
  font_thickness,
760
  )
761
 
762
- # Draw centered half width layout if requested
763
- if "half" in crop_types:
764
- half_width = width // 2
765
- half_left_x = max(
766
- 0, min(width - half_width, original_center_x - half_width // 2)
 
 
 
 
 
 
 
767
  )
768
- half_right_x = half_left_x + half_width
 
769
  cv2.rectangle(
770
  visualization,
771
- (half_left_x, 0),
772
- (half_right_x, height),
773
- colors["half"],
774
  thickness,
775
  )
776
  cv2.putText(
777
  visualization,
778
- "Half Width",
779
- (half_left_x + 5, 90),
780
  font,
781
  font_scale,
782
- colors["half"],
783
  font_thickness,
784
  )
785
 
786
- # Draw centered third width layout if requested
787
- if "third" in crop_types:
788
- third_width = width // 3
789
- third_left_x = max(
790
- 0, min(width - third_width, original_center_x - third_width // 2)
791
  )
792
- third_right_x = third_left_x + third_width
 
793
  cv2.rectangle(
794
  visualization,
795
- (third_left_x, 0),
796
- (third_right_x, height),
797
- colors["third"],
798
  thickness,
799
  )
800
  cv2.putText(
801
  visualization,
802
- "Third Width",
803
- (third_left_x + 5, 120),
804
  font,
805
  font_scale,
806
- colors["third"],
807
  font_thickness,
808
  )
809
 
810
- # Draw centered two-thirds width layout if requested
811
- if "two_thirds" in crop_types:
812
- two_thirds_width = (width * 2) // 3
813
- two_thirds_left_x = max(
814
- 0, min(width - two_thirds_width, original_center_x - two_thirds_width // 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
  )
816
- two_thirds_right_x = two_thirds_left_x + two_thirds_width
 
 
 
 
 
 
 
 
 
 
 
 
 
817
  cv2.rectangle(
818
  visualization,
819
- (two_thirds_left_x, 0),
820
- (two_thirds_right_x, height),
821
- colors["two_thirds"],
822
  thickness,
823
  )
824
  cv2.putText(
825
  visualization,
826
- "Two-Thirds Width",
827
- (two_thirds_left_x + 5, 150),
828
  font,
829
  font_scale,
830
- colors["two_thirds"],
831
  font_thickness,
832
  )
833
 
@@ -847,15 +1572,6 @@ def draw_crops_on_original(image, left_division, right_division, crop_types):
847
  (0, 0, 0),
848
  2,
849
  )
850
- cv2.putText(
851
- visualization,
852
- "Person Center",
853
- (original_center_x + 10, original_center_y),
854
- font,
855
- font_scale,
856
- (255, 255, 255),
857
- font_thickness,
858
- )
859
 
860
  # Convert back to PIL format
861
  visualization_pil = Image.fromarray(cv2.cvtColor(visualization, cv2.COLOR_BGR2RGB))
@@ -865,7 +1581,7 @@ def draw_crops_on_original(image, left_division, right_division, crop_types):
865
 
866
  def get_image_crop(cid=None, rsid=None, uid=None):
867
  """
868
- Function that returns both 16:9 and 9:16 crops and layout variations for visualization.
869
 
870
  Returns:
871
  gr.Gallery: Gallery of all generated images
@@ -874,6 +1590,7 @@ def get_image_crop(cid=None, rsid=None, uid=None):
874
  image_paths = get_sprite_firebase(cid, rsid, uid)
875
  except Exception:
876
  image_paths = ["data/sprite1.jpg", "data/sprite2.jpg"]
 
877
  # Lists to store all images
878
  all_images = []
879
  all_captions = []
@@ -923,32 +1640,70 @@ def get_image_crop(cid=None, rsid=None, uid=None):
923
 
924
  print(f"Using divisions: left={left_division}, right={right_division}")
925
 
926
- # Create layouts and cutouts
927
- layouts, cutout_image, cutout_16_9, cutout_9_16 = create_layouts(
928
- mid_image, left_division, right_division
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
  )
930
 
931
- # Create the first visualization with 16:9 and 9:16 crops only
932
- aspect_ratio_visualization = draw_crops_on_original(
933
- mid_image, left_division, right_division, ["16:9", "9:16"]
934
  )
 
 
935
 
936
- # Create the second visualization with the remaining layouts
937
- other_layouts_visualization = draw_crops_on_original(
938
  mid_image,
939
  left_division,
940
  right_division,
941
- ["cutout", "half", "third", "two_thirds"],
 
 
 
 
 
942
  )
943
 
944
- # Add visualizations showing crops
945
- all_images.append(aspect_ratio_visualization)
 
 
 
 
 
 
 
946
  all_captions.append(
947
- f"Aspect Ratio Crops (16:9 & 9:16) {aspect_ratio_visualization.size}"
948
  )
949
 
950
- all_images.append(other_layouts_visualization)
951
- all_captions.append(f"Other Layout Options {other_layouts_visualization.size}")
 
 
 
 
 
 
 
 
 
 
952
 
953
  # Add input and middle image to gallery
954
  all_images.append(input_image)
@@ -957,21 +1712,25 @@ def get_image_crop(cid=None, rsid=None, uid=None):
957
  all_images.append(mid_image)
958
  all_captions.append(f"Middle Thumbnail {mid_image.size}")
959
 
960
- # Add cutout images to gallery
961
- all_images.append(cutout_image)
962
- all_captions.append(f"Cutout Image {cutout_image.size}")
 
963
 
964
- all_images.append(cutout_16_9)
965
- all_captions.append(f"16:9 Crop {cutout_16_9.size}")
 
 
966
 
967
- all_images.append(cutout_9_16)
968
- all_captions.append(f"9:16 Crop {cutout_9_16.size}")
 
 
969
 
970
- # Add layout variations
971
- for i, layout in enumerate(layouts):
972
- label = ["Half Width", "Third Width", "Two-Thirds Width"][i]
973
  all_images.append(layout)
974
- all_captions.append(f"{label} {layout.size}")
975
 
976
  # Return gallery with all images
977
  return gr.Gallery(value=list(zip(all_images, all_captions)))
 
79
  input_image,
80
  num_divisions=20,
81
  margin=90,
82
+ font_size=70,
83
  dot_spacing=20,
84
  ):
85
  """
 
384
 
385
  def create_layouts(image, left_division, right_division):
386
  """
387
+ Create different layout variations of the image using specific aspect ratios.
388
+ All layout variations will be centered on detected persons.
389
 
390
  Args:
391
  image: PIL Image
 
393
  right_division: Right division index (1-20)
394
 
395
  Returns:
396
+ tuple: (standard_crops, threehalfs_layouts, twothirdhalfs_layouts, twoequalhalfs_layouts, visualization_data)
397
  """
398
  # Convert PIL Image to cv2 format
399
  if isinstance(image, Image.Image):
 
476
  x_end = min(cutout_width, x_start + new_width)
477
  cutout_16_9 = cutout_image[:, x_start:x_end].copy()
478
 
479
+ # For 9:16 version (centered on person, adjusted upward for face visibility)
480
  target_width_9_16 = int(cutout_height * aspect_9_16)
481
+
482
+ # Adjust center point upward by 20% of person height to ensure face is visible
483
+ adjusted_center_y = int(cutout_center_y - (person_height * 0.2))
484
+
485
  if target_width_9_16 <= cutout_width:
486
  # Center horizontally around person
487
  x_start = int(
 
494
  )
495
  )
496
  x_end = int(min(cutout_width, x_start + target_width_9_16))
497
+
498
+ # Use adjusted center point for vertical positioning
499
+ y_start = int(
500
+ max(
501
+ 0,
502
+ min(
503
+ cutout_height - cutout_height,
504
+ adjusted_center_y - cutout_height // 2,
505
+ ),
506
+ )
507
+ )
508
+ cutout_9_16 = cutout_image[y_start:, x_start:x_end].copy()
509
  else:
510
  # Handle rare case where we need to adjust height
511
  new_height = int(cutout_width / aspect_9_16)
512
+
513
+ # Use adjusted center point for vertical positioning
514
  y_start = int(
515
+ max(0, min(cutout_height - new_height, adjusted_center_y - new_height // 2))
516
  )
517
  y_end = int(min(cutout_height, y_start + new_height))
518
  cutout_9_16 = cutout_image[y_start:y_end, :].copy()
519
 
520
  # 4. Scale the center back to original image coordinates
521
  original_center_x = left_boundary + cutout_center_x
522
+ original_center_y = cutout_center_y
523
+ original_person_top = person_top
524
+
525
+ # Store visualization data for drawing
526
+ visualization_data = {
527
+ "original_center_x": original_center_x,
528
+ "original_center_y": original_center_y,
529
+ "original_person_top": original_person_top,
530
+ "original_person_height": person_height,
531
+ "cutout_bounds": (left_boundary, right_boundary),
532
+ }
533
+
534
+ # 5. Create new layout variations - each segment is independently centered on the subject
535
+
536
+ # ----- Create crops for threehalfs layout -----
537
+ # For 16:9 (three 5.3:9 segments, each independently centered)
538
+ aspect_5_3_9 = 5.3 / 9
539
+
540
+ # Calculate dimensions for each segment
541
+ segment_height_16_9 = cutout_height # Use full height
542
+ segment_width_16_9 = int(segment_height_16_9 * aspect_5_3_9)
543
+
544
+ # Create three segments for 16:9 threehalfs - all centered on the person
545
+ threehalfs_16_9_segments = []
546
+ for i in range(3):
547
+ # Each segment is centered on the person
548
+ segment_x_start = int(
549
+ max(
550
+ 0,
551
+ min(
552
+ cutout_width - segment_width_16_9,
553
+ cutout_center_x - segment_width_16_9 // 2,
554
+ ),
555
+ )
556
+ )
557
+ segment_x_end = int(min(cutout_width, segment_x_start + segment_width_16_9))
558
+
559
+ # Create the segment
560
+ segment = cutout_image[:, segment_x_start:segment_x_end].copy()
561
+
562
+ # Add a label for visualization
563
+ label = f"Part {i+1}"
564
+ cv2.putText(
565
+ segment,
566
+ label,
567
+ (10, 30),
568
+ cv2.FONT_HERSHEY_SIMPLEX,
569
+ 0.8,
570
+ (255, 255, 255),
571
+ 2,
572
+ cv2.LINE_AA,
573
+ )
574
+
575
+ threehalfs_16_9_segments.append(segment)
576
+
577
+ # For 9:16 (three 9:5.3 segments, each independently centered)
578
+ aspect_9_5_3 = 9 / 5.3
579
+
580
+ # Calculate dimensions for each segment
581
+ segment_width_9_16 = cutout_9_16.shape[1] # Use full width of 9:16 crop
582
+ segment_height_9_16 = int(segment_width_9_16 / aspect_9_5_3)
583
+
584
+ # Get adjusted center for 9:16 segments (move up by 20% of person height)
585
+ cutout_9_16_center_y = cutout_9_16.shape[0] // 2
586
+ adjusted_9_16_center_y = int(cutout_9_16_center_y - (person_height * 0.2))
587
+ cutout_9_16_height = cutout_9_16.shape[0]
588
+
589
+ # Create three segments for 9:16 threehalfs - all centered on the person
590
+ threehalfs_9_16_segments = []
591
+
592
+ for i in range(3):
593
+ # Each segment is centered on the person with adjusted center point
594
+ segment_y_start = int(
595
+ max(
596
+ 0,
597
+ min(
598
+ cutout_9_16_height - segment_height_9_16,
599
+ adjusted_9_16_center_y - segment_height_9_16 // 2,
600
+ ),
601
+ )
602
+ )
603
+ segment_y_end = int(
604
+ min(cutout_9_16_height, segment_y_start + segment_height_9_16)
605
+ )
606
 
607
+ # Create the segment
608
+ segment = cutout_9_16[segment_y_start:segment_y_end, :].copy()
 
 
 
 
609
 
610
+ # Add a label for visualization
611
+ label = f"Part {i+1}"
612
+ cv2.putText(
613
+ segment,
614
+ label,
615
+ (10, 30),
616
+ cv2.FONT_HERSHEY_SIMPLEX,
617
+ 0.8,
618
+ (255, 255, 255),
619
+ 2,
620
+ cv2.LINE_AA,
621
+ )
622
+
623
+ threehalfs_9_16_segments.append(segment)
624
+
625
+ # ----- Create crops for twothirdhalfs layout -----
626
+ # For 16:9 (two segments: 10.6:9 and 5.3:9 OR 5.3:9 and 10.6:9)
627
+ aspect_10_6_9 = 10.6 / 9
628
+
629
+ # Calculate dimensions for segments
630
+ segment1_height_16_9 = cutout_height # Use full height
631
+ segment1_width_16_9 = int(segment1_height_16_9 * aspect_10_6_9)
632
+ segment2_height_16_9 = cutout_height # Use full height
633
+ segment2_width_16_9 = int(segment2_height_16_9 * aspect_5_3_9)
634
+
635
+ # Create segments for 16:9 twothirdhalfs var1 (10.6:9 then 5.3:9)
636
+ # Both segments independently centered on the person
637
+
638
+ # First segment (10.6:9)
639
+ segment_x_start = int(
640
+ max(
641
+ 0,
642
+ min(
643
+ cutout_width - segment1_width_16_9,
644
+ cutout_center_x - segment1_width_16_9 // 2,
645
+ ),
646
+ )
647
  )
648
+ segment_x_end = int(min(cutout_width, segment_x_start + segment1_width_16_9))
649
+ segment1 = cutout_image[:, segment_x_start:segment_x_end].copy()
650
 
651
+ # Add label
652
+ cv2.putText(
653
+ segment1,
654
+ "10.6:9",
655
+ (10, 30),
656
+ cv2.FONT_HERSHEY_SIMPLEX,
657
+ 0.8,
658
+ (255, 255, 255),
659
+ 2,
660
+ cv2.LINE_AA,
661
  )
 
 
662
 
663
+ # Second segment (5.3:9)
664
+ segment_x_start = int(
665
+ max(
666
+ 0,
667
+ min(
668
+ cutout_width - segment2_width_16_9,
669
+ cutout_center_x - segment2_width_16_9 // 2,
670
+ ),
671
+ )
672
+ )
673
+ segment_x_end = int(min(cutout_width, segment_x_start + segment2_width_16_9))
674
+ segment2 = cutout_image[:, segment_x_start:segment_x_end].copy()
675
+
676
+ # Add label
677
+ cv2.putText(
678
+ segment2,
679
+ "5.3:9",
680
+ (10, 30),
681
+ cv2.FONT_HERSHEY_SIMPLEX,
682
+ 0.8,
683
+ (255, 255, 255),
684
+ 2,
685
+ cv2.LINE_AA,
686
+ )
687
+
688
+ twothirdhalfs_16_9_var1_segments = [segment1, segment2]
689
+
690
+ # Create segments for 16:9 twothirdhalfs var2 (5.3:9 then 10.6:9)
691
+ # First segment (5.3:9) - reuse segment2 from var1
692
+ # Second segment (10.6:9) - reuse segment1 from var1
693
+ twothirdhalfs_16_9_var2_segments = [segment2.copy(), segment1.copy()]
694
+
695
+ # For 9:16 (two segments stacked: 9:10.6 and 9:5.3 OR 9:5.3 and 9:10.6)
696
+ aspect_9_10_6 = 9 / 10.6
697
+ aspect_9_5_3 = 9 / 5.3
698
+
699
+ # Calculate dimensions for segments
700
+ segment1_width_9_16 = cutout_9_16.shape[1] # Use full width of 9:16 crop
701
+ segment1_height_9_16 = int(segment1_width_9_16 / aspect_9_10_6)
702
+ segment2_width_9_16 = cutout_9_16.shape[1] # Use full width of 9:16 crop
703
+ segment2_height_9_16 = int(segment2_width_9_16 / aspect_9_5_3)
704
+
705
+ # Create segments for 9:16 twothirdhalfs var1 (9:10.6 then 9:5.3)
706
+ # Both segments independently centered on the person with adjusted center point
707
+
708
+ # First segment (9:10.6)
709
+ segment_y_start = int(
710
+ max(
711
+ 0,
712
+ min(
713
+ cutout_9_16_height - segment1_height_9_16,
714
+ adjusted_9_16_center_y - segment1_height_9_16 // 2,
715
+ ),
716
+ )
717
+ )
718
+ segment_y_end = int(min(cutout_9_16_height, segment_y_start + segment1_height_9_16))
719
+ segment1 = cutout_9_16[segment_y_start:segment_y_end, :].copy()
720
 
721
+ # Add label
722
+ cv2.putText(
723
+ segment1,
724
+ "9:10.6",
725
+ (10, 30),
726
+ cv2.FONT_HERSHEY_SIMPLEX,
727
+ 0.8,
728
+ (255, 255, 255),
729
+ 2,
730
+ cv2.LINE_AA,
731
+ )
732
+
733
+ # Second segment (9:5.3)
734
+ segment_y_start = int(
735
+ max(
736
+ 0,
737
+ min(
738
+ cutout_9_16_height - segment2_height_9_16,
739
+ adjusted_9_16_center_y - segment2_height_9_16 // 2,
740
+ ),
741
+ )
742
+ )
743
+ segment_y_end = int(min(cutout_9_16_height, segment_y_start + segment2_height_9_16))
744
+ segment2 = cutout_9_16[segment_y_start:segment_y_end, :].copy()
745
+
746
+ # Add label
747
+ cv2.putText(
748
+ segment2,
749
+ "9:5.3",
750
+ (10, 30),
751
+ cv2.FONT_HERSHEY_SIMPLEX,
752
+ 0.8,
753
+ (255, 255, 255),
754
+ 2,
755
+ cv2.LINE_AA,
756
+ )
757
+
758
+ twothirdhalfs_9_16_var1_segments = [segment1, segment2]
759
+
760
+ # Create segments for 9:16 twothirdhalfs var2 (9:5.3 then 9:10.6)
761
+ # First segment (9:5.3) - reuse segment2 from var1
762
+ # Second segment (9:10.6) - reuse segment1 from var1
763
+ twothirdhalfs_9_16_var2_segments = [segment2.copy(), segment1.copy()]
764
+
765
+ # ----- Create crops for twoequalhalfs layout -----
766
+ # For 16:9 (two 8:9 segments side by side)
767
+ aspect_8_9 = 8 / 9
768
+
769
+ # Calculate dimensions for segments
770
+ segment_height_16_9_equal = cutout_height # Use full height
771
+ segment_width_16_9_equal = int(segment_height_16_9_equal * aspect_8_9)
772
+
773
+ # Create segments for 16:9 twoequalhalfs - both centered on the person
774
+ # First segment (8:9)
775
+ segment_x_start = int(
776
+ max(
777
+ 0,
778
+ min(
779
+ cutout_width - segment_width_16_9_equal,
780
+ cutout_center_x - segment_width_16_9_equal // 2,
781
+ ),
782
+ )
783
+ )
784
+ segment_x_end = int(min(cutout_width, segment_x_start + segment_width_16_9_equal))
785
+ segment1 = cutout_image[:, segment_x_start:segment_x_end].copy()
786
+
787
+ # Add label
788
+ cv2.putText(
789
+ segment1,
790
+ "8:9 (1)",
791
+ (10, 30),
792
+ cv2.FONT_HERSHEY_SIMPLEX,
793
+ 0.8,
794
+ (255, 255, 255),
795
+ 2,
796
+ cv2.LINE_AA,
797
+ )
798
+
799
+ # Second segment (identical to first for equal halfs)
800
+ segment2 = segment1.copy()
801
+
802
+ # Update label for segment 2
803
+ cv2.putText(
804
+ segment2,
805
+ "8:9 (2)",
806
+ (10, 30),
807
+ cv2.FONT_HERSHEY_SIMPLEX,
808
+ 0.8,
809
+ (255, 255, 255),
810
+ 2,
811
+ cv2.LINE_AA,
812
+ )
813
+
814
+ twoequalhalfs_16_9_segments = [segment1, segment2]
815
+
816
+ # For 9:16 (two 9:8 segments stacked)
817
+ aspect_9_8 = 9 / 8
818
+
819
+ # Calculate dimensions for segments
820
+ segment_width_9_16_equal = cutout_9_16.shape[1] # Use full width of 9:16 crop
821
+ segment_height_9_16_equal = int(segment_width_9_16_equal / aspect_9_8)
822
+
823
+ # Create segments for 9:16 twoequalhalfs - both centered on the person with adjusted center point
824
+ # First segment (9:8)
825
+ segment_y_start = int(
826
+ max(
827
+ 0,
828
+ min(
829
+ cutout_9_16_height - segment_height_9_16_equal,
830
+ adjusted_9_16_center_y - segment_height_9_16_equal // 2,
831
+ ),
832
+ )
833
+ )
834
+ segment_y_end = int(
835
+ min(cutout_9_16_height, segment_y_start + segment_height_9_16_equal)
836
+ )
837
+ segment1 = cutout_9_16[segment_y_start:segment_y_end, :].copy()
838
+
839
+ # Add label
840
+ cv2.putText(
841
+ segment1,
842
+ "9:8 (1)",
843
+ (10, 30),
844
+ cv2.FONT_HERSHEY_SIMPLEX,
845
+ 0.8,
846
+ (255, 255, 255),
847
+ 2,
848
+ cv2.LINE_AA,
849
+ )
850
+
851
+ # Second segment (identical to first for equal halfs)
852
+ segment2 = segment1.copy()
853
+
854
+ # Update label for segment 2
855
+ cv2.putText(
856
+ segment2,
857
+ "9:8 (2)",
858
+ (10, 30),
859
+ cv2.FONT_HERSHEY_SIMPLEX,
860
+ 0.8,
861
+ (255, 255, 255),
862
+ 2,
863
+ cv2.LINE_AA,
864
+ )
865
+
866
+ twoequalhalfs_9_16_segments = [segment1, segment2]
867
+
868
+ # 6. Create composite layouts by joining segments
869
+ # Function to create a composite image
870
+ def create_composite(segments, horizontal=True):
871
+ if not segments:
872
+ return None
873
+
874
+ if horizontal:
875
+ # Calculate the total width and max height
876
+ total_width = sum(segment.shape[1] for segment in segments)
877
+ max_height = max(segment.shape[0] for segment in segments)
878
+
879
+ # Create a canvas
880
+ composite = np.zeros((max_height, total_width, 3), dtype=np.uint8)
881
+
882
+ # Place segments side by side
883
+ x_offset = 0
884
+ for segment in segments:
885
+ h, w = segment.shape[:2]
886
+ composite[:h, x_offset : x_offset + w] = segment
887
+ x_offset += w
888
+
889
+ else: # vertical stacking
890
+ # Calculate the max width and total height
891
+ max_width = max(segment.shape[1] for segment in segments)
892
+ total_height = sum(segment.shape[0] for segment in segments)
893
+
894
+ # Create a canvas
895
+ composite = np.zeros((total_height, max_width, 3), dtype=np.uint8)
896
+
897
+ # Place segments top to bottom
898
+ y_offset = 0
899
+ for segment in segments:
900
+ h, w = segment.shape[:2]
901
+ composite[y_offset : y_offset + h, :w] = segment
902
+ y_offset += h
903
+
904
+ return composite
905
+
906
+ # Create composite layouts
907
+ threehalfs_16_9_composite = create_composite(
908
+ threehalfs_16_9_segments, horizontal=True
909
+ )
910
+ threehalfs_9_16_composite = create_composite(
911
+ threehalfs_9_16_segments, horizontal=False
912
+ )
913
+
914
+ twothirdhalfs_16_9_var1_composite = create_composite(
915
+ twothirdhalfs_16_9_var1_segments, horizontal=True
916
+ )
917
+ twothirdhalfs_16_9_var2_composite = create_composite(
918
+ twothirdhalfs_16_9_var2_segments, horizontal=True
919
+ )
920
+ twothirdhalfs_9_16_var1_composite = create_composite(
921
+ twothirdhalfs_9_16_var1_segments, horizontal=False
922
+ )
923
+ twothirdhalfs_9_16_var2_composite = create_composite(
924
+ twothirdhalfs_9_16_var2_segments, horizontal=False
925
+ )
926
+
927
+ twoequalhalfs_16_9_composite = create_composite(
928
+ twoequalhalfs_16_9_segments, horizontal=True
929
+ )
930
+ twoequalhalfs_9_16_composite = create_composite(
931
+ twoequalhalfs_9_16_segments, horizontal=False
932
+ )
933
+
934
+ # Add labels to all composites
935
  def add_label(img, label):
936
+ if img is None:
937
+ return None
938
+
939
+ font = cv2.FONT_HERSHEY_SIMPLEX
940
+ label_settings = {
941
+ "fontScale": 1.0,
942
+ "fontFace": font,
943
+ "thickness": 2,
944
+ }
945
+
946
  # Draw background for text
947
  text_size = cv2.getTextSize(
948
+ label,
949
+ fontFace=label_settings["fontFace"],
950
+ fontScale=label_settings["fontScale"],
951
+ thickness=label_settings["thickness"],
952
  )
953
+
954
  cv2.rectangle(
955
  img,
956
  (10, 10),
 
958
  (0, 0, 0),
959
  -1,
960
  ) # Black background
961
+
962
  # Draw text
963
  cv2.putText(
964
  img,
965
  label,
966
  (15, 15 + text_size[0][1]),
967
+ fontFace=label_settings["fontFace"],
968
+ fontScale=label_settings["fontScale"],
969
+ thickness=label_settings["thickness"],
970
  color=(255, 255, 255),
971
  lineType=cv2.LINE_AA,
972
  )
973
  return img
974
 
975
+ # Label the basic crops
976
+ cutout_image_labeled = add_label(cutout_image.copy(), "Cutout")
977
+ cutout_16_9_labeled = add_label(cutout_16_9.copy(), "16:9")
978
+ cutout_9_16_labeled = add_label(cutout_9_16.copy(), "9:16")
979
+
980
+ # Label the composite layouts
981
+ threehalfs_16_9_labeled = add_label(threehalfs_16_9_composite, "Three Halfs 16:9")
982
+ threehalfs_9_16_labeled = add_label(threehalfs_9_16_composite, "Three Halfs 9:16")
983
+
984
+ twothirdhalfs_16_9_var1_labeled = add_label(
985
+ twothirdhalfs_16_9_var1_composite, "Two Thirds Var1 16:9"
986
+ )
987
+ twothirdhalfs_16_9_var2_labeled = add_label(
988
+ twothirdhalfs_16_9_var2_composite, "Two Thirds Var2 16:9"
989
+ )
990
+ twothirdhalfs_9_16_var1_labeled = add_label(
991
+ twothirdhalfs_9_16_var1_composite, "Two Thirds Var1 9:16"
992
+ )
993
+ twothirdhalfs_9_16_var2_labeled = add_label(
994
+ twothirdhalfs_9_16_var2_composite, "Two Thirds Var2 9:16"
995
+ )
996
+
997
+ twoequalhalfs_16_9_labeled = add_label(
998
+ twoequalhalfs_16_9_composite, "Two Equal Halfs 16:9"
999
+ )
1000
+ twoequalhalfs_9_16_labeled = add_label(
1001
+ twoequalhalfs_9_16_composite, "Two Equal Halfs 9:16"
1002
+ )
1003
 
1004
  # Convert all output images to PIL format
1005
+ def cv2_to_pil(img):
1006
+ if img is None:
1007
+ return None
1008
+ return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
1009
+
1010
+ # Convert standard crops
1011
+ standard_crops = {
1012
+ "cutout": cv2_to_pil(cutout_image_labeled),
1013
+ "16:9": cv2_to_pil(cutout_16_9_labeled),
1014
+ "9:16": cv2_to_pil(cutout_9_16_labeled),
1015
+ }
1016
+
1017
+ # Convert threehalfs layouts
1018
+ threehalfs_layouts = {
1019
+ "16:9": cv2_to_pil(threehalfs_16_9_labeled),
1020
+ "9:16": cv2_to_pil(threehalfs_9_16_labeled),
1021
+ }
1022
+
1023
+ # Convert twothirdhalfs layouts
1024
+ twothirdhalfs_layouts = {
1025
+ "16:9_var1": cv2_to_pil(twothirdhalfs_16_9_var1_labeled),
1026
+ "16:9_var2": cv2_to_pil(twothirdhalfs_16_9_var2_labeled),
1027
+ "9:16_var1": cv2_to_pil(twothirdhalfs_9_16_var1_labeled),
1028
+ "9:16_var2": cv2_to_pil(twothirdhalfs_9_16_var2_labeled),
1029
+ }
1030
 
1031
+ # Convert twoequalhalfs layouts
1032
+ twoequalhalfs_layouts = {
1033
+ "16:9": cv2_to_pil(twoequalhalfs_16_9_labeled),
1034
+ "9:16": cv2_to_pil(twoequalhalfs_9_16_labeled),
1035
+ }
1036
 
1037
+ return (
1038
+ standard_crops,
1039
+ threehalfs_layouts,
1040
+ twothirdhalfs_layouts,
1041
+ twoequalhalfs_layouts,
1042
+ visualization_data,
1043
+ )
1044
 
1045
 
1046
+ def draw_layout_regions(
1047
+ image, left_division, right_division, visualization_data, layout_type
1048
+ ):
1049
  """
1050
+ Create a visualization showing the layout regions overlaid on the original image.
1051
+ Each region is independently centered on the subject, as in practice different videos
1052
+ would be stacked in these layouts.
1053
 
1054
  Args:
1055
  image: PIL Image
1056
  left_division: Left division index (1-20)
1057
  right_division: Right division index (1-20)
1058
+ visualization_data: Dictionary with visualization data from create_layouts
1059
+ layout_type: Type of layout to visualize (e.g., "standard", "threehalfs", "twothirdhalfs_var1", etc.)
1060
 
1061
  Returns:
1062
+ PIL Image: Original image with layout regions visualized
1063
  """
1064
  # Convert PIL Image to cv2 format
1065
  if isinstance(image, Image.Image):
 
1073
  # Get image dimensions
1074
  height, width = image_cv.shape[:2]
1075
 
1076
+ # Extract visualization data
1077
+ original_center_x = visualization_data["original_center_x"]
1078
+ original_center_y = visualization_data["original_center_y"]
1079
+ original_person_top = visualization_data["original_person_top"]
1080
+ original_person_height = visualization_data["original_person_height"]
1081
+ left_boundary, right_boundary = visualization_data["cutout_bounds"]
1082
+ cutout_width = right_boundary - left_boundary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1083
 
1084
+ # Define colors for different layouts (BGR format)
1085
  colors = {
1086
+ "standard": {"16:9": (0, 255, 0), "9:16": (255, 0, 0)}, # Green, Blue
1087
+ "threehalfs": {"16:9": (0, 165, 255), "9:16": (255, 255, 0)}, # Orange, Cyan
1088
+ "twothirdhalfs_var1": {
1089
+ "16:9": (255, 0, 255),
1090
+ "9:16": (128, 0, 128),
1091
+ }, # Magenta, Purple
1092
+ "twothirdhalfs_var2": {
1093
+ "16:9": (0, 255, 255),
1094
+ "9:16": (128, 128, 0),
1095
+ }, # Yellow, Teal
1096
+ "twoequalhalfs": {
1097
+ "16:9": (0, 128, 128),
1098
+ "9:16": (255, 165, 0),
1099
+ }, # Dark Cyan, Blue-Green
1100
  }
1101
 
1102
  # Define line thickness and font
 
1105
  font_scale = 0.8
1106
  font_thickness = 2
1107
 
1108
+ # Draw standard layouts (16:9 and 9:16)
1109
+ if layout_type == "standard":
1110
+ # Draw 16:9 crop
1111
+ aspect_16_9 = 16 / 9
1112
+ target_height_16_9 = int(cutout_width / aspect_16_9)
1113
+
1114
+ # Calculate 20% of person height for top margin
1115
+ top_margin = int(original_person_height * 0.2)
1116
+ y_start = int(max(0, original_person_top - top_margin))
1117
+ if y_start + target_height_16_9 > height:
1118
+ y_start = int(max(0, height - target_height_16_9))
1119
+ y_end = int(min(height, y_start + target_height_16_9))
1120
+
1121
  cv2.rectangle(
1122
  visualization,
1123
+ (left_boundary, y_start),
1124
+ (right_boundary, y_end),
1125
+ colors["standard"]["16:9"],
1126
  thickness,
1127
  )
1128
  cv2.putText(
1129
  visualization,
1130
+ "16:9",
1131
+ (left_boundary + 5, y_start + 30),
1132
  font,
1133
  font_scale,
1134
+ colors["standard"]["16:9"],
1135
  font_thickness,
1136
  )
1137
 
1138
+ # Draw 9:16 crop
1139
+ aspect_9_16 = 9 / 16
1140
+ target_width_9_16 = int(height * aspect_9_16)
 
 
 
 
 
 
 
1141
 
1142
+ x_start = max(
1143
+ 0,
1144
+ min(width - target_width_9_16, original_center_x - target_width_9_16 // 2),
1145
+ )
1146
+ x_end = x_start + target_width_9_16
1147
 
1148
+ cv2.rectangle(
1149
+ visualization,
1150
+ (x_start, 0),
1151
+ (x_end, height),
1152
+ colors["standard"]["9:16"],
1153
+ thickness,
1154
+ )
1155
+ cv2.putText(
1156
+ visualization,
1157
+ "9:16",
1158
+ (x_start + 5, 30),
1159
+ font,
1160
+ font_scale,
1161
+ colors["standard"]["9:16"],
1162
+ font_thickness,
1163
+ )
1164
 
1165
+ # Draw threehalfs layouts - each segment is centered on the subject
1166
+ elif layout_type == "threehalfs":
1167
+ # For 16:9 (three 5.3:9 segments side by side - visually only)
1168
+ aspect_5_3_9 = 5.3 / 9
1169
+ segment_height = height
1170
+ segment_width = int(segment_height * aspect_5_3_9)
1171
+
1172
+ # Calculate total width for visualization purposes
1173
+ total_width = segment_width * 3
1174
+ start_x = max(0, original_center_x - total_width // 2)
1175
+
1176
+ for i in range(3):
1177
+ # For visualization, we'll place them side by side
1178
+ vis_segment_x_start = start_x + i * segment_width
1179
+ vis_segment_x_end = vis_segment_x_start + segment_width
1180
+
1181
+ # But each segment would actually be centered on the subject independently
1182
+ # Here we also draw the centered version more faintly
1183
+ actual_segment_x_start = max(
1184
+ 0, min(width - segment_width, original_center_x - segment_width // 2)
1185
+ )
1186
+ actual_segment_x_end = min(width, actual_segment_x_start + segment_width)
1187
 
1188
+ # Draw the visualization placement (side by side)
1189
  cv2.rectangle(
1190
  visualization,
1191
+ (vis_segment_x_start, 0),
1192
+ (vis_segment_x_end, segment_height),
1193
+ colors["threehalfs"]["16:9"],
1194
  thickness,
1195
  )
1196
+
1197
+ # Draw the actual centered placement with dashed lines
1198
+ if i > 0: # Only draw centered versions for parts 2 and 3
1199
+ for j in range(0, segment_height, 20): # Dashed line effect
1200
+ if j % 40 < 20: # Skip every other segment
1201
+ cv2.line(
1202
+ visualization,
1203
+ (actual_segment_x_start, j),
1204
+ (actual_segment_x_start, min(j + 20, segment_height)),
1205
+ colors["threehalfs"]["16:9"],
1206
+ 1,
1207
+ )
1208
+ cv2.line(
1209
+ visualization,
1210
+ (actual_segment_x_end, j),
1211
+ (actual_segment_x_end, min(j + 20, segment_height)),
1212
+ colors["threehalfs"]["16:9"],
1213
+ 1,
1214
+ )
1215
+
1216
  cv2.putText(
1217
  visualization,
1218
+ f"16:9 Part {i+1}",
1219
+ (vis_segment_x_start + 5, 30 + i * 30),
1220
  font,
1221
  font_scale,
1222
+ colors["threehalfs"]["16:9"],
1223
  font_thickness,
1224
  )
1225
 
1226
+ # For 9:16 (three 9:5.3 segments stacked top to bottom - visually only)
 
 
 
1227
  aspect_9_16 = 9 / 16
1228
+ target_width_9_16 = int(height * aspect_9_16)
1229
+ x_start = max(
1230
+ 0,
1231
+ min(width - target_width_9_16, original_center_x - target_width_9_16 // 2),
1232
+ )
1233
+ x_end = x_start + target_width_9_16
1234
+
1235
+ aspect_9_5_3 = 9 / 5.3
1236
+ segment_width_9_16 = target_width_9_16
1237
+ segment_height_9_16 = int(segment_width_9_16 / aspect_9_5_3)
1238
+
1239
+ # Calculate total height for visualization purposes
1240
+ total_height = segment_height_9_16 * 3
1241
+ start_y = max(0, height // 2 - total_height // 2)
1242
+
1243
+ for i in range(3):
1244
+ # For visualization, we'll place them stacked
1245
+ vis_segment_y_start = start_y + i * segment_height_9_16
1246
+ vis_segment_y_end = min(height, vis_segment_y_start + segment_height_9_16)
1247
+
1248
+ # But each segment would actually be centered on the subject independently
1249
+ # Here we also draw the centered version more faintly
1250
+ actual_segment_y_start = max(
1251
+ 0,
1252
+ min(
1253
+ height - segment_height_9_16,
1254
+ original_center_y - segment_height_9_16 // 2,
1255
+ ),
1256
+ )
1257
+ actual_segment_y_end = min(
1258
+ height, actual_segment_y_start + segment_height_9_16
1259
+ )
1260
+
1261
+ # Draw the visualization placement (stacked)
1262
+ cv2.rectangle(
1263
+ visualization,
1264
+ (x_start, vis_segment_y_start),
1265
+ (x_end, vis_segment_y_end),
1266
+ colors["threehalfs"]["9:16"],
1267
+ thickness,
1268
+ )
1269
+
1270
+ # Draw the actual centered placement with dashed lines
1271
+ if i > 0: # Only draw centered versions for parts 2 and 3
1272
+ for j in range(x_start, x_end, 20): # Dashed line effect
1273
+ if j % 40 < 20: # Skip every other segment
1274
+ cv2.line(
1275
+ visualization,
1276
+ (j, actual_segment_y_start),
1277
+ (min(j + 20, x_end), actual_segment_y_start),
1278
+ colors["threehalfs"]["9:16"],
1279
+ 1,
1280
+ )
1281
+ cv2.line(
1282
+ visualization,
1283
+ (j, actual_segment_y_end),
1284
+ (min(j + 20, x_end), actual_segment_y_end),
1285
+ colors["threehalfs"]["9:16"],
1286
+ 1,
1287
+ )
1288
+
1289
+ cv2.putText(
1290
+ visualization,
1291
+ f"9:16 Part {i+1}",
1292
+ (x_start + 5, vis_segment_y_start + 30),
1293
+ font,
1294
+ font_scale,
1295
+ colors["threehalfs"]["9:16"],
1296
+ font_thickness,
1297
+ )
1298
+
1299
+ # Draw twothirdhalfs layouts
1300
+ elif layout_type == "twothirdhalfs_var1" or layout_type == "twothirdhalfs_var2":
1301
+ aspect_key = "16:9" if layout_type.endswith("var1") else "9:16"
1302
+ layout_color = colors[
1303
+ (
1304
+ "twothirdhalfs_var1"
1305
+ if layout_type.endswith("var1")
1306
+ else "twothirdhalfs_var2"
1307
+ )
1308
+ ][aspect_key]
1309
+
1310
+ if aspect_key == "16:9":
1311
+ # For 16:9 (two segments side by side)
1312
+ aspect_10_6_9 = 10.6 / 9
1313
+ aspect_5_3_9 = 5.3 / 9
1314
+
1315
+ segment1_height = height
1316
+ segment1_width = int(
1317
+ segment1_height
1318
+ * (aspect_10_6_9 if layout_type.endswith("var1") else aspect_5_3_9)
1319
+ )
1320
+ segment2_height = height
1321
+ segment2_width = int(
1322
+ segment2_height
1323
+ * (aspect_5_3_9 if layout_type.endswith("var1") else aspect_10_6_9)
1324
+ )
1325
+
1326
+ # First segment
1327
+ segment_center_x = original_center_x - segment2_width // 4
1328
+ segment_x_start = int(
1329
+ max(
1330
+ 0,
1331
+ min(width - segment1_width, segment_center_x - segment1_width // 2),
1332
+ )
1333
+ )
1334
+ segment_x_end = int(min(width, segment_x_start + segment1_width))
1335
+
1336
+ cv2.rectangle(
1337
+ visualization,
1338
+ (segment_x_start, 0),
1339
+ (segment_x_end, segment1_height),
1340
+ layout_color,
1341
+ thickness,
1342
+ )
1343
+ cv2.putText(
1344
+ visualization,
1345
+ f"16:9 Part 1",
1346
+ (segment_x_start + 5, 30),
1347
+ font,
1348
+ font_scale,
1349
+ layout_color,
1350
+ font_thickness,
1351
+ )
1352
 
1353
+ # Second segment
1354
+ segment_center_x = original_center_x + segment1_width // 4
1355
+ segment_x_start = int(
1356
+ max(
1357
+ 0,
1358
+ min(width - segment2_width, segment_center_x - segment2_width // 2),
1359
+ )
1360
+ )
1361
+ segment_x_end = int(min(width, segment_x_start + segment2_width))
1362
+
1363
+ cv2.rectangle(
1364
+ visualization,
1365
+ (segment_x_start, 0),
1366
+ (segment_x_end, segment2_height),
1367
+ layout_color,
1368
+ thickness,
1369
+ )
1370
+ cv2.putText(
1371
+ visualization,
1372
+ f"16:9 Part 2",
1373
+ (segment_x_start + 5, 60),
1374
+ font,
1375
+ font_scale,
1376
+ layout_color,
1377
+ font_thickness,
1378
+ )
1379
+ else: # aspect_key == "9:16"
1380
+ # For 9:16 (two segments stacked)
1381
+ aspect_9_16 = 9 / 16
1382
+ target_width_9_16 = int(height * aspect_9_16)
1383
  x_start = max(
1384
  0,
1385
  min(
1386
+ width - target_width_9_16,
1387
  original_center_x - target_width_9_16 // 2,
1388
  ),
1389
  )
1390
  x_end = x_start + target_width_9_16
1391
+
1392
+ aspect_9_10_6 = 9 / 10.6
1393
+ aspect_9_5_3 = 9 / 5.3
1394
+
1395
+ segment1_width = target_width_9_16
1396
+ segment1_height = int(
1397
+ segment1_width
1398
+ / (aspect_9_10_6 if layout_type.endswith("var1") else aspect_9_5_3)
1399
+ )
1400
+ segment2_width = target_width_9_16
1401
+ segment2_height = int(
1402
+ segment2_width
1403
+ / (aspect_9_5_3 if layout_type.endswith("var1") else aspect_9_10_6)
1404
+ )
1405
+
1406
+ # First segment (top)
1407
+ segment_y_start = 0
1408
+ segment_y_end = min(height, segment_y_start + segment1_height)
1409
+
1410
+ cv2.rectangle(
1411
+ visualization,
1412
+ (x_start, segment_y_start),
1413
+ (x_end, segment_y_end),
1414
+ layout_color,
1415
+ thickness,
1416
+ )
1417
+ cv2.putText(
1418
+ visualization,
1419
+ f"9:16 Part 1",
1420
+ (x_start + 5, segment_y_start + 30),
1421
+ font,
1422
+ font_scale,
1423
+ layout_color,
1424
+ font_thickness,
1425
+ )
1426
+
1427
+ # Second segment (bottom)
1428
+ segment_y_start = segment_y_end
1429
+ segment_y_end = min(height, segment_y_start + segment2_height)
1430
+
1431
  cv2.rectangle(
1432
+ visualization,
1433
+ (x_start, segment_y_start),
1434
+ (x_end, segment_y_end),
1435
+ layout_color,
1436
+ thickness,
1437
  )
1438
  cv2.putText(
1439
  visualization,
1440
+ f"9:16 Part 2",
1441
+ (x_start + 5, segment_y_start + 30),
1442
  font,
1443
  font_scale,
1444
+ layout_color,
1445
  font_thickness,
1446
  )
1447
 
1448
+ # Draw twoequalhalfs layouts
1449
+ elif layout_type == "twoequalhalfs":
1450
+ # For 16:9 (two 8:9 segments side by side)
1451
+ aspect_8_9 = 8 / 9
1452
+
1453
+ segment_height = height
1454
+ segment_width = int(segment_height * aspect_8_9)
1455
+
1456
+ # First segment (left)
1457
+ segment_center_x = original_center_x - segment_width // 2
1458
+ segment_x_start = int(
1459
+ max(0, min(width - segment_width, segment_center_x - segment_width // 2))
1460
  )
1461
+ segment_x_end = int(min(width, segment_x_start + segment_width))
1462
+
1463
  cv2.rectangle(
1464
  visualization,
1465
+ (segment_x_start, 0),
1466
+ (segment_x_end, segment_height),
1467
+ colors["twoequalhalfs"]["16:9"],
1468
  thickness,
1469
  )
1470
  cv2.putText(
1471
  visualization,
1472
+ f"16:9 Equal 1",
1473
+ (segment_x_start + 5, 30),
1474
  font,
1475
  font_scale,
1476
+ colors["twoequalhalfs"]["16:9"],
1477
  font_thickness,
1478
  )
1479
 
1480
+ # Second segment (right)
1481
+ segment_center_x = original_center_x + segment_width // 2
1482
+ segment_x_start = int(
1483
+ max(0, min(width - segment_width, segment_center_x - segment_width // 2))
 
1484
  )
1485
+ segment_x_end = int(min(width, segment_x_start + segment_width))
1486
+
1487
  cv2.rectangle(
1488
  visualization,
1489
+ (segment_x_start, 0),
1490
+ (segment_x_end, segment_height),
1491
+ colors["twoequalhalfs"]["16:9"],
1492
  thickness,
1493
  )
1494
  cv2.putText(
1495
  visualization,
1496
+ f"16:9 Equal 2",
1497
+ (segment_x_start + 5, 60),
1498
  font,
1499
  font_scale,
1500
+ colors["twoequalhalfs"]["16:9"],
1501
  font_thickness,
1502
  )
1503
 
1504
+ # For 9:16 (two 9:8 segments stacked)
1505
+ aspect_9_16 = 9 / 16
1506
+ target_width_9_16 = int(height * aspect_9_16)
1507
+ x_start = max(
1508
+ 0,
1509
+ min(width - target_width_9_16, original_center_x - target_width_9_16 // 2),
1510
+ )
1511
+ x_end = x_start + target_width_9_16
1512
+
1513
+ aspect_9_8 = 9 / 8
1514
+ segment_width_9_16 = target_width_9_16
1515
+ segment_height_9_16 = int(segment_width_9_16 / aspect_9_8)
1516
+
1517
+ # First segment (top)
1518
+ segment_y_start = 0
1519
+ segment_y_end = min(height, segment_y_start + segment_height_9_16)
1520
+
1521
+ cv2.rectangle(
1522
+ visualization,
1523
+ (x_start, segment_y_start),
1524
+ (x_end, segment_y_end),
1525
+ colors["twoequalhalfs"]["9:16"],
1526
+ thickness,
1527
  )
1528
+ cv2.putText(
1529
+ visualization,
1530
+ f"9:16 Equal 1",
1531
+ (x_start + 5, segment_y_start + 30),
1532
+ font,
1533
+ font_scale,
1534
+ colors["twoequalhalfs"]["9:16"],
1535
+ font_thickness,
1536
+ )
1537
+
1538
+ # Second segment (bottom)
1539
+ segment_y_start = segment_y_end
1540
+ segment_y_end = min(height, segment_y_start + segment_height_9_16)
1541
+
1542
  cv2.rectangle(
1543
  visualization,
1544
+ (x_start, segment_y_start),
1545
+ (x_end, segment_y_end),
1546
+ colors["twoequalhalfs"]["9:16"],
1547
  thickness,
1548
  )
1549
  cv2.putText(
1550
  visualization,
1551
+ f"9:16 Equal 2",
1552
+ (x_start + 5, segment_y_start + 30),
1553
  font,
1554
  font_scale,
1555
+ colors["twoequalhalfs"]["9:16"],
1556
  font_thickness,
1557
  )
1558
 
 
1572
  (0, 0, 0),
1573
  2,
1574
  )
 
 
 
 
 
 
 
 
 
1575
 
1576
  # Convert back to PIL format
1577
  visualization_pil = Image.fromarray(cv2.cvtColor(visualization, cv2.COLOR_BGR2RGB))
 
1581
 
1582
  def get_image_crop(cid=None, rsid=None, uid=None):
1583
  """
1584
+ Function that returns both standard and layout variations for visualization.
1585
 
1586
  Returns:
1587
  gr.Gallery: Gallery of all generated images
 
1590
  image_paths = get_sprite_firebase(cid, rsid, uid)
1591
  except Exception:
1592
  image_paths = ["data/sprite1.jpg", "data/sprite2.jpg"]
1593
+
1594
  # Lists to store all images
1595
  all_images = []
1596
  all_captions = []
 
1640
 
1641
  print(f"Using divisions: left={left_division}, right={right_division}")
1642
 
1643
+ # Create layouts and cutouts using the new function
1644
+ (
1645
+ standard_crops,
1646
+ threehalfs_layouts,
1647
+ twothirdhalfs_layouts,
1648
+ twoequalhalfs_layouts,
1649
+ visualization_data,
1650
+ ) = create_layouts(mid_image, left_division, right_division)
1651
+
1652
+ # Create all the required visualizations
1653
+ # 1. Standard aspect ratio visualization (16:9 and 9:16)
1654
+ standard_visualization = draw_layout_regions(
1655
+ mid_image, left_division, right_division, visualization_data, "standard"
1656
+ )
1657
+ all_images.append(standard_visualization)
1658
+ all_captions.append(
1659
+ f"Standard Aspect Ratios (16:9 & 9:16) {standard_visualization.size}"
1660
  )
1661
 
1662
+ # 2. Threehalfs visualization
1663
+ threehalfs_visualization = draw_layout_regions(
1664
+ mid_image, left_division, right_division, visualization_data, "threehalfs"
1665
  )
1666
+ all_images.append(threehalfs_visualization)
1667
+ all_captions.append(f"Three Halfs Layout {threehalfs_visualization.size}")
1668
 
1669
+ # 3. Twothirdhalfs var1 visualization
1670
+ twothirdhalfs_var1_visualization = draw_layout_regions(
1671
  mid_image,
1672
  left_division,
1673
  right_division,
1674
+ visualization_data,
1675
+ "twothirdhalfs_var1",
1676
+ )
1677
+ all_images.append(twothirdhalfs_var1_visualization)
1678
+ all_captions.append(
1679
+ f"Two-Thirds Halfs Var1 Layout {twothirdhalfs_var1_visualization.size}"
1680
  )
1681
 
1682
+ # 4. Twothirdhalfs var2 visualization
1683
+ twothirdhalfs_var2_visualization = draw_layout_regions(
1684
+ mid_image,
1685
+ left_division,
1686
+ right_division,
1687
+ visualization_data,
1688
+ "twothirdhalfs_var2",
1689
+ )
1690
+ all_images.append(twothirdhalfs_var2_visualization)
1691
  all_captions.append(
1692
+ f"Two-Thirds Halfs Var2 Layout {twothirdhalfs_var2_visualization.size}"
1693
  )
1694
 
1695
+ # 5. Twoequalhalfs visualization
1696
+ twoequalhalfs_visualization = draw_layout_regions(
1697
+ mid_image,
1698
+ left_division,
1699
+ right_division,
1700
+ visualization_data,
1701
+ "twoequalhalfs",
1702
+ )
1703
+ all_images.append(twoequalhalfs_visualization)
1704
+ all_captions.append(
1705
+ f"Two Equal Halfs Layout {twoequalhalfs_visualization.size}"
1706
+ )
1707
 
1708
  # Add input and middle image to gallery
1709
  all_images.append(input_image)
 
1712
  all_images.append(mid_image)
1713
  all_captions.append(f"Middle Thumbnail {mid_image.size}")
1714
 
1715
+ # Add standard crops
1716
+ for key, crop in standard_crops.items():
1717
+ all_images.append(crop)
1718
+ all_captions.append(f"{key} {crop.size}")
1719
 
1720
+ # Add threehalfs layouts
1721
+ for key, layout in threehalfs_layouts.items():
1722
+ all_images.append(layout)
1723
+ all_captions.append(f"Three Halfs {key} {layout.size}")
1724
 
1725
+ # Add twothirdhalfs layouts
1726
+ for key, layout in twothirdhalfs_layouts.items():
1727
+ all_images.append(layout)
1728
+ all_captions.append(f"Two-Thirds Halfs {key} {layout.size}")
1729
 
1730
+ # Add twoequalhalfs layouts
1731
+ for key, layout in twoequalhalfs_layouts.items():
 
1732
  all_images.append(layout)
1733
+ all_captions.append(f"Two Equal Halfs {key} {layout.size}")
1734
 
1735
  # Return gallery with all images
1736
  return gr.Gallery(value=list(zip(all_images, all_captions)))