Skip to content

Commit 9e13953

Browse files
authoredFeb 9, 2025··
feat(image): add AI-generated avatars (#3126)
1 parent f9dd560 commit 9e13953

File tree

6 files changed

+112
-7
lines changed

6 files changed

+112
-7
lines changed
 

‎src/modules/image/index.ts

+47-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { toBase64 } from '../../internal/base64';
22
import { deprecated } from '../../internal/deprecated';
33
import { ModuleBase } from '../../internal/module-base';
4+
import type { SexType } from '../person';
45

56
/**
67
* Module to generate images.
@@ -11,7 +12,7 @@ import { ModuleBase } from '../../internal/module-base';
1112
*
1213
* For a random placeholder image containing only solid color and text, use [`urlPlaceholder()`](https://fakerjs.dev/api/image.html#urlplaceholder) (uses a third-party service) or [`dataUri()`](https://fakerjs.dev/api/image.html#datauri) (returns a SVG string).
1314
*
14-
* For a random user avatar image, use [`avatar()`](https://fakerjs.dev/api/image.html#avatar).
15+
* For a random user avatar image, use [`avatar()`](https://fakerjs.dev/api/image.html#avatar), or [`personPortrait()`](https://fakerjs.dev/api/image.html#personportrait) which has more control over the size and sex of the person.
1516
*
1617
* If you need more control over the content of the images, you can pass a `category` parameter e.g. `'cat'` or `'nature'` to [`urlLoremFlickr()`](https://fakerjs.dev/api/image.html#urlloremflickr) or simply use [`faker.helpers.arrayElement()`](https://fakerjs.dev/api/helpers.html#arrayelement) with your own array of image URLs.
1718
*/
@@ -27,7 +28,11 @@ export class ImageModule extends ModuleBase {
2728
*/
2829
avatar(): string {
2930
// Add new avatar providers here, when adding a new one.
30-
return this.avatarGitHub();
31+
const avatarMethod = this.faker.helpers.arrayElement([
32+
this.personPortrait,
33+
this.avatarGitHub,
34+
]);
35+
return avatarMethod();
3136
}
3237

3338
/**
@@ -45,6 +50,45 @@ export class ImageModule extends ModuleBase {
4550
)}`;
4651
}
4752

53+
/**
54+
* Generates a random square portrait (avatar) of a person.
55+
* These are static images of fictional people created by an AI, Stable Diffusion 3.
56+
* The image URLs are served via the JSDelivr CDN and subject to their [terms of use](https://www.jsdelivr.com/terms).
57+
*
58+
* @param options Options for generating an AI avatar.
59+
* @param options.sex The sex of the person for the avatar. Can be `'female'` or `'male'`. If not provided, defaults to a random selection.
60+
* @param options.size The size of the image. Can be `512`, `256`, `128`, `64` or `32`. If not provided, defaults to `512`.
61+
*
62+
* @example
63+
* faker.image.personPortrait() // 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/57.jpg'
64+
* faker.image.personPortrait({ sex: 'male', size: '128' }) // 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/128/27.jpg'
65+
*
66+
* @since 9.5.0
67+
*/
68+
personPortrait(
69+
options: {
70+
/**
71+
* The sex of the person for the avatar.
72+
* Can be `'female'` or `'male'`.
73+
*
74+
* @default faker.person.sexType()
75+
*/
76+
sex?: SexType;
77+
/**
78+
* The size of the image.
79+
* Can be `512`, `256`, `128`, `64` or `32`.
80+
*
81+
* @default 512
82+
*/
83+
size?: 512 | 256 | 128 | 64 | 32;
84+
} = {}
85+
): string {
86+
const { sex = this.faker.person.sexType(), size = 512 } = options;
87+
const baseURL =
88+
'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait';
89+
return `${baseURL}/${sex}/${size}/${this.faker.number.int({ min: 0, max: 99 })}.jpg`;
90+
}
91+
4892
/**
4993
* Generates a random avatar from `https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar`.
5094
*
@@ -59,7 +103,7 @@ export class ImageModule extends ModuleBase {
59103
avatarLegacy(): string {
60104
deprecated({
61105
deprecated: 'faker.image.avatarLegacy()',
62-
proposed: 'faker.image.avatar()',
106+
proposed: 'faker.image.avatar() or faker.image.personPortrait()',
63107
since: '9.0.2',
64108
until: '10.0.0',
65109
});

‎test/integration/modules/image.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ describe('image', () => {
7474
});
7575
});
7676

77+
describe('personPortrait', () => {
78+
it('should return a random asset url', async () => {
79+
const actual = faker.image.personPortrait();
80+
await assertWorkingUrl(actual);
81+
});
82+
});
83+
7784
describe('url', () => {
7885
it('should return a random image url', async () => {
7986
const actual = faker.image.url();

‎test/modules/__snapshots__/image.spec.ts.snap

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`image > 42 > avatar 1`] = `"https://avatars.githubusercontent.com/u/37454012"`;
3+
exports[`image > 42 > avatar 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/512/73.jpg"`;
44

55
exports[`image > 42 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/37454012"`;
66

@@ -22,6 +22,14 @@ exports[`image > 42 > dataUri > with width 1`] = `"data:image/svg+xml;base64,PHN
2222

2323
exports[`image > 42 > dataUri > with width and height 1`] = `"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20baseProfile%3D%22full%22%20width%3D%22128%22%20height%3D%22128%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%238ead33%22%2F%3E%3Ctext%20x%3D%2264%22%20y%3D%2264%22%20font-size%3D%2220%22%20alignment-baseline%3D%22middle%22%20text-anchor%3D%22middle%22%20fill%3D%22white%22%3E128x128%3C%2Ftext%3E%3C%2Fsvg%3E"`;
2424

25+
exports[`image > 42 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/95.jpg"`;
26+
27+
exports[`image > 42 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/37.jpg"`;
28+
29+
exports[`image > 42 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/37.jpg"`;
30+
31+
exports[`image > 42 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/128/95.jpg"`;
32+
2533
exports[`image > 42 > url > noArgs 1`] = `"https://picsum.photos/seed/993RBH1Y/1498/3802"`;
2634

2735
exports[`image > 42 > url > with height 1`] = `"https://picsum.photos/seed/B993RBH1Y/1498/128"`;
@@ -76,7 +84,7 @@ exports[`image > 42 > urlPlaceholder > with width 1`] = `"https://via.placeholde
7684

7785
exports[`image > 42 > urlPlaceholder > with width and height 1`] = `"https://via.placeholder.com/128x128/8ead33/1ddf0f.webp?text=benevolentia%20attonbitus%20auctus"`;
7886

79-
exports[`image > 1211 > avatar 1`] = `"https://avatars.githubusercontent.com/u/92852016"`;
87+
exports[`image > 1211 > avatar 1`] = `"https://avatars.githubusercontent.com/u/89347165"`;
8088

8189
exports[`image > 1211 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/92852016"`;
8290

@@ -98,6 +106,14 @@ exports[`image > 1211 > dataUri > with width 1`] = `"data:image/svg+xml;charset=
98106

99107
exports[`image > 1211 > dataUri > with width and height 1`] = `"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgYmFzZVByb2ZpbGU9ImZ1bGwiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWQ0ZmVmIi8+PHRleHQgeD0iNjQiIHk9IjY0IiBmb250LXNpemU9IjIwIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0id2hpdGUiPjEyOHgxMjg8L3RleHQ+PC9zdmc+"`;
100108

109+
exports[`image > 1211 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/512/89.jpg"`;
110+
111+
exports[`image > 1211 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/92.jpg"`;
112+
113+
exports[`image > 1211 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/92.jpg"`;
114+
115+
exports[`image > 1211 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/128/89.jpg"`;
116+
101117
exports[`image > 1211 > url > noArgs 1`] = `"https://loremflickr.com/3714/3573?lock=8982492793493979"`;
102118

103119
exports[`image > 1211 > url > with height 1`] = `"https://picsum.photos/seed/ZFGLlH/3714/128"`;
@@ -152,7 +168,7 @@ exports[`image > 1211 > urlPlaceholder > with width 1`] = `"https://via.placehol
152168

153169
exports[`image > 1211 > urlPlaceholder > with width and height 1`] = `"https://via.placeholder.com/128x128/ed4fef/a7fbae.webp?text=dapifer%20usque%20unde"`;
154170

155-
exports[`image > 1337 > avatar 1`] = `"https://avatars.githubusercontent.com/u/26202467"`;
171+
exports[`image > 1337 > avatar 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/27.jpg"`;
156172

157173
exports[`image > 1337 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/26202467"`;
158174

@@ -174,6 +190,14 @@ exports[`image > 1337 > dataUri > with width 1`] = `"data:image/svg+xml;base64,P
174190

175191
exports[`image > 1337 > dataUri > with width and height 1`] = `"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20baseProfile%3D%22full%22%20width%3D%22128%22%20height%3D%22128%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%23536a7b%22%2F%3E%3Ctext%20x%3D%2264%22%20y%3D%2264%22%20font-size%3D%2220%22%20alignment-baseline%3D%22middle%22%20text-anchor%3D%22middle%22%20fill%3D%22white%22%3E128x128%3C%2Ftext%3E%3C%2Fsvg%3E"`;
176192

193+
exports[`image > 1337 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/15.jpg"`;
194+
195+
exports[`image > 1337 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/26.jpg"`;
196+
197+
exports[`image > 1337 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/26.jpg"`;
198+
199+
exports[`image > 1337 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/128/15.jpg"`;
200+
177201
exports[`image > 1337 > url > noArgs 1`] = `"https://loremflickr.com/1048/635?lock=4137158724208997"`;
178202

179203
exports[`image > 1337 > url > with height 1`] = `"https://loremflickr.com/1048/128?lock=2505140979113303"`;

‎test/modules/image.spec.ts

+29
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ describe('image', () => {
108108
type: 'svg-uri',
109109
});
110110
});
111+
112+
t.describe('personPortrait', (t) => {
113+
t.it('noArgs')
114+
.it('with sex', { sex: 'female' })
115+
.it('with size', { size: 128 })
116+
.it('with sex and size', { sex: 'male', size: 256 });
117+
});
111118
});
112119

113120
describe('avatar', () => {
@@ -144,6 +151,28 @@ describe('image', () => {
144151
});
145152
});
146153

154+
describe('personPortrait', () => {
155+
it('should return a random avatar url from AI', () => {
156+
const imageUrl = faker.image.personPortrait();
157+
158+
expect(imageUrl).toBeTypeOf('string');
159+
expect(imageUrl).toMatch(
160+
/^https:\/\/cdn\.jsdelivr\.net\/gh\/faker-js\/assets-person-portrait\/(female|male)\/512\/\d{1,2}\.jpg$/
161+
);
162+
expect(() => new URL(imageUrl)).not.toThrow();
163+
});
164+
165+
it('should return a random avatar url from AI with fixed size and sex', () => {
166+
const imageUrl = faker.image.personPortrait({ sex: 'male', size: 128 });
167+
168+
expect(imageUrl).toBeTypeOf('string');
169+
expect(imageUrl).toMatch(
170+
/^https:\/\/cdn\.jsdelivr\.net\/gh\/faker-js\/assets-person-portrait\/male\/128\/\d{1,2}\.jpg$/
171+
);
172+
expect(() => new URL(imageUrl)).not.toThrow();
173+
});
174+
});
175+
147176
describe('url', () => {
148177
it('should return a random image url', () => {
149178
const actual = faker.image.url();

‎test/scripts/apidocs/__snapshots__/verify-jsdoc-tags.spec.ts.snap

+1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ exports[`check docs completeness > all modules and methods are present 1`] = `
243243
"avatarGitHub",
244244
"avatarLegacy",
245245
"dataUri",
246+
"personPortrait",
246247
"url",
247248
"urlLoremFlickr",
248249
"urlPicsumPhotos",

‎test/scripts/apidocs/verify-jsdoc-tags.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ ${examples}`;
147147
if (!examples.includes('import ')) {
148148
const imports = [
149149
// collect the imports for the various locales e.g. fakerDE_CH
150-
...new Set(examples.match(/(?<!\.)faker[^.]*(?=\.)/g)),
150+
...new Set(examples.match(/(?<!\.)faker[^.-]*(?=\.)/g)),
151151
];
152152

153153
if (imports.length > 0) {

0 commit comments

Comments
 (0)
Please sign in to comment.