Source code for renoir.color.prompt

"""
Generative AI colour prompt module.

Converts renoir colour analysis results into structured prompts for
generative AI image and video models (DALL-E, Midjourney, Stable Diffusion,
Runway, Sora). Bridges the gap between computational colour analysis and
AI-assisted design workflows.
"""

from typing import List, Dict, Tuple, Optional, Union


[docs] class PromptGenerator: """ Generate structured colour prompts for generative AI models. Composes outputs from renoir's colour analysis, naming, and harmony detection into descriptive prompt strings that can be used with image/video generation APIs. Example: >>> from renoir.color import ColorExtractor, PromptGenerator >>> from PIL import Image >>> extractor = ColorExtractor() >>> img = Image.open('artwork.jpg') >>> colors = extractor.extract_dominant_colors(img, n_colors=5) >>> gen = PromptGenerator() >>> prompt = gen.generate(colors) >>> print(prompt) """ def __init__(self, vocabulary: str = "artist"): """ Initialize PromptGenerator. Args: vocabulary: Colour naming vocabulary to use (default: 'artist'). """ self.vocabulary = vocabulary
[docs] def generate( self, colors: List[Tuple[int, int, int]], proportions: Optional[List[float]] = None, style: Optional[str] = None, medium: Optional[str] = None, mood: Optional[str] = None, subject: Optional[str] = None, include_harmony: bool = True, include_temperature: bool = True, include_complexity: bool = True, target_model: str = "generic", ) -> str: """ Generate a structured colour prompt from a palette. Args: colors: List of RGB tuples (typically from ColorExtractor). proportions: Optional colour proportions (should sum to 1.0). If None, equal proportions are assumed. style: Optional art style descriptor (e.g. 'impressionist', 'minimalist', 'art deco'). medium: Optional medium descriptor (e.g. 'oil painting', 'watercolour', 'digital illustration'). mood: Optional mood descriptor (e.g. 'serene', 'dramatic'). subject: Optional subject descriptor (e.g. 'landscape', 'portrait', 'abstract composition'). include_harmony: Include harmony analysis in prompt (default: True). include_temperature: Include warm/cool distribution (default: True). include_complexity: Include CCI score description (default: True). target_model: Target model hint — 'generic', 'midjourney', 'dalle', 'stable_diffusion' (default: 'generic'). Returns: Structured prompt string. Example: >>> gen = PromptGenerator() >>> colors = [(255, 87, 51), (0, 50, 200), (255, 255, 240)] >>> prompt = gen.generate(colors, style='impressionist', ... medium='oil painting') >>> print(prompt) """ from .namer import ColorNamer from .analysis import ColorAnalyzer namer = ColorNamer(vocabulary=self.vocabulary) analyzer = ColorAnalyzer() if not colors: return "" if proportions is None: proportions = [1.0 / len(colors)] * len(colors) # Name each colour named = [] for color, prop in zip(colors, proportions): name = namer.name(color) pct = round(prop * 100) named.append((name, pct, color)) # Sort by proportion descending named.sort(key=lambda x: x[1], reverse=True) # Build palette description parts = [] # Subject + medium + style opener opener_parts = [] if subject: opener_parts.append(subject.capitalize()) if medium: opener_parts.append(medium) if style: opener_parts.append(f"in {style} style") if opener_parts: parts.append(" ".join(opener_parts) + ".") # Colour palette palette_desc = "Colour palette: " + ", ".join( f"{name} ({pct}%)" if pct > 0 else name for name, pct, _ in named ) parts.append(palette_desc + ".") # Dominant colour dominant_name, _, _ = named[0] parts.append(f"Dominant colour: {dominant_name}.") # Harmony analysis if include_harmony and len(colors) >= 2: harmony = analyzer.analyze_color_harmony(colors) dominant_harmony = harmony["dominant_harmony"] if dominant_harmony != "none": harmony_desc = dominant_harmony.replace("_", " ") parts.append(f"Colour harmony: {harmony_desc}.") # Temperature distribution if include_temperature: temp = analyzer.analyze_color_temperature_distribution(colors) warm_pct = temp["warm_percentage"] cool_pct = temp["cool_percentage"] dom_temp = temp["dominant_temperature"] parts.append( f"Colour temperature: {dom_temp}-dominant " f"({warm_pct:.0f}% warm, {cool_pct:.0f}% cool)." ) # Complexity if include_complexity and len(colors) >= 2: complexity = analyzer.calculate_color_complexity(colors, proportions) cci = complexity["cci"] if cci < 0.3: complexity_word = "Low" elif cci < 0.6: complexity_word = "Moderate" else: complexity_word = "High" parts.append(f"{complexity_word} colour complexity (CCI: {cci:.2f}).") # Mood if mood: parts.append(f"Mood: {mood}.") # Model-specific formatting prompt = " ".join(parts) if target_model == "midjourney": prompt = prompt + " --v 6" elif target_model == "stable_diffusion": # SD prefers comma-separated keywords at the end prompt = prompt + ", highly detailed, professional colour grading" return prompt
[docs] def generate_variation_prompts( self, colors: List[Tuple[int, int, int]], n_variations: int = 3, **kwargs, ) -> List[str]: """ Generate multiple prompt variations from a single palette. Creates variations by rotating emphasis across palette colours and varying descriptors. Args: colors: List of RGB tuples. n_variations: Number of variations to generate (default: 3). **kwargs: Additional arguments passed to generate(). Returns: List of prompt strings. Example: >>> gen = PromptGenerator() >>> colors = [(255, 0, 0), (0, 0, 255), (255, 255, 0)] >>> prompts = gen.generate_variation_prompts(colors, n_variations=3) >>> for i, p in enumerate(prompts): ... print(f"Variation {i+1}: {p[:80]}...") """ from .namer import ColorNamer namer = ColorNamer(vocabulary=self.vocabulary) variations = [] for i in range(min(n_variations, len(colors))): # Emphasise colour i; give every other colour a small uniform base base = 0.1 proportions = [base] * len(colors) proportions[i % len(colors)] = 1.0 - base * (len(colors) - 1) # Renormalise to sum to 1.0 total = sum(proportions) proportions = [p / total for p in proportions] prompt = self.generate(colors, proportions=proportions, **kwargs) variations.append(prompt) return variations
[docs] def palette_to_prompt_keywords( self, colors: List[Tuple[int, int, int]], ) -> List[str]: """ Extract concise keyword descriptors from a palette. Useful for tagging or short-form prompts. Args: colors: List of RGB tuples. Returns: List of keyword strings. Example: >>> gen = PromptGenerator() >>> keywords = gen.palette_to_prompt_keywords([(255, 0, 0), (0, 0, 255)]) >>> print(keywords) ['Cadmium Red Light', 'Prussian Blue', 'complementary', 'warm-cool contrast'] """ from .namer import ColorNamer from .analysis import ColorAnalyzer namer = ColorNamer(vocabulary=self.vocabulary) analyzer = ColorAnalyzer() keywords = [] # Colour names for color in colors: keywords.append(namer.name(color)) # Harmony type if len(colors) >= 2: harmony = analyzer.analyze_color_harmony(colors) dom = harmony["dominant_harmony"] if dom != "none": keywords.append(dom.replace("_", " ")) # Temperature temp = analyzer.analyze_color_temperature_distribution(colors) dom_temp = temp["dominant_temperature"] warm_pct = temp["warm_percentage"] cool_pct = temp["cool_percentage"] if warm_pct > 60: keywords.append("warm palette") elif cool_pct > 60: keywords.append("cool palette") elif abs(warm_pct - cool_pct) < 20: keywords.append("warm-cool contrast") # Saturation sat = analyzer.calculate_saturation_score(colors) if sat > 70: keywords.append("vibrant") elif sat < 30: keywords.append("muted") return keywords