Spaces:
Sleeping
composition_service.py
Purpose
Business logic for smart multi-image composition. Builds intelligent prompts based on image types, camera angles, shot types, and lighting conditions. Implements Google's best practices for Gemini 2.5 Flash Image multi-image composition.
Responsibilities
- Build intelligent composition prompts from user selections
- Support multiple image types (Subject, Background, Style, etc.)
- Apply camera angle and lighting best practices
- Generate compositions with consistent perspective
- Suggest appropriate aspect ratios for composition types
- Validate composition inputs
- Inherit generation capabilities from GenerationService
Dependencies
services.generation_service.GenerationService- Base class (inherits generation methods)models.generation_request.GenerationRequest- Request dataclassmodels.generation_result.GenerationResult- Result dataclassutils.logging_utils- Loggingconfig.settings.Settings- ConfigurationPIL.Image- Image handling
Source
Extracted from composition_assistant_addon.py (Gradio implementation). Refactored to use new architecture and add generation capabilities.
Public Interface
CompositionService(GenerationService) class
Inheritance:
- Extends
GenerationServiceto reuse generation methods - Adds composition-specific prompt building
Class Constants:
IMAGE_TYPES = [
"Subject/Character",
"Background/Environment",
"Style Reference",
"Product",
"Texture",
"Not Used"
]
SHOT_TYPES = [
"close-up shot",
"medium shot",
"full body shot",
"wide shot",
"extreme close-up",
"establishing shot"
]
CAMERA_ANGLES = [
"eye-level perspective",
"low-angle perspective",
"high-angle perspective",
"bird's-eye view",
"Dutch angle (tilted)",
"over-the-shoulder"
]
LIGHTING_OPTIONS = [
"Auto (match images)",
"natural daylight",
"golden hour sunlight",
"soft diffused light",
"dramatic side lighting",
"backlit silhouette",
"studio lighting",
"moody atmospheric lighting",
"neon/artificial lighting"
]
Constructor:
def __init__(self, api_key: Optional[str] = None)
api_key: Optional Gemini API key- Initializes parent GenerationService
Key Methods
build_composition_prompt(image1_type="Subject/Character", image2_type="Background/Environment", image3_type="Not Used", camera_angles=None, lighting="Auto (match images)", shot_type="medium shot", custom_instructions="", is_character_sheet=False) -> str
Build intelligent composition prompt based on selections.
Based on Google's Best Practices:
- Narrative, descriptive language
- Camera angles, lens types, lighting
- Match perspectives and light direction
- Specific about placement
Args:
image1_type: Type of first image (default: "Subject/Character")image2_type: Type of second image (default: "Background/Environment")image3_type: Type of third image (default: "Not Used")camera_angles: List of selected camera angles (optional)lighting: Lighting description (default: "Auto (match images)")shot_type: Type of shot (default: "medium shot")custom_instructions: Additional instructions (default: "")is_character_sheet: Character sheet mode (default: False)
Returns:
- Formatted prompt string
Usage:
service = CompositionService()
# Subject into background
prompt = service.build_composition_prompt(
image1_type="Subject/Character",
image2_type="Background/Environment",
camera_angles=["eye-level perspective"],
lighting="natural daylight",
shot_type="full body shot",
custom_instructions="Character is walking forward"
)
# Style transfer
prompt = service.build_composition_prompt(
image1_type="Subject/Character",
image2_type="Style Reference",
shot_type="medium shot",
custom_instructions="Apply watercolor painting style"
)
# Character sheet
prompt = service.build_composition_prompt(
image1_type="Subject/Character",
image2_type="Style Reference",
is_character_sheet=True
)
compose_images(images, image_types, camera_angles=None, lighting="Auto (match images)", shot_type="medium shot", custom_instructions="", is_character_sheet=False, aspect_ratio="16:9", temperature=0.7, backend=Settings.BACKEND_GEMINI) -> GenerationResult
Complete composition workflow: build prompt + generate.
Args:
images: List of up to 3 images (None for unused slots)image_types: List of image types corresponding to imagescamera_angles: Selected camera angles (optional)lighting: Lighting option (default: "Auto (match images)")shot_type: Shot type (default: "medium shot")custom_instructions: Custom instructions (default: "")is_character_sheet: Character sheet mode (default: False)aspect_ratio: Output aspect ratio (default: "16:9")temperature: Generation temperature (default: 0.7)backend: Backend to use (default: Gemini)
Returns:
GenerationResultobject
Usage:
service = CompositionService(api_key="your-key")
# Load images
subject = Image.open("character.png")
background = Image.open("forest.png")
# Compose
result = service.compose_images(
images=[subject, background, None],
image_types=["Subject/Character", "Background/Environment", "Not Used"],
camera_angles=["eye-level perspective", "low-angle perspective"],
lighting="soft diffused light",
shot_type="full body shot",
custom_instructions="Character is exploring the forest",
aspect_ratio="16:9",
temperature=0.7,
backend="Gemini API (Cloud)"
)
if result.success:
result.image.show()
print(f"Generated in {result.generation_time:.1f}s")
else:
print(f"Error: {result.message}")
get_suggested_aspect_ratio(shot_type, is_character_sheet=False) -> str
Suggest aspect ratio based on composition type.
Logic:
- Character sheet → "16:9" (wide for multi-view)
- Full body/wide shots → "16:9" (landscape)
- Close-ups → "3:4" (portrait)
- Balanced compositions → "1:1" (square)
Args:
shot_type: Shot typeis_character_sheet: Character sheet mode (default: False)
Returns:
- Suggested aspect ratio string
Usage:
ratio = service.get_suggested_aspect_ratio(
shot_type="full body shot",
is_character_sheet=False
) # Returns "16:9"
ratio = service.get_suggested_aspect_ratio(
shot_type="close-up shot"
) # Returns "3:4"
validate_composition_inputs(images, image_types) -> tuple[bool, Optional[str]]
Validate composition inputs.
Checks:
- At least one image provided
- Image types length matches images
- Valid image types
Args:
images: List of imagesimage_types: List of image types
Returns:
- Tuple of
(is_valid: bool, error_message: Optional[str])
Usage:
is_valid, error = service.validate_composition_inputs(
images=[subject, background, None],
image_types=["Subject/Character", "Background/Environment", "Not Used"]
)
if not is_valid:
st.error(f"Invalid input: {error}")
Prompt Building Logic
Subject + Background
image1_type="Subject/Character"
image2_type="Background/Environment"
# Generates:
"A photorealistic full body shot placing the subject from image one
into the environment from image two. Shot from a eye-level perspective.
The scene is illuminated by natural daylight, matching the lighting
direction and quality across all elements. Maintain consistent perspective,
scale, and depth. Create a natural, seamless composition with realistic
shadows and reflections. Photorealistic, high quality, professional photography."
Style Transfer
image1_type="Subject/Character"
image2_type="Style Reference"
# Generates:
"Transform the subject from image one into the artistic style shown
in image two. Maintain consistent perspective, scale, and depth.
Create a natural, seamless composition with realistic shadows and
reflections. Photorealistic, high quality, professional photography."
Character Sheet
image1_type="Subject/Character"
is_character_sheet=True
# Generates:
"Create a character sheet design with multiple views and poses of
the same character. Based on the character from image one, Include
front view, side view, back view, and detail shots. Maintain consistent
character design, colors, and proportions across all views. Create a
natural, seamless composition with realistic shadows and reflections.
Photorealistic, high quality, professional photography."
Best Practices Applied
Based on Google's Gemini 2.5 Flash Image documentation:
- Narrative Language: Use descriptive, story-like prompts
- Camera Specifics: Include angle, perspective, lens type
- Lighting Details: Specify lighting type and direction
- Perspective Matching: Explicitly request consistent perspective
- Realism Keywords: Include "photorealistic", "professional photography"
- Element Ordering: Images before text in API calls
Usage Examples
Example 1: Character in Environment
service = CompositionService(api_key="your-key")
character = Image.open("hero.png")
environment = Image.open("castle.png")
result = service.compose_images(
images=[character, environment],
image_types=["Subject/Character", "Background/Environment"],
camera_angles=["low-angle perspective"],
lighting="dramatic side lighting",
shot_type="full body shot",
custom_instructions="Hero standing heroically in front of castle",
aspect_ratio="16:9"
)
Example 2: Product with Texture
product = Image.open("watch.png")
texture = Image.open("marble.png")
result = service.compose_images(
images=[product, texture],
image_types=["Product", "Texture"],
camera_angles=["high-angle perspective"],
lighting="studio lighting",
shot_type="close-up shot",
aspect_ratio="1:1"
)
Example 3: Three-Image Composition
character = Image.open("character.png")
background = Image.open("background.png")
style_ref = Image.open("style.png")
result = service.compose_images(
images=[character, background, style_ref],
image_types=["Subject/Character", "Background/Environment", "Style Reference"],
camera_angles=["eye-level perspective"],
lighting="natural daylight",
shot_type="medium shot",
custom_instructions="Apply style from third image to the composition",
aspect_ratio="16:9"
)
Example 4: Auto Aspect Ratio
# Get suggested aspect ratio
aspect_ratio = service.get_suggested_aspect_ratio(
shot_type="full body shot"
)
result = service.compose_images(
images=[character, background],
image_types=["Subject/Character", "Background/Environment"],
shot_type="full body shot",
aspect_ratio=aspect_ratio # Uses "16:9"
)
Error Handling
All methods return consistent error format:
try:
result = service.compose_images(...)
if not result.success:
print(f"Composition failed: {result.message}")
except Exception as e:
logger.exception(f"Composition error: {e}")
return GenerationResult.error_result(f"Composition error: {str(e)}")
Inheritance from GenerationService
Reuses these methods:
router.generate()- Backend generationcheck_backend_availability()- Backend healthget_all_backend_status()- All backend status- All BackendRouter functionality
Adds:
build_composition_prompt()- Intelligent prompt buildingcompose_images()- Complete composition workflowget_suggested_aspect_ratio()- Aspect ratio suggestionsvalidate_composition_inputs()- Input validation
Related Files
services/generation_service.py- Parent classcomposition_assistant_addon.py(old) - Original Gradio implementation sourcecore/backend_router.py- Backend routingmodels/generation_request.py- Request structuremodels/generation_result.py- Result structureui/pages/02_🎬_Composition_Assistant.py- UI that uses this service