Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -391,15 +391,20 @@ class CertificateGenerator:
|
|
| 391 |
def _add_photo(self, certificate: Image.Image, photo_path: str):
|
| 392 |
"""Add a clear circular profile photo in the top-right corner"""
|
| 393 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
# Open and process photo
|
| 395 |
photo = Image.open(photo_path)
|
|
|
|
| 396 |
|
| 397 |
-
# Define size for circular photo
|
| 398 |
size = (120, 120)
|
| 399 |
|
| 400 |
-
# Convert to
|
| 401 |
-
if photo.mode
|
| 402 |
-
photo = photo.convert('
|
| 403 |
|
| 404 |
# Create high-quality circular mask
|
| 405 |
mask = Image.new('L', size, 0)
|
|
@@ -409,47 +414,43 @@ class CertificateGenerator:
|
|
| 409 |
# Resize photo maintaining aspect ratio
|
| 410 |
aspect = photo.width / photo.height
|
| 411 |
if aspect > 1:
|
| 412 |
-
# Width is greater
|
| 413 |
new_height = size[1]
|
| 414 |
new_width = int(new_height * aspect)
|
| 415 |
-
photo = photo.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
| 416 |
-
# Center crop
|
| 417 |
-
left = (new_width - size[0]) // 2
|
| 418 |
-
photo = photo.crop((left, 0, left + size[0], size[1]))
|
| 419 |
else:
|
| 420 |
-
# Height is greater
|
| 421 |
new_width = size[0]
|
| 422 |
new_height = int(new_width / aspect)
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
top = (new_height - size[1]) // 2
|
| 426 |
photo = photo.crop((0, top, size[0], top + size[1]))
|
| 427 |
|
| 428 |
-
# Create
|
| 429 |
output = Image.new('RGBA', size, (0, 0, 0, 0))
|
| 430 |
output.paste(photo, (0, 0))
|
| 431 |
-
|
| 432 |
-
# Apply antialiasing to the mask
|
| 433 |
-
mask = mask.filter(ImageFilter.GaussianBlur(radius=1))
|
| 434 |
output.putalpha(mask)
|
| 435 |
|
| 436 |
-
# Position in top-right corner
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
position = (certificate.width - size[0] - padding_right, padding_top)
|
| 440 |
|
| 441 |
-
# Add
|
| 442 |
bg = Image.new('RGBA', size, (255, 255, 255, 255))
|
| 443 |
-
|
| 444 |
-
certificate.paste(bg, position, mask=bg_mask)
|
| 445 |
|
| 446 |
# Paste the photo
|
| 447 |
-
certificate.paste(output,
|
| 448 |
-
|
|
|
|
| 449 |
except Exception as e:
|
| 450 |
print(f"Error adding photo: {str(e)}")
|
| 451 |
-
|
| 452 |
-
|
| 453 |
|
| 454 |
def generate(
|
| 455 |
self,
|
|
@@ -459,43 +460,37 @@ class CertificateGenerator:
|
|
| 459 |
company_logo: Optional[str] = None,
|
| 460 |
participant_photo: Optional[str] = None
|
| 461 |
) -> str:
|
| 462 |
-
"""Generate certificate with improved
|
| 463 |
try:
|
| 464 |
-
|
|
|
|
| 465 |
draw = ImageDraw.Draw(certificate)
|
| 466 |
|
| 467 |
-
# Add
|
| 468 |
self._add_professional_border(draw)
|
| 469 |
|
| 470 |
-
# Add photo if provided (now centered)
|
| 471 |
-
if participant_photo:
|
| 472 |
-
self._add_photo(certificate, participant_photo)
|
| 473 |
-
# Adjust vertical spacing for content when photo is present
|
| 474 |
-
content_start_y = 180 # Increased to accommodate photo
|
| 475 |
-
else:
|
| 476 |
-
content_start_y = 140 # Original spacing when no photo
|
| 477 |
-
|
| 478 |
# Load fonts
|
| 479 |
fonts = self._load_fonts()
|
| 480 |
|
| 481 |
-
# Add content with adjusted vertical position
|
| 482 |
-
self._add_content(
|
| 483 |
-
draw,
|
| 484 |
-
fonts,
|
| 485 |
-
str(name),
|
| 486 |
-
str(course_name),
|
| 487 |
-
float(score),
|
| 488 |
-
y_offset=content_start_y
|
| 489 |
-
)
|
| 490 |
-
|
| 491 |
# Add company logo if provided
|
| 492 |
-
if company_logo:
|
| 493 |
self._add_logo(certificate, company_logo)
|
| 494 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
return self._save_certificate(certificate)
|
| 496 |
|
| 497 |
except Exception as e:
|
| 498 |
-
print(f"Error generating certificate: {e}")
|
|
|
|
|
|
|
| 499 |
return None
|
| 500 |
|
| 501 |
def _create_base_certificate(self) -> Image.Image:
|
|
@@ -516,10 +511,15 @@ class CertificateGenerator:
|
|
| 516 |
return certificate
|
| 517 |
|
| 518 |
def _save_certificate(self, certificate: Image.Image) -> str:
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
|
| 524 |
class QuizApp:
|
| 525 |
def __init__(self, api_key: str):
|
|
|
|
| 391 |
def _add_photo(self, certificate: Image.Image, photo_path: str):
|
| 392 |
"""Add a clear circular profile photo in the top-right corner"""
|
| 393 |
try:
|
| 394 |
+
if not photo_path or not os.path.exists(photo_path):
|
| 395 |
+
print(f"Photo path does not exist: {photo_path}")
|
| 396 |
+
return
|
| 397 |
+
|
| 398 |
# Open and process photo
|
| 399 |
photo = Image.open(photo_path)
|
| 400 |
+
print(f"Loaded photo: {photo.size}, {photo.mode}") # Debug info
|
| 401 |
|
| 402 |
+
# Define size for circular photo
|
| 403 |
size = (120, 120)
|
| 404 |
|
| 405 |
+
# Convert to RGB if not already
|
| 406 |
+
if photo.mode not in ('RGB', 'RGBA'):
|
| 407 |
+
photo = photo.convert('RGB')
|
| 408 |
|
| 409 |
# Create high-quality circular mask
|
| 410 |
mask = Image.new('L', size, 0)
|
|
|
|
| 414 |
# Resize photo maintaining aspect ratio
|
| 415 |
aspect = photo.width / photo.height
|
| 416 |
if aspect > 1:
|
|
|
|
| 417 |
new_height = size[1]
|
| 418 |
new_width = int(new_height * aspect)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
else:
|
|
|
|
| 420 |
new_width = size[0]
|
| 421 |
new_height = int(new_width / aspect)
|
| 422 |
+
|
| 423 |
+
photo = photo.resize((new_width, max(new_height, 1)), Image.Resampling.LANCZOS)
|
| 424 |
+
|
| 425 |
+
# Center crop
|
| 426 |
+
if aspect > 1:
|
| 427 |
+
left = (new_width - size[0]) // 2
|
| 428 |
+
photo = photo.crop((left, 0, left + size[0], size[1]))
|
| 429 |
+
else:
|
| 430 |
top = (new_height - size[1]) // 2
|
| 431 |
photo = photo.crop((0, top, size[0], top + size[1]))
|
| 432 |
|
| 433 |
+
# Create circular photo
|
| 434 |
output = Image.new('RGBA', size, (0, 0, 0, 0))
|
| 435 |
output.paste(photo, (0, 0))
|
|
|
|
|
|
|
|
|
|
| 436 |
output.putalpha(mask)
|
| 437 |
|
| 438 |
+
# Position in top-right corner
|
| 439 |
+
photo_x = certificate.width - size[0] - 60 # 60px from right
|
| 440 |
+
photo_y = 40 # 40px from top
|
|
|
|
| 441 |
|
| 442 |
+
# Add white background circle
|
| 443 |
bg = Image.new('RGBA', size, (255, 255, 255, 255))
|
| 444 |
+
certificate.paste(bg, (photo_x, photo_y), mask=mask)
|
|
|
|
| 445 |
|
| 446 |
# Paste the photo
|
| 447 |
+
certificate.paste(output, (photo_x, photo_y), mask=output)
|
| 448 |
+
print(f"Successfully added photo at position ({photo_x}, {photo_y})")
|
| 449 |
+
|
| 450 |
except Exception as e:
|
| 451 |
print(f"Error adding photo: {str(e)}")
|
| 452 |
+
import traceback
|
| 453 |
+
traceback.print_exc()
|
| 454 |
|
| 455 |
def generate(
|
| 456 |
self,
|
|
|
|
| 460 |
company_logo: Optional[str] = None,
|
| 461 |
participant_photo: Optional[str] = None
|
| 462 |
) -> str:
|
| 463 |
+
"""Generate certificate with improved photo handling"""
|
| 464 |
try:
|
| 465 |
+
# Create base certificate
|
| 466 |
+
certificate = Image.new('RGB', self.certificate_size, self.background_color)
|
| 467 |
draw = ImageDraw.Draw(certificate)
|
| 468 |
|
| 469 |
+
# Add border
|
| 470 |
self._add_professional_border(draw)
|
| 471 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
# Load fonts
|
| 473 |
fonts = self._load_fonts()
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
# Add company logo if provided
|
| 476 |
+
if company_logo and os.path.exists(company_logo):
|
| 477 |
self._add_logo(certificate, company_logo)
|
| 478 |
|
| 479 |
+
# Add participant photo if provided
|
| 480 |
+
if participant_photo:
|
| 481 |
+
print(f"Processing photo: {participant_photo}") # Debug info
|
| 482 |
+
self._add_photo(certificate, participant_photo)
|
| 483 |
+
|
| 484 |
+
# Add content
|
| 485 |
+
self._add_content(draw, fonts, str(name), str(course_name), float(score))
|
| 486 |
+
|
| 487 |
+
# Save certificate
|
| 488 |
return self._save_certificate(certificate)
|
| 489 |
|
| 490 |
except Exception as e:
|
| 491 |
+
print(f"Error generating certificate: {str(e)}")
|
| 492 |
+
import traceback
|
| 493 |
+
traceback.print_exc()
|
| 494 |
return None
|
| 495 |
|
| 496 |
def _create_base_certificate(self) -> Image.Image:
|
|
|
|
| 511 |
return certificate
|
| 512 |
|
| 513 |
def _save_certificate(self, certificate: Image.Image) -> str:
|
| 514 |
+
"""Save certificate with improved error handling"""
|
| 515 |
+
try:
|
| 516 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
|
| 517 |
+
certificate.save(temp_file.name, 'PNG', quality=95)
|
| 518 |
+
print(f"Certificate saved to: {temp_file.name}") # Debug info
|
| 519 |
+
return temp_file.name
|
| 520 |
+
except Exception as e:
|
| 521 |
+
print(f"Error saving certificate: {str(e)}")
|
| 522 |
+
return None
|
| 523 |
|
| 524 |
class QuizApp:
|
| 525 |
def __init__(self, api_key: str):
|