kerenmasku commited on
Commit
d9212ab
·
verified ·
1 Parent(s): b001f29

Upload bottom.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. bottom.py +298 -0
bottom.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytesseract
2
+ import cv2
3
+ import numpy as np
4
+ from PIL import Image, ImageDraw, ImageFont
5
+ import argparse
6
+
7
+ class AndroidBottom:
8
+ def __init__(self, font_path_medium="Roboto-Medium.ttf", font_path_regular="Roboto-Regular.ttf"):
9
+ self.font_path_medium = font_path_medium
10
+ self.font_path_regular = font_path_regular
11
+
12
+ @staticmethod
13
+ def is_dark_mode(bg_color):
14
+ """Deteksi dark mode dari warna background (BGR)."""
15
+ r, g, b = float(bg_color[2]), float(bg_color[1]), float(bg_color[0])
16
+ brightness = (r * 299 + g * 587 + b * 114) / 1000
17
+ return brightness < 128
18
+
19
+ def extract_anggota_count_from_ocr(self, data):
20
+ for i, text in enumerate(data['text']):
21
+ if text.lower() in ["anggota", "members"]:
22
+ if i > 0 and data['text'][i-1].isdigit():
23
+ return int(data['text'][i-1])
24
+ return 0
25
+
26
+ def replace_anggota(self, image, jumlah, font_path=None, show_preview=False, return_theme=False, skip_ocr=False, extracted_data=None, get_ocr_count_only=False):
27
+ """Process image and return result with optional parameters"""
28
+ try:
29
+ # Handle image path or image array
30
+ if isinstance(image, str):
31
+ # If image is a path, read it
32
+ image = cv2.imread(image)
33
+ if image is None:
34
+ print(f"Error: Tidak dapat membaca gambar {image}")
35
+ return None
36
+
37
+ # Use provided OCR data or perform OCR
38
+ if skip_ocr and extracted_data is not None:
39
+ data = extracted_data
40
+ else:
41
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
42
+ data = pytesseract.image_to_data(gray, output_type=pytesseract.Output.DICT)
43
+
44
+ if get_ocr_count_only:
45
+ return self.extract_anggota_count_from_ocr(data)
46
+
47
+ h, w = image.shape[:2]
48
+ found = False
49
+ for i, text in enumerate(data['text']):
50
+ if text.lower() in ["anggota", "members"]:
51
+ if i > 0 and data['text'][i-1].isdigit():
52
+ found = True
53
+ # Tentukan label pengganti sesuai bahasa
54
+ label = text if text.lower() in ["anggota", "members"] else "anggota"
55
+ replace_text = f"{jumlah} {label}"
56
+
57
+ x = min(data['left'][i-1], data['left'][i])
58
+ y = min(data['top'][i-1], data['top'][i])
59
+ w_box = data['width'][i-1] + data['width'][i]
60
+ h_box = max(data['height'][i-1], data['height'][i])
61
+ margin = 10
62
+
63
+ # Ambil warna background dari 10% ke kanan dari posisi anggota
64
+ anggota_right = x + w_box
65
+ bg_x = int(anggota_right + (w * 0.1)) # 10% ke kanan dari anggota
66
+ bg_x = min(bg_x, w - 1) # Pastikan tidak melebihi lebar gambar
67
+ bg_color = image[y + h_box//2, bg_x] # Ambil dari tengah tinggi anggota
68
+
69
+ dark_mode = self.is_dark_mode(bg_color)
70
+ theme = 'Dark Mode' if dark_mode else 'Light Mode'
71
+ if dark_mode:
72
+ rect_color = tuple(int(x) for x in bg_color)
73
+ text_color = (88,92,98,255)
74
+ else:
75
+ rect_color = tuple(int(x) for x in bg_color)
76
+ text_color = (90, 94, 95, 255)
77
+
78
+ # Deteksi sederhana ketebalan font (bold/regular)
79
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
80
+ roi = gray[y:y+h_box, x:x+w_box]
81
+ _, binary = cv2.threshold(roi, 180, 255, cv2.THRESH_BINARY_INV)
82
+ black_ratio = np.sum(binary == 255) / (w_box * h_box)
83
+ print(f"Rasio pixel hitam di area: {black_ratio:.2f}")
84
+ if black_ratio > 0.25:
85
+ print("Kemungkinan besar: Bold")
86
+ auto_font_path = self.font_path_medium
87
+ else:
88
+ print("Kemungkinan besar: Regular/Tipis")
89
+ auto_font_path = self.font_path_regular
90
+
91
+ # Jika user tidak override font, pakai auto_font_path
92
+ effective_font_path = font_path if font_path not in [None, "", "auto"] else auto_font_path
93
+ # Pastikan effective_font_path adalah string path
94
+ if not isinstance(effective_font_path, str) or not effective_font_path:
95
+ effective_font_path = auto_font_path
96
+
97
+ extra_margin_right = 20 # misal 20px ke kanan
98
+ cv2.rectangle(
99
+ image,
100
+ (x - margin, y - margin),
101
+ (x + w_box + margin + extra_margin_right, y + h_box + margin),
102
+ rect_color,
103
+ -1
104
+ )
105
+
106
+ # Tulis teks pengganti dengan PIL agar font bisa custom
107
+ image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
108
+ draw = ImageDraw.Draw(image_pil)
109
+ try:
110
+ font = ImageFont.truetype(effective_font_path, int(h_box * 1.2))
111
+ except Exception as e:
112
+ print(f"Font error: {e}, fallback ke default font.")
113
+ font = ImageFont.load_default()
114
+ bbox = draw.textbbox((0, 0), replace_text, font=font)
115
+ text_width = bbox[2] - bbox[0]
116
+ text_height = bbox[3] - bbox[1]
117
+ # Geser teks ke kanan juga
118
+ text_x = data['left'][i-1]
119
+ text_y = data['top'][i-1] + (data['height'][i-1] - text_height) // 2
120
+ draw.text((text_x, text_y), replace_text, font=font, fill=text_color)
121
+ image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
122
+ break
123
+
124
+ if not found:
125
+ print("Tidak ditemukan pasangan angka + 'anggota'/'members'.")
126
+ return None
127
+
128
+ # cv2.imwrite('output.png', image)
129
+ # if show_preview:
130
+ # cv2.imshow('Preview (Tekan q untuk keluar)', image)
131
+ # while True:
132
+ # key = cv2.waitKey(1) & 0xFF
133
+ # if key == ord('q'):
134
+ # break
135
+ # cv2.destroyAllWindows()
136
+
137
+ result = image if not return_theme else (image, theme)
138
+ return result
139
+
140
+ except Exception as e:
141
+ print(f"Error in replace_anggota: {str(e)}")
142
+ return None
143
+
144
+ class IPhoneBottom:
145
+ def __init__(self, font_path_medium="SFUIText-Bold.otf", font_path_regular="SFUIText-Semibold.otf"):
146
+ self.font_path_medium = font_path_medium
147
+ self.font_path_regular = font_path_regular
148
+
149
+ @staticmethod
150
+ def is_dark_mode(bg_color):
151
+ """Deteksi dark mode dari warna background (BGR)."""
152
+ r, g, b = float(bg_color[2]), float(bg_color[1]), float(bg_color[0])
153
+ brightness = (r * 299 + g * 587 + b * 114) / 1000
154
+ return brightness < 128
155
+
156
+ def extract_anggota_count_from_ocr(self, data):
157
+ for i, text in enumerate(data['text']):
158
+ if text.lower() in ["anggota", "members"]:
159
+ if i > 0 and data['text'][i-1].isdigit():
160
+ return int(data['text'][i-1])
161
+ return 0
162
+
163
+ def replace_anggota(self, image, jumlah, font_path=None, show_preview=False, return_theme=False, skip_ocr=False, extracted_data=None, get_ocr_count_only=False):
164
+ """Process image and return result with optional parameters"""
165
+ try:
166
+ # Handle image path or image array
167
+ if isinstance(image, str):
168
+ # If image is a path, read it
169
+ image = cv2.imread(image)
170
+ if image is None:
171
+ print(f"Error: Tidak dapat membaca gambar {image}")
172
+ return None
173
+
174
+ # Use provided OCR data or perform OCR
175
+ if skip_ocr and extracted_data is not None:
176
+ data = extracted_data
177
+ else:
178
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
179
+ scale = 2
180
+ processing = cv2.resize(gray, (gray.shape[1]*scale, gray.shape[0]*scale), interpolation=cv2.INTER_CUBIC)
181
+ data = pytesseract.image_to_data(processing, output_type=pytesseract.Output.DICT)
182
+
183
+ if get_ocr_count_only:
184
+ return self.extract_anggota_count_from_ocr(data)
185
+
186
+ h, w = image.shape[:2]
187
+ found = False
188
+ for i, text in enumerate(data['text']):
189
+ if text.lower() in ["anggota", "members"]:
190
+ if i > 0 and data['text'][i-1].isdigit():
191
+ found = True
192
+ # Tentukan label pengganti sesuai bahasa
193
+ label = text if text.lower() in ["anggota", "members"] else "anggota"
194
+ replace_text = f"{jumlah} {label}"
195
+
196
+ # Konversi koordinat hasil OCR ke gambar asli
197
+ if extracted_data is None:
198
+ scale = 2
199
+ x = int(min(data['left'][i-1], data['left'][i]) / scale)
200
+ y = int(min(data['top'][i-1], data['top'][i]) / scale)
201
+ w_box = int((data['width'][i-1] + data['width'][i]) / scale)
202
+ h_box = int(max(data['height'][i-1], data['height'][i]) / scale)
203
+ else:
204
+ x = min(data['left'][i-1], data['left'][i])
205
+ y = min(data['top'][i-1], data['top'][i])
206
+ w_box = data['width'][i-1] + data['width'][i]
207
+ h_box = max(data['height'][i-1], data['height'][i])
208
+ margin = 10
209
+
210
+ # Ambil warna background dari 10% ke kanan dari posisi anggota
211
+ anggota_right = x + w_box
212
+ bg_x = int(anggota_right + (w * 0.1)) # 10% ke kanan dari anggota
213
+ bg_x = min(bg_x, w - 1) # Pastikan tidak melebihi lebar gambar
214
+ bg_color = image[y + h_box//2, bg_x] # Ambil dari tengah tinggi anggota
215
+
216
+ dark_mode = self.is_dark_mode(bg_color)
217
+ theme = 'Dark Mode' if dark_mode else 'Light Mode'
218
+ if dark_mode:
219
+ print("MANTAP MEN")
220
+ rect_color = (10, 10, 10, 255)
221
+ text_color = (244, 244, 244, 255)
222
+ else:
223
+ rect_color = tuple(int(x) for x in bg_color)
224
+ text_color = (0, 0, 0, 255)
225
+
226
+ # Deteksi sederhana ketebalan font (bold/regular)
227
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
228
+ roi = gray[y:y+h_box, x:x+w_box]
229
+ _, binary = cv2.threshold(roi, 180, 255, cv2.THRESH_BINARY_INV)
230
+ black_ratio = np.sum(binary == 255) / (w_box * h_box)
231
+ if black_ratio > 0.5:
232
+ print("Bold")
233
+ auto_font_path = self.font_path_medium
234
+ else:
235
+ print("Regular")
236
+ auto_font_path = self.font_path_regular
237
+
238
+ # Jika user tidak override font, pakai auto_font_path
239
+ effective_font_path = font_path if font_path not in [None, "", "auto"] else auto_font_path
240
+ # Pastikan effective_font_path adalah string path
241
+ if not isinstance(effective_font_path, str) or not effective_font_path:
242
+ effective_font_path = auto_font_path
243
+
244
+ extra_margin_right = 20
245
+ cv2.rectangle(
246
+ image,
247
+ (x - margin, y - margin),
248
+ (x + w_box + margin + extra_margin_right, y + h_box + margin),
249
+ rect_color,
250
+ -1
251
+ )
252
+
253
+ # Tulis teks pengganti dengan PIL agar font bisa custom
254
+ image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
255
+ draw = ImageDraw.Draw(image_pil)
256
+ try:
257
+ font = ImageFont.truetype(effective_font_path, int(h_box * 1.2))
258
+ except Exception as e:
259
+ print(f"Font error: {e}, fallback ke default font.")
260
+ font = ImageFont.load_default()
261
+ bbox = draw.textbbox((0, 0), replace_text, font=font)
262
+ text_width = bbox[2] - bbox[0]
263
+ text_height = bbox[3] - bbox[1]
264
+ text_x = x
265
+ text_y = y + (h_box - text_height) // 2
266
+ draw.text((text_x, text_y), replace_text, font=font, fill=text_color)
267
+ image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
268
+ break
269
+
270
+ if not found:
271
+ print("Tidak ditemukan pasangan angka + 'anggota'/'members'.")
272
+ return None
273
+
274
+ # cv2.imwrite('output.png', image)
275
+ # if show_preview:
276
+ # cv2.imshow('Preview (Tekan q untuk keluar)', image)
277
+ # while True:
278
+ # key = cv2.waitKey(1) & 0xFF
279
+ # if key == ord('q'):
280
+ # break
281
+ # cv2.destroyAllWindows()
282
+
283
+ result = image if not return_theme else (image, theme)
284
+ return result
285
+
286
+ except Exception as e:
287
+ print(f"Error in replace_anggota: {str(e)}")
288
+ return None
289
+
290
+
291
+ if __name__ == "__main__":
292
+ parser = argparse.ArgumentParser(description="OCR dan replace jumlah anggota/members dengan angka custom.")
293
+ parser.add_argument("image_path", help="Path ke file gambar")
294
+ parser.add_argument("jumlah", help="Jumlah anggota/members yang diinginkan (angka saja)")
295
+ parser.add_argument("--font", default=None, help="Path font TTF (override, opsional)")
296
+ args = parser.parse_args()
297
+ replacer = IPhoneBottom()
298
+ replacer.replace_anggota(args.image_path, args.jumlah, args.font)