diff --git a/docs/content/3.providers/picsum.md b/docs/content/3.providers/picsum.md
new file mode 100644
index 000000000..0a4e50827
--- /dev/null
+++ b/docs/content/3.providers/picsum.md
@@ -0,0 +1,102 @@
+---
+title: Picsum
+description: Nuxt Image with Lorem Picsum placeholder images.
+links:
+ - label: Source
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/image/blob/main/src/runtime/providers/picsum.ts
+ size: xs
+---
+
+Integration between [Lorem Picsum](https://picsum.photos/) and the image module.
+
+Lorem Picsum provides random placeholder images. It is useful during development or for demo purposes.
+
+To use this provider you just need to enable it in your Nuxt config:
+
+```ts [nuxt.config.ts]
+export default defineNuxtConfig({
+ image: {
+ picsum: {}
+ }
+})
+```
+
+## Usage
+
+### Random image
+
+Get a random image with specific dimensions:
+
+```vue
+
+```
+
+### Specific image by ID
+
+Use `id/{id}` as the `src` to get a specific image:
+
+```vue
+
+```
+
+### Seeded random image
+
+Use `seed/{seed}` as the `src` to get a consistent random image based on a seed string:
+
+```vue
+
+```
+
+## Modifiers
+
+### `grayscale`
+
+Return the image in grayscale.
+
+```vue
+
+```
+
+### `blur`
+
+Apply a blur effect to the image. Accepts values from 1 to 10.
+
+```vue
+
+```
+
+::note
+The `src` value must be either `id/{id}` for a specific image, `seed/{seed}` for a seeded random image, or `/` for a fully random image. Other `src` values are ignored and a random image will be returned.
+::
+
+::note
+Picsum does not support height-only resizing. When only `height` is provided without `width`, a square image of that dimension will be returned.
+::
diff --git a/docs/public/providers/picsum.svg b/docs/public/providers/picsum.svg
new file mode 100644
index 000000000..d40078ed9
--- /dev/null
+++ b/docs/public/providers/picsum.svg
@@ -0,0 +1 @@
+
diff --git a/playground/app/providers.ts b/playground/app/providers.ts
index 6f26f6e93..cb84bb37a 100644
--- a/playground/app/providers.ts
+++ b/playground/app/providers.ts
@@ -435,6 +435,38 @@ export const providers: Provider[] = [
},
],
},
+ // Picsum (Lorem Picsum placeholder images)
+ {
+ name: 'picsum',
+ samples: [
+ {
+ src: 'id/237',
+ width: 200,
+ height: 300,
+ },
+ {
+ src: 'id/870',
+ width: 300,
+ height: 200,
+ modifiers: {
+ grayscale: true,
+ },
+ },
+ {
+ src: 'id/1025',
+ width: 250,
+ height: 250,
+ modifiers: {
+ blur: 5,
+ },
+ },
+ {
+ src: 'seed/picsum',
+ width: 200,
+ height: 200,
+ },
+ ],
+ },
// Prepr
{
name: 'prepr',
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index a5e4767f5..39fd2b216 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -91,6 +91,7 @@ export default defineNuxtConfig({
baseURL: 'https://netlify-photo-gallery.netlify.app',
},
prismic: {},
+ picsum: {},
prepr: {
projectName: 'nuxt-prepr-demo',
},
diff --git a/src/provider.ts b/src/provider.ts
index 6de7fe553..d3abc643a 100644
--- a/src/provider.ts
+++ b/src/provider.ts
@@ -36,6 +36,7 @@ export const BuiltInProviders = [
'netlify',
'netlifyLargeMedia',
'netlifyImageCdn',
+ 'picsum',
'prepr',
'none',
'prismic',
diff --git a/src/runtime/providers/picsum.ts b/src/runtime/providers/picsum.ts
new file mode 100644
index 000000000..a3d34cbed
--- /dev/null
+++ b/src/runtime/providers/picsum.ts
@@ -0,0 +1,74 @@
+// https://picsum.photos/ - Lorem Picsum placeholder images
+
+import { joinURL, withQuery } from 'ufo'
+import { defineProvider } from '../utils/provider'
+
+interface PicsumModifiers {
+ grayscale?: boolean
+ blur?: number
+}
+
+interface PicsumOptions {
+ baseURL?: string
+ modifiers?: PicsumModifiers
+}
+
+export const picsumCDN = 'https://picsum.photos/'
+
+export default defineProvider({
+ getImage: (src, { modifiers, baseURL = picsumCDN }) => {
+ const { width, height, grayscale, blur, ...otherModifiers } = modifiers || {}
+
+ // Build the path
+ // Picsum URL format: https://picsum.photos/[id/{id}/]{width}[/{height}]
+ // Examples:
+ // - Random: https://picsum.photos/200/300
+ // - Specific ID: https://picsum.photos/id/237/200/300
+ // - Square: https://picsum.photos/200
+
+ const parts: string[] = []
+
+ // If src is provided and not empty, it could be:
+ // - "id/237" for a specific image
+ // - "seed/picsum" for a seeded image
+ if (src && src !== '/') {
+ const [type, id] = (src.startsWith('/') ? src.slice(1) : src).split('/')
+ if (type && (type === 'id' || type === 'seed')) {
+ parts.push(`${type}/${id}`)
+ }
+ }
+
+ // Add dimensions - these come after the ID/seed path
+ if (width) {
+ parts.push(String(width))
+ }
+ if (height) {
+ parts.push(String(height))
+ }
+
+ // Build query parameters for modifiers
+ const query: Record = {}
+
+ if (grayscale) {
+ query.grayscale = ''
+ }
+
+ if (blur !== undefined && blur > 0) {
+ // Picsum blur accepts values from 1-10
+ query.blur = Math.min(Math.max(Math.round(blur), 1), 10)
+ }
+
+ // Add any other custom modifiers (excluding standard ones that don't apply to picsum)
+ for (const [key, value] of Object.entries(otherModifiers)) {
+ if (value !== undefined && value !== null && !['fit', 'format', 'quality', 'background'].includes(key)) {
+ query[key] = value as string | number
+ }
+ }
+
+ const url = joinURL(baseURL, ...parts)
+
+ return {
+ url: Object.keys(query).length > 0 ? withQuery(url, query) : url,
+ }
+ },
+})
diff --git a/test/e2e/__snapshots__/picsum.json5 b/test/e2e/__snapshots__/picsum.json5
new file mode 100644
index 000000000..e0ede4c65
--- /dev/null
+++ b/test/e2e/__snapshots__/picsum.json5
@@ -0,0 +1,14 @@
+{
+ "requests": [
+ "https://picsum.photos/id/1025/250/250?blur=5",
+ "https://picsum.photos/id/237/200/300",
+ "https://picsum.photos/id/870/300/200?grayscale",
+ "https://picsum.photos/seed/picsum/200/200",
+ ],
+ "sources": [
+ "https://picsum.photos/id/237/200/300",
+ "https://picsum.photos/id/870/300/200?grayscale",
+ "https://picsum.photos/id/1025/250/250?blur=5",
+ "https://picsum.photos/seed/picsum/200/200",
+ ],
+}
\ No newline at end of file
diff --git a/test/nuxt/providers.test.ts b/test/nuxt/providers.test.ts
index 2fde75a84..12671e082 100644
--- a/test/nuxt/providers.test.ts
+++ b/test/nuxt/providers.test.ts
@@ -12,6 +12,7 @@ import cloudflare from '../../dist/runtime/providers/cloudflare'
import cloudinary from '../../dist/runtime/providers/cloudinary'
import twicpics from '../../dist/runtime/providers/twicpics'
import fastly from '../../dist/runtime/providers/fastly'
+import picsum from '../../dist/runtime/providers/picsum'
import prepr from '../../dist/runtime/providers/prepr'
import glide from '../../dist/runtime/providers/glide'
import imgix from '../../dist/runtime/providers/imgix'
@@ -462,6 +463,16 @@ describe('Providers', () => {
expect(generated).toMatchObject(image.prepr)
}
})
+ it('picsum', () => {
+ const providerOptions = {}
+
+ for (const image of images) {
+ const [_src, modifiers] = image.args
+ const generated = picsum().getImage('', { modifiers: { ...modifiers }, ...providerOptions }, emptyContext)
+ expect(generated).toMatchObject(image.picsum)
+ }
+ })
+
it('contentful', () => {
const providerOptions = {
baseURL: '',
diff --git a/test/providers.ts b/test/providers.ts
index 01b662bd7..f25b56fc2 100644
--- a/test/providers.ts
+++ b/test/providers.ts
@@ -37,6 +37,7 @@ export const images = [
hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/auto_image/cltsrex89477t08unlckqx9ue' },
caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg' },
bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net' },
+ picsum: { url: 'https://picsum.photos/' },
},
{
args: ['/test.png', { width: 200 }],
@@ -76,6 +77,7 @@ export const images = [
hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200/auto_image/cltsrex89477t08unlckqx9ue' },
caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200' },
bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200' },
+ picsum: { url: 'https://picsum.photos/200' },
},
{
args: ['/test.png', { height: 200 }],
@@ -114,6 +116,7 @@ export const images = [
hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=height:200/auto_image/cltsrex89477t08unlckqx9ue' },
caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?h=200' },
bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?height=200' },
+ picsum: { url: 'https://picsum.photos/200' },
},
{
args: ['/test.png', { width: 200, height: 200 }],
@@ -152,6 +155,7 @@ export const images = [
hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200,height:200/auto_image/cltsrex89477t08unlckqx9ue' },
caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200&h=200' },
bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200&height=200' },
+ picsum: { url: 'https://picsum.photos/200/200' },
},
{
args: ['/test.png', { width: 200, height: 200, fit: 'contain' }],
@@ -190,6 +194,7 @@ export const images = [
hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200,height:200,fit:max/auto_image/cltsrex89477t08unlckqx9ue' },
caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200&h=200' },
bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200&height=200' },
+ picsum: { url: 'https://picsum.photos/200/200' },
},
{
args: ['/test.png', { width: 200, height: 200, fit: 'contain', format: 'jpeg' }],
@@ -228,5 +233,6 @@ export const images = [
hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200,height:200,fit:max/output=format:jpeg/cltsrex89477t08unlckqx9ue' },
caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200&h=200' },
bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200&height=200' },
+ picsum: { url: 'https://picsum.photos/200/200' },
},
] as const