
Nano Banana 2
- 2 installs
- 9 repo stars
- Updated March 17, 2026
- enzed/skills
Generates and edits images with Google Gemini Nano Banana 2 via a CLI, including transparency extraction through difference matting.
About
Generates and edits images with Google Gemini's Nano Banana 2 (gemini-3.1-flash-image-preview) via a bundled CLI script. A developer uses it to create images, edit existing ones, or extract true-alpha transparent cutouts for game assets, icons, and stickers.
- Text-to-image, image editing, and multi-turn editing via Gemini 3.1 Flash
- Transparency extraction using difference-matting against white and black backgrounds
Nano Banana 2 by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,166 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 25, 2026 (Skillselion catalog sync)
npx skills add https://github.com/enzed/skills --skill nano-banana-2Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 9 |
| Last updated | March 17, 2026 |
| Repository | enzed/skills ↗ |
What it does
Generates and edits images with Google Gemini Nano Banana 2 via a CLI, including transparency extraction through difference matting.
Files
Nano Banana 2 Image Generation & Editing
Generate and edit images using Google's Gemini 3.1 Flash model with advanced transparency support.
Prerequisites
1. Dependencies:
pip install google-genai Pillow numpy python-dotenv2. API Key: The script loads from .env automatically. Only ask the user if the script fails with "No API key found".
CLI Usage (REQUIRED)
ALWAYS use the CLI script. Do NOT write Python code or create .py files.
Run scripts/generate.py directly:
# Basic generation
python scripts/generate.py "a cute banana sticker" -o banana.png
# With transparency (for game assets, stickers, icons)
python scripts/generate.py "pixel art sword" -o sword.png --transparent
# Custom size and aspect ratio
python scripts/generate.py "game logo" -o logo.png --size 4K --ratio 16:9Options:
-o, --output- Output filename (default: output.png)--transparent- Extract true alpha channel using difference matting--size- 1K, 2K, or 4K (default: 2K)--ratio- Aspect ratio: 1:1, 16:9, 9:16, etc. (default: 1:1)--model- Model override (default: gemini-3.1-flash-image-preview)
Note: The script loads the API key from .env automatically. Do not check for API keys manually or ask the user about them - just run the script and it will error with instructions if the key is missing.
Intent Detection
Analyze user request to determine:
| Intent | Triggers | Action |
|---|---|---|
| Generate | "create", "generate", "make", "draw", "design" | Text-to-image |
| Edit | "edit", "change", "modify", "update", "fix" | Image-to-image |
| Transparency | "transparent", "remove background", "alpha", "cutout", "PNG with transparency" | Use difference matting |
| Text overlay | "add text", "write on", "label", "caption" | Use Gemini 3.1 Flash for accurate text |
Resolution Selection
Choose resolution based on use case:
| Resolution | Best For | Pixel Output |
|---|---|---|
| 1K | Quick previews, thumbnails, web icons | ~1024px |
| 2K | Social media, standard web images | ~2048px |
| 4K | Print, professional assets, sprite sheets | ~4096px |
Heuristics:
- Sprite sheets, game assets, print materials → 4K
- Social media, blog images, presentations → 2K
- Quick tests, thumbnails, prototypes → 1K
When uncertain, ask user or default to 2K.
Aspect Ratios
Available: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9
Selection guide:
- Square content (icons, avatars, social posts) →
1:1 - Portrait (mobile, vertical video) →
9:16or3:4 - Landscape (desktop, presentations) →
16:9or3:2 - Cinematic/ultrawide →
21:9
Core Implementation
Basic Generation
from google import genai
from google.genai import types
from PIL import Image
import io
client = genai.Client()
response = client.models.generate_content(
model="gemini-3.1-flash-image-preview",
contents="Your descriptive prompt here",
config=types.GenerateContentConfig(
response_modalities=['IMAGE'],
image_config=types.ImageConfig(
aspect_ratio="1:1", # or other ratio
image_size="2K" # 1K, 2K, or 4K
),
),
)
# Extract image from response
for part in response.parts:
if part.inline_data is not None:
image = Image.open(io.BytesIO(part.inline_data.data))
image.save("output.png")
breakImage Editing
# Load existing image
input_image = Image.open("input.png")
response = client.models.generate_content(
model="gemini-3.1-flash-image-preview",
contents=[
input_image,
"Edit instruction: Change the background to sunset colors"
],
config=types.GenerateContentConfig(
response_modalities=['TEXT', 'IMAGE'],
image_config=types.ImageConfig(
aspect_ratio="1:1",
image_size="2K"
),
),
)Multi-Turn Editing
Preserve context across edits using thought signatures:
# First edit
response1 = client.models.generate_content(
model="gemini-3.1-flash-image-preview",
contents=[image, "Add a red hat"],
config=config,
)
# Continue editing (include previous response)
response2 = client.models.generate_content(
model="gemini-3.1-flash-image-preview",
contents=[
image,
"Add a red hat",
response1, # Include for context preservation
"Now make the hat blue instead"
],
config=config,
)Transparency Extraction
When user needs transparent images, use difference matting. See scripts/transparency.py.
When to use:
- User explicitly asks for transparency
- Game sprites, icons, logos
- Assets that will be composited
- Cutouts and stickers
Process: 1. Generate image on pure white background (#FFFFFF) 2. Edit same image to pure black background (#000000) 3. Calculate alpha from pixel differences 4. Recover original colors
Key insight: Opaque pixels appear identical on both backgrounds (distance ≈ 0), transparent pixels show background color (max distance).
from scripts.transparency import extract_alpha_difference_matting
# After generating white and black background versions
final_image = extract_alpha_difference_matting(img_on_white, img_on_black)
final_image.save("output.png") # RGBA with true transparencyPrompt Engineering
Fundamental Principle
"Describe the scene, don't just list keywords."
Narrative paragraphs outperform disconnected word lists.
Effective Prompt Structure
[Style/Medium] of [Subject] in [Context/Setting], [Lighting], [Additional details]Examples:
# Photorealistic
A professional studio photograph of a brass steampunk pocket watch,
shot with a 50mm lens, soft diffused lighting from the left,
shallow depth of field with bokeh background, 4K HDR quality.
# Illustration
A detailed digital illustration of a medieval blacksmith's forge,
isometric perspective, warm orange glow from the furnace,
dieselpunk aesthetic with exposed pipes and riveted metal plates.
# Product mockup
A product photography shot of a ceramic coffee mug on a marble surface,
natural window lighting, minimalist Scandinavian style, clean white background.Text in Images
For images containing text, use Gemini 3.1 Flash:
- Keep text to 25 characters or less per element
- Use 2-3 distinct text phrases maximum
- Specify font style generally (bold, elegant, handwritten)
- Indicate size (small, medium, large)
Quality Modifiers
Add these for enhanced output:
- Photography: 4K, HDR, studio photo, professional lighting
- Art: detailed, by a professional, high-quality illustration
- General: high-fidelity, crisp details, polished finish
Error Handling
from google.genai import errors
def generate_with_retry(client, *, model, contents, config, max_attempts=5):
for attempt in range(1, max_attempts + 1):
try:
return client.models.generate_content(
model=model, contents=contents, config=config
)
except errors.APIError as e:
code = getattr(e, "code", None) or getattr(e, "status", None)
if code not in (429, 500, 502, 503, 504) or attempt >= max_attempts:
raise
delay = min(30, 2 ** (attempt - 1))
time.sleep(delay)Model Selection
| Model | Use Case |
|---|---|
gemini-3.1-flash-image-preview | Fast generation, image editing, text rendering, multi-turn, transparency workflows |
gemini-3-pro-image-preview | Complex edits, highest quality output |
imagen-4.0-generate-001 | Photorealistic images, no editing needed |
Default to gemini-3.1-flash-image-preview for most tasks.
File References
scripts/generate.py- CLI for image generation (use this instead of writing code)scripts/transparency.py- Difference matting implementationreferences/prompts.md- Extended prompt examples by category
Prompt Engineering Reference
Extended examples and templates for Nano Banana 2 image generation.
Prompt Structure Template
[Medium/Style] of [Subject] [doing action] in [Setting/Context],
[Lighting description], [Camera/perspective details],
[Quality modifiers], [Style-specific details]Category Examples
Photorealistic Photography
A professional product photograph of a vintage brass compass
on a weathered wooden table, natural window light from the left,
shallow depth of field with creamy bokeh, shot with 85mm portrait lens,
4K resolution, studio quality.An editorial fashion photograph of a woman wearing a flowing red dress
walking through a misty forest at dawn, golden hour backlighting,
captured with a Canon 5D Mark IV, 24-70mm lens at f/2.8,
cinematic color grading, high-end magazine quality.Digital Illustration
A detailed digital illustration of a steampunk airship
hovering above Victorian London rooftops at sunset,
warm orange and purple sky, brass and copper metal details,
gears and steam pipes visible, isometric perspective,
high-quality game asset style.A whimsical watercolor illustration of a cozy bookshop interior
with floor-to-ceiling shelves, a cat sleeping on a reading chair,
warm lamplight, soft pastel colors with ink outlines,
children's book illustration style.Game Assets & Sprites
A 2D isometric game building sprite of a medieval blacksmith forge,
pixel art style with modern detail, warm orange glow from furnace,
anvil and tools visible, stone walls with wooden beams,
clean edges suitable for game engine, transparent background.A top-down RPG treasure chest sprite, closed position,
ornate gold trim on dark wood, gemstone inlays,
32x32 pixel grid aligned, retro 16-bit style,
pure transparent background.Icons & UI Elements
A flat design app icon of a lightning bolt,
electric blue gradient on dark background,
rounded corners, clean vector style,
suitable for iOS app store, 1024x1024 square format.A minimalist line icon set for a weather app:
sun, cloud, rain, snow, and wind symbols,
consistent 2px stroke weight, white on transparent,
modern UI design, 64x64 each.Concept Art
A dramatic concept art painting of a lone knight
standing before a massive dragon in a volcanic cavern,
dynamic lighting with lava glow and rim light,
epic fantasy atmosphere, painterly brushstrokes,
cinematic widescreen composition, AAA game quality.Product Mockups
A lifestyle product mockup of a minimalist ceramic coffee mug
on a marble countertop next to a croissant and newspaper,
soft morning light from a nearby window,
Scandinavian interior design aesthetic,
clean and aspirational brand photography style.Logos & Branding
A modern minimalist logo design for a tech startup called "Nexus",
abstract geometric shapes suggesting connection and innovation,
clean lines, balanced negative space,
works at small sizes, professional corporate identity style.Text-Heavy Designs
Use Gemini 3.1 Flash for accurate text rendering:
A vintage-style coffee shop menu board,
hand-lettered chalk typography on dark green background,
featuring "ESPRESSO $3" and "LATTE $4" in decorative script,
rustic wooden frame, warm cafe lighting,
authentic hand-crafted appearance.Quality Modifier Reference
Photography
- 4K, 8K resolution
- HDR, high dynamic range
- Studio lighting, professional lighting
- Shot with [specific camera/lens]
- Shallow/deep depth of field
- Golden hour, blue hour, dramatic lighting
Digital Art
- Highly detailed
- Professional quality
- Award-winning
- Trending on ArtStation
- Concept art quality
- AAA game quality
Style References
- In the style of [art movement]: impressionism, art deco, art nouveau
- [Medium] style: oil painting, watercolor, charcoal, pencil sketch
- [Era] aesthetic: 80s retro, vintage, futuristic, cyberpunk
Aspect Ratio Guidelines
| Ratio | Use Cases |
|---|---|
| 1:1 | Social media posts, profile pictures, app icons |
| 3:2 | Standard photography, prints |
| 4:3 | Presentations, traditional displays |
| 16:9 | Desktop wallpapers, YouTube thumbnails, widescreen |
| 9:16 | Mobile wallpapers, Instagram stories, TikTok |
| 21:9 | Ultrawide monitors, cinematic banners |
Transparency Considerations
When generating for transparency:
- Explicitly request: "on a pure white background" or "transparent background"
- Avoid: white/light colored subjects (hard to extract)
- Best for: game sprites, icons, logos, stickers, cutout elements
- Use difference matting workflow for best results
#!/usr/bin/env python3
"""
CLI for generating images with Google Gemini.
Usage:
python scripts/generate.py "a cute banana sticker" --output sticker.png
python scripts/generate.py "pixel art sword" --output sword.png --transparent
python scripts/generate.py "game logo" --output logo.png --size 4K --ratio 16:9
"""
import argparse
import io
import os
import sys
from pathlib import Path
def main():
parser = argparse.ArgumentParser(
description="Generate images using Google Gemini",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python scripts/generate.py "a banana sticker" -o banana.png
python scripts/generate.py "pixel art sword" -o sword.png --transparent
python scripts/generate.py "game logo" -o logo.png --size 4K --ratio 16:9
"""
)
parser.add_argument("prompt", help="Image generation prompt")
parser.add_argument("-o", "--output", default="output.png", help="Output filename (default: output.png)")
parser.add_argument("--transparent", action="store_true", help="Extract transparency using difference matting")
parser.add_argument("--size", choices=["1K", "2K", "4K"], default="2K", help="Image size (default: 2K)")
parser.add_argument("--ratio", default="1:1",
choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
help="Aspect ratio (default: 1:1)")
parser.add_argument("--model", default="gemini-3.1-flash-image-preview", help="Model to use")
args = parser.parse_args()
# Load environment variables
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass # dotenv is optional, user may have exported the key directly
api_key = os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
if not api_key:
print("Error: No API key found.", file=sys.stderr)
print("Set GOOGLE_API_KEY in .env or export it directly.", file=sys.stderr)
print("Get a key at: https://aistudio.google.com/apikey", file=sys.stderr)
sys.exit(1)
# Import dependencies
try:
from google import genai
from google.genai import types
from PIL import Image
except ImportError as e:
print(f"Error: Missing dependency - {e}", file=sys.stderr)
print("Install with: pip install google-genai Pillow numpy", file=sys.stderr)
sys.exit(1)
# Initialize client
client = genai.Client(api_key=api_key)
if args.transparent:
# Use transparency workflow
try:
import numpy as np
from transparency import extract_alpha_difference_matting, background_is_black
except ImportError:
# Try relative import for when run from repo root
script_dir = Path(__file__).parent
sys.path.insert(0, str(script_dir))
from transparency import extract_alpha_difference_matting, background_is_black
print(f"Generating transparent image: {args.prompt}")
print("Step 1/3: Generating on white background...")
white_prompt = f"{args.prompt}\n\nIMPORTANT: Use a pure white background (#FFFFFF, RGB 255,255,255). Do not use white anywhere on the subject itself."
response = client.models.generate_content(
model=args.model,
contents=white_prompt,
config=types.GenerateContentConfig(
response_modalities=['IMAGE'],
image_config=types.ImageConfig(
aspect_ratio=args.ratio,
image_size=args.size
),
),
)
img_on_white = None
for part in response.parts:
if part.inline_data is not None:
img_on_white = Image.open(io.BytesIO(part.inline_data.data))
break
if img_on_white is None:
print("Error: Failed to generate image on white background", file=sys.stderr)
sys.exit(1)
print("Step 2/3: Converting to black background...")
img_on_black = None
for attempt in range(1, 6):
black_prompt = """Replace ONLY the background with pure black (#000000, RGB 0,0,0).
Keep EVERYTHING else exactly unchanged:
- Same subject in exact same position
- Same colors on the subject
- Same details and features
Only change the white background pixels to pure black."""
response = client.models.generate_content(
model=args.model,
contents=[img_on_white, black_prompt],
config=types.GenerateContentConfig(
response_modalities=['TEXT', 'IMAGE'],
image_config=types.ImageConfig(
aspect_ratio=args.ratio,
image_size=args.size
),
),
)
for part in response.parts:
if part.inline_data is not None:
img_on_black = Image.open(io.BytesIO(part.inline_data.data))
break
if img_on_black is not None and background_is_black(img_on_black):
break
print(f" Retry {attempt}/5 - background not black enough...")
if img_on_black is None or not background_is_black(img_on_black):
print("Error: Failed to convert to black background", file=sys.stderr)
sys.exit(1)
print("Step 3/3: Extracting transparency...")
final_image = extract_alpha_difference_matting(img_on_white, img_on_black)
else:
# Simple generation
print(f"Generating image: {args.prompt}")
response = client.models.generate_content(
model=args.model,
contents=args.prompt,
config=types.GenerateContentConfig(
response_modalities=['IMAGE'],
image_config=types.ImageConfig(
aspect_ratio=args.ratio,
image_size=args.size
),
),
)
final_image = None
for part in response.parts:
if part.inline_data is not None:
final_image = Image.open(io.BytesIO(part.inline_data.data))
break
if final_image is None:
print("Error: Failed to generate image", file=sys.stderr)
sys.exit(1)
# Save output
final_image.save(args.output)
print(f"Saved: {args.output}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Difference matting for true alpha channel extraction.
This technique generates an image on both white and black backgrounds,
then mathematically extracts the true alpha channel from the difference.
Usage:
from transparency import extract_alpha_difference_matting
final = extract_alpha_difference_matting(img_on_white, img_on_black)
final.save("output.png") # RGBA with true transparency
"""
import math
import numpy as np
from PIL import Image
def extract_alpha_difference_matting(
img_on_white: Image.Image,
img_on_black: Image.Image
) -> Image.Image:
"""
Extract true alpha channel using difference matting technique.
Algorithm:
- If a pixel is 100% opaque, it looks the same on black and white (distance = 0)
- If a pixel is 100% transparent, it shows the background (distance = max)
- Semi-transparent pixels fall in between
Args:
img_on_white: Image generated with pure white (#FFFFFF) background
img_on_black: Same image with pure black (#000000) background
Returns:
RGBA image with true transparency extracted
"""
# Convert to numpy arrays
white_arr = np.array(img_on_white.convert('RGB'), dtype=np.float32)
black_arr = np.array(img_on_black.convert('RGB'), dtype=np.float32)
# Distance between pure white (255,255,255) and pure black (0,0,0)
# sqrt(255^2 + 255^2 + 255^2) ≈ 441.67
bg_dist = math.sqrt(3 * 255 * 255)
# Calculate pixel distance between the two images
diff = white_arr - black_arr
pixel_dist = np.sqrt(np.sum(diff ** 2, axis=2))
# Calculate alpha: opaque pixels have small distance, transparent have large
alpha = 1.0 - (pixel_dist / bg_dist)
alpha = np.clip(alpha, 0, 1)
# Recover original color from the black background version
# C_observed = C_original * alpha + background * (1 - alpha)
# For black background: C_observed = C_original * alpha
# So: C_original = C_observed / alpha
alpha_expanded = np.expand_dims(alpha, axis=2)
alpha_safe = np.where(alpha_expanded > 0.01, alpha_expanded, 1.0)
rgb_recovered = black_arr / alpha_safe
rgb_recovered = np.clip(rgb_recovered, 0, 255).astype(np.uint8)
# Combine RGB with alpha channel
alpha_uint8 = (alpha * 255).astype(np.uint8)
# Create RGBA image
rgba = np.dstack((rgb_recovered, alpha_uint8))
return Image.fromarray(rgba, 'RGBA')
def background_is_black(image: Image.Image, threshold: int = 20) -> bool:
"""
Verify that an image has a black background by checking corner pixels.
Args:
image: Image to check
threshold: Maximum average RGB value for "black" (default 20)
Returns:
True if all corners are near-black
"""
rgb = image.convert("RGB")
w, h = rgb.size
corners = [(0, 0), (w - 1, 0), (0, h - 1), (w - 1, h - 1)]
for x, y in corners:
r, g, b = rgb.getpixel((x, y))
if (r + g + b) / 3 > threshold:
return False
return True
def generate_transparent_image(
client,
prompt: str,
aspect_ratio: str = "1:1",
image_size: str = "2K",
model: str = "gemini-3.1-flash-image-preview",
max_black_attempts: int = 5
) -> Image.Image:
"""
Generate an image with true transparency using difference matting.
This is a complete workflow that:
1. Generates on white background
2. Edits to black background
3. Extracts alpha via difference matting
Args:
client: google.genai.Client instance
prompt: Image generation prompt (will be augmented with background instruction)
aspect_ratio: Output aspect ratio
image_size: Output resolution (1K, 2K, 4K)
model: Model to use
max_black_attempts: Max retries for black background conversion
Returns:
RGBA image with true transparency
"""
from google.genai import types
import io
import time
# Step 1: Generate on white background
white_prompt = f"{prompt}\n\nIMPORTANT: Use a pure white background (#FFFFFF, RGB 255,255,255). Do not use white anywhere on the subject itself."
response = client.models.generate_content(
model=model,
contents=white_prompt,
config=types.GenerateContentConfig(
response_modalities=['IMAGE'],
image_config=types.ImageConfig(
aspect_ratio=aspect_ratio,
image_size=image_size
),
),
)
img_on_white = None
for part in response.parts:
if part.inline_data is not None:
img_on_white = Image.open(io.BytesIO(part.inline_data.data))
break
if img_on_white is None:
raise RuntimeError("Failed to generate image on white background")
# Step 2: Edit to black background
img_on_black = None
for attempt in range(1, max_black_attempts + 1):
black_prompt = """Replace ONLY the background with pure black (#000000, RGB 0,0,0).
Keep EVERYTHING else exactly unchanged:
- Same subject in exact same position
- Same colors on the subject
- Same details and features
Only change the white background pixels to pure black."""
response = client.models.generate_content(
model=model,
contents=[img_on_white, black_prompt],
config=types.GenerateContentConfig(
response_modalities=['TEXT', 'IMAGE'],
image_config=types.ImageConfig(
aspect_ratio=aspect_ratio,
image_size=image_size
),
),
)
for part in response.parts:
if part.inline_data is not None:
img_on_black = Image.open(io.BytesIO(part.inline_data.data))
break
if img_on_black is not None and background_is_black(img_on_black):
break
if attempt < max_black_attempts:
time.sleep(1)
if img_on_black is None or not background_is_black(img_on_black):
raise RuntimeError("Failed to convert to black background after retries")
# Step 3: Extract alpha
return extract_alpha_difference_matting(img_on_white, img_on_black)