groueix commited on
Commit
131728e
·
1 Parent(s): b19d03f

added french version, reverted cli

Browse files
Files changed (3) hide show
  1. app.py +8 -1
  2. copaint/cli.py +156 -46
  3. copaint/gradio_ui.py +172 -66
app.py CHANGED
@@ -10,9 +10,16 @@ def main():
10
  default=True,
11
  help="Share the app publicly (default: true)"
12
  )
 
 
 
 
 
 
 
13
  args = parser.parse_args()
14
 
15
- demo = build_gradio_ui()
16
  # check if /dev/shm exists
17
  if os.path.exists("/dev/shm"):
18
  demo.launch(share=args.share, allowed_paths=["/dev/shm"])
 
10
  default=True,
11
  help="Share the app publicly (default: true)"
12
  )
13
+ parser.add_argument(
14
+ "-l", "--language",
15
+ type=str,
16
+ default="en",
17
+ choices=["en", "fr"],
18
+ help="Language to use (default: en)"
19
+ )
20
  args = parser.parse_args()
21
 
22
+ demo = build_gradio_ui(language=args.language)
23
  # check if /dev/shm exists
24
  if os.path.exists("/dev/shm"):
25
  demo.launch(share=args.share, allowed_paths=["/dev/shm"])
copaint/cli.py CHANGED
@@ -1,54 +1,129 @@
1
- """Command line interface for Copaint pdf generator.
2
- """
3
  import argparse
 
 
 
4
  import numpy as np
5
- import logging
 
6
 
7
- from copaint.copaint import image_to_pdf
8
-
9
- # Configure logging
10
- logging.basicConfig(
11
- level=logging.INFO,
12
- format='%(asctime)s - %(levelname)s - %(message)s',
13
- datefmt='%Y-%m-%d %H:%M:%S'
14
- )
15
- logger = logging.getLogger(__name__)
16
-
17
- # Default identifiers for unique_identifier parameter
18
- default_identifiers = [
19
- "Mauricette", "Gertrude", "Bernadette", "Henriette", "Georgette", "Antoinette", "Colette", "Suzette", "Yvette", "Paulette",
20
- "Juliette", "Odette", "Marinette", "Lucette", "Pierrette", "Cosette", "Rosette", "Claudette", "Violette", "Josette"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  ]
22
 
