Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from PIL import Image, ImageDraw, ImageFont, ImageFilter | |
| import random | |
| import datetime | |
| import io | |
| import os | |
| import tempfile # Needed for creating temporary files | |
| from gradio_calendar import Calendar | |
| # --- Image Generation Function (Adapted for Gradio) --- | |
| def generate_fuel_bill_image( | |
| company_name: str, | |
| company_address: str, | |
| attendant_id: str, | |
| vehicle_number: str, | |
| mobile_number: str, | |
| fuel_point_id: str, | |
| nozzle_number: str, | |
| fuel_type: str, | |
| product: str, | |
| density: str, | |
| preset_type: str, | |
| rate: float, | |
| amount_option: str, # "random" or "manual" | |
| manual_sale_amount: float, | |
| min_rand_amount: float, | |
| max_rand_amount: float, | |
| min_bill_date: datetime.date, # New parameter for min date | |
| max_bill_date: datetime.date, # New parameter for max date | |
| image_width: int, | |
| image_height: int, | |
| font_size_input: int, | |
| line_spacing_input: int, | |
| margin_left_input: int, | |
| logo_y_offset_input: int, | |
| texture_opacity_input: float, | |
| blur_radius_input: float, | |
| selected_crumple_file_path: str, # path from gr.Dropdown or None | |
| custom_crumple_file_obj, # tempfile object from gr.File | |
| selected_logo_file_path: str, # path from gr.Dropdown or None | |
| custom_logo_file_obj # tempfile object from gr.File | |
| ) -> tuple[Image.Image, gr.File]: # Now returns a tuple: (PIL Image, Gradio File) | |
| # Determine sale_amount based on selected option | |
| sale_amount = manual_sale_amount | |
| if amount_option == "Random": | |
| sale_amount = round(random.uniform(min_rand_amount, max_rand_amount), 2) | |
| # --- Helper Functions (from your original code) --- | |
| def generate_bill_number(): | |
| return f"{random.randint(100000, 999999)}-ORGNL" | |
| def generate_transaction_id(): | |
| return f"{random.randint(1000000000000000, 9999999999999999):016d}" | |
| def generate_random_datetime_between_dates(start_date: datetime.date, end_date: datetime.date): | |
| # Ensure start_date is not after end_date | |
| if start_date > end_date: | |
| start_date, end_date = end_date, start_date # Swap if invalid | |
| time_between_dates = end_date - start_date | |
| days_between_dates = time_between_dates.days | |
| if days_between_dates < 0: # Should not happen with swap, but for robustness | |
| random_number_of_days = 0 | |
| else: | |
| random_number_of_days = random.randrange(days_between_dates + 1) | |
| random_date = start_date + datetime.timedelta(days=random_number_of_days) | |
| random_hour = random.randint(8, 23) | |
| random_minute = random.randint(0, 59) | |
| random_second = random.randint(0, 59) | |
| random_time = datetime.time(random_hour, random_minute, random_second) | |
| return random_date.strftime("%d/%m/%Y"), random_time.strftime("%H:%M:%S") | |
| def calculate_volume(rate_val, sale_amount_val): | |
| try: | |
| return round(float(sale_amount_val) / float(rate_val), 2) | |
| except ZeroDivisionError: | |
| return 0.0 | |
| # --- Core Generation Logic --- | |
| bill_number = generate_bill_number() | |
| transaction_id = generate_transaction_id() | |
| date, time = generate_random_datetime_between_dates(min_bill_date, max_bill_date) | |
| volume = calculate_volume(rate, sale_amount) | |
| # Use the passed input values for image dimensions and aesthetics | |
| width = image_width | |
| height = image_height | |
| font_size = font_size_input | |
| line_spacing = line_spacing_input | |
| margin_left = margin_left_input | |
| logo_y_offset = logo_y_offset_input | |
| texture_opacity = texture_opacity_input | |
| blur_radius = blur_radius_input | |
| bg_color = (245, 245, 235) | |
| text_color = (0, 0, 0) | |
| # Define paths for assets (assuming they are in the same directory as app.py) | |
| # Gradio will handle making these accessible | |
| script_dir = os.path.dirname(__file__) | |
| font_file = os.path.join(script_dir, "cour.ttf") | |
| data = [ | |
| f"B111 No:{bill_number}", | |
| f"Trns. ID:{transaction_id}", | |
| f"Atnd. ID:{attendant_id if attendant_id else ''}", | |
| f"Vehicle No:{vehicle_number}", | |
| f"Mobile No: {mobile_number}", | |
| f"Date :{date}", | |
| f"Time :{time}", | |
| f"FIP. ID :{fuel_point_id}", | |
| f"Nozzle No:{nozzle_number}", | |
| f"Fuel : {fuel_type}", | |
| f"Prouct : {product}", | |
| f"Density: {density}", | |
| f"Preset Type : {preset_type}", | |
| f"Rate :Rs. {rate:.2f}", | |
| f"Sale :Rs.{sale_amount:.2f}", | |
| f"Volume :{volume:.2f}", | |
| "THANK YOU!", | |
| "PLEASE VISIT AGAIN" | |
| ] | |
| # --- Crumple Texture --- | |
| crumple = None | |
| if custom_crumple_file_obj: # Custom uploaded file takes precedence | |
| try: | |
| crumple = Image.open(custom_crumple_file_obj.name).convert("RGB") | |
| crumple = crumple.resize((width, height)) | |
| except Exception as e: | |
| gr.Warning(f"Could not load custom background texture. Error: {e}. Using plain background.") | |
| elif selected_crumple_file_path and selected_crumple_file_path != "None": # Selected default texture | |
| try: | |
| crumple = Image.open(selected_crumple_file_path).convert("RGB") | |
| crumple = crumple.resize((width, height)) | |
| except FileNotFoundError: | |
| gr.Warning(f"Selected default texture '{selected_crumple_file_path}' not found. Using plain background.") | |
| except Exception as e: | |
| gr.Warning(f"Error loading selected default texture: {e}. Using plain background.") | |
| # else: crumple remains None, leading to a plain background | |
| # --- Create base image --- | |
| base_image = Image.new("RGB", (width, height), bg_color) | |
| if crumple: | |
| base_image = Image.blend(base_image, crumple, texture_opacity) | |
| # --- Create a layer for the text and logo (transparent background) --- | |
| text_layer = Image.new("RGBA", (width, height), (0, 0, 0, 0)) | |
| draw = ImageDraw.Draw(text_layer) | |
| # --- Load font --- | |
| try: | |
| font = ImageFont.truetype(font_file, size=font_size) | |
| except IOError: | |
| gr.Warning(f"Font file '{font_file}' not found. Using default font.") | |
| font = ImageFont.load_default() | |
| # --- Load Logo --- | |
| logo_height = 0 | |
| logo_max_width = width - (margin_left * 2) # Ensure logo fits within margins | |
| if custom_logo_file_obj: # Custom uploaded logo takes precedence | |
| try: | |
| logo = Image.open(custom_logo_file_obj.name) | |
| if logo.mode != 'RGBA': | |
| logo = logo.convert("RGBA") | |
| if logo.width > logo_max_width: | |
| ratio = logo_max_width / logo.width | |
| logo = logo.resize((int(logo.width * ratio), int(logo.height * ratio))) | |
| logo_x = (width - logo.width) // 2 | |
| text_layer.paste(logo, (logo_x, logo_y_offset), logo) | |
| logo_height = logo.height | |
| except Exception as e: | |
| gr.Warning(f"Could not load custom logo. Error: {e}. Proceeding without logo.") | |
| elif selected_logo_file_path and selected_logo_file_path != "None": # Selected default logo | |
| try: | |
| logo = Image.open(selected_logo_file_path) | |
| if logo.mode != 'RGBA': | |
| logo = logo.convert("RGBA") | |
| if logo.width > logo_max_width: | |
| ratio = logo_max_width / logo.width | |
| logo = logo.resize((int(logo.width * ratio), int(logo.height * ratio))) | |
| logo_x = (width - logo.width) // 2 | |
| text_layer.paste(logo, (logo_x, logo_y_offset), logo) | |
| logo_height = logo.height | |
| except FileNotFoundError: | |
| gr.Warning(f"Selected default logo '{selected_logo_file_path}' not found. Generating without logo.") | |
| except Exception as e: | |
| gr.Warning(f"Error loading selected default logo: {e}. Proceeding without logo.") | |
| # --- Company Name and Address (Centered) --- | |
| y_offset = logo_y_offset + logo_height + 20 | |
| company_address_lines = company_address.split('\n') | |
| for line in company_address_lines: | |
| company_name_width = draw.textlength(line, font=font) | |
| draw.text(((width - company_name_width) // 2, y_offset), line, font=font, fill=text_color) | |
| y_offset += line_spacing | |
| y_offset += 20 | |
| # --- Transaction Data (Left-Aligned) --- | |
| for line in data: | |
| x_offset = margin_left | |
| y_offset += line_spacing | |
| draw.text((x_offset, y_offset), line, font=font, fill=text_color) | |
| final_image = Image.alpha_composite(base_image.convert("RGBA"), text_layer) | |
| final_image = final_image.filter(ImageFilter.GaussianBlur(radius=blur_radius)).convert("RGB") | |
| # --- Save image to a temporary file for download --- | |
| # Create a temporary file and save the image to it | |
| temp_dir = tempfile.mkdtemp() | |
| bill_filename = f"fuel_bill_scan_{random.randint(1000, 9999)}.jpg" | |
| temp_filepath = os.path.join(temp_dir, bill_filename) | |
| final_image.save(temp_filepath, format='JPEG', quality=85) | |
| # Return the PIL image for display and the temporary file path for download | |
| return final_image, gr.File(temp_filepath, type="filepath", label="Download Generated Bill") | |
| # --- Gradio Interface Layout --- | |
| # Helper to get available default assets for Gradio dropdowns | |
| def get_default_assets(extensions): | |
| current_dir = os.path.dirname(__file__) | |
| files = [] | |
| if os.path.exists(current_dir): | |
| for f in os.listdir(current_dir): | |
| if any(f.lower().endswith(ext) for ext in extensions): | |
| files.append(os.path.join(current_dir, f)) # Return full path | |
| return ["None"] + files | |
| # Get paths to default textures and logos | |
| default_texture_paths = get_default_assets([".jpg", ".jpeg", ".png"]) | |
| default_logo_paths = get_default_assets([".png", ".jpg", ".jpeg"]) | |
| with gr.Blocks(title="Fuel Bill Image Generator") as demo: | |
| gr.Markdown( | |
| """ | |
| # ⛽ Custom Fuel Bill Image Generator | |
| Create realistic-looking fuel bill images with customizable details, | |
| background textures, and your own logo! | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("## ⚙️ Bill Details Customization") | |
| gr.Markdown("### Company Information") | |
| company_name_input = gr.Textbox(label="Company Name:", value="IndianOil") | |
| company_address_input = gr.Textbox( | |
| label="Company Address:", | |
| value="Welcomes You\nM/S AAKASH BROTHERS\nVILL WAZIRABAD SEC 52\nGURUGRAM HR 122003\nTel . No.: 9311559777", | |
| lines=5 | |
| ) | |
| gr.Markdown("### Transaction Information") | |
| with gr.Row(): | |
| attendant_id_input = gr.Textbox(label="Attendant ID:", value="") | |
| vehicle_number_input = gr.Textbox(label="Vehicle Number:", value="Not Entered") | |
| with gr.Row(): | |
| mobile_number_input = gr.Textbox(label="Mobile Number:", value="Not Entered") | |
| fuel_point_id_input = gr.Textbox(label="Fuel Point ID:", value="2") | |
| with gr.Row(): | |
| nozzle_number_input = gr.Textbox(label="Nozzle Number:", value="2") | |
| fuel_type_input = gr.Textbox(label="Fuel Type:", value="PETROL") | |
| with gr.Row(): | |
| product_input = gr.Textbox(label="Product:", value="XP95") | |
| density_input = gr.Textbox(label="Density:", value="778.1kg/m3") | |
| preset_type_input = gr.Textbox(label="Preset Type:", value="Volume") | |
| gr.Markdown("### Amount Details") | |
| rate_input = gr.Number(label="Rate (Rs.):", value=102.07, step=0.01) | |
| amount_option = gr.Radio( | |
| choices=["Random", "Manual"], | |
| value="Random", | |
| label="Sale Amount Option:", | |
| interactive=True | |
| ) | |
| with gr.Column(visible=True) as random_amount_col: | |
| min_rand_amount = gr.Number(label="Min Random Amount", value=2000.0, step=100.0) | |
| max_rand_amount = gr.Number(label="Max Random Amount", value=5000.0, step=100.0) | |
| with gr.Column(visible=True) as manual_amount_col: | |
| manual_sale_amount = gr.Number(label="Manual Sale Amount (Rs.):", value=2500.00, step=0.01) | |
| # Link visibility of columns to radio button | |
| amount_option.change( | |
| lambda value: (gr.Column.update(visible=value == "Random"), gr.Column.update(visible=value == "Manual")), | |
| inputs=amount_option, | |
| outputs=[random_amount_col, manual_amount_col] | |
| ) | |
| gr.Markdown("### Bill Date Range") | |
| with gr.Row(): | |
| min_bill_date_input = Calendar(label="Min Bill Date", value="2024-01-01") | |
| max_bill_date_input = Calendar(label="Max Bill Date", value=datetime.date.today().strftime("%Y-%m-%d")) | |
| gr.Markdown("## 🖼️ Background & Logo Selection") | |
| gr.Markdown("### Background Texture") | |
| selected_texture = gr.Dropdown( | |
| label="Select a default texture:", | |
| choices=default_texture_paths, | |
| value=next((path for path in default_texture_paths if "scan_texture.jpg" in path), "None") # Pre-select if default exists | |
| ) | |
| custom_crumple_file = gr.File(label="Or upload a custom background texture (PNG, JPG, JPEG)", type="filepath") | |
| gr.Markdown("### Logo") | |
| selected_logo = gr.Dropdown( | |
| label="Select a default logo:", | |
| choices=default_logo_paths, | |
| value=next((path for path in default_logo_paths if "indian_oil_logo.png" in path), "None") # Pre-select if default exists | |
| ) | |
| custom_logo_file = gr.File(label="Or upload a custom logo (PNG, JPG, JPEG)", type="filepath") | |
| with gr.Column(scale=1): | |
| gr.Markdown("## Image Aesthetics") | |
| image_width = gr.Slider(label="Image Width", minimum=200, maximum=400, value=250, step=10) | |
| image_height = gr.Slider(label="Image Height", minimum=500, maximum=900, value=700, step=10) | |
| font_size_input = gr.Slider(label="Font Size", minimum=10, maximum=20, value=14, step=1) | |
| line_spacing_input = gr.Slider(label="Line Spacing", minimum=15, maximum=25, value=18, step=1) | |
| margin_left_input = gr.Slider(label="Left Margin", minimum=10, maximum=50, value=20, step=1) | |
| logo_y_offset_input = gr.Slider(label="Logo Y-offset", minimum=0, maximum=100, value=20, step=5) | |
| texture_opacity_input = gr.Slider(label="Texture Opacity", minimum=0.0, maximum=1.0, value=1.0, step=0.05) | |
| blur_radius_input = gr.Slider(label="Blur Radius", minimum=0.0, maximum=1.0, value=0.2, step=0.05) | |
| generate_button = gr.Button("Generate Fuel Bill Image") | |
| gr.Markdown("## Generated Image:") | |
| output_image = gr.Image(label="Your Custom Fuel Bill Image", type="pil") | |
| download_file_output = gr.File(label="Download Bill Image", interactive=False) # New output for download | |
| generate_button.click( | |
| fn=generate_fuel_bill_image, | |
| inputs=[ | |
| company_name_input, | |
| company_address_input, | |
| attendant_id_input, | |
| vehicle_number_input, | |
| mobile_number_input, | |
| fuel_point_id_input, | |
| nozzle_number_input, | |
| fuel_type_input, | |
| product_input, | |
| density_input, | |
| preset_type_input, | |
| rate_input, | |
| amount_option, | |
| manual_sale_amount, | |
| min_rand_amount, | |
| max_rand_amount, | |
| min_bill_date_input, | |
| max_bill_date_input, | |
| image_width, | |
| image_height, | |
| font_size_input, | |
| line_spacing_input, | |
| margin_left_input, | |
| logo_y_offset_input, | |
| texture_opacity_input, | |
| blur_radius_input, | |
| selected_texture, | |
| custom_crumple_file, | |
| selected_logo, | |
| custom_logo_file | |
| ], | |
| outputs=[output_image, download_file_output] # Now outputs both the image and the downloadable file | |
| ) | |
| demo.launch() |