lybic-guiagents 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lybic-guiagents might be problematic. Click here for more details.

Files changed (85) hide show
  1. desktop_env/__init__.py +1 -0
  2. desktop_env/actions.py +203 -0
  3. desktop_env/controllers/__init__.py +0 -0
  4. desktop_env/controllers/python.py +471 -0
  5. desktop_env/controllers/setup.py +882 -0
  6. desktop_env/desktop_env.py +509 -0
  7. desktop_env/evaluators/__init__.py +5 -0
  8. desktop_env/evaluators/getters/__init__.py +41 -0
  9. desktop_env/evaluators/getters/calc.py +15 -0
  10. desktop_env/evaluators/getters/chrome.py +1774 -0
  11. desktop_env/evaluators/getters/file.py +154 -0
  12. desktop_env/evaluators/getters/general.py +42 -0
  13. desktop_env/evaluators/getters/gimp.py +38 -0
  14. desktop_env/evaluators/getters/impress.py +126 -0
  15. desktop_env/evaluators/getters/info.py +24 -0
  16. desktop_env/evaluators/getters/misc.py +406 -0
  17. desktop_env/evaluators/getters/replay.py +20 -0
  18. desktop_env/evaluators/getters/vlc.py +86 -0
  19. desktop_env/evaluators/getters/vscode.py +35 -0
  20. desktop_env/evaluators/metrics/__init__.py +160 -0
  21. desktop_env/evaluators/metrics/basic_os.py +68 -0
  22. desktop_env/evaluators/metrics/chrome.py +493 -0
  23. desktop_env/evaluators/metrics/docs.py +1011 -0
  24. desktop_env/evaluators/metrics/general.py +665 -0
  25. desktop_env/evaluators/metrics/gimp.py +637 -0
  26. desktop_env/evaluators/metrics/libreoffice.py +28 -0
  27. desktop_env/evaluators/metrics/others.py +92 -0
  28. desktop_env/evaluators/metrics/pdf.py +31 -0
  29. desktop_env/evaluators/metrics/slides.py +957 -0
  30. desktop_env/evaluators/metrics/table.py +585 -0
  31. desktop_env/evaluators/metrics/thunderbird.py +176 -0
  32. desktop_env/evaluators/metrics/utils.py +719 -0
  33. desktop_env/evaluators/metrics/vlc.py +524 -0
  34. desktop_env/evaluators/metrics/vscode.py +283 -0
  35. desktop_env/providers/__init__.py +35 -0
  36. desktop_env/providers/aws/__init__.py +0 -0
  37. desktop_env/providers/aws/manager.py +278 -0
  38. desktop_env/providers/aws/provider.py +186 -0
  39. desktop_env/providers/aws/provider_with_proxy.py +315 -0
  40. desktop_env/providers/aws/proxy_pool.py +193 -0
  41. desktop_env/providers/azure/__init__.py +0 -0
  42. desktop_env/providers/azure/manager.py +87 -0
  43. desktop_env/providers/azure/provider.py +207 -0
  44. desktop_env/providers/base.py +97 -0
  45. desktop_env/providers/gcp/__init__.py +0 -0
  46. desktop_env/providers/gcp/manager.py +0 -0
  47. desktop_env/providers/gcp/provider.py +0 -0
  48. desktop_env/providers/virtualbox/__init__.py +0 -0
  49. desktop_env/providers/virtualbox/manager.py +463 -0
  50. desktop_env/providers/virtualbox/provider.py +124 -0
  51. desktop_env/providers/vmware/__init__.py +0 -0
  52. desktop_env/providers/vmware/manager.py +455 -0
  53. desktop_env/providers/vmware/provider.py +105 -0
  54. gui_agents/__init__.py +0 -0
  55. gui_agents/agents/Action.py +209 -0
  56. gui_agents/agents/__init__.py +0 -0
  57. gui_agents/agents/agent_s.py +832 -0
  58. gui_agents/agents/global_state.py +610 -0
  59. gui_agents/agents/grounding.py +651 -0
  60. gui_agents/agents/hardware_interface.py +129 -0
  61. gui_agents/agents/manager.py +568 -0
  62. gui_agents/agents/translator.py +132 -0
  63. gui_agents/agents/worker.py +355 -0
  64. gui_agents/cli_app.py +560 -0
  65. gui_agents/core/__init__.py +0 -0
  66. gui_agents/core/engine.py +1496 -0
  67. gui_agents/core/knowledge.py +449 -0
  68. gui_agents/core/mllm.py +555 -0
  69. gui_agents/tools/__init__.py +0 -0
  70. gui_agents/tools/tools.py +727 -0
  71. gui_agents/unit_test/__init__.py +0 -0
  72. gui_agents/unit_test/run_tests.py +65 -0
  73. gui_agents/unit_test/test_manager.py +330 -0
  74. gui_agents/unit_test/test_worker.py +269 -0
  75. gui_agents/utils/__init__.py +0 -0
  76. gui_agents/utils/analyze_display.py +301 -0
  77. gui_agents/utils/common_utils.py +263 -0
  78. gui_agents/utils/display_viewer.py +281 -0
  79. gui_agents/utils/embedding_manager.py +53 -0
  80. gui_agents/utils/image_axis_utils.py +27 -0
  81. lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
  82. lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
  83. lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
  84. lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
  85. lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,637 @@
1
+ import os
2
+ import logging
3
+ from typing import List, Union
4
+ from skimage.metrics import structural_similarity as ssim
5
+ from PIL import Image, ImageChops, ImageStat
6
+
7
+
8
+ def compare_image_list(pred_img_path_list: Union[str, List[str]],
9
+ gold_img_path_list: Union[str, List[str]]) -> float:
10
+ """ Compare two image lists, only if all images are the same, return 1.0, otherwise return 0.0
11
+ """
12
+ if type(pred_img_path_list) != list:
13
+ pred_img_path_list = [pred_img_path_list]
14
+ gold_img_path_list = [gold_img_path_list]
15
+ for pred_img_path, gold_img_path in zip(pred_img_path_list, gold_img_path_list):
16
+ if not pred_img_path or not gold_img_path:
17
+ return 0.0
18
+ pred_img = Image.open(pred_img_path)
19
+ gold_img = Image.open(gold_img_path)
20
+ diff = ImageChops.difference(pred_img, gold_img)
21
+ if diff.getbbox():
22
+ return 0.0
23
+ return 1.0
24
+
25
+
26
+ def get_gimp_export_path():
27
+ # Path to GIMP's configuration file. This example assumes GIMP version 2.10.
28
+ # You need to adjust the path according to the GIMP version and user's file system.
29
+ gimp_config_file = os.path.expanduser("~/.config/GIMP/2.10/gimprc")
30
+
31
+ try:
32
+ # Open and read the configuration file
33
+ with open(gimp_config_file, 'r') as file:
34
+ for line in file:
35
+ # Search for the default export path setting
36
+ if "default-export-path" in line:
37
+ # Extract the current path from the line (assuming it's enclosed in quotes)
38
+ current_path = line.split('"')[1]
39
+ # Compare the current path with the expected path
40
+ return current_path
41
+ except FileNotFoundError:
42
+ # Handle the case where the configuration file is not found
43
+ logging.debug("GIMP configuration file not found")
44
+ return False
45
+
46
+
47
+ def check_file_exists(directory, filename):
48
+ file_path = os.path.join(directory, filename)
49
+ return 1 if os.path.isfile(file_path) else 0
50
+
51
+
52
+ def increase_saturation(image1_path: str, image2_path: str) -> float:
53
+ def calculate_saturation(image):
54
+ # convert the image to HSV mode
55
+ hsv_image = image.convert("HSV")
56
+
57
+ saturation_channel = hsv_image.split()[1]
58
+
59
+ # calculate the mean saturation level
60
+ stat = ImageStat.Stat(saturation_channel)
61
+ mean_saturation = stat.mean[0]
62
+
63
+ return mean_saturation
64
+
65
+ image1 = Image.open(image1_path)
66
+ image2 = Image.open(image2_path)
67
+
68
+ # calculate the saturation level of each image
69
+ saturation1 = calculate_saturation(image1)
70
+ saturation2 = calculate_saturation(image2)
71
+
72
+ return 1 if saturation1 < saturation2 else 0
73
+
74
+
75
+ def decrease_brightness(image1_path: str, image2_path: str) -> float:
76
+ def calculate_brightness(image):
77
+ # Convert the image to grayscale mode
78
+ grayscale_image = image.convert("L")
79
+
80
+ # Get the image data
81
+ pixels = list(grayscale_image.getdata())
82
+
83
+ brightness = sum(pixels) / len(pixels)
84
+ return brightness
85
+
86
+ image1 = Image.open(image1_path)
87
+ image2 = Image.open(image2_path)
88
+
89
+ brightness1 = calculate_brightness(image1)
90
+ brightness2 = calculate_brightness(image2)
91
+
92
+ return 1 if brightness1 > brightness2 else 0
93
+
94
+
95
+ import cv2
96
+ import numpy as np
97
+
98
+
99
+ def find_yellow_triangle(image):
100
+ # Convert the image to RGBA
101
+ rgba = cv2.cvtColor(image, cv2.COLOR_BGR2RGBA)
102
+
103
+ # define range of yellow color in HSV
104
+ lower_yellow = np.array([0, 0, 0], dtype=np.uint8)
105
+ upper_yellow = np.array([255, 255, 255], dtype=np.uint8)
106
+
107
+ # expand the dimensions of lower and upper yellow to match the image dimensions
108
+ lower_yellow = np.reshape(lower_yellow, (1, 1, 3))
109
+ upper_yellow = np.reshape(upper_yellow, (1, 1, 3))
110
+ # build a mask for the yellow color
111
+ mask = cv2.inRange(rgba, lower_yellow, upper_yellow)
112
+
113
+ # search for contours in the mask
114
+ contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
115
+
116
+ # choose the largest contour
117
+ max_contour = max(contours, key=cv2.contourArea)
118
+
119
+ # calculate the center of the contour
120
+ M = cv2.moments(max_contour)
121
+ cx = int(M['m10'] / M['m00'])
122
+ cy = int(M['m01'] / M['m00'])
123
+
124
+ return cx, cy
125
+
126
+
127
+ def compare_triangle_positions(image1, image2):
128
+ image1 = cv2.imread(image1, cv2.IMREAD_COLOR)
129
+ image2 = cv2.imread(image2, cv2.IMREAD_COLOR)
130
+ # find the center of the yellow triangle in each image
131
+ cx1, cy1 = find_yellow_triangle(image1)
132
+ cx2, cy2 = find_yellow_triangle(image2)
133
+
134
+ # calculate the distance between the center of the triangle and the center of the image
135
+ center_distance1 = np.sqrt(
136
+ (cx1 - image1.shape[1] // 2) ** 2 + (cy1 - image1.shape[0] // 2) ** 2)
137
+ center_distance2 = np.sqrt(
138
+ (cx2 - image2.shape[1] // 2) ** 2 + (cy2 - image2.shape[0] // 2) ** 2)
139
+
140
+ return 1 if center_distance1 > center_distance2 else 0
141
+
142
+
143
+ # Functions for the GIMP evaluator
144
+ def calculate_brightness(image):
145
+ """Calculate the average brightness of an image"""
146
+ grayscale = image.convert('L')
147
+ stat = ImageStat.Stat(grayscale)
148
+ return stat.mean[0]
149
+
150
+
151
+ def normalize_brightness(image, target_brightness):
152
+ """Normalize the brightness of an image to a target brightness in [0, 1]"""
153
+ current_brightness = calculate_brightness(image)
154
+ factor = target_brightness / current_brightness
155
+
156
+ # Apply a point transform to each pixel
157
+ def point_transform(x):
158
+ return min(255, max(0, int(x * factor)))
159
+
160
+ return image.point(point_transform)
161
+
162
+
163
+ def measure_saturation(hsv_image):
164
+ """Measure the average saturation of an image"""
165
+ # Split into H, S, V channels
166
+ _, s, _ = hsv_image.split()
167
+ # Convert the saturation channel to a numpy array
168
+ s_array = np.array(s)
169
+ # Calculate the average saturation
170
+ avg_saturation = np.mean(s_array)
171
+ return avg_saturation
172
+
173
+
174
+ def calculate_contrast(image):
175
+ """Calculate the contrast of an image as the standard deviation of the pixel
176
+ values."""
177
+ pixels = np.asarray(image, dtype=np.float32)
178
+ return np.std(pixels)
179
+
180
+
181
+ def calculate_image_sharpness(image_path):
182
+ # Load the image in grayscale
183
+ image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
184
+ # Apply the Laplacian operator
185
+ laplacian = cv2.Laplacian(image, cv2.CV_64F)
186
+ # Calculate the variance
187
+ variance = np.var(laplacian)
188
+ return variance
189
+
190
+
191
+ def structure_check_by_mse(img1, img2, threshold=0.03):
192
+ """Check if two images are approximately the same by MSE"""
193
+ mse = np.mean(
194
+ (np.array(img1, dtype=np.float32) / 255
195
+ - np.array(img2, dtype=np.float32) / 255) ** 2)
196
+ structure_same = True if mse < threshold else False
197
+ logging.debug(f"MSE: {mse}, threshold: {threshold}")
198
+ return structure_same
199
+
200
+
201
+ def structure_check_by_ssim(img1, img2, threshold=0.9):
202
+ """Check if two images are approximately the same by SSIM"""
203
+ similarity = ssim(np.array(img1), np.array(img2), multichannel=True, channel_axis=-1)
204
+ logging.debug("SSIM: %s", similarity)
205
+ return similarity >= threshold
206
+
207
+
208
+ def check_brightness_decrease_and_structure_sim(src_path, tgt_path, threshold=0.03):
209
+ """
210
+ Check the brightness of src is lower than tgt and the structures are similar
211
+ gimp:7a4deb26-d57d-4ea9-9a73-630f66a7b568
212
+ """
213
+ if src_path is None or tgt_path is None:
214
+ return 0.
215
+
216
+ img_src = Image.open(src_path)
217
+ img_tgt = Image.open(tgt_path)
218
+
219
+ # Brightness comparison
220
+ brightness_src = calculate_brightness(img_src)
221
+ brightness_tgt = calculate_brightness(img_tgt)
222
+ brightness_reduced = brightness_tgt > brightness_src
223
+
224
+ # print(f"Brightness src: {brightness_src}, tgt: {brightness_tgt}, reduced: {brightness_reduced}")
225
+
226
+ # Normalize and compare images
227
+ target_brightness = 128
228
+ img_src_normalized = normalize_brightness(img_src, target_brightness)
229
+ img_tgt_normalized = normalize_brightness(img_tgt, target_brightness)
230
+
231
+ structure_same = structure_check_by_mse(img_src_normalized, img_tgt_normalized, threshold=threshold)
232
+ if brightness_reduced and structure_same:
233
+ return 1.
234
+ else:
235
+ return 0.
236
+
237
+
238
+ def check_saturation_increase_and_structure_sim(src_path, tgt_path):
239
+ """
240
+ Check the saturation of src is higher than tgt and the structures are similar
241
+ gimp:554785e9-4523-4e7a-b8e1-8016f565f56a
242
+ """
243
+ if src_path is None or tgt_path is None:
244
+ return 0.
245
+
246
+ img_src = Image.open(src_path)
247
+ hsv_img_src = img_src.convert('HSV')
248
+ img_tgt = Image.open(tgt_path)
249
+ hsv_img_tgt = img_tgt.convert('HSV')
250
+
251
+ # Saturation comparison
252
+ src_saturation = measure_saturation(hsv_img_src)
253
+ tgt_saturation = measure_saturation(hsv_img_tgt)
254
+
255
+ saturation_increased = tgt_saturation < src_saturation
256
+
257
+ # Structure comparison
258
+ h1, s1, v1 = hsv_img_src.split()
259
+ h2, s2, v2 = hsv_img_tgt.split()
260
+ h_same = structure_check_by_ssim(h1, h2)
261
+ v_same = structure_check_by_ssim(v1, v2)
262
+ if h_same and v_same:
263
+ structure_same = True
264
+ else:
265
+ structure_same = False
266
+
267
+ if saturation_increased and structure_same:
268
+ return 1.
269
+ else:
270
+ return 0.
271
+
272
+
273
+ def check_file_exists_and_structure_sim(src_path, tgt_path):
274
+ """
275
+ Check if the image has been exported to the desktop
276
+ gimp:77b8ab4d-994f-43ac-8930-8ca087d7c4b4
277
+ """
278
+ if src_path is None or tgt_path is None:
279
+ return 0.
280
+
281
+ # Check if the file exists
282
+ export_file_exists = os.path.isfile(src_path)
283
+ if not export_file_exists:
284
+ return 0.
285
+
286
+ # Check whether the target image is the same as the source image
287
+ img_src = Image.open(src_path)
288
+ img_tgt = Image.open(tgt_path)
289
+ structure_same = structure_check_by_ssim(img_src, img_tgt)
290
+
291
+ if structure_same:
292
+ return 1.
293
+ else:
294
+ return 0.
295
+
296
+
297
+ def check_triangle_position(tgt_path):
298
+ """
299
+ Check if the triangle is in the middle of the image.
300
+ gimp:f4aec372-4fb0-4df5-a52b-79e0e2a5d6ce
301
+ """
302
+ if tgt_path is None:
303
+ return 0.
304
+
305
+ # Load the image
306
+ img = Image.open(tgt_path)
307
+ img_array = np.array(img)
308
+
309
+ # We assume the triangle is a different color from the background
310
+ # Find the unique colors
311
+ unique_colors, counts = np.unique(img_array.reshape(-1, img_array.shape[2]), axis=0,
312
+ return_counts=True)
313
+ unique_colors_sorted = unique_colors[np.argsort(counts)]
314
+
315
+ # Assuming the background is the most common color and the triangle is a different color
316
+ triangle_color = unique_colors_sorted[1]
317
+
318
+ # Create a mask where the triangle pixels are True
319
+ triangle_mask = np.all(img_array == triangle_color, axis=2)
320
+
321
+ # Get the coordinates of the triangle pixels
322
+ triangle_coords = np.argwhere(triangle_mask)
323
+
324
+ # Calculate the centroid of the triangle
325
+ centroid = triangle_coords.mean(axis=0)
326
+
327
+ # Check if the centroid is approximately in the middle of the image
328
+ image_center = np.array(img_array.shape[:2]) / 2
329
+
330
+ # We will consider the triangle to be in the middle if the centroid is within 5% of the image's center
331
+ tolerance = 0.05 * np.array(img_array.shape[:2])
332
+ middle = np.all(np.abs(centroid - image_center) < tolerance)
333
+
334
+ if bool(middle):
335
+ return 1.
336
+ else:
337
+ return 0.
338
+
339
+
340
+ def check_structure_sim(src_path, tgt_path):
341
+ """
342
+ Check if the structure of the two images are similar
343
+ gimp:2a729ded-3296-423d-aec4-7dd55ed5fbb3
344
+ """
345
+ if src_path is None or tgt_path is None:
346
+ return 0.
347
+
348
+ img_src = Image.open(src_path)
349
+ img_tgt = Image.open(tgt_path)
350
+ structure_same = structure_check_by_ssim(img_src, img_tgt)
351
+ if structure_same:
352
+ return 1.
353
+ else:
354
+ return 0.
355
+
356
+
357
+ def check_structure_sim_resized(src_path, tgt_path):
358
+ """
359
+ Check if the structure of the two images are similar after resizing.
360
+ gimp:d16c99dc-2a1e-46f2-b350-d97c86c85c15
361
+ """
362
+ if src_path is None or tgt_path is None:
363
+ return 0.
364
+
365
+ img_src = Image.open(src_path)
366
+ img_tgt = Image.open(tgt_path)
367
+
368
+ # Check if source image has transparency and extract content area
369
+ if img_src.mode in ('RGBA', 'LA') or 'transparency' in img_src.info:
370
+ if img_src.mode != 'RGBA':
371
+ img_src = img_src.convert('RGBA')
372
+
373
+ # Get alpha channel and find bounding box of non-transparent pixels
374
+ alpha = img_src.split()[-1]
375
+ bbox = alpha.getbbox()
376
+
377
+ if bbox is None:
378
+ # Image is completely transparent
379
+ logging.debug("Source image is completely transparent")
380
+ return 0.
381
+
382
+ # Crop to content area only
383
+ img_src_content = img_src.crop(bbox)
384
+ logging.debug(f"Source image cropped from {img_src.size} to {img_src_content.size}")
385
+
386
+ # Convert to RGB for comparison
387
+ img_src_content = img_src_content.convert('RGB')
388
+ img_src_resized = img_src_content.resize(img_tgt.size)
389
+ else:
390
+ # No transparency, resize normally
391
+ img_src_resized = img_src.resize(img_tgt.size)
392
+
393
+ # Ensure target image is RGB for comparison
394
+ if img_tgt.mode != 'RGB':
395
+ img_tgt = img_tgt.convert('RGB')
396
+
397
+ # Check if the structure is similar
398
+ structure_same = structure_check_by_ssim(img_src_resized, img_tgt)
399
+ if structure_same:
400
+ return 1.
401
+ else:
402
+ return 0.
403
+
404
+
405
+ def check_contrast_increase_and_structure_sim(src_path, tgt_path):
406
+ """
407
+ Check if the src image has higher contrast than the tgt image and the structures are similar
408
+ gimp:f723c744-e62c-4ae6-98d1-750d3cd7d79d
409
+ """
410
+ if src_path is None or tgt_path is None:
411
+ return 0.
412
+
413
+ # Load images
414
+ source_image = Image.open(src_path)
415
+ target_image = Image.open(tgt_path)
416
+
417
+ # Calculate contrast
418
+ source_contrast = calculate_contrast(source_image)
419
+ target_contrast = calculate_contrast(target_image)
420
+ higher_contrast = target_contrast < source_contrast
421
+
422
+ # Check structure
423
+ structure_same = structure_check_by_ssim(source_image, target_image, threshold=0.65)
424
+
425
+ if higher_contrast and structure_same:
426
+ return 1.
427
+ else:
428
+ return 0.
429
+
430
+
431
+ def check_config_status(actual_config_path, rule):
432
+ """
433
+ Check if the GIMP status is as expected
434
+ """
435
+ if actual_config_path is None:
436
+ return 0.
437
+
438
+ with open(actual_config_path, 'r') as f:
439
+ content = f.readlines()
440
+
441
+ for line in content:
442
+ if line.startswith('#') or line == '\n':
443
+ continue
444
+ items = line.strip().lstrip('(').rstrip(')\n').split()
445
+ if isinstance(rule["key"], str):
446
+ if items[0] == rule["key"] and items[-1] == rule["value"]:
447
+ return 1.
448
+ elif isinstance(rule["key"], list) and len(rule["key"]) == 2:
449
+ if items[0] == rule["key"][0] \
450
+ and items[1] == rule["key"][1] \
451
+ and items[-1] == rule["value"]:
452
+ return 1.
453
+ return 0.
454
+
455
+
456
+ def check_image_size(src_path, rule):
457
+ """
458
+ Check if the size of the src image is correct
459
+ multi-apps:42f4d1c7-4521-4161-b646-0a8934e36081
460
+ """
461
+ if src_path is None:
462
+ return 0.
463
+
464
+ # Load the image
465
+ img = Image.open(src_path)
466
+
467
+ # Check if we should ignore transparent parts
468
+ ignore_transparent = rule.get("ignore_transparent", False)
469
+
470
+ if ignore_transparent and img.mode in ('RGBA', 'LA') or 'transparency' in img.info:
471
+ # Calculate bounding box of non-transparent pixels
472
+ if img.mode != 'RGBA':
473
+ img = img.convert('RGBA')
474
+
475
+ # Get alpha channel
476
+ alpha = img.split()[-1]
477
+
478
+ # Find bounding box of non-transparent pixels
479
+ bbox = alpha.getbbox()
480
+
481
+ if bbox is None:
482
+ # Image is completely transparent
483
+ actual_width = 0
484
+ actual_height = 0
485
+ else:
486
+ # Calculate actual content size
487
+ actual_width = bbox[2] - bbox[0]
488
+ actual_height = bbox[3] - bbox[1]
489
+
490
+ logging.debug(f"Original size: {img.size}, Content size: {actual_width}x{actual_height}")
491
+ else:
492
+ # Use original image size
493
+ actual_width = img.size[0]
494
+ actual_height = img.size[1]
495
+ logging.debug(f"Image size: {img.size}")
496
+
497
+ # Check the size
498
+ if rule.get("height", None) is not None:
499
+ height_same = actual_height == rule["height"]
500
+ else:
501
+ height_same = True
502
+ if rule.get("width", None) is not None:
503
+ width_same = actual_width == rule["width"]
504
+ else:
505
+ width_same = True
506
+
507
+ if height_same and width_same:
508
+ logging.debug(f"height_same: {height_same}, width_same: {width_same}")
509
+ return 1.
510
+ else:
511
+ logging.debug(f"height_same: {height_same}, width_same: {width_same}")
512
+ return 0.
513
+
514
+
515
+ def check_palette_and_structure_sim(src_path, tgt_path):
516
+ """
517
+ Check if the src image is palette-based and the structure of the two images are similar
518
+ gimp:06ca5602-62ca-47f6-ad4f-da151cde54cc
519
+ """
520
+ if src_path is None or tgt_path is None:
521
+ return 0.
522
+
523
+ # Check if the source image is palette-based
524
+ source_image = Image.open(src_path)
525
+ palette_based = source_image.mode == 'P'
526
+
527
+ # Check structure
528
+ target_image = Image.open(tgt_path)
529
+ source_image = source_image.convert('RGB')
530
+ structure_same = structure_check_by_ssim(source_image, target_image)
531
+ if palette_based and structure_same:
532
+ return 1.
533
+ else:
534
+ return 0.
535
+
536
+
537
+ def check_textbox_on_leftside(src_path):
538
+ """
539
+ Check if the textbox is on the left side of the image.
540
+ gimp:e2dd0213-26db-4349-abe5-d5667bfd725c
541
+ """
542
+ if src_path is None:
543
+ return 0.
544
+
545
+ source_image = Image.open(src_path)
546
+ gray_image = source_image.convert("L")
547
+ width, height = source_image.size
548
+
549
+ # Find the bounds of the black text
550
+ left_most_dark_pixel = width # Start with the farthest possible left position
551
+ for y in range(height):
552
+ for x in range(width):
553
+ # If the pixel is dark, consider it as part of the text
554
+ if gray_image.getpixel((x, y)) < 128: # Arbitrary threshold for "dark"
555
+ left_most_dark_pixel = min(left_most_dark_pixel, x)
556
+ break # Stop after finding the first dark pixel in this row
557
+
558
+ # Here we define "almost" on the left side as being within the left 5% of the image
559
+ if left_most_dark_pixel < width * 0.05:
560
+ return 1.
561
+ else:
562
+ return 0.
563
+
564
+
565
+ def check_image_mirror(src_path, tgt_path):
566
+ """
567
+ Check if the image is mirrored
568
+ gimp:72f83cdc-bf76-4531-9a1b-eb893a13f8aa
569
+ """
570
+ if src_path is None or tgt_path is None:
571
+ return 0.
572
+
573
+ # Load images
574
+ source_image = Image.open(src_path)
575
+ target_image = Image.open(tgt_path)
576
+
577
+ # Check if the image is mirrored
578
+ transposed_image = source_image.transpose(Image.FLIP_LEFT_RIGHT)
579
+ # Use 0.99 because the image may not be exactly mirrored by gimp
580
+ mirrored = structure_check_by_ssim(transposed_image, target_image, 0.99)
581
+ if mirrored:
582
+ return 1.
583
+ else:
584
+ return 0.
585
+
586
+
587
+ def check_green_background(src_path, tgt_path):
588
+ """
589
+ Check if the background of the source image is green.
590
+ gimp:734d6579-c07d-47a8-9ae2-13339795476b
591
+ """
592
+ if src_path is None or tgt_path is None:
593
+ return 0.
594
+
595
+ # Load images
596
+ source_image = Image.open(src_path)
597
+ target_image = Image.open(tgt_path)
598
+
599
+ source_pixels = np.array(source_image)
600
+ target_pixels = np.array(target_image)
601
+
602
+ for x in range(target_image.width):
603
+ for y in range(target_image.height):
604
+ # Identify background pixel in target image (not black)
605
+ if tuple(target_pixels[x, y][:3]) != (0, 0, 0):
606
+ # Check if corresponding pixel in source image is green
607
+ # Here, "green" means more green than red or blue
608
+ r, g, b = source_pixels[x, y][:3]
609
+ if not (g > r and g > b):
610
+ return 0.
611
+
612
+ return 1.
613
+
614
+
615
+ def check_sharper(src_path, tgt_path):
616
+ """
617
+ Check if the source image is sharper than the target image.
618
+ multi-app:bb7db4c2-30b5-4be7-8dd7-b8c4ec7d3108
619
+ """
620
+ sharpness_src = calculate_image_sharpness(src_path)
621
+ sharpness_tgt = calculate_image_sharpness(tgt_path)
622
+ return 1.0 if sharpness_src > sharpness_tgt else 0.0
623
+
624
+
625
+ def check_image_file_size(src_path, rule):
626
+ """
627
+ Check if the size of the src image within 500KB
628
+ """
629
+ if src_path is None:
630
+ return 0.0
631
+
632
+ # Check the size
633
+ file_size = os.path.getsize(src_path)
634
+ if file_size < rule["max_size"]:
635
+ return 1.0
636
+ else:
637
+ return 0.0
@@ -0,0 +1,28 @@
1
+ import fnmatch
2
+ from typing import Dict, List
3
+
4
+ import lxml.cssselect
5
+ import lxml.etree
6
+ from lxml.etree import _Element as Element
7
+
8
+ _libconf_namespaces = [("oor", "http://openoffice.org/2001/registry")]
9
+ _libconf_ns_mapping = dict(_libconf_namespaces)
10
+ _setup_locale_selector = lxml.cssselect.CSSSelector('item[oor|path$=L10N]>prop[oor|name=ooSetupSystemLocale]>value',
11
+ namespaces=_libconf_ns_mapping)
12
+ _locale_selector = lxml.cssselect.CSSSelector('item[oor|path$=L10N]>prop[oor|name=ooLocale]>value',
13
+ namespaces=_libconf_ns_mapping)
14
+
15
+
16
+ def check_libre_locale(config_file: str, rules: Dict[str, List[str]]) -> float:
17
+ config: Element = lxml.etree.parse(config_file).getroot()
18
+ setup_locale_setting: List[Element] = _setup_locale_selector(config)
19
+ locale_setting: List[Element] = _locale_selector(config)
20
+
21
+ setup_locale_setting: str = setup_locale_setting[0].text \
22
+ if len(setup_locale_setting) > 0 \
23
+ else locale_setting[0].text
24
+
25
+ return float(any(fnmatch.fnmatchcase(setup_locale_setting, ptn) \
26
+ for ptn in rules["locale_set"]
27
+ )
28
+ )