23
- def default_identifier():
24
- """Return a random default identifier."""
25
- return np.random.choice(default_identifiers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  def get_grid_size(nparticipants, input_image):
28
- """Get grid size based on number of participants."""
29
- # Compute the grid size based on the number of participants
30
- # We want to find h_cells and w_cells such that h_cells * w_cells >= nparticipants
31
- # and h_cells/w_cells is close to the aspect ratio of the image
32
- from PIL import Image
33
- image = Image.open(input_image)
34
- w, h = image.size
35
  aspect_ratio = w/h
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- # Find the smallest grid that can accommodate nparticipants
38
- min_cells = int(np.ceil(np.sqrt(nparticipants)))
39
- for i in range(min_cells, min_cells+10):
40
- for j in range(min_cells, min_cells+10):
41
- if i*j >= nparticipants:
42
- ratio = j/i
43
- if abs(ratio - aspect_ratio) < 0.2:
44
- logger.info(f"Found grid size {i}x{j} for {nparticipants} participants")
45
- return i, j
46
 
47
- # If no good ratio is found, just use the smallest grid that can accommodate nparticipants
48
- h_cells = min_cells
49
- w_cells = int(np.ceil(nparticipants/min_cells))
50
- logger.warning(f"No good aspect ratio found. Using {h_cells}x{w_cells} grid for {nparticipants} participants")
51
- return h_cells, w_cells
52
 
53
  def main():
54
  parser = argparse.ArgumentParser(description='CoPaint')
@@ -66,19 +141,46 @@ def main():
66
  parser.add_argument('--min_cell_size_in_cm', type=int, default=2, help='minimum size of cells in cm')
67
  parser.add_argument('--debug', action='store_true', help='debug mode')
68
 
 
69
  args = parser.parse_args()
70
 
71
- if args.debug:
72
- logger.setLevel(logging.DEBUG)
73
 
74
  if args.unique_identifier is None:
75
  # select one at random
76
  idx = np.random.choice(len(default_identifiers), 1)
77
  args.unique_identifier = default_identifiers[idx[0]]
78
- logger.info(f"Using random identifier: {args.unique_identifier}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  # generate a copaint pdf based on the number of participants
81
- if args.nparticipants:
82
  # assert other parameters are not set
83
  assert(args.h_cells is None), "When choosing via number of participants, the number of H cells can't be set"
84
  assert(args.w_cells is None ), "When choosing via number of participants, the number of W cells can't be set"
@@ -88,12 +190,20 @@ def main():
88
  image_to_pdf(args.input_image, args.copaint_logo, args.outputfolder, h_cells, w_cells,
89
  unique_identifier=args.unique_identifier, cell_size_in_cm=args.cell_size_in_cm,
90
  a4=args.a4, high_res=args.high_res, min_cell_size_in_cm=args.min_cell_size_in_cm, debug=args.debug)
 
 
 
 
 
 
 
 
91
 
92
  # Generate the copaint pdf using the specified number of cells
93
  else:
94
  image_to_pdf(args.input_image, args.copaint_logo, args.outputfolder, args.h_cells, args.w_cells,
95
  unique_identifier=args.unique_identifier, cell_size_in_cm=args.cell_size_in_cm,
96
  a4=args.a4, high_res=args.high_res, min_cell_size_in_cm=args.min_cell_size_in_cm, debug=args.debug)
97
-
98
  if __name__ == '__main__':
99
  main()
 
 
 
1
  import argparse
2
+ from copaint.copaint import image_to_pdf
3
+ # from copaint import image_to_copaint_pdf
4
+ from PIL import Image
5
  import numpy as np
6
+ import random
7
+ actual_participants_over_participants = 0.7 # how many people actually show up compared to the number of participants
8
 
9
+ # Default list of identifiers
10
+ default_identifiers_en = [
11
+ "sunshine",
12
+ "bliss",
13
+ "smile",
14
+ "serenity",
15
+ "laughter",
16
+ "breeze",
17
+ "harmony",
18
+ "glee",
19
+ "cheer",
20
+ "delight",
21
+ "hope",
22
+ "sparkle",
23
+ "kindness",
24
+ "charm",
25
+ "grace",
26
+ "radiance",
27
+ "jubilee",
28
+ "flutter",
29
+ "playful",
30
+ "whimsy",
31
+ "gleam",
32
+ "glow",
33
+ "twinkle",
34
+ "love",
35
+ "joy",
36
+ "peace",
37
+ "cheeky",
38
+ "amity",
39
+ "blissful",
40
+ "grateful"
41
  ]
42
 
43
+ default_identifiers_fr = [
44
+ "soleil", # sunshine
45
+ "joie", # joy
46
+ "bise", # soft breeze / kiss
47
+ "charmant", # charming
48
+ "éclat", # sparkle / brilliance
49
+ "rêve", # dream
50
+ "douceur", # softness / sweetness
51
+ "espoir", # hope
52
+ "lueur", # glow
53
+ "gaieté", # cheerfulness
54
+ "plume", # feather
55
+ "bisou", # kiss
56
+ "amour", # love
57
+ "paix", # peace
58
+ "fleur", # flower
59
+ "rosée", # dew
60
+ "papillon", # butterfly
61
+ "souffle", # breath
62
+ "bulle", # bubble
63
+ "câlin", # cuddle
64
+ "grâce", # grace
65
+ "mistral", # southern wind
66
+ "lumière", # light
67
+ "frisson", # shiver (pleasant)
68
+ "cocorico", # rooster cry, French pride symbol
69
+ "azur", # blue sky
70
+ "jojo", # playful diminutive for joy
71
+ "rire", # laughter
72
+ "chouette" # owl / also means “great”
73
+ ]
74
+ # Function to get a default identifier (random or specified)
75
+ def default_identifier(index=None, language="en"):
76
+ """
77
+ Get a default identifier from the list.
78
+
79
+ Args:
80
+ index: Optional index to get a specific identifier.
81
+ If None, returns a random identifier.
82
+
83
+ Returns:
84
+ str: A default identifier
85
+ """
86
+ if language == "en":
87
+ if index is not None and 0 <= index < len(default_identifiers_fr):
88
+ return default_identifiers_fr[index]
89
+ return random.choice(default_identifiers_fr)
90
+ elif language == "fr":
91
+ if index is not None and 0 <= index < len(default_identifiers_en):
92
+ return default_identifiers_en[index]
93
+ return random.choice(default_identifiers_en)
94
 
95
  def get_grid_size(nparticipants, input_image):
96
+ """ Takes the number of participants and the input image and returns the grid size, with the objective of making each cell as square as possible."""
97
+ # get the dimensions of the input image, load with PIL
98
+ input_image = Image.open(input_image)
99
+ w, h = input_image.size
100
+ n_cell = nparticipants * actual_participants_over_participants
 
 
101
  aspect_ratio = w/h
102
+ h_cells = np.sqrt(aspect_ratio * n_cell)
103
+ w_cells = aspect_ratio * h_cells
104
+
105
+ """
106
+ We have the following equations:
107
+ (1) w_cells/h_cells = aspect_ratio (as close as possible up to w_cells and h_cells being integers)
108
+ (2) h_cells * w_cells = n_cell
109
+
110
+ Solving to
111
+ (1) w_cells = aspect_ratio * h_cells
112
+ # replace in (2)
113
+ (2) h_cells * aspect_ratio * h_cells = n_cell
114
+ Leads to (3) h_cells^2 * aspect_ratio = n_cell
115
+ Leads to h_cells = sqrt(n_cell / aspect_ratio)
116
+ """
117
+ h_cells = np.round(np.sqrt(n_cell / aspect_ratio))
118
+ w_cells = np.round(aspect_ratio * h_cells)
119
+
120
+ # convert to integers
121
+ h_cells = int(h_cells)
122
+ w_cells = int(w_cells)
123
 
 
 
 
 
 
 
 
 
 
124
 
125
+ print(f"Using {h_cells} x {w_cells} = {h_cells*w_cells} grid size for a canvas of size {h}*{w} and {nparticipants} participants (actual {n_cell})")
126
+ return int(h_cells), int(w_cells)
 
 
 
127
 
128
  def main():
129
  parser = argparse.ArgumentParser(description='CoPaint')
 
141
  parser.add_argument('--min_cell_size_in_cm', type=int, default=2, help='minimum size of cells in cm')
142
  parser.add_argument('--debug', action='store_true', help='debug mode')
143
 
144
+ # done adding arguments
145
  args = parser.parse_args()
146
 
 
 
147
 
148
  if args.unique_identifier is None:
149
  # select one at random
150
  idx = np.random.choice(len(default_identifiers), 1)
151
  args.unique_identifier = default_identifiers[idx[0]]
152
+
153
+ presets = [
154
+ [2, 3], # 6 people
155
+ [3, 3], # 9 people
156
+ [3, 4], # 12 people
157
+ [4, 4], # 16 people
158
+ [4, 5], # 20 people
159
+ [4, 6], # 24 people
160
+ [5, 6], # 30 people
161
+ [6, 8], # 48 people
162
+ [7, 9], # 63 people
163
+ [7, 10], # 70 people
164
+ [8, 10], # 80 people
165
+ [8, 12], # 96 people
166
+ ]
167
+ preset_number_of_guests = [presets[i][0]*presets[i][1] for i in range(len(presets))]
168
+
169
+ # generate all presets
170
+ if args.use_presets:
171
+ # disregard other parameters and use the presets
172
+ # assert other parameters are not set
173
+ assert(args.h_cells is None), "When using presets, the number of H cells can't be set"
174
+ assert(args.w_cells is None), "When using presets, the number of W cells can't be set"
175
+ assert(args.nparticipants is None), "When using presets, the number of participants can't be set"
176
+
177
+ for preset in presets:
178
+ image_to_pdf(args.input_image, args.copaint_logo, args.outputfolder, preset[0], preset[1],
179
+ unique_identifier=args.unique_identifier, cell_size_in_cm=args.cell_size_in_cm,
180
+ a4=args.a4, high_res=args.high_res, min_cell_size_in_cm=args.min_cell_size_in_cm, debug=args.debug)
181
 
182
  # generate a copaint pdf based on the number of participants
183
+ elif args.nparticipants:
184
  # assert other parameters are not set
185
  assert(args.h_cells is None), "When choosing via number of participants, the number of H cells can't be set"
186
  assert(args.w_cells is None ), "When choosing via number of participants, the number of W cells can't be set"
 
190
  image_to_pdf(args.input_image, args.copaint_logo, args.outputfolder, h_cells, w_cells,
191
  unique_identifier=args.unique_identifier, cell_size_in_cm=args.cell_size_in_cm,
192
  a4=args.a4, high_res=args.high_res, min_cell_size_in_cm=args.min_cell_size_in_cm, debug=args.debug)
193
+
194
+ # # Depracated find the first preset that can accomodate the number of participants
195
+ # preset_number_of_guests_inflated_by_losers = actual_participants_over_participants*args.nparticipants
196
+ # for i, preset in enumerate(presets):
197
+ # if preset_number_of_guests_inflated_by_losers <= preset_number_of_guests[i]:
198
+ # print(f"Using preset {preset} for {args.nparticipants} participants")
199
+ # image_to_copaint_pdf(args.input_image, args.copaint_logo, args.outputfolder, preset[0], preset[1])
200
+ # break
201
 
202
  # Generate the copaint pdf using the specified number of cells
203
  else:
204
  image_to_pdf(args.input_image, args.copaint_logo, args.outputfolder, args.h_cells, args.w_cells,
205
  unique_identifier=args.unique_identifier, cell_size_in_cm=args.cell_size_in_cm,
206
  a4=args.a4, high_res=args.high_res, min_cell_size_in_cm=args.min_cell_size_in_cm, debug=args.debug)
207
+
208
  if __name__ == '__main__':
209
  main()
copaint/gradio_ui.py CHANGED
@@ -69,7 +69,8 @@ def add_grid_to_image(image, h_cells, w_cells):
69
  return image
70
 
71
 
72
- def get_canvas_ratio_message(image, h_cells, w_cells):
 
73
  w,h = image.size
74
  aspect_ratio = w/h
75
  if aspect_ratio > 1:
@@ -92,33 +93,57 @@ def get_canvas_ratio_message(image, h_cells, w_cells):
92
  else:
93
 
94
  example_str = ""
95
-
96
- if closest_ratio_str == "1:1":
97
- if h_cells == 2 and h_cells == 2:
98
- example_str = ", for example, a 6” by 6” canvas."
99
- if h_cells == 3 and h_cells == 3:
100
- example_str = ", for example, an 8” by 8” canvas."
101
- elif closest_ratio_str == "5:6":
102
- example_str = ", for example, a 10” by 12” canvas."
103
- elif closest_ratio_str == "3:4":
104
- if (h_cells == 3 and w_cells == 4) or (h_cells == 4 and w_cells == 3):
105
- example_str = ", for example, a 6” by 8” canvas."
106
- if (h_cells == 4 and w_cells == 6) or (h_cells == 6 and w_cells == 4):
107
- example_str = ", for example, an 18” by 24”, or a 12” by 16” canvas."
108
- elif closest_ratio_str == "2:3" and ((h_cells == 6 and w_cells == 9) or (h_cells == 9 and w_cells == 6)):
109
- example_str = ", for example, a 24” by 36” canvas."
110
- elif closest_ratio_str == "1:2":
111
- example_str = ", for example, a 12” by 24” canvas."
112
-
113
- return_str = f"You have chosen a <b>{h_cells}x{w_cells} Grid ({h_cells*w_cells} squares)</b>.\n\n"
114
- return_str += f"Preparing your canvas: <b>choose a canvas with a {closest_ratio_str} ratio</b> to respect your design's size of {w}x{h} pixels{example_str}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  print(f"return_str: {return_str}")
116
  return f"<div style='font-size: 1.2em; line-height: 1.5;'>{return_str}</div>"
117
 
118
- def add_grid_and_display_ratio(image, h_cells, w_cells):
119
  if image is None:
120
  return None, gr.update(visible=False)
121
- return add_grid_to_image(image, h_cells, w_cells), gr.update(visible=True, value=get_canvas_ratio_message(image, h_cells, w_cells))
122
 
123
 
124
  def process_copaint(
@@ -131,6 +156,7 @@ def process_copaint(
131
  min_cell_size_in_cm=2,
132
  copaint_name="",
133
  copaint_logo=None,
 
134
  ):
135
  """Process the input and generate Copaint PDF"""
136
  # Create temporary directories for processing
@@ -157,7 +183,7 @@ def process_copaint(
157
 
158
  if copaint_name == "" or copaint_name is None:
159
  from copaint.cli import default_identifier
160
- copaint_name = default_identifier()
161
 
162
  if a4 == "A4":
163
  a4 = True
@@ -187,19 +213,27 @@ def process_copaint(
187
  shutil.rmtree(temp_input_dir)
188
 
189
 
190
- def build_gradio_ui():
191
  # Create Gradio Interface
192
  with gr.Blocks(title="Copaint Generator", theme='NoCrypt/miku') as demo:
193
 
194
- gr.Markdown("# 🤖 Copaint Generator")
195
- gr.Markdown("Upload an image with your painting design and set grid parameters to generate a Copaint PDF template 🖨️📄✂️ for your next collaborative painting activities. 🎨🖌️")
 
 
 
 
196
 
197
  # --- inputs ---
198
  with gr.Row(equal_height=True):
199
 
200
  # Upload Design Template
201
  with gr.Column(scale=2):
202
- input_image = gr.Image(type="pil", image_mode="RGBA", label="Upload Your Design")
 
 
 
 
203
 
204
  input_image.upload(
205
  fn=load_with_transparency,
@@ -208,11 +242,41 @@ def build_gradio_ui():
208
  )
209
  with gr.Column(scale=1):
210
  # Grid
211
- with gr.Tab("Grid Layout"):
212
- gr.Markdown("<div style='text-align: center; font-weight: bold;'>Squares' Grid</div>")
213
- w_cells = gr.Number(label="↔ (width)", value=4, precision=0)
214
- h_cells = gr.Number(label=" by ↕ (heigth)", value=6, precision=0)
 
 
 
 
 
 
 
 
 
 
 
 
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  gr.Examples(
217
  examples=[
218
  [6, 9],
@@ -221,69 +285,110 @@ def build_gradio_ui():
221
  [3, 4],
222
  [2, 2]
223
  ],
224
- example_labels=[
225
- "Copaint Wedding 6x9 Grid (54 squares)",
226
- "Copaint Classic 4x6 Grid (24 squares)",
227
- "Copaint Mini 3x3 Grid (9 squares)",
228
- "Copaint Mini 3x4 Grid (12 squares)",
229
- "Copaint Mini 2x2 Grid (4 squares)"],
230
  inputs=[w_cells, h_cells],
231
  )
232
 
233
  # Grid + Design preview
234
- gr.Markdown("<div style='text-align: center; font-weight: bold;'>Preview</div>")
 
 
 
235
 
236
  output_image = gr.Image(label=None, show_label=False, show_fullscreen_button=False, interactive=False, show_download_button=False)
237
 
238
  # canvas ratio message
239
- canvas_msg = gr.Markdown(label="Canvas Ratio", visible=False)
 
 
 
 
240
 
241
  # PDF options
242
- with gr.Tab("PDF Printing Options"):
243
- use_a4 = gr.Dropdown(choices=["US letter", "A4"], label="Paper Format", value="US letter")
244
-
245
- with gr.Accordion("Advanced settings (optional)", open=False):
 
 
 
 
 
 
 
 
 
 
 
246
  with gr.Row():
247
  with gr.Column(scale=1):
248
- high_res = gr.Checkbox(label="High Resolution Mode (>20sec long processing)")
249
-
250
- cell_size = gr.Number(label="Square Size, in cm (optional)",
251
- value="",
252
- info="If none is provided, the design size automatically adjusts to fit on a single page. In most situations, you don't need this.")
253
-
254
- copaint_name = gr.Textbox(label="Add a Custom Design Name (optional)",
255
- value="",
256
- max_length=10,
257
- info="You can add a custom design name: it will appear on the back of each square, in the top left corner.")
258
 
259
- copaint_logo = gr.Image(type="pil",
260
- label="Add a Custom Logo (optional)")
261
- gr.Markdown(
262
- "<div style='font-size: 0.85em;'>"
263
- "You can add a custom logo: it will appear on the back of each square, in the bottom right corner."
264
- "</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
  # --- outputs ---
267
  with gr.Row():
268
  # PDF
269
  with gr.Column(scale=1):
270
- submit_btn = gr.Button("Generate Copaint PDF", variant="primary")
 
 
 
271
 
272
  with gr.Row():
273
  with gr.Column(scale=1):
274
- output_file = gr.File(label="Download PDF", visible=False, interactive=False)
 
 
 
275
  with gr.Column(scale=1):
276
- output_error_msg = gr.Textbox(label="Error Message", visible=False)
 
 
 
277
 
278
  with gr.Row():
279
  with gr.Column(scale=1):
280
- output_pdf = PDF(label="PDF Preview")#, show_label=False, show_fullscreen_button=False, interactive=False, show_download_button=False)
 
 
 
281
 
282
  # Update output_image: trigger update when any input changes
283
  for component in [input_image, h_cells, w_cells]:
284
  component.change(
285
  fn=add_grid_and_display_ratio,
286
- inputs=[input_image, h_cells, w_cells],
287
  outputs=[output_image, canvas_msg]
288
  )
289
 
@@ -304,7 +409,8 @@ def build_gradio_ui():
304
  cell_size_in_cm=cell_size if cell_size else None,
305
  min_cell_size_in_cm=float(2),
306
  copaint_name=copaint_name,
307
- copaint_logo=copaint_logo
 
308
  )
309
 
310
  if error:
 
69
  return image
70
 
71
 
72
+ def get_canvas_ratio_message(image, h_cells, w_cells, language="en"):
73
+
74
  w,h = image.size
75
  aspect_ratio = w/h
76
  if aspect_ratio > 1:
 
93
  else:
94
 
95
  example_str = ""
96
+ if language == "en":
97
+ if closest_ratio_str == "1:1":
98
+ if h_cells == 2 and h_cells == 2:
99
+ example_str = ", for example, a 6” by 6” canvas."
100
+ if h_cells == 3 and h_cells == 3:
101
+ example_str = ", for example, an 8” by 8” canvas."
102
+ elif closest_ratio_str == "5:6":
103
+ example_str = ", for example, a 10” by 12” canvas."
104
+ elif closest_ratio_str == "3:4":
105
+ if (h_cells == 3 and w_cells == 4) or (h_cells == 4 and w_cells == 3):
106
+ example_str = ", for example, a 6” by 8” canvas."
107
+ if (h_cells == 4 and w_cells == 6) or (h_cells == 6 and w_cells == 4):
108
+ example_str = ", for example, an 18” by 24”, or a 12” by 16” canvas."
109
+ elif closest_ratio_str == "2:3" and ((h_cells == 6 and w_cells == 9) or (h_cells == 9 and w_cells == 6)):
110
+ example_str = ", for example, a 24” by 36” canvas."
111
+ elif closest_ratio_str == "1:2":
112
+ example_str = ", for example, a 12” by 24” canvas."
113
+ elif language == "fr":
114
+ if closest_ratio_str == "1:1":
115
+ if h_cells == 2 and h_cells == 2:
116
+ example_str = ", par exemple, une toile de 6” par 6”."
117
+ if h_cells == 3 and h_cells == 3:
118
+ example_str = ", par exemple, une toile de 8” par 8”."
119
+ elif closest_ratio_str == "5:6":
120
+ if h_cells == 6 and w_cells == 9:
121
+ example_str = ", for example, a 24” by 36” canvas."
122
+ if h_cells == 9 and w_cells == 6:
123
+ example_str = ", for example, a 36” by 24” canvas."
124
+ elif closest_ratio_str == "4:5":
125
+ if h_cells == 4 and w_cells == 6:
126
+ example_str = ", for example, a 6” by 8” canvas."
127
+ if h_cells == 6 and w_cells == 4:
128
+ example_str = ", for example, an 18” by 24”, or a 12” by 16” canvas."
129
+ elif closest_ratio_str == "2:3" and ((h_cells == 6 and w_cells == 9) or (h_cells == 9 and w_cells == 6)):
130
+ example_str = ", for example, a 24” by 36” canvas."
131
+ elif closest_ratio_str == "1:2":
132
+ example_str = ", for example, a 12” by 24” canvas."
133
+
134
+ if language == "en":
135
+ return_str = f"You have chosen a <b>{h_cells}x{w_cells} Grid ({h_cells*w_cells} squares)</b>.\n\n"
136
+ return_str += f"Preparing your canvas: <b>choose a canvas with a {closest_ratio_str} ratio</b> to respect your design's size of {w}x{h} pixels{example_str}"
137
+ else:
138
+ return_str = f"Vous avez choisi une grille de {h_cells}x{w_cells} ({h_cells*w_cells} tuiles).\n\n"
139
+ return_str += f"Préparation de la toile: <b>choisissez une toile avec un aspect-ratio de {closest_ratio_str}</b> pour respecter la taille de votre design, {w}x{h} pixels{example_str}"
140
  print(f"return_str: {return_str}")
141
  return f"<div style='font-size: 1.2em; line-height: 1.5;'>{return_str}</div>"
142
 
143
+ def add_grid_and_display_ratio(image, h_cells, w_cells, language="en"):
144
  if image is None:
145
  return None, gr.update(visible=False)
146
+ return add_grid_to_image(image, h_cells, w_cells), gr.update(visible=True, value=get_canvas_ratio_message(image, h_cells, w_cells, language))
147
 
148
 
149
  def process_copaint(
 
156
  min_cell_size_in_cm=2,
157
  copaint_name="",
158
  copaint_logo=None,
159
+ language="en"
160
  ):
161
  """Process the input and generate Copaint PDF"""
162
  # Create temporary directories for processing
 
183
 
184
  if copaint_name == "" or copaint_name is None:
185
  from copaint.cli import default_identifier
186
+ copaint_name = default_identifier(language=language)
187
 
188
  if a4 == "A4":
189
  a4 = True
 
213
  shutil.rmtree(temp_input_dir)
214
 
215
 
216
+ def build_gradio_ui(language="en"):
217
  # Create Gradio Interface
218
  with gr.Blocks(title="Copaint Generator", theme='NoCrypt/miku') as demo:
219
 
220
+ if language == "en":
221
+ gr.Markdown("# 🤖 Copaint Generator")
222
+ gr.Markdown("Upload an image, set grid parameters, and we generate your Copaint PDF 🖨️📄✂️ for your next collaborative painting activity. 🎨🖌️")
223
+ elif language == "fr":
224
+ gr.Markdown("# 🤖 Générateur de Copaint")
225
+ gr.Markdown("Téléchargez une image, définissez les paramètres de la grille, on vous génére votre PDF Copaint 🖨️📄✂️ pour votre prochaines activité de peinture collaboratives. 🎨🖌️")
226
 
227
  # --- inputs ---
228
  with gr.Row(equal_height=True):
229
 
230
  # Upload Design Template
231
  with gr.Column(scale=2):
232
+ if language == "en":
233
+ label_input_image = "Upload Your Design"
234
+ elif language == "fr":
235
+ label_input_image = "Déposer votre image"
236
+ input_image = gr.Image(type="pil", image_mode="RGBA", label=label_input_image)
237
 
238
  input_image.upload(
239
  fn=load_with_transparency,
 
242
  )
243
  with gr.Column(scale=1):
244
  # Grid
245
+ if language == "en":
246
+ label_grid_layout = "Grid Layout"
247
+ elif language == "fr":
248
+ label_grid_layout = "Grille"
249
+ with gr.Tab(label_grid_layout):
250
+ if language == "en":
251
+ gr.Markdown("<div style='text-align: left; font-weight: bold;'>Squares' Grid</div>")
252
+ w_cells = gr.Number(label="↔ (width)", value=4, precision=0)
253
+ h_cells = gr.Number(label=" by ↕ (heigth)", value=6, precision=0)
254
+ language_gr_state = gr.State("en")
255
+ elif language == "fr":
256
+ gr.Markdown("<div style='text-align: center; font-weight: bold;'>Grille de tuiles</div>")
257
+ w_cells = gr.Number(label="↔ (largeur)", value=4, precision=0)
258
+ h_cells = gr.Number(label=" par ↕ (hauteur)", value=6, precision=0)
259
+ language_gr_state = gr.State("fr")
260
+
261
 
262
+ examples_labels_en = [
263
+ "Copaint Wedding 6x9 Grid (54 squares)",
264
+ "Copaint Classic 4x6 Grid (24 squares)",
265
+ "Copaint Mini 3x3 Grid (9 squares)",
266
+ "Copaint Mini 3x4 Grid (12 squares)",
267
+ "Copaint Mini 2x2 Grid (4 squares)"
268
+ ]
269
+ examples_labels_fr = [
270
+ "Copaint Mariage 6x9 Grille (54 tuiles)",
271
+ "Copaint Classique 4x6 Grille (24 tuiles)",
272
+ "Copaint Mini 3x3 Grille (9 tuiles)",
273
+ "Copaint Mini 3x4 Grille (12 tuiles)",
274
+ "Copaint Mini 2x2 Grille (4 tuiles)"
275
+ ]
276
+ if language == "en":
277
+ example_labels = examples_labels_en
278
+ elif language == "fr":
279
+ example_labels = examples_labels_fr
280
  gr.Examples(
281
  examples=[
282
  [6, 9],
 
285
  [3, 4],
286
  [2, 2]
287
  ],
288
+ example_labels=example_labels,
 
 
 
 
 
289
  inputs=[w_cells, h_cells],
290
  )
291
 
292
  # Grid + Design preview
293
+ if language == "en":
294
+ gr.Markdown("<div style='text-align: center; font-weight: bold;'>Preview</div>")
295
+ elif language == "fr":
296
+ gr.Markdown("<div style='text-align: center; font-weight: bold;'>Aperçu</div>")
297
 
298
  output_image = gr.Image(label=None, show_label=False, show_fullscreen_button=False, interactive=False, show_download_button=False)
299
 
300
  # canvas ratio message
301
+ if language == "en":
302
+ canvas_msg_label = "Canvas Ratio"
303
+ elif language == "fr":
304
+ canvas_msg_label = "Aspect Ratio"
305
+ canvas_msg = gr.Markdown(label=canvas_msg_label, visible=False)
306
 
307
  # PDF options
308
+ if language == "en":
309
+ label_pdf_options = "PDF Printing Options"
310
+ elif language == "fr":
311
+ label_pdf_options = "Options d'impression"
312
+ with gr.Tab(label_pdf_options):
313
+ if language == "en":
314
+ use_a4 = gr.Dropdown(choices=["US letter", "A4"], label="Paper Format", value="US letter")
315
+ elif language == "fr":
316
+ use_a4 = gr.Dropdown(choices=["A4", "US letter"], label="Format de papier", value="A4")
317
+
318
+ if language == "en":
319
+ label_advanced_settings = "Advanced settings (optional)"
320
+ elif language == "fr":
321
+ label_advanced_settings = "Options avancées (optionnel)"
322
+ with gr.Accordion(label_advanced_settings, open=False):
323
  with gr.Row():
324
  with gr.Column(scale=1):
325
+ if language == "en":
326
+ high_res = gr.Checkbox(label="High Resolution Mode (>20sec long processing)")
 
 
 
 
 
 
 
 
327
 
328
+ cell_size = gr.Number(label="Square Size, in cm (optional)",
329
+ value="",
330
+ info="If none is provided, the design size automatically adjusts to fit on a single page. In most situations, you don't need this.")
331
+
332
+ copaint_name = gr.Textbox(label="Add a Custom Design Name (optional)",
333
+ value="",
334
+ max_length=10,
335
+ info="You can add a custom design name: it will appear on the back of each square, in the top left corner.")
336
+
337
+ copaint_logo = gr.Image(type="pil",
338
+ label="Add a Custom Logo (optional)")
339
+ gr.Markdown(
340
+ "<div style='font-size: 0.85em;'>"
341
+ "You can add a custom logo: it will appear on the back of each square, in the bottom right corner."
342
+ "</div>")
343
+ elif language == "fr":
344
+ high_res = gr.Checkbox(label="Mode haute résolution (>20sec de traitement)")
345
+ cell_size = gr.Number(label="Taille des tuiles, en cm (optionnel)",
346
+ value="",
347
+ info="Si non fournie, la taille des tuiles est choisie automatiquement pour que tout tienne sur une seule page. Dans la plupart des cas, vous n'avez pas besoin de cette option.")
348
+ copaint_name = gr.Textbox(label="Ajouter un nom personnalisé à votre design (optionnel)",
349
+ value="",
350
+ max_length=10,
351
+ info="Vous pouvez ajouter un nom personnalisé: il apparaîtra sur le dos de chaque tuile, dans le coin supérieur gauche.")
352
+ copaint_logo = gr.Image(type="pil",
353
+ label="Ajouter un logo personnalisé (optionnel)")
354
+ gr.Markdown(
355
+ "<div style='font-size: 0.85em;'>"
356
+ "Vous pouvez ajouter un logo personnalisé: il apparaîtra sur le dos de chaque tuile, dans le coin inférieur droit."
357
+ "</div>")
358
 
359
  # --- outputs ---
360
  with gr.Row():
361
  # PDF
362
  with gr.Column(scale=1):
363
+ if language == "en":
364
+ submit_btn = gr.Button("Generate Copaint PDF", variant="primary")
365
+ elif language == "fr":
366
+ submit_btn = gr.Button("Générer le PDF Copaint", variant="primary")
367
 
368
  with gr.Row():
369
  with gr.Column(scale=1):
370
+ if language == "en":
371
+ output_file = gr.File(label="Download PDF", visible=False, interactive=False)
372
+ elif language == "fr":
373
+ output_file = gr.File(label="Télécharger le PDF", visible=False, interactive=False)
374
  with gr.Column(scale=1):
375
+ if language == "en":
376
+ output_error_msg = gr.Textbox(label="Error Message", visible=False)
377
+ elif language == "fr":
378
+ output_error_msg = gr.Textbox(label="Message d'erreur", visible=False)
379
 
380
  with gr.Row():
381
  with gr.Column(scale=1):
382
+ if language == "en":
383
+ output_pdf = PDF(label="PDF Preview")#, show_label=False, show_fullscreen_button=False, interactive=False, show_download_button=False)
384
+ elif language == "fr":
385
+ output_pdf = PDF(label="Aperçu du PDF")#, show_label=False, show_fullscreen_button=False, interactive=False, show_download_button=False)
386
 
387
  # Update output_image: trigger update when any input changes
388
  for component in [input_image, h_cells, w_cells]:
389
  component.change(
390
  fn=add_grid_and_display_ratio,
391
+ inputs=[input_image, h_cells, w_cells, language_gr_state],
392
  outputs=[output_image, canvas_msg]
393
  )
394
 
 
409
  cell_size_in_cm=cell_size if cell_size else None,
410
  min_cell_size_in_cm=float(2),
411
  copaint_name=copaint_name,
412
+ copaint_logo=copaint_logo,
413
+ language=language
414
  )
415
 
416
  if error: