Spaces:
Sleeping
Sleeping
File size: 16,289 Bytes
339cd6d 1951804 5a6f3af 339cd6d 1951804 339cd6d 99bca06 339cd6d b7619b8 339cd6d 1951804 99bca06 339cd6d 99bca06 339cd6d 1951804 339cd6d 99bca06 339cd6d 99bca06 339cd6d 2889dc8 339cd6d 99bca06 e80f9dc 339cd6d 4e91c41 1951804 339cd6d 99bca06 1951804 339cd6d 1951804 339cd6d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
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() |