Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(image): image transformation #305

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions docs/content/1.docs/2.features/image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: Image Transformation
navigation.title: Image
description: Add image transformation to your NuxtHub project.
---

## Getting Started

Enable the image transformation in your NuxtHub project by adding the `image` property to the `hub` object in your `nuxt.config.ts` file.


```ts [nuxt.config.ts]
export default defineNuxtConfig({
hub: {
image: {
trustedDomains: ['hub.nuxt.com'],
templates: {
small: { width: 128, height: 128, format: 'webp' },
medium: { width: 512, height: 512, format: 'webp' }
}
}
}
})
```

NuxtHub will add an specific route to your project to transform the image. The route is `/_hub/image/<template>/<source-image>`. For example, to transform an image with the `small` template, you can use the following URL:

```html
<img src="https://hub.nuxt.com/_hub/image/small/example-image.jpg" />
```




## Trusted Domains

By default the image transformation is not allowed for any remote images and you can only transform images that are uploaded via `hubBlob()`. If you want to allow remote images, you can add the trusted domains to the `trustedDomains` option.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
hub: {
image: {
trustedDomains: ['hub.nuxt.com', 'example.com']
}
}
})
```

## Templates

In order to improve security and prevent service abuse, you need to define transformation templates for your images. You can define as many templates as you want. These templates will be used to transform the requested image.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
hub: {
image: {
templates: {
small: { width: 128, height: 128, format: 'webp' },
medium: { width: 512, height: 512, format: 'webp' }
}
}
}
})
```

### Template Options

The template options can be defined for each template.

#### `width`

The width of the transformed image in pixels.

#### `height`

The height of the transformed image in pixels.

#### `format`

The format of the transformed image. Can be `png`, `jpeg` or `webp`.

#### `jpeg_quality`

The quality of the JPEG image. This option is only used when the `format` is `jpeg`.

#### `rotate`

The angle of rotation in degrees.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"test:watch": "vitest watch"
},
"dependencies": {
"@cf-wasm/photon": "^0.1.24",
"@cloudflare/workers-types": "^4.20240925.0",
"@nuxt/devtools-kit": "^1.5.1",
"@nuxt/kit": "^3.13.2",
Expand All @@ -55,6 +56,7 @@
"ufo": "^1.5.4",
"uncrypto": "^0.1.3",
"unstorage": "^1.12.0",
"unwasm": "^0.3.9",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions playground/app/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const links = [
{ label: 'AI', to: '/ai' },
{ label: 'Browser', to: '/browser' },
{ label: 'Blob', to: '/blob' },
{ label: 'Image', to: '/image' },
{ label: 'Database', to: '/database' },
{ label: 'KV', to: '/kv' }
]
Expand Down
79 changes: 79 additions & 0 deletions playground/app/pages/image.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script setup lang="ts">
const source = ref<string>('https://hub.nuxt.com/images/landing/nuxthub-schema.png')
const options = reactive({
width: 600,
height: 400,
format: 'webp',
rotate: 0
})

const { data: blobData } = await useFetch('/api/blob', {
query: {
folded: false,
limit: 4
},
deep: true
})

const imageSrc = computed(() => {
return `/_hub/image/${Object.entries(options).map(([key, value]) => `${key}=${value}`).join(',')}/${source.value}`
})

const files = computed(() => blobData.value?.blobs || [])
</script>

<template>
<UCard>
<div class="flex gap-4 mb-4">
<UFormGroup label="Width x Height">
<div class="flex gap-2">
<USelect
v-model="options.width"
:options="[300, 600, 900, 1200]"
/>
x
<USelect
v-model="options.height"
:options="[200, 400, 600, 800]"
/>
</div>
</UFormGroup>
<UFormGroup label="Rotate">
<USelect
v-model="options.rotate"
:options="[0, 90, 180, 270]"
/>
</UFormGroup>
<UFormGroup label="Format">
<USelect
v-model="options.format"
:options="['webp', 'jpeg', 'png']"
/>
</UFormGroup>
</div>
<div class="flex-1 h-96 flex items-center justify-center relative">
<UProgress animation="carousel" class="w-32 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />

<img :key="imageSrc" :src="imageSrc" class="z-10 w-full h-full object-contain">
</div>
<div class="flex-2">
<div v-if="files?.length" class="flex overflow-scroll gap-2 mt-4">
<UCard
v-for="file of files"
:key="file.pathname"
:ui="{
body: {
base: 'space-y-0',
padding: ''
}
}"
class="overflow-hidden relative h-32 w-32 cursor-pointer"
@click="source = file.pathname"
>
<img v-if="file.contentType?.startsWith('image/')" :src="`/api/blob/${file.pathname}`" class="h-32 w-32 object-cover">
</UCard>
</div>
<UAlert v-else title="You don't have any files yet." />
</div>
</UCard>
</template>
7 changes: 7 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export default defineNuxtConfig({
browser: true,
kv: true,
cache: true,
image: {
trustedDomains: ['hub.nuxt.com'],
templates: {
small: { width: 128, height: 128, format: 'webp' },
medium: { width: 512, height: 512, format: 'webp' }
}
},
bindings: {
compatibilityDate: '2024-10-02',
compatibilityFlags: ['nodejs_compat']
Expand Down
Loading