Nette Documentation Preview

syntax
Image Generation
****************

.[perex]
You describe what you want to see and get an image back. We will look at how to generate and save one, how to give the model a reference to work from, how the providers differ, and what you will run into once price and waiting time come up.


The First Image
===============

Generating takes a single method. You tell it which model should draw, and what:

```php
$client = new AIAccess\Provider\OpenAI\Client($apiKey);

$image = $client->generateImage('gpt-image-2', 'A lighthouse on a cliff in a storm, flat vector illustration');
$image->save('/path/to/lighthouse.png');
```

The return value is not a string with a URL but a `Media` object holding the image data itself. You already know it from the chapter on [images as input |multimodal]; it is the same object, traveling to the model one way and back from it the other.

Besides `save()` it gives you the raw data through `getData()`, in case you want to store the image yourself, in a database say, or send it straight to the browser.


Find Out What You Received
==========================

Here is the first trap that is easy to fall into: **do not assume you will get a PNG**. OpenAI sends PNG by default, but Gemini returns JPEG. Hard-code the `.png` extension and you end up with a JPEG saved under the wrong name.

The image tells you its content type, so use it:

```php
$extension = explode('/', $image->getMimeType())[1];
$image->save("/path/to/lighthouse.$extension");
```


A Reference Instead of Words Alone
==================================

Alongside the description you can give the model images to work from. That is useful for edits, for variations on one motif, or for holding a single style across a set of images:

```php
use AIAccess\Media;

$image = $client->generateImage(
	'gpt-image-2',
	'The same lighthouse, but on a sunny morning',
	references: [Media::fromFile('/path/to/lighthouse.png')],
);
```

There can be more than one reference. Grok does not accept them at all and refuses them with an `AIAccess\LogicException` before the request even leaves.


What Each Provider Can Do
=========================

| Provider | Generating | References | Where it runs                      |
|----------|------------|------------|------------------------------------|
| OpenAI   | ✅         | ✅         | a separate endpoint for images     |
| Gemini   | ✅         | ✅         | ordinary chat, with an image model |
| Grok     | ✅         | ➖         | a separate endpoint for images     |
| Claude   | ➖         | ➖         | does not generate images           |
| DeepSeek | ➖         | ➖         | does not generate images           |

The last column is worth explaining, because it is a neat illustration of what the library does for you. **Gemini has no image endpoint at all.** Its image models are addressed exactly like ordinary chat, only asking for a picture instead of text. You never need to know that: `generateImage()` looks the same with all three providers.

OpenAI additionally takes the optional `size`, `quality`, `background` and `format` parameters, telling it how large and how good an image you want and whether the background should be transparent:

```php
$image = $client->generateImage(
	'gpt-image-2',
	'An envelope icon, flat style',
	size: '1024x1024',
	quality: 'low',
	background: 'transparent',
);
```


Before You Put It into Production
=================================

**Images are orders of magnitude more expensive than text.** While an ordinary question costs a fraction of a cent, a single high-quality image costs real money. While testing, generate at low quality and a smaller resolution; that is more than enough to prove the code works.

**When you generate many of them, consider [batch processing |batch].** Providers charge roughly half for a deferred answer, and in practice image generation in a batch tends not to be slower than one at a time, rather the opposite. Images are added to an ordinary batch with `addImageRequest()`, which OpenAI and Gemini support.

**Generating takes a long time.** An ordinary image appears within seconds, but high quality with references can take minutes. The HTTP client's default timeout is 180 seconds, which may not be enough for those cases, so raise it:

```php
$http = (new AIAccess\Http\CurlClient)->setOptions(requestTimeout: 600);
$client = new AIAccess\Provider\OpenAI\Client($apiKey, $http);
```

**Gemini needs a paid project.** Its image models have a daily quota of zero on the free tier, so the call fails with a quota error even though you have generated nothing yet.

And one obvious thing that is easy to forget: instead of an image the model may refuse to draw, typically for content its rules do not allow. You then get an `AIAccess\UnexpectedResponseException`, because there is no image in the response. Expect that, especially when the description is assembled from user input.


Where to Go Next
================

- [Images and documents as input |multimodal] - the opposite direction, when the model should look at an image
- [HTTP layer |http] - timeouts, retries and request logging
- [Error handling |errors] - what the individual exceptions mean
- [Providers |providers] - what each one can do and how they differ

Image Generation

You describe what you want to see and get an image back. We will look at how to generate and save one, how to give the model a reference to work from, how the providers differ, and what you will run into once price and waiting time come up.

The First Image

Generating takes a single method. You tell it which model should draw, and what:

$client = new AIAccess\Provider\OpenAI\Client($apiKey);

$image = $client->generateImage('gpt-image-2', 'A lighthouse on a cliff in a storm, flat vector illustration');
$image->save('/path/to/lighthouse.png');

The return value is not a string with a URL but a Media object holding the image data itself. You already know it from the chapter on images as input; it is the same object, traveling to the model one way and back from it the other.

Besides save() it gives you the raw data through getData(), in case you want to store the image yourself, in a database say, or send it straight to the browser.

Find Out What You Received

Here is the first trap that is easy to fall into: do not assume you will get a PNG. OpenAI sends PNG by default, but Gemini returns JPEG. Hard-code the .png extension and you end up with a JPEG saved under the wrong name.

The image tells you its content type, so use it:

$extension = explode('/', $image->getMimeType())[1];
$image->save("/path/to/lighthouse.$extension");

A Reference Instead of Words Alone

Alongside the description you can give the model images to work from. That is useful for edits, for variations on one motif, or for holding a single style across a set of images:

use AIAccess\Media;

$image = $client->generateImage(
	'gpt-image-2',
	'The same lighthouse, but on a sunny morning',
	references: [Media::fromFile('/path/to/lighthouse.png')],
);

There can be more than one reference. Grok does not accept them at all and refuses them with an AIAccess\LogicException before the request even leaves.

What Each Provider Can Do

Provider Generating References Where it runs
OpenAI a separate endpoint for images
Gemini ordinary chat, with an image model
Grok a separate endpoint for images
Claude does not generate images
DeepSeek does not generate images

The last column is worth explaining, because it is a neat illustration of what the library does for you. Gemini has no image endpoint at all. Its image models are addressed exactly like ordinary chat, only asking for a picture instead of text. You never need to know that: generateImage() looks the same with all three providers.

OpenAI additionally takes the optional size, quality, background and format parameters, telling it how large and how good an image you want and whether the background should be transparent:

$image = $client->generateImage(
	'gpt-image-2',
	'An envelope icon, flat style',
	size: '1024x1024',
	quality: 'low',
	background: 'transparent',
);

Before You Put It into Production

Images are orders of magnitude more expensive than text. While an ordinary question costs a fraction of a cent, a single high-quality image costs real money. While testing, generate at low quality and a smaller resolution; that is more than enough to prove the code works.

When you generate many of them, consider batch processing. Providers charge roughly half for a deferred answer, and in practice image generation in a batch tends not to be slower than one at a time, rather the opposite. Images are added to an ordinary batch with addImageRequest(), which OpenAI and Gemini support.

Generating takes a long time. An ordinary image appears within seconds, but high quality with references can take minutes. The HTTP client's default timeout is 180 seconds, which may not be enough for those cases, so raise it:

$http = (new AIAccess\Http\CurlClient)->setOptions(requestTimeout: 600);
$client = new AIAccess\Provider\OpenAI\Client($apiKey, $http);

Gemini needs a paid project. Its image models have a daily quota of zero on the free tier, so the call fails with a quota error even though you have generated nothing yet.

And one obvious thing that is easy to forget: instead of an image the model may refuse to draw, typically for content its rules do not allow. You then get an AIAccess\UnexpectedResponseException, because there is no image in the response. Expect that, especially when the description is assembled from user input.

Where to Go Next