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
- Images and documents as input – the opposite direction, when the model should look at an image
- HTTP layer – timeouts, retries and request logging
- Error handling – what the individual exceptions mean
- Providers – what each one can do and how they differ