Back to Blog
6 min read

Prompt Engineering Patterns for AI Image Generation

Introduction

Effective prompt engineering is the key to getting consistent, high-quality results from AI image generation models like DALL-E 2. This post covers proven patterns and techniques for crafting prompts that produce the images you want.

Understanding How Image Models Interpret Prompts

AI image models process prompts by:

  1. Breaking down text into concepts and relationships
  2. Mapping concepts to learned visual representations
  3. Generating images that combine these elements

Understanding this helps write better prompts.

The Anatomy of an Effective Prompt

Basic Structure

[Subject] + [Style] + [Details] + [Mood/Atmosphere] + [Technical Specifications]

Example:

A golden retriever (subject)
in impressionist painting style (style)
playing in a sunny meadow with wildflowers (details)
joyful and peaceful atmosphere (mood)
soft natural lighting (technical)

Prompt Engineering Patterns

Pattern 1: Layered Description

Build your prompt in layers from general to specific:

from dataclasses import dataclass
from typing import Optional, List

@dataclass
class LayeredPrompt:
    # Layer 1: Core subject
    subject: str

    # Layer 2: Action or state
    action: Optional[str] = None

    # Layer 3: Environment
    environment: Optional[str] = None

    # Layer 4: Style
    style: Optional[str] = None

    # Layer 5: Mood
    mood: Optional[str] = None

    # Layer 6: Technical details
    technical: Optional[List[str]] = None

    def build(self) -> str:
        layers = [self.subject]

        if self.action:
            layers.append(self.action)
        if self.environment:
            layers.append(f"in {self.environment}")
        if self.style:
            layers.append(f"{self.style} style")
        if self.mood:
            layers.append(f"{self.mood} atmosphere")
        if self.technical:
            layers.extend(self.technical)

        return ", ".join(layers)

# Usage
prompt = LayeredPrompt(
    subject="A wise old wizard",
    action="reading an ancient spellbook",
    environment="a candlelit tower library",
    style="fantasy illustration",
    mood="mysterious and scholarly",
    technical=["detailed", "dramatic lighting", "8k resolution"]
)

print(prompt.build())

Pattern 2: Style Anchoring

Anchor your prompt to known styles or artists:

STYLE_ANCHORS = {
    "photorealistic": [
        "photorealistic",
        "hyperrealistic",
        "professional photography",
        "DSLR quality",
        "8k resolution"
    ],
    "illustration": [
        "digital illustration",
        "detailed artwork",
        "artstation trending",
        "concept art"
    ],
    "painting": [
        "oil painting",
        "classical painting style",
        "museum quality",
        "rich brushstrokes"
    ],
    "anime": [
        "anime style",
        "studio ghibli inspired",
        "detailed anime art",
        "vibrant colors"
    ],
    "minimalist": [
        "minimalist design",
        "clean lines",
        "simple shapes",
        "limited color palette"
    ]
}

def apply_style_anchor(subject: str, style_key: str) -> str:
    """Apply style anchoring to a subject."""
    if style_key not in STYLE_ANCHORS:
        return subject

    style_terms = ", ".join(STYLE_ANCHORS[style_key])
    return f"{subject}, {style_terms}"

# Usage
prompt = apply_style_anchor(
    "A dragon flying over mountains",
    "illustration"
)
print(prompt)
# Output: A dragon flying over mountains, digital illustration, detailed artwork,
#         artstation trending, concept art

Pattern 3: Composition Control

Control the composition and framing:

COMPOSITION_TERMS = {
    "portrait": "portrait shot, centered subject, shallow depth of field",
    "landscape": "wide landscape shot, rule of thirds, scenic view",
    "close_up": "extreme close-up, macro detail, sharp focus",
    "wide_angle": "wide angle shot, expansive view, dramatic perspective",
    "birds_eye": "bird's eye view, top-down perspective, aerial shot",
    "low_angle": "low angle shot, looking up, dramatic perspective",
    "symmetrical": "symmetrical composition, centered, balanced",
    "dynamic": "dynamic composition, action shot, motion blur"
}

def apply_composition(subject: str, composition_key: str) -> str:
    """Apply composition terms to prompt."""
    comp_terms = COMPOSITION_TERMS.get(composition_key, "")
    return f"{subject}, {comp_terms}" if comp_terms else subject

# Usage
prompt = apply_composition(
    "A samurai warrior in traditional armor",
    "low_angle"
)

Pattern 4: Lighting Specification

Lighting dramatically affects the final image:

LIGHTING_PRESETS = {
    "golden_hour": "golden hour lighting, warm tones, long shadows, sunset glow",
    "studio": "professional studio lighting, soft shadows, even illumination",
    "dramatic": "dramatic lighting, high contrast, deep shadows, spotlight effect",
    "natural": "natural daylight, soft diffused light, realistic lighting",
    "neon": "neon lighting, cyberpunk atmosphere, colorful glow, night scene",
    "moonlight": "moonlit scene, cool blue tones, night atmosphere, soft glow",
    "backlit": "backlit silhouette, rim lighting, glowing edges",
    "overcast": "overcast sky, diffused light, soft even shadows"
}

class LightingComposer:
    def __init__(self, base_prompt: str):
        self.base_prompt = base_prompt
        self.lighting = None

    def with_lighting(self, lighting_key: str) -> 'LightingComposer':
        self.lighting = LIGHTING_PRESETS.get(lighting_key, lighting_key)
        return self

    def build(self) -> str:
        if self.lighting:
            return f"{self.base_prompt}, {self.lighting}"
        return self.base_prompt

# Usage
prompt = (LightingComposer("A mysterious castle on a cliff")
    .with_lighting("moonlight")
    .build())

Pattern 5: Negative Prompting (Conceptual)

While DALL-E 2 doesn’t support negative prompts directly, you can guide away from unwanted elements:

def positive_redirect(subject: str, avoid_concepts: List[str]) -> str:
    """Redirect away from unwanted concepts using positive language."""

    REDIRECTS = {
        "blurry": "sharp focus, high detail",
        "dark": "well-lit, bright",
        "crowded": "clean composition, single subject",
        "cartoon": "realistic, detailed",
        "amateur": "professional quality, expert craftsmanship",
        "noisy": "clean image, smooth gradients"
    }

    additions = []
    for concept in avoid_concepts:
        if concept in REDIRECTS:
            additions.append(REDIRECTS[concept])

    if additions:
        return f"{subject}, {', '.join(additions)}"
    return subject

# Usage
prompt = positive_redirect(
    "A portrait of a young woman",
    ["blurry", "amateur", "dark"]
)
# Output: A portrait of a young woman, sharp focus, high detail,
#         professional quality, expert craftsmanship, well-lit, bright

Building a Prompt Library

Reusable Components

class PromptLibrary:
    def __init__(self):
        self.subjects = {}
        self.styles = {}
        self.modifiers = {}

    def add_subject(self, key: str, description: str):
        self.subjects[key] = description

    def add_style(self, key: str, style_terms: List[str]):
        self.styles[key] = style_terms

    def add_modifier(self, key: str, modifier: str):
        self.modifiers[key] = modifier

    def compose(
        self,
        subject_key: str,
        style_key: str = None,
        modifier_keys: List[str] = None
    ) -> str:
        """Compose a prompt from library components."""
        parts = []

        # Add subject
        if subject_key in self.subjects:
            parts.append(self.subjects[subject_key])
        else:
            parts.append(subject_key)

        # Add style
        if style_key and style_key in self.styles:
            parts.extend(self.styles[style_key])

        # Add modifiers
        if modifier_keys:
            for mod_key in modifier_keys:
                if mod_key in self.modifiers:
                    parts.append(self.modifiers[mod_key])

        return ", ".join(parts)

# Build library
library = PromptLibrary()

# Add subjects
library.add_subject("forest_path", "A winding path through an ancient forest")
library.add_subject("city_street", "A busy city street with tall buildings")
library.add_subject("space_station", "A futuristic space station orbiting Earth")

# Add styles
library.add_style("realistic", ["photorealistic", "detailed", "8k resolution"])
library.add_style("painterly", ["oil painting style", "artistic", "museum quality"])
library.add_style("scifi", ["sci-fi concept art", "futuristic", "detailed design"])

# Add modifiers
library.add_modifier("morning", "early morning light, misty atmosphere")
library.add_modifier("night", "night scene, dramatic lighting")
library.add_modifier("rain", "rainy weather, wet surfaces, reflections")

# Use library
prompt = library.compose(
    "forest_path",
    "painterly",
    ["morning", "rain"]
)
print(prompt)

Testing and Iteration

A/B Testing Prompts

from typing import Dict
import random

class PromptTester:
    def __init__(self, image_generator):
        self.generator = image_generator
        self.results = []

    def test_variations(
        self,
        base_subject: str,
        variations: Dict[str, str]
    ) -> List[dict]:
        """Test multiple prompt variations."""
        results = []

        for name, modifier in variations.items():
            prompt = f"{base_subject}, {modifier}"

            # Generate image
            urls = self.generator(prompt)

            results.append({
                "name": name,
                "prompt": prompt,
                "urls": urls
            })

        self.results.extend(results)
        return results

    def compare_styles(self, subject: str, styles: List[str]) -> List[dict]:
        """Compare different style applications."""
        variations = {style: style for style in styles}
        return self.test_variations(subject, variations)

# Usage example structure
# tester = PromptTester(generate_image)
# results = tester.compare_styles(
#     "A medieval knight",
#     ["photorealistic", "oil painting", "digital art", "anime style"]
# )

Best Practices Summary

  1. Be Specific: Vague prompts produce vague results
  2. Layer Your Description: Build from subject to style to details
  3. Use Style Anchors: Reference known styles for consistency
  4. Control Composition: Specify framing and perspective
  5. Specify Lighting: Light dramatically affects mood
  6. Iterate and Test: Compare variations to find what works
  7. Build a Library: Reuse successful prompt components

Conclusion

Effective prompt engineering transforms AI image generation from hit-or-miss to reliable and consistent. By understanding how models interpret prompts and using structured approaches, you can achieve better results with fewer iterations.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.