Image Generation with NeuroLink: Gemini Imagen and Beyond
Generate images programmatically using NeuroLink's unified API backed by Gemini Imagen models. Control size, style, and build production image pipelines in TypeScript.
In this guide, you will generate images using NeuroLink’s integration with Gemini Imagen and other image generation providers. You will configure image generation parameters, implement prompt engineering for visual content, handle different output formats, and build a batch image generation pipeline.
NeuroLink provides a unified image generation API that lets you generate images from text prompts with the same SDK and patterns you use for text generation. Currently backed by Google Gemini’s Imagen models, the API handles prompt-to-image generation, output format handling (base64 and URLs), and integration with your existing TypeScript codebase. The result is a single generate() call that produces publication-ready images.
In this tutorial, you will learn how to generate images with various configurations, engineer effective prompts, build a production image pipeline that combines LLM-powered prompt optimization with image generation, and handle errors and safety filters gracefully.
Architecture Overview
The image generation pipeline follows a straightforward flow from input to output, with NeuroLink handling the provider communication and response processing:
flowchart LR
subgraph Input["Input"]
PROMPT["Text Prompt"]
CONFIG["Generation Config<br/>size, style, count"]
REF["Reference Image<br/>(optional)"]
end
subgraph NeuroLink["NeuroLink SDK"]
IGEN["Image Generation Service"]
PROC["Image Processor"]
end
subgraph Provider["Provider"]
GEMINI["Google Gemini Imagen"]
end
subgraph Output["Output"]
IMG1["Generated Image 1"]
IMG2["Generated Image 2"]
META["Metadata<br/>dimensions, format"]
end
PROMPT --> IGEN
CONFIG --> IGEN
REF --> IGEN
IGEN --> GEMINI
GEMINI --> PROC
PROC --> IMG1 & IMG2 & META
style PROMPT fill:#3b82f6,stroke:#2563eb,color:#fff
style IGEN fill:#6366f1,stroke:#4f46e5,color:#fff
style GEMINI fill:#f59e0b,stroke:#d97706,color:#fff
style IMG1 fill:#22c55e,stroke:#16a34a,color:#fff
style IMG2 fill:#22c55e,stroke:#16a34a,color:#fff
You provide a text prompt, optional configuration, and an optional reference image. NeuroLink routes the request to the Gemini Imagen model via the Vertex AI provider, processes the response, and returns the generated images as base64-encoded data or URLs along with metadata.
Basic Image Generation
Generating an image requires a single generate() call with an image-capable model:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { NeuroLink } from '@juspay/neurolink';
const neurolink = new NeuroLink();
// Generate an image from a text prompt using generate() with an image-capable model
const result = await neurolink.generate({
input: { text: 'A modern minimalist office workspace with a MacBook, coffee cup, and plant, soft natural lighting, photography style' },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
// Result contains generated image in imageOutput
if (result.imageOutput?.base64) {
const fs = await import('fs/promises');
await fs.writeFile('generated-workspace.png', Buffer.from(result.imageOutput.base64, 'base64'));
console.log('Image saved');
}
if (result.imageOutput?.url) {
console.log('Image URL:', result.imageOutput.url);
}
The response includes imageOutput with either a base64 field (for direct binary access) or a url field (for CDN-hosted results), depending on the provider configuration.
Note: Image generation uses the
vertexprovider with an image-capable model likegemini-2.5-flash-image. The samegenerate()method handles both text and image generation – the model determines the output type.
Configuration Options
Image Size and Aspect Ratio
Control the output dimensions by including size directives in your prompt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Square image (1:1) -- use generate() with image-capable model
const square = await neurolink.generate({
input: { text: 'Product icon for a meditation app' },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
// Landscape (16:9) for blog headers
const landscape = await neurolink.generate({
input: { text: 'Abstract technology background with flowing data streams, landscape 16:9 format' },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
// Portrait (9:16) for mobile content
const portrait = await neurolink.generate({
input: { text: 'Smartphone app mockup showing a fitness dashboard, portrait 9:16 format' },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
Generating Multiple Variations
When you need several options to choose from, generate variations by making multiple generate() calls with slightly different prompts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Generate variations by making multiple generate() calls
const prompts = [
'Company logo: abstract neural network symbol, blue and purple gradient, flat design, variation 1',
'Company logo: abstract neural network symbol, blue and purple gradient, flat design, variation 2',
'Company logo: abstract neural network symbol, blue and purple gradient, flat design, variation 3',
'Company logo: abstract neural network symbol, blue and purple gradient, flat design, variation 4',
];
const variations = await Promise.all(
prompts.map(prompt =>
neurolink.generate({
input: { text: prompt },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
})
)
);
// Save all variations
for (let i = 0; i < variations.length; i++) {
if (variations[i].imageOutput?.base64) {
const fs = await import('fs/promises');
await fs.writeFile(
`logo-variation-${i + 1}.png`,
Buffer.from(variations[i].imageOutput.base64, 'base64')
);
}
}
Negative Prompts and Style Control
Guide the model away from unwanted styles by including negative directives in your prompt:
1
2
3
4
5
const result = await neurolink.generate({
input: { text: 'Professional headshot of a business executive, natural lighting, neutral background. Avoid: cartoon, anime, illustration, low quality, blurry, distorted' },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
The “Avoid:” prefix works as a negative prompt, telling the model what visual characteristics to exclude from the generated image.
Prompt Engineering for Image Generation
The quality of generated images depends heavily on prompt quality. Here are the techniques that produce the best results:
| Technique | Example | Effect |
|---|---|---|
| Be specific about style | “watercolor painting style” | Controls artistic style |
| Specify lighting | “soft natural window lighting” | Controls mood and atmosphere |
| Define composition | “close-up, centered, shallow depth of field” | Controls framing and focus |
| Include context | “in a modern office setting” | Grounds the scene |
| Use negative prompts | “Avoid: blurry, low quality” | Prevents unwanted artifacts |
| Reference art styles | “in the style of flat vector illustration” | Matches brand aesthetic |
Prompt Structure Formula
A well-structured image prompt follows this pattern:
[Subject] + [Style] + [Lighting] + [Composition] + [Setting] + [Quality modifiers]
For example: “A software engineer working at a standing desk (subject), photorealistic style (style), warm afternoon sunlight from a window (lighting), medium shot from the side (composition), in a modern tech office with plants (setting), high detail, 4K quality (quality).”
This structured approach produces significantly more consistent and predictable results than vague prompts like “person working at computer.”
Building an Image Generation Pipeline
The most powerful pattern combines LLM-powered prompt optimization with image generation. The LLM generates an optimized, detailed prompt from a high-level description, and then that prompt drives the image generation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { NeuroLink } from '@juspay/neurolink';
const neurolink = new NeuroLink();
interface BlogPost {
title: string;
summary: string;
style: 'photography' | 'illustration' | 'abstract';
}
async function generateBlogIllustration(post: BlogPost): Promise<Buffer> {
// Step 1: Use LLM to create an optimized image prompt from the blog post
const promptResult = await neurolink.generate({
input: { text: `Create a detailed image generation prompt for a blog post illustration.
Blog title: ${post.title}
Summary: ${post.summary}
Style: ${post.style}
The prompt should be vivid, specific, and optimized for AI image generation.
Return ONLY the image prompt, nothing else.` },
provider: 'openai',
model: 'gpt-4o',
temperature: 0.7,
});
// Step 2: Generate the image using generate() with image-capable model
const imageResult = await neurolink.generate({
input: { text: promptResult.content },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
if (!imageResult.imageOutput?.base64) {
throw new Error('No image generated');
}
return Buffer.from(imageResult.imageOutput.base64, 'base64');
}
// Usage
const illustration = await generateBlogIllustration({
title: 'The Future of Multi-Agent AI Systems',
summary: 'How networks of specialized AI agents will transform enterprise software',
style: 'abstract',
});
This two-stage pipeline consistently produces better images than direct prompts because the LLM expands vague descriptions into detailed, visually specific prompts that the image model can execute precisely.
flowchart LR
BLOG["Blog Post<br/>Title + Summary"] --> LLM["LLM<br/>Optimize Prompt"]
LLM --> IMAGEN["Image Gen<br/>Gemini Imagen"]
IMAGEN --> PROCESS["Post-Process<br/>Resize, Optimize"]
PROCESS --> CDN["CDN Upload"]
CDN --> LIVE["Live on Blog"]
style BLOG fill:#3b82f6,stroke:#2563eb,color:#fff
style LLM fill:#6366f1,stroke:#4f46e5,color:#fff
style IMAGEN fill:#f59e0b,stroke:#d97706,color:#fff
style PROCESS fill:#10b981,stroke:#059669,color:#fff
style CDN fill:#8b5cf6,stroke:#7c3aed,color:#fff
style LIVE fill:#22c55e,stroke:#16a34a,color:#fff
Image Editing and Inpainting
Beyond generating images from scratch, image-capable models support several editing workflows:
- Reference-guided generation: Provide an existing image as a reference alongside your text prompt to guide the style or composition of the generated image.
- Inpainting: Modify specific regions of an existing image while preserving the rest. Useful for product mockups where you want to change the background or replace a logo.
- Style transfer: Apply the visual style of one image to the content of another. This helps maintain brand consistency across a series of images.
- Upscaling: Enhance the resolution of a generated image for print-quality output.
These workflows use the same generate() method with additional input images and descriptive prompts that guide the editing operation.
Error Handling and Safety
Image generation APIs include content safety filters that reject prompts violating usage policies. Your code should handle these rejections gracefully:
1
2
3
4
5
6
7
8
9
10
11
12
13
try {
const result = await neurolink.generate({
input: { text: userProvidedPrompt },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
return result.imageOutput;
} catch (error) {
if (error.message?.includes('safety') || error.message?.includes('policy')) {
return { error: 'The requested image could not be generated due to content policy. Please revise your prompt.' };
}
throw error;
}
Rate Limiting
Image generation endpoints typically have stricter rate limits than text generation. For batch operations, use concurrency control with p-limit to stay within provider limits:
1
2
3
4
5
6
7
8
9
10
11
12
13
import pLimit from 'p-limit';
const limit = pLimit(3); // Max 3 concurrent image generations
const images = await Promise.all(
prompts.map(prompt =>
limit(() => neurolink.generate({
input: { text: prompt },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
}))
)
);
Retry Patterns
Transient failures (network timeouts, temporary service issues) should be retried with exponential backoff. Permanent failures (safety rejections, invalid prompts) should not:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function generateWithRetry(prompt: string, maxRetries = 3): Promise<Buffer> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await neurolink.generate({
input: { text: prompt },
provider: 'vertex',
model: 'gemini-2.5-flash-image',
});
if (result.imageOutput?.base64) {
return Buffer.from(result.imageOutput.base64, 'base64');
}
throw new Error('No image in response');
} catch (error) {
// Do not retry safety/policy rejections
if (error.message?.includes('safety') || error.message?.includes('policy')) {
throw error;
}
if (attempt === maxRetries) throw error;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
}
}
throw new Error('Max retries exceeded');
}
Cost Management
Image generation is more expensive than text generation. Here are strategies to control costs:
- Generate fewer variations: Instead of generating 10 options and picking the best, use the LLM prompt optimization pipeline to get a better prompt, then generate 2-3 targeted variations.
- Use smaller sizes for previews: Generate low-resolution previews for review, then regenerate the approved design at full resolution.
- Cache generated images: Store generated images by prompt hash. If the same prompt is requested again, serve the cached version.
- Set budget limits: Track generation count and cost per user or session. Reject requests that exceed the budget.
- Batch efficiently: Schedule non-urgent image generation for off-peak hours when rate limits are less contended.
Production Integration Patterns
For production deployments, consider these integration patterns:
- CDN delivery: Upload generated images to object storage (S3, GCS) and serve through a CDN for fast global delivery.
- Background job queues: Use Bull or BullMQ to queue image generation jobs, especially for batch operations that can tolerate latency.
- Webhook notifications: For longer-running batch operations, notify the requester via webhook when all images are ready.
- Thumbnail generation: Generate multiple sizes from each image for gallery views, social media cards, and responsive layouts.
What’s Next
You have completed all the steps in this guide. To continue building on what you have learned:
- Review the code examples and adapt them for your specific use case
- Start with the simplest pattern first and add complexity as your requirements grow
- Monitor performance metrics to validate that each change improves your system
- Consult the NeuroLink documentation for advanced configuration options
Related posts:
