Yesterday I introduced you to Adobe's new offering, Firefly Services, and demonstrated a simple example of how to generate images from prompt using the REST APIs. Today I thought I'd share one of the little demos I've made with the API, and one specifically built to help out with my blog - generating headers.
My usual process for headers is to go to the Firefly website, enter a prompt, let it load, and then promptly change it to landscape and re-generate my prompt again. I always feel bad that the initial, square, images are essentially trashed. It occurred to me I could build a Node.js utility to generate the images at the exact right size and even quickly display them. Here's how I did it.
First, I designed the CLI so I can simply pass in a prompt. Here's how I handled that:
if(process.argv.length < 3) {
console.log(styleText('red', 'Usage: makeheader.js <<prompt>>'));
process.exit(1);
}
const prompt = process.argv[2];
console.log(styleText('green', `Generating headers for: ${prompt}`));
Next, I authenticate, and create my images:
let token = await getFFAccessToken(FF_CLIENT_ID, FF_CLIENT_SECRET);
let result = await textToImage(prompt, FF_CLIENT_ID, token);
I showed both of these methods yesterday, but my parameters for the Firefly API to generate images are slightly tweaked though. First, the authentication method again:
async function getFFAccessToken(id, secret) {
const params = new URLSearchParams();
params.append('grant_type', 'client_credentials');
params.append('client_id', id);
params.append('client_secret', secret);
params.append('scope', 'openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis');
let resp = await fetch('https://ims-na1.adobelogin.com/ims/token/v3',
{
method: 'POST',
body: params
}
);
let data = await resp.json();
return data.access_token;
}
And here's the call to the text to image API:
async function textToImage(text, id, token) {
let body = {
"n":4,
"prompt":text,
"size":{
"width":"2304",
"height":"1792"
}
}
let req = await fetch('https://firefly-api.adobe.io/v2/images/generate', {
method:'POST',
headers: {
'X-Api-Key':id,
'Authorization':`Bearer ${token}`,
'Content-Type':'application/json'
},
body: JSON.stringify(body)
});
let resp = await req.json();
return resp;
}
Note two things here:
- First, I set
n
to 4 so I get 4 results, not the default of 1. - My size is hard coded to the landscape size.
Ok, so that's the easy bit honestly. But I wanted to do something cool with the results. There is a really useful npm package called open
that will open URLs and files. The result of the Firefly API call above will include 4 URLs and I could have simply opened all four of them in individual browser tabs, but I wanted one page where I could see them all, much like the Firefly website. While not directly supported by open
yet, I got around it by generating a temporary HTML file locally:
let html = `
<style>
img {
max-width: 650px;
}
.results {
display: grid;
grid-template-columns: repeat(2, 50%);
}
</style>
<h2>Results for Prompt: ${prompt}</h2>
<div class="results">
`;
result.outputs.forEach(i => {
html += `<p><img src="${i.image.presignedUrl}"></p>`;
});
html += '</div>';
let filename = `${uuid4()}.html`;
fs.writeFileSync(filename, html, 'utf8');
await open(filename, {
wait: true
});
fs.unlinkSync(filename);
So now what happens is, I run my prompt, and when it's done, I get an HTML page. Here's the result of using:
node makeheader "a somber, moody picture of a cat in painters clothes, standing before an easel, thinking about what to pain
t next"
And yes, I used the fourth image for this post. Here's the complete script, but you can also find it in my Firefly API repo: https://github.com/cfjedimaster/fireflyapi/tree/main/demos/makeheader
// Requires Node 21.7.0
process.loadEnvFile();
import { styleText } from 'node:util';
import { v4 as uuid4 } from 'uuid';
import open from 'open';
import fs from 'fs';
const FF_CLIENT_ID = process.env.FF_CLIENT_ID;
const FF_CLIENT_SECRET = process.env.FF_CLIENT_SECRET;
async function getFFAccessToken(id, secret) {
const params = new URLSearchParams();
params.append('grant_type', 'client_credentials');
params.append('client_id', id);
params.append('client_secret', secret);
params.append('scope', 'openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis');
let resp = await fetch('https://ims-na1.adobelogin.com/ims/token/v3',
{
method: 'POST',
body: params
}
);
let data = await resp.json();
return data.access_token;
}
async function textToImage(text, id, token) {
let body = {
"n":4,
"prompt":text,
"size":{
"width":"2304",
"height":"1792"
}
}
let req = await fetch('https://firefly-api.adobe.io/v2/images/generate', {
method:'POST',
headers: {
'X-Api-Key':id,
'Authorization':`Bearer ${token}`,
'Content-Type':'application/json'
},
body: JSON.stringify(body)
});
let resp = await req.json();
return resp;
}
if(process.argv.length < 3) {
console.log(styleText('red', 'Usage: makeheader.js <<prompt>>'));
process.exit(1);
}
const prompt = process.argv[2];
console.log(styleText('green', `Generating headers for: ${prompt}`));
let token = await getFFAccessToken(FF_CLIENT_ID, FF_CLIENT_SECRET);
let result = await textToImage(prompt, FF_CLIENT_ID, token);
console.log(styleText('green', 'Results generated - creating preview...'));
let html = `
<style>
img {
max-width: 650px;
}
.results {
display: grid;
grid-template-columns: repeat(2, 50%);
}
</style>
<h2>Results for Prompt: ${prompt}</h2>
<div class="results">
`;
result.outputs.forEach(i => {
html += `<p><img src="${i.image.presignedUrl}"></p>`;
});
html += '</div>';
let filename = `${uuid4()}.html`;
fs.writeFileSync(filename, html, 'utf8');
await open(filename, {
wait: true
});
fs.unlinkSync(filename);