Spaces:
Paused
Paused
| import cv2 | |
| import pytesseract | |
| from PIL import Image, ImageDraw, ImageFont | |
| import numpy as np | |
| import argparse | |
| import io | |
| import base64 | |
| class AndroidEditor: | |
| def __init__(self, font_path="Roboto-Regular.ttf"): | |
| self.font_path = font_path | |
| def find_text_position(text_list, target_text, start_idx=0): | |
| """Mencari posisi teks target dalam list teks""" | |
| for i in range(start_idx, len(text_list)): | |
| if target_text in text_list[i]: | |
| return i | |
| return None | |
| def get_position_data(extracted_data, idx): | |
| """Mendapatkan data posisi dari indeks tertentu""" | |
| if idx is None or idx >= len(extracted_data['left']): | |
| return None | |
| return { | |
| "left": extracted_data['left'][idx], | |
| "top": extracted_data['top'][idx], | |
| "width": extracted_data['width'][idx], | |
| "height": extracted_data['height'][idx], | |
| } | |
| def is_dark_mode(bg_color): | |
| """Mendeteksi apakah background menggunakan dark mode berdasarkan kecerahan warna""" | |
| r, g, b = float(bg_color[0]), float(bg_color[1]), float(bg_color[2]) | |
| brightness = (r * 299 + g * 587 + b * 114) / 1000 | |
| return brightness < 128 | |
| def process_image(self, image_path, anggota): | |
| image = cv2.imread(image_path) | |
| if image is None: | |
| return | |
| result = self._process_core(image, anggota, show_preview=True) | |
| return result | |
| def process_image_bytes(self, image_bytes, anggota): | |
| image_stream = io.BytesIO(image_bytes) | |
| pil_image = Image.open(image_stream).convert('RGB') | |
| image = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) | |
| result, theme = self._process_core(image, anggota, show_preview=False, return_theme=True) | |
| if result is not None: | |
| pil_result = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) | |
| output_io = io.BytesIO() | |
| pil_result.save(output_io, format='PNG') | |
| img_b64 = base64.b64encode(output_io.getvalue()).decode('utf-8') | |
| return img_b64, theme | |
| return None, None | |
| def _process_core(self, image, anggota, show_preview=False, return_theme=False): | |
| # Ekstrak teks dari gambar | |
| extracted_data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT) | |
| text_list = extracted_data['text'] | |
| # Inisialisasi variabel posisi | |
| group_position = None | |
| split_position = None | |
| member_position = None | |
| member_count_position = None | |
| second_member_position = None | |
| second_member_count_position = None | |
| lang = '' | |
| group_idx = self.find_text_position(text_list, "Grup") | |
| if group_idx is None: | |
| group_idx = self.find_text_position(text_list, "Group") | |
| if group_idx is not None: | |
| lang = 'id' if "Grup" in text_list[group_idx] else 'en' | |
| group_position = self.get_position_data(extracted_data, group_idx) | |
| split_idx = self.find_text_position(text_list, "路", group_idx) | |
| if split_idx is None: | |
| for i in range(group_idx, min(group_idx + 4, len(text_list))): | |
| if "-" in text_list[i]: | |
| split_idx = i | |
| break | |
| split_position = self.get_position_data(extracted_data, split_idx) | |
| member_idx = self.find_text_position(text_list, "anggota", group_idx) | |
| if member_idx is None: | |
| member_idx = self.find_text_position(text_list, "member", group_idx) | |
| member_position = self.get_position_data(extracted_data, member_idx) | |
| for i in range(group_idx, min(group_idx + 5, len(text_list))): | |
| if text_list[i].isdigit(): | |
| member_count_position = self.get_position_data(extracted_data, i) | |
| break | |
| second_member_idx = self.find_text_position(text_list, "Anggota", group_idx + 4) | |
| if second_member_idx is not None: | |
| second_member_position = self.get_position_data(extracted_data, second_member_idx) | |
| for i in range(second_member_idx - 3, second_member_idx): | |
| if i >= 0 and text_list[i].isdigit(): | |
| second_member_count_position = self.get_position_data(extracted_data, i) | |
| break | |
| else: | |
| return None | |
| if member_position is None: | |
| return None | |
| x = member_position['left'] + member_position['width'] + 100 | |
| y = member_position['top'] + member_position['height'] - 100 | |
| bg_color = image[y, x] | |
| rgb = (int(bg_color[0]), int(bg_color[1]), int(bg_color[2])) | |
| is_dark = self.is_dark_mode(bg_color) | |
| theme = 'Dark Mode' if is_dark else 'Light Mode' | |
| text_color = (147,151,154,255) if is_dark else (90, 94, 95, 255) | |
| for position in [group_position, split_position, member_position, member_count_position, | |
| second_member_position, second_member_count_position]: | |
| if position: | |
| margin_horizontal = 10 | |
| cv2.rectangle( | |
| image, | |
| (position['left'] - margin_horizontal, position['top']), | |
| (position['left'] + position['width'] + margin_horizontal, position['top'] + position['height']), | |
| rgb, | |
| -1, | |
| ) | |
| if member_count_position: | |
| updated_member_count = { | |
| 'id': f"Grup 路 {anggota} anggota", | |
| 'en': f"Group 路 {anggota} members" | |
| }.get(lang) | |
| original_height = member_count_position['height'] | |
| original_width = member_count_position['width'] | |
| font_size = int(original_height * 2.0) | |
| font = ImageFont.truetype(self.font_path, font_size) | |
| image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) | |
| draw = ImageDraw.Draw(image_pil) | |
| min_size = int(original_height * 1.5) | |
| max_size = int(original_height * 2.5) | |
| max_iterations = 10 | |
| iteration = 0 | |
| while min_size <= max_size and iteration < max_iterations: | |
| font = ImageFont.truetype(self.font_path, font_size) | |
| text_bbox = draw.textbbox((0, 0), updated_member_count, font=font) | |
| text_height = text_bbox[3] - text_bbox[1] | |
| text_width = text_bbox[2] - text_bbox[0] | |
| if abs(text_height - original_height) <= 2 and text_width <= original_width * 1.5: | |
| break | |
| if text_height > original_height or text_width > original_width * 1.5: | |
| font_size = int(font_size * 0.95) | |
| else: | |
| font_size = int(font_size * 1.05) | |
| font_size = max(min_size, min(max_size, font_size)) | |
| iteration += 1 | |
| text_width = text_bbox[2] - text_bbox[0] | |
| image_width = image.shape[1] | |
| text_x = (image_width - text_width) // 2 | |
| text_y = member_count_position['top'] - 2 | |
| draw.text((text_x, text_y), updated_member_count, font=font, fill=text_color) | |
| image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR) | |
| if second_member_count_position: | |
| updated_second_member_count = { | |
| 'id': f"{anggota} Anggota", | |
| 'en': f"{anggota} Members" | |
| }.get(lang) | |
| original_height = second_member_count_position['height'] | |
| original_width = second_member_count_position['width'] | |
| font_size = int(original_height * 2.0) | |
| font = ImageFont.truetype(self.font_path, font_size) | |
| image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) | |
| draw = ImageDraw.Draw(image_pil) | |
| min_size = int(original_height * 1.5) | |
| max_size = int(original_height * 2.5) | |
| max_iterations = 10 | |
| iteration = 0 | |
| while min_size <= max_size and iteration < max_iterations: | |
| font = ImageFont.truetype(self.font_path, font_size) | |
| text_bbox = draw.textbbox((0, 0), updated_second_member_count, font=font) | |
| text_height = text_bbox[3] - text_bbox[1] | |
| text_width = text_bbox[2] - text_bbox[0] | |
| if abs(text_height - original_height) <= 2 and text_width <= original_width * 1.5: | |
| break | |
| if text_height > original_height or text_width > original_width * 1.5: | |
| font_size = int(font_size * 0.95) | |
| else: | |
| font_size = int(font_size * 1.05) | |
| font_size = max(min_size, min(max_size, font_size)) | |
| iteration += 1 | |
| text_width = text_bbox[2] - text_bbox[0] | |
| text_x = second_member_count_position['left'] | |
| text_y = second_member_count_position['top'] - 2 | |
| draw.text((text_x, text_y), updated_second_member_count, font=font, fill=text_color) | |
| image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR) | |
| cv2.imwrite('output.png', image) | |
| if show_preview: | |
| cv2.imshow('Preview (Tekan q untuk keluar)', image) | |
| while True: | |
| key = cv2.waitKey(1) & 0xFF | |
| if key == ord('q'): | |
| break | |
| cv2.destroyAllWindows() | |
| if return_theme: | |
| return image, theme | |
| return image | |
| if __name__ == '__main__': | |
| parser = argparse.ArgumentParser(description='Proses gambar grup') | |
| parser.add_argument('image_path', help='Path ke file gambar') | |
| parser.add_argument('anggota', help='Jumlah anggota') | |
| args = parser.parse_args() | |
| editor = AndroidEditor() | |
| editor.process_image(args.image_path, args.anggota) | |