Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image | |
| import cv2 | |
| import time | |
| # دالة لقياس أداء الدوال | |
| def timing_decorator(func): | |
| def wrapper(*args, **kwargs): | |
| start_time = time.time() | |
| result = func(*args, **kwargs) | |
| end_time = time.time() | |
| print(f"⏱️ {func.__name__} استغرقت: {end_time - start_time:.2f} ثانية") | |
| return result | |
| return wrapper | |
| def get_mask_bbox(mask: Image.Image): | |
| """نسخة محسنة من الدالة""" | |
| try: | |
| mask_np = np.array(mask) | |
| if len(mask_np.shape) == 3: | |
| # استخدام القناة الأخيرة فقط (عادةً ما تكون الألفا) | |
| mask_np = mask_np[..., -1] if mask_np.shape[2] == 4 else mask_np[..., 0] | |
| # إيجاد النقاط غير الصفرية بشكل أكثر كفاءة | |
| nonzero_indices = np.where(mask_np > 0) | |
| if len(nonzero_indices[0]) == 0: | |
| return None | |
| y_min, y_max = np.min(nonzero_indices[0]), np.max(nonzero_indices[0]) | |
| x_min, x_max = np.min(nonzero_indices[1]), np.max(nonzero_indices[1]) | |
| return int(x_min), int(y_min), int(x_max), int(y_max) | |
| except Exception as e: | |
| print(f"خطأ في get_mask_bbox: {e}") | |
| return None | |
| def mask_to_alpha(mask: Image.Image, smooth_edges=True, morph_level=0): | |
| # تحسين استخراج قناة ألفا من القناع | |
| if mask.mode == 'RGBA': | |
| mask_np = np.array(mask)[..., -1] # استخدام قناة ألفا مباشرة | |
| else: | |
| mask_np = np.array(mask.convert("L")) | |
| if not smooth_edges: | |
| mask_np = np.where(mask_np > 127, 255, 0).astype(np.uint8) | |
| if morph_level > 0: | |
| mask_np = cv2.GaussianBlur(mask_np, (0, 0), sigmaX=morph_level, sigmaY=morph_level, borderType=cv2.BORDER_DEFAULT) | |
| mask_np = np.clip(mask_np, 0, 255).astype(np.uint8) | |
| return Image.fromarray(mask_np) | |
| def crop_masked_object(img: Image.Image, mask: Image.Image, smooth_edges=True, morph_level=0, scale=1.0, rotation=0, transparency=1.0): | |
| bbox = get_mask_bbox(mask) | |
| if bbox is None: | |
| return None, None | |
| xmin, ymin, xmax, ymax = bbox | |
| # قص الجزء المحدد فقط من الصورة | |
| cropped_img = img.crop((xmin, ymin, xmax + 1, ymax + 1)).convert("RGBA") | |
| cropped_mask = mask.crop((xmin, ymin, xmax + 1, ymax + 1)) | |
| # تطبيق القناع على الصورة لاستخراج العنصر فقط | |
| alpha_mask = mask_to_alpha(cropped_mask, smooth_edges, morph_level) | |
| cropped_img.putalpha(alpha_mask) | |
| if scale != 1.0: | |
| new_size = (int(cropped_img.size[0] * scale), int(cropped_img.size[1] * scale)) | |
| cropped_img = cropped_img.resize(new_size, Image.LANCZOS) | |
| if rotation != 0: | |
| cropped_img = cropped_img.rotate(rotation, expand=True) | |
| if transparency < 1.0: | |
| alpha = cropped_img.split()[-1].point(lambda p: int(p * transparency)) | |
| cropped_img.putalpha(alpha) | |
| return cropped_img, bbox | |
| def paste_object_on_mask(base_img, base_mask, obj_img, obj_bbox, fit_mode="size"): | |
| base_bbox = get_mask_bbox(base_mask) | |
| if base_bbox is None or obj_bbox is None: | |
| return None | |
| bxmin, bymin, bxmax, bymax = base_bbox | |
| bw, bh = bxmax - bxmin + 1, bymax - bymin + 1 | |
| ow, oh = obj_img.size | |
| if fit_mode == "size": | |
| # حساب عامل التحجيم لتكبير أو تصغير الكائن ليتناسب مع القناع (الحفاظ على التناسب) | |
| scale_factor = min(bw / ow, bh / oh) | |
| new_ow, new_oh = int(ow * scale_factor), int(oh * scale_factor) | |
| obj_img = obj_img.resize((new_ow, new_oh), Image.LANCZOS) | |
| x_paste = bxmin + (bw - new_ow) // 2 | |
| y_paste = bymin + (bh - new_oh) // 2 | |
| else: # fit_mode == "shape" | |
| # تغيير حجم الكائن ليملأ القناع تماماً (قد يؤدي إلى تشويه) | |
| obj_img = obj_img.resize((bw, bh), Image.LANCZOS) | |
| x_paste = bxmin | |
| y_paste = bymin | |
| # إنشاء نتيجة بنفس حجم الصورة الأساسية | |
| result = base_img.copy().convert("RGBA") | |
| result.paste(obj_img, (x_paste, y_paste), obj_img) | |
| return result | |
| def extract_mask_from_editor(editor_data): | |
| """استخراج القناع من بيانات ImageMask - نسخة محسنة""" | |
| if editor_data is None: | |
| return None | |
| # إذا تم رفع قناع جاهز | |
| if isinstance(editor_data, Image.Image): | |
| return editor_data.convert("L") | |
| # إذا تم استخدام أداة الرسم | |
| if isinstance(editor_data, dict): | |
| # محاولة استخراج القناع من الطبقات | |
| if "layers" in editor_data and len(editor_data["layers"]) > 0: | |
| layer = editor_data["layers"][0] | |
| if isinstance(layer, Image.Image): | |
| # تحويل الطبقة إلى تدرج رمادي | |
| if layer.mode == 'RGBA': | |
| # استخدام قناة ألفا كمقنع | |
| return layer.split()[-1] | |
| else: | |
| return layer.convert("L") | |
| # إذا لم توجد طبقات، استخدام الخلفية لإنشاء قناع فارغ | |
| elif "background" in editor_data: | |
| bg = editor_data["background"] | |
| if isinstance(bg, Image.Image): | |
| return Image.new("L", bg.size, 0) | |
| return None | |
| def process_transfer(image1_editor, mask1_upload, image2_editor, mask2_upload, | |
| scale, transparency, rotation, smooth_edges, morph_level, fit_mode): | |
| # التحقق من وجود الصور | |
| if image1_editor is None or image2_editor is None: | |
| return None, None, None, "**❌ يرجى رفع الصورتين وتحديد الأقنعة." | |
| # استخراج الصور الأساسية | |
| if isinstance(image1_editor, dict) and "background" in image1_editor: | |
| base_img = image1_editor["background"].convert("RGBA") | |
| else: | |
| base_img = image1_editor.convert("RGBA") if isinstance(image1_editor, Image.Image) else None | |
| # استخراج الصورة الثانية | |
| if isinstance(image2_editor, dict) and "background" in image2_editor: | |
| obj_img = image2_editor["background"].convert("RGBA") | |
| else: | |
| obj_img = image2_editor.convert("RGBA") if isinstance(image2_editor, Image.Image) else None | |
| if base_img is None or obj_img is None: | |
| return None, None, None, "**❌ يرجى رفع الصورتين." | |
| # استخراج الأقنعة للصورة الأولى | |
| base_mask = extract_mask_from_editor(mask1_upload) if mask1_upload is not None else extract_mask_from_editor(image1_editor) | |
| # استخراج الأقنعة للصورة الثانية (إما من الرسم أو الرفع) | |
| obj_mask = extract_mask_from_editor(mask2_upload) if mask2_upload is not None else extract_mask_from_editor(image2_editor) | |
| if base_mask is None or obj_mask is None: | |
| return None, None, None, "**❌ يجب رسم أو رفع قناع على كلتا الصورتين." | |
| # التحقق من أن الأقنعة تحتوي على مناطق محددة | |
| base_bbox = get_mask_bbox(base_mask) | |
| obj_bbox = get_mask_bbox(obj_mask) | |
| if base_bbox is None: | |
| return None, None, None, "**❌ لم يتم تحديد أي منطقة في الصورة الأولى. يرجى الرسم أو رفع قناع." | |
| if obj_bbox is None: | |
| return None, None, None, "**❌ لم يتم تحديد أي كائن في الصورة الثانية. يرجى الرسم أو رفع قناع." | |
| # قص الجزء المحدد من الصورة الثانية (العنصر المحدد فقط) | |
| cropped_obj, obj_bbox = crop_masked_object(obj_img, obj_mask, smooth_edges, morph_level, scale, rotation, transparency) | |
| if cropped_obj is None: | |
| return None, None, None, "**❌ لم يتم تحديد أي كائن في الصورة الثانية." | |
| # لصق الجزء في منطقة القناع بالصورة الأولى | |
| result_img = paste_object_on_mask(base_img, base_mask, cropped_obj, obj_bbox, fit_mode) | |
| if result_img is None: | |
| return None, None, None, "**❌ لم يتم تحديد منطقة مناسبة للصق في الصورة الأولى." | |
| # التأكد من أن الصورة الناتجة بنفس حجم الصورة الأساسية | |
| if result_img.size != base_img.size: | |
| result_img = result_img.resize(base_img.size, Image.LANCZOS) | |
| return base_mask, obj_mask, result_img, "**✅ تم نقل الكائن بنجاح!" | |
| def create_interface(): | |
| css = """ | |
| .silver-border { | |
| border: 3px solid #666666 !important; | |
| border-radius: 12px !important; | |
| box-shadow: 0 0 8px #b0b0b0; | |
| background: #f8f8f8; | |
| } | |
| .transfer-btn { | |
| background: linear-gradient(145deg, #FF8C00, #FF6B00) !important; | |
| color: white !important; | |
| border: none !important; | |
| padding: 12px 24px !important; | |
| font-size: 16px !important; | |
| font-weight: bold !important; | |
| border-radius: 8px !important; | |
| transition: all 0.2s ease !important; | |
| } | |
| .transfer-btn:hover { | |
| background: linear-gradient(145deg, #FF6B00, #E55D00) !important; | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important; | |
| } | |
| .transfer-btn:active { | |
| background: linear-gradient(145deg, #E55D00, #CC5500) !important; | |
| transform: translateY(2px) !important; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important; | |
| } | |
| """ | |
| with gr.Blocks(title="نقل كائن من صورة إلى أخرى مع القناع والمحاذاة", css=css) as demo: | |
| gr.Markdown(""" | |
| ⏳ **ملاحظة:** عند أول استخدام، قد يستغرق التطبيق 20-30 ثانية للبدء بسبب تحميل النماذج. | |
| بعد ذلك ستعمل الأداة بسرعة. | |
| """) | |
| gr.Markdown(""" | |
| # 🖼️ نقل كائن من صورة إلى أخرى مع القناع والمحاذاة | |
| يمكنك تعديل إعدادات نقل الكائن من هنا، أو تركها على الإعدادات الافتراضية. | |
| **التعليمات:** | |
| 1. ارفع الصورة الأولى (صورة الخلفية) وارسم أو ارفع قناع على مكان اللصق. | |
| 2. ارفع الصورة الثانية (الصورة التي بها الكائن) وحدد عليها بالقلم. | |
| 3. أو ارفع قناع جاهز للكائن في الصورة الثانية (يجب أن يكون القناع أبيض على أسود). | |
| 4. اضغط زر "نقل الكائن" وستظهر النتيجة! | |
| **ملاحظة:** سيتم تكبير أو تصغير الكائن تلقائيًا ليتناسب مع حجم القناع في الصورة الأولى. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| image1_editor = gr.ImageMask( | |
| label="الصورة الأولى (اختر مكان اللصق بالقلم)", | |
| type="pil", | |
| height=400, | |
| elem_classes=["silver-border"] | |
| ) | |
| mask1_upload = gr.Image( | |
| label="أو رفع قناع جاهز للصورة الأولى (اختياري)", | |
| type="pil", | |
| height=200, | |
| elem_classes=["silver-border"] | |
| ) | |
| with gr.Column(): | |
| image2_editor = gr.ImageMask( | |
| label="الصورة الثانية (ارفع الصورة وحدد عليها بالقلم)", | |
| type="pil", | |
| height=400, | |
| elem_classes=["silver-border"] | |
| ) | |
| mask2_upload = gr.Image( | |
| label="أو رفع قناع جاهز للصورة الثانية (بدون تحديد بالقلم)", | |
| type="pil", | |
| height=200, | |
| elem_classes=["silver-border"] | |
| ) | |
| with gr.Row(): | |
| scale = gr.Slider(0.5, 2.0, value=1.0, step=0.05, label="حجم الكائن (Scale) - قبل التكيف مع القناع", elem_classes=["silver-border"]) | |
| transparency = gr.Slider(0.2, 1.0, value=1.0, step=0.05, label="شفافية الكائن (Transparency)", elem_classes=["silver-border"]) | |
| rotation = gr.Slider(-180, 180, value=0, step=1, label="تدوير الكائن (Rotation)", elem_classes=["silver-border"]) | |
| with gr.Row(): | |
| smooth_edges = gr.Checkbox(value=True, label="تفعيل الحواف الناعمة (Smooth Edges/Anti-aliasing)", elem_classes=["silver-border"]) | |
| morph_level = gr.Slider(0, 10, value=0, step=1, label="مستوى تنعيم القناع (Morphological Smoothing)", elem_classes=["silver-border"]) | |
| fit_mode = gr.Radio( | |
| choices=["size", "shape"], | |
| value="size", | |
| label="وضع المحاذاة", | |
| info="size: يحافظ على التناسب | shape: يملأ المساحة (قد يشوه الصورة)", | |
| elem_classes=["silver-border"] | |
| ) | |
| with gr.Row(): | |
| transfer_btn = gr.Button("🚀 نقل الكائن", elem_classes=["transfer-btn"]) | |
| with gr.Row(): | |
| base_mask = gr.Image(label="قناع الصورة الأولى المستخدم", type="pil", height=200, elem_classes=["silver-border"]) | |
| obj_mask = gr.Image(label="قناع الصورة الثانية المستخدم", type="pil", height=200, elem_classes=["silver-border"]) | |
| result_img = gr.Image(label="النتيجة النهائية (بنفس حجم الصورة الأولى)", type="pil", height=400, elem_classes=["silver-border"]) | |
| status = gr.Markdown() | |
| transfer_btn.click( | |
| fn=process_transfer, | |
| inputs=[image1_editor, mask1_upload, image2_editor, mask2_upload, scale, transparency, rotation, smooth_edges, morph_level, fit_mode], | |
| outputs=[base_mask, obj_mask, result_img, status] | |
| ) | |
| return demo | |
| def main(): | |
| demo = create_interface() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=True, | |
| max_file_size="100MB" | |
| ) | |
| if __name__ == "__main__": | |
| main() |