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()