├── .eslintignore
├── playground
├── src
│ ├── api
│ │ └── .gitkeep
│ ├── extensions
│ │ └── .gitkeep
│ ├── admin
│ │ ├── webpack.config.example.js
│ │ └── app.example.js
│ └── index.js
├── public
│ ├── uploads
│ │ └── .gitkeep
│ └── robots.txt
├── database
│ └── migrations
│ │ └── .gitkeep
├── .eslintignore
├── favicon.png
├── config
│ ├── api.js
│ ├── admin.js
│ ├── server.js
│ ├── plugins.js
│ ├── middlewares.js
│ └── database.js
├── .env.example
├── .editorconfig
├── .eslintrc
├── package.json
├── .gitignore
└── README.md
├── .github
├── FUNDING.yml
└── workflows
│ ├── gh-release.yml
│ └── docs.yml
├── .eslintrc
├── strapi-server.js
├── docs
├── public
│ ├── icon.png
│ ├── uil-image-resize-landscape.svg
│ ├── uil-image-resize-landscape-dark.svg
│ ├── logo-rest-cache-light.svg
│ ├── fluent-emoji-cloud-with-lightning.svg
│ └── logo-rest-cache-dark.svg
├── .vitepress
│ ├── theme
│ │ ├── custom.css
│ │ ├── index.js
│ │ └── Layout.vue
│ └── config.js
├── index.md
└── guide
│ ├── index.md
│ └── modifiers.md
├── src
├── index.js
├── config
│ ├── schema.js
│ └── index.js
├── register.js
└── middleware.js
├── README.md
├── package.json
├── .gitignore
└── CHANGELOG.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.json
2 | *.md
--------------------------------------------------------------------------------
/playground/src/api/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/public/uploads/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/src/extensions/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: strapi
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@strapi-community"
3 | }
--------------------------------------------------------------------------------
/playground/.eslintignore:
--------------------------------------------------------------------------------
1 | .cache
2 | build
3 | **/node_modules/**
4 |
--------------------------------------------------------------------------------
/strapi-server.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const plugin = require('./src')
4 |
5 | module.exports = plugin
6 |
--------------------------------------------------------------------------------
/docs/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strapi-community/strapi-plugin-local-image-sharp/HEAD/docs/public/icon.png
--------------------------------------------------------------------------------
/playground/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strapi-community/strapi-plugin-local-image-sharp/HEAD/playground/favicon.png
--------------------------------------------------------------------------------
/playground/config/api.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rest: {
3 | defaultLimit: 25,
4 | maxLimit: 100,
5 | withCount: true,
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/playground/public/robots.txt:
--------------------------------------------------------------------------------
1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines:
2 | # User-Agent: *
3 | # Disallow: /
4 |
--------------------------------------------------------------------------------
/playground/.env.example:
--------------------------------------------------------------------------------
1 | HOST=0.0.0.0
2 | PORT=1337
3 | APP_KEYS="toBeModified1,toBeModified2"
4 | API_TOKEN_SALT=tobemodified
5 | ADMIN_JWT_SECRET=tobemodified
6 | JWT_SECRET=tobemodified
7 |
--------------------------------------------------------------------------------
/playground/config/admin.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ env }) => ({
2 | auth: {
3 | secret: env('ADMIN_JWT_SECRET'),
4 | },
5 | apiToken: {
6 | salt: env('API_TOKEN_SALT'),
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/playground/config/server.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ env }) => ({
2 | host: env('HOST', '0.0.0.0'),
3 | port: env.int('PORT', 1337),
4 | app: {
5 | keys: env.array('APP_KEYS'),
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --vp-c-brand: #8e75ff;
3 | --vp-c-brand-light: #a091ed;
4 | --vp-c-brand-lighter: #a091ed;
5 | --vp-c-brand-dark: #8e75ff;
6 | --vp-c-brand-darker: #8e75ff;
7 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { config } = require('./config');
4 | const { register } = require('./register')
5 |
6 | const plugin = {
7 | config,
8 | register,
9 | }
10 |
11 | module.exports = plugin
--------------------------------------------------------------------------------
/playground/config/plugins.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | 'local-image-sharp': {
5 | config: {
6 | cacheDir: '.image-cache', // Can be set with env var STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR
7 | },
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | import DefaultTheme from 'vitepress/theme'
2 | import Layout from './Layout.vue'
3 | import './custom.css'
4 |
5 | export default {
6 | ...DefaultTheme,
7 | // override the Layout with a wrapper component that
8 | // injects the slots
9 | Layout
10 | }
--------------------------------------------------------------------------------
/playground/config/middlewares.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | 'strapi::errors',
3 | 'strapi::security',
4 | 'strapi::cors',
5 | 'strapi::poweredBy',
6 | 'strapi::logger',
7 | 'strapi::query',
8 | 'strapi::body',
9 | 'strapi::session',
10 | 'strapi::favicon',
11 | 'strapi::public',
12 | ];
13 |
--------------------------------------------------------------------------------
/src/config/schema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yup = require('yup');
4 |
5 | const pluginConfigSchema = yup.object().shape({
6 | cacheDir: yup.string(),
7 | maxAge: yup.number().moreThan(0),
8 | paths: yup.array().of(yup.string()),
9 | });
10 |
11 | module.exports = {
12 | pluginConfigSchema,
13 | };
14 |
--------------------------------------------------------------------------------
/playground/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,*.yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/playground/src/admin/webpack.config.example.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* eslint-disable no-unused-vars */
4 | module.exports = (config, webpack) => {
5 | // Note: we provide webpack above so you should not `require` it
6 | // Perform customizations to webpack config
7 | // Important: return the modified config
8 | return config;
9 | };
10 |
--------------------------------------------------------------------------------
/playground/config/database.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = ({ env }) => ({
4 | connection: {
5 | client: 'sqlite',
6 | connection: {
7 | filename: path.join(
8 | __dirname,
9 | '..',
10 | env('DATABASE_FILENAME', '.tmp/data.db')
11 | ),
12 | },
13 | useNullAsDefault: true,
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { pluginConfigSchema } = require('./schema');
4 |
5 | module.exports = {
6 | config: {
7 | default: ({ env }) => ({
8 | cacheDir: env('STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR', ''),
9 | maxAge: 3600,
10 | paths: ['/uploads']
11 | }),
12 | validator(config) {
13 | pluginConfigSchema.validateSync(config);
14 | },
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 |
4 | hero:
5 | name: Local Image Sharp
6 | text: Transform images on demand with sharp
7 | tagline: Dynamically resize, format and optimize images based on url modifiers.
8 | image:
9 | light: /uil-image-resize-landscape.svg
10 | dark: /uil-image-resize-landscape-dark.svg
11 | alt: ""
12 | actions:
13 | - theme: brand
14 | text: Guide
15 | link: /guide/
16 | - theme: alt
17 | text: View on GitHub
18 | link: https://github.com/strapi-community/strapi-plugin-local-image-sharp
19 | ---
20 |
--------------------------------------------------------------------------------
/playground/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | /**
5 | * An asynchronous register function that runs before
6 | * your application is initialized.
7 | *
8 | * This gives you an opportunity to extend code.
9 | */
10 | register(/*{ strapi }*/) {},
11 |
12 | /**
13 | * An asynchronous bootstrap function that runs before
14 | * your application gets started.
15 | *
16 | * This gives you an opportunity to set up your data model,
17 | * run jobs, or perform some special logic.
18 | */
19 | bootstrap(/*{ strapi }*/) {},
20 | };
21 |
--------------------------------------------------------------------------------
/playground/src/admin/app.example.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | locales: [
3 | // 'ar',
4 | // 'fr',
5 | // 'cs',
6 | // 'de',
7 | // 'dk',
8 | // 'es',
9 | // 'he',
10 | // 'id',
11 | // 'it',
12 | // 'ja',
13 | // 'ko',
14 | // 'ms',
15 | // 'nl',
16 | // 'no',
17 | // 'pl',
18 | // 'pt-BR',
19 | // 'pt',
20 | // 'ru',
21 | // 'sk',
22 | // 'sv',
23 | // 'th',
24 | // 'tr',
25 | // 'uk',
26 | // 'vi',
27 | // 'zh-Hans',
28 | // 'zh',
29 | ],
30 | };
31 |
32 | const bootstrap = (app) => {
33 | console.log(app);
34 | };
35 |
36 | export default {
37 | config,
38 | bootstrap,
39 | };
40 |
--------------------------------------------------------------------------------
/playground/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@babel/eslint-parser",
3 | "extends": ["eslint:recommended"],
4 | "env": {
5 | "commonjs": true,
6 | "es6": true,
7 | "node": true,
8 | "browser": false
9 | },
10 | "parserOptions": {
11 | "requireConfigFile": false,
12 | "ecmaFeatures": {
13 | "experimentalObjectRestSpread": true,
14 | "jsx": false
15 | },
16 | "sourceType": "module"
17 | },
18 | "globals": {
19 | "strapi": true
20 | },
21 | "rules": {
22 | "indent": ["error", 2, { "SwitchCase": 1 }],
23 | "linebreak-style": ["error", "unix"],
24 | "no-console": 0,
25 | "quotes": ["error", "single"],
26 | "semi": ["error", "always"]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground",
3 | "private": true,
4 | "version": "0.1.0",
5 | "description": "A Strapi application",
6 | "scripts": {
7 | "develop": "strapi develop",
8 | "start": "strapi start",
9 | "build": "strapi build",
10 | "strapi": "strapi"
11 | },
12 | "devDependencies": {},
13 | "dependencies": {
14 | "@strapi/strapi": "4.5.5",
15 | "@strapi/plugin-users-permissions": "4.5.5",
16 | "@strapi/plugin-i18n": "4.5.5",
17 | "strapi-plugin-local-image-sharp": "link:..",
18 | "better-sqlite3": "7.4.6"
19 | },
20 | "author": {
21 | "name": "A Strapi developer"
22 | },
23 | "strapi": {
24 | "uuid": "de665554-7499-4829-b112-0a4ded35dc98"
25 | },
26 | "engines": {
27 | "node": ">=14.19.1 <=18.x.x",
28 | "npm": ">=6.0.0"
29 | },
30 | "license": "MIT"
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/gh-release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | # trigger release on every tag push
5 | push:
6 | tags:
7 | - 'v*.*.*'
8 |
9 | jobs:
10 | release:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | # extract tag from the github ref (e.g. refs/tags/v1.2.3)
15 | - name: Set up tag meta
16 | id: meta
17 | run: |
18 | echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
19 |
20 | # create a new release on github with discussion
21 | - name: Create release
22 | uses: softprops/action-gh-release@v1
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | with:
26 | tag_name: ${{ steps.meta.outputs.tag }}
27 | name: Release ${{ steps.meta.outputs.tag }}
28 | body: View [CHANGELOG.md](https://github.com/strapi-community/strapi-plugin-local-images-sharp/blob/main/CHANGELOG.md) for details
29 | draft: false
30 | prerelease: false
--------------------------------------------------------------------------------
/docs/.vitepress/theme/Layout.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/docs/public/uil-image-resize-landscape.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/uil-image-resize-landscape-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/register.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Router = require('@koa/router')
4 | const { createIPX } = require('ipx')
5 | const { resolve } = require('path')
6 | const { existsSync, mkdirSync } = require('fs')
7 | const { createMiddleware } = require('./middleware')
8 |
9 | function register({ strapi }) {
10 | const config = strapi.config.get('plugin.local-image-sharp')
11 | config.srcDir = strapi.dirs?.static?.public ?? strapi.dirs?.public
12 |
13 | strapi.log.info(
14 | `Using Local Image Sharp plugin`
15 | );
16 | strapi.log.info(
17 | `- Source directory: ${config.srcDir}`
18 | );
19 |
20 | if (config.cacheDir) {
21 | const cwd = process.cwd()
22 | config.cacheDir = resolve(cwd, config.cacheDir)
23 |
24 | // prevent cache directory from being in source directory
25 | if (config.cacheDir.startsWith(config.srcDir)) {
26 | throw new Error('Cache directory cannot be inside source directory')
27 | }
28 |
29 | // check if directory exists
30 | if (!existsSync(config.cacheDir)) {
31 | mkdirSync(config.cacheDir, { recursive: true })
32 | }
33 |
34 | strapi.log.info(
35 | `- Cache directory: ${config.cacheDir}`
36 | );
37 | }
38 |
39 | const router = new Router()
40 | config.paths.forEach(path => {
41 | const ipx = createIPX({
42 | dir: config.srcDir + path,
43 | })
44 |
45 | router.get(`${path}/(.*)`, createMiddleware(ipx))
46 | })
47 |
48 | strapi.server.use(router.routes())
49 | }
50 |
51 | module.exports = {
52 | register,
53 | }
54 |
--------------------------------------------------------------------------------
/playground/.gitignore:
--------------------------------------------------------------------------------
1 | .image-cache
2 |
3 | ############################
4 | # OS X
5 | ############################
6 |
7 | .DS_Store
8 | .AppleDouble
9 | .LSOverride
10 | Icon
11 | .Spotlight-V100
12 | .Trashes
13 | ._*
14 |
15 |
16 | ############################
17 | # Linux
18 | ############################
19 |
20 | *~
21 |
22 |
23 | ############################
24 | # Windows
25 | ############################
26 |
27 | Thumbs.db
28 | ehthumbs.db
29 | Desktop.ini
30 | $RECYCLE.BIN/
31 | *.cab
32 | *.msi
33 | *.msm
34 | *.msp
35 |
36 |
37 | ############################
38 | # Packages
39 | ############################
40 |
41 | *.7z
42 | *.csv
43 | *.dat
44 | *.dmg
45 | *.gz
46 | *.iso
47 | *.jar
48 | *.rar
49 | *.tar
50 | *.zip
51 | *.com
52 | *.class
53 | *.dll
54 | *.exe
55 | *.o
56 | *.seed
57 | *.so
58 | *.swo
59 | *.swp
60 | *.swn
61 | *.swm
62 | *.out
63 | *.pid
64 |
65 |
66 | ############################
67 | # Logs and databases
68 | ############################
69 |
70 | .tmp
71 | *.log
72 | *.sql
73 | *.sqlite
74 | *.sqlite3
75 |
76 |
77 | ############################
78 | # Misc.
79 | ############################
80 |
81 | *#
82 | ssl
83 | .idea
84 | nbproject
85 | public/uploads/*
86 | !public/uploads/.gitkeep
87 |
88 | ############################
89 | # Node.js
90 | ############################
91 |
92 | lib-cov
93 | lcov.info
94 | pids
95 | logs
96 | results
97 | node_modules
98 | .node_history
99 |
100 | ############################
101 | # Tests
102 | ############################
103 |
104 | testApp
105 | coverage
106 |
107 | ############################
108 | # Strapi
109 | ############################
110 |
111 | .env
112 | license.txt
113 | exports
114 | *.cache
115 | dist
116 | build
117 | .strapi-updater.json
118 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.js:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'module'
2 | import { defineConfig } from 'vitepress'
3 |
4 | const require = createRequire(import.meta.url)
5 | const pkg = require('../../package.json')
6 |
7 | export default defineConfig({
8 | title: "Local Image Sharp",
9 | description: "Dynamically resize, format and optimize images based on url modifiers.",
10 | base: "/strapi-plugin-local-image-sharp/",
11 | lastUpdated: true,
12 | themeConfig: {
13 | socialLinks: [
14 | { icon: 'github', link: 'https://github.com/strapi-community/strapi-plugin-local-image-sharp' },
15 | ],
16 | editLink: {
17 | pattern: 'https://github.com/strapi-community/strapi-plugin-local-image-sharp/edit/main/docs/:path',
18 | text: 'Edit this page on GitHub'
19 | },
20 | logo: {
21 | src: "/icon.png",
22 | },
23 | outline: [2,3],
24 | footer: {
25 | message: 'Made with ❤️ by Strapi Community'
26 | },
27 | nav: [
28 | {
29 | text: "Guide",
30 | link: "/guide/",
31 | activeMatch: '/guide/',
32 | },
33 | {
34 | text: pkg.version,
35 | items: [
36 | {
37 | text: 'Changelog',
38 | link: 'https://github.com/strapi-community/strapi-plugin-local-image-sharp/blob/main/CHANGELOG.md'
39 | },
40 | {
41 | text: 'Strapi Community',
42 | link: 'https://github.com/strapi-community'
43 | }
44 | ]
45 | }
46 | ],
47 | sidebar: {
48 | '/guide/': [
49 | {
50 | text: 'Guide',
51 | items: [
52 | { text: 'Quick Start Guide', link: '/guide/' },
53 | { text: 'Modifiers', link: '/guide/modifiers' },
54 | ]
55 | },
56 | ],
57 | }
58 | }
59 | })
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Local Image Sharp
3 |
4 |
Dynamically resize, format and optimize images based on url modifiers.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ## Table of Contents
20 |
21 | - [✨ Features](#-features)
22 | - [🖐 Installation](#-installation)
23 | - [🚚 Usage](#-usage)
24 | - [Contributing](#contributing)
25 | - [License](#license)
26 |
27 |
28 | ## ✨ Features
29 |
30 | Convert any uploaded images with local provider using sharp modifier.
31 | No extra configuration needed, the modifiers will be applied based on the url.
32 |
33 | This is made by [ipx](https://github.com/unjs/ipx)
34 |
35 | ## 🚚 Getting Started
36 |
37 | [Read the Docs to Learn More.](https://strapi-community.github.io/strapi-plugin-local-image-sharp/)
38 |
39 | ## Contributing
40 |
41 | I/We are actively looking for contributors, maintainers, and others to help shape this package. As this plugins sole purpose within the Strapi community is to be used by other developers and plugin maintainers to get fast responses time.
42 |
43 | If interested please feel free to email the lead maintainer Sacha at: sacha@digisquad.io or ping `stf#3254` on Discord.
44 |
45 | ## License
46 |
47 | See the [LICENSE](./LICENSE.md) file for licensing information.
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: docs
2 |
3 | on:
4 | # trigger deployment on push to main branch and if docs/ is updated
5 | push:
6 | branches: [main]
7 | paths:
8 | - "docs/**"
9 | # trigger deployment manually
10 | workflow_dispatch:
11 |
12 | jobs:
13 | docs:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 | with:
19 | # fetch all commits to get last updated time or other git log info
20 | fetch-depth: 0
21 |
22 | - name: Setup Node.js
23 | uses: actions/setup-node@v1
24 | with:
25 | # choose node.js version to use
26 | node-version: "14"
27 |
28 | # cache node_modules
29 | - name: Cache dependencies
30 | uses: actions/cache@v2
31 | id: yarn-cache
32 | with:
33 | path: |
34 | **/node_modules
35 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
36 | restore-keys: |
37 | ${{ runner.os }}-yarn-
38 |
39 | # install dependencies if the cache did not hit
40 | - name: Install dependencies
41 | if: steps.yarn-cache.outputs.cache-hit != 'true'
42 | run: yarn --frozen-lockfile
43 |
44 | # run build script
45 | - name: Build VitePress site
46 | run: yarn docs:build
47 |
48 | # please check out the docs of the workflow for more details
49 | # @see https://github.com/crazy-max/ghaction-github-pages
50 | - name: Deploy to GitHub Pages
51 | uses: crazy-max/ghaction-github-pages@v2
52 | with:
53 | # deploy to gh-pages branch
54 | target_branch: gh-pages
55 | # deploy the default output dir of VitePress
56 | build_dir: docs/.vitepress/dist
57 | env:
58 | # @see https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret
59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "strapi-plugin-local-image-sharp",
3 | "version": "1.7.0",
4 | "description": "Dynamically resize, format and optimize images based on url modifiers",
5 | "strapi": {
6 | "displayName": "Local Image Sharp",
7 | "name": "local-image-sharp",
8 | "description": "Dynamically resize, format and optimize images based on url modifiers",
9 | "required": false,
10 | "kind": "plugin"
11 | },
12 | "keywords": [
13 | "strapi",
14 | "plugin",
15 | "images",
16 | "sharp",
17 | "optimize",
18 | "resize"
19 | ],
20 | "files": [
21 | "src",
22 | "strapi-server.js"
23 | ],
24 | "scripts": {
25 | "docs:dev": "vitepress dev docs",
26 | "docs:build": "vitepress build docs",
27 | "docs:serve": "vitepress serve docs",
28 | "eslint": "eslint .",
29 | "eslint:fix": "eslint . --fix",
30 | "release": "npx standard-version && git push --follow-tags origin main && npm publish"
31 | },
32 | "dependencies": {
33 | "@koa/router": "^10.1.1",
34 | "etag": "^1.8.1",
35 | "ipx": "^0.9.10",
36 | "ohash": "^1.0.0",
37 | "qs": "^6.10.3",
38 | "ufo": "^0.8.5"
39 | },
40 | "peerDependencies": {
41 | "@strapi/strapi": "^4.0.0",
42 | "yup": "^0.32.9"
43 | },
44 | "devDependencies": {
45 | "@babel/eslint-parser": "^7.19.1",
46 | "@giscus/vue": "^2.2.6",
47 | "@strapi-community/eslint-config": "^0.4.3",
48 | "eslint": "8.20.0",
49 | "vitepress": "^1.0.0-alpha.35"
50 | },
51 | "author": {
52 | "name": "Sacha Stafyniak",
53 | "email": "sacha@digisquad.io",
54 | "url": "https://digisquad.io"
55 | },
56 | "maintainers": [
57 | {
58 | "name": "Strapi Community",
59 | "url": "https://github.com/strapi-community"
60 | },
61 | {
62 | "name": "Sacha Stafyniak",
63 | "email": "sacha@digisquad.io",
64 | "url": "https://digisquad.io",
65 | "lead": true
66 | }
67 | ],
68 | "bugs": {
69 | "url": "https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues"
70 | },
71 | "homepage": "https://github.com/strapi-community/strapi-plugin-local-image-sharp",
72 | "engines": {
73 | "node": ">=14.x.x <=18.x.x",
74 | "npm": ">=6.0.0"
75 | },
76 | "license": "MIT"
77 | }
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ############################
2 | # OS X
3 | ############################
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 | Icon
9 | .Spotlight-V100
10 | .Trashes
11 | ._*
12 |
13 |
14 | ############################
15 | # Linux
16 | ############################
17 |
18 | *~
19 |
20 |
21 | ############################
22 | # Windows
23 | ############################
24 |
25 | Thumbs.db
26 | ehthumbs.db
27 | Desktop.ini
28 | $RECYCLE.BIN/
29 | *.cab
30 | *.msi
31 | *.msm
32 | *.msp
33 |
34 |
35 | ############################
36 | # Packages
37 | ############################
38 |
39 | *.7z
40 | *.csv
41 | *.dat
42 | *.dmg
43 | *.gz
44 | *.iso
45 | *.jar
46 | *.rar
47 | *.tar
48 | *.zip
49 | *.com
50 | *.class
51 | *.dll
52 | *.exe
53 | *.o
54 | *.seed
55 | *.so
56 | *.swo
57 | *.swp
58 | *.swn
59 | *.swm
60 | *.out
61 | *.pid
62 |
63 |
64 | ############################
65 | # Logs and databases
66 | ############################
67 |
68 | .tmp
69 | *.log
70 | *.sql
71 | *.sqlite
72 |
73 |
74 | ############################
75 | # Misc.
76 | ############################
77 |
78 | *#
79 | .idea
80 | nbproject
81 | .vscode/
82 |
83 |
84 | ############################
85 | # Node.js
86 | ############################
87 |
88 | lib-cov
89 | lcov.info
90 | pids
91 | logs
92 | results
93 | build
94 | node_modules
95 | .node_history
96 | package-lock.json
97 | **/package-lock.json
98 | !docs/package-lock.json
99 | *.heapsnapshot
100 |
101 |
102 | ############################
103 | # Tests
104 | ############################
105 |
106 | testApp
107 | coverage
108 | cypress/screenshots
109 | cypress/videos
110 |
111 |
112 | ############################
113 | # Documentation
114 | ############################
115 |
116 | dist
117 |
118 | ############################
119 | # Builds
120 | ############################
121 |
122 | packages/generators/app/files/public/
123 | schema.graphql
124 |
125 | ############################
126 | # Example app
127 | ############################
128 |
129 | .dev
130 | # *.cache
131 |
132 | ############################
133 | # Visual Studio Code
134 | ############################
135 |
136 | front-workspace.code-workspace
137 | .yarn
138 | .yarnrc
139 |
140 | cache
--------------------------------------------------------------------------------
/playground/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Getting started with Strapi
2 |
3 | Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html) (CLI) which lets you scaffold and manage your project in seconds.
4 |
5 | ### `develop`
6 |
7 | Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-develop)
8 |
9 | ```
10 | npm run develop
11 | # or
12 | yarn develop
13 | ```
14 |
15 | ### `start`
16 |
17 | Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-start)
18 |
19 | ```
20 | npm run start
21 | # or
22 | yarn start
23 | ```
24 |
25 | ### `build`
26 |
27 | Build your admin panel. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-build)
28 |
29 | ```
30 | npm run build
31 | # or
32 | yarn build
33 | ```
34 |
35 | ## ⚙️ Deployment
36 |
37 | Strapi gives you many possible deployment options for your project. Find the one that suits you on the [deployment section of the documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment.html).
38 |
39 | ## 📚 Learn more
40 |
41 | - [Resource center](https://strapi.io/resource-center) - Strapi resource center.
42 | - [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.
43 | - [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.
44 | - [Strapi blog](https://docs.strapi.io) - Official Strapi blog containing articles made by the Strapi team and the community.
45 | - [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.
46 |
47 | Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!
48 |
49 | ## ✨ Community
50 |
51 | - [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.
52 | - [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.
53 | - [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.
54 |
55 | ---
56 |
57 | 🤫 Psst! [Strapi is hiring](https://strapi.io/careers).
58 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ## [1.7.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.6.0...v1.7.0) (2023-07-31)
6 |
7 |
8 | ### Features
9 |
10 | * allow to configure custom paths ([#29](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/29)) ([0992821](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/0992821469bed878534ed8b1c5058447f2a8151d))
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * docs ([#22](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/22)) ([c1519a4](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/c1519a4b316a8a2501ac0f3b60029ab5a9102d75))
16 |
17 | ## [1.6.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.4.0...v1.6.0) (2023-02-28)
18 |
19 |
20 | ### Features
21 |
22 | * add `maxAge` option for Cache-Control HTTP response header ([#20](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/20)) ([d81843b](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/d81843bdeb4fd8611873c73c8b1275569a9980dd))
23 |
24 | ## [1.4.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.3.0...v1.4.0) (2023-01-06)
25 |
26 |
27 | ### Features
28 |
29 | * add cacheDir option to save generated file ([3b52e0f](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/3b52e0f3b8ebfb537690bf970c0a340ddd36aeaa))
30 |
31 | ## [1.3.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.2.1...v1.3.0) (2023-01-06)
32 |
33 |
34 | ### Features
35 |
36 | * add avif to allowed types ([26b256f](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/26b256f8d334bd14652d3120ed279ec190374d45))
37 | * create documentation with vitepress ([dd9b374](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/dd9b374d94258841796fcb67088c4403f0ff85eb))
38 |
39 |
40 | ### Bug Fixes
41 |
42 | * broken path modifiers ([5e75ff6](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/5e75ff6c3601cfb6685c5722b2a7022fc8f2cd66)), closes [#12](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/12)
43 |
44 | ### [1.2.1](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.2.0...v1.2.1) (2022-10-12)
45 |
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Quick Start Guide
3 | ---
4 |
5 | # Quick Start Guide
6 |
7 | Convert any uploaded images with local provider using sharp modifier.
8 | No extra configuration needed, the modifiers will be applied based on the url.
9 |
10 | > This is made using [ipx](https://github.com/unjs/ipx)
11 |
12 | To install this plugin, run the following command in your Strapi project:
13 |
14 | ```bash
15 | yarn add strapi-plugin-local-image-sharp
16 | ```
17 |
18 | ## Usage
19 |
20 | This plugin works by setting modifiers either the path, or in the query string parameters.
21 |
22 | - Original image:
23 | `http://localhost:1337/uploads/buffalo_56442f4096.png`
24 | - WebP (Path modifier):
25 | `http://localhost:1337/uploads/format_webp/buffalo_56442f4096.png`
26 | - WebP (Query parameters):
27 | `http://localhost:1337/uploads/buffalo_56442f4096.png?format=webp`
28 |
29 | ### Using path modifiers
30 |
31 | Change format to `webp` and keep other things same as source:
32 |
33 | `http://localhost:1337/uploads/f_webp/buffalo_56442f4096.png`
34 |
35 | Keep original format `png` and set width to `200`:
36 |
37 | `http://localhost:1337/uploads/w_200/buffalo_56442f4096.png`
38 |
39 | You can combine modifiers using a coma, for example:
40 | Resize to `200x200px` using `embed` method and change format to `webp`:
41 |
42 | `http://localhost:1337/uploads/embed,f_webp,s_200x200/buffalo_56442f4096.png`
43 |
44 | ### Using query parameters modifiers
45 |
46 | Change format to `webp` and keep other things same as source:
47 |
48 | `http://localhost:1337/uploads/buffalo_56442f4096.png?format=webp`
49 |
50 | Keep original format `png` and set width to `200`:
51 |
52 | `http://localhost:1337/uploads/buffalo_56442f4096.png?width=200`
53 |
54 | You can combine modifiers using a coma, for example:
55 | Resize to `200x200px` using `embed` method and change format to `webp`:
56 |
57 | `http://localhost:1337/uploads/buffalo_56442f4096.png?format=webp&resize=200x200&embed`
58 |
59 | ## Configuration
60 |
61 | ### `cacheDir`
62 |
63 | The directory where the generated files will be stored.
64 |
65 | > _By default, no value is set, so cache is disabled, meaning that the image will be generated on every request._
66 |
67 | You can set the cache directory using `STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR` environment variable. Or you can set it in `config/plugins.js`:
68 |
69 | ::: code-group
70 |
71 | ```bash [enviroment variables]
72 | STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR=.image-cache yarn start
73 | # or STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR=.image-cache yarn develop
74 | ```
75 |
76 | ```js [config/plugins.js]
77 | "use strict";
78 |
79 | module.exports = {
80 | // ...
81 |
82 | "local-image-sharp": {
83 | config: {
84 | cacheDir: ".image-cache",
85 | },
86 | },
87 |
88 | // ...
89 | };
90 | ```
91 |
92 | :::
93 |
94 | ::: info
95 | When providing a relative path, it will be resolved from the root of your project.
96 | :::
97 |
98 | ::: tip
99 | Don't forget to add `.image-cache` to your `.gitignore` file.
100 | :::
101 |
102 | ### `maxAge`
103 |
104 | You can set the `Cache-Control` HTTP response header to improve the load performance. It's a good practice cache static resources using HTTP caching. [See more here](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl)
105 |
106 | ```js [config/plugins.js]
107 | "use strict";
108 |
109 | module.exports = {
110 | // ...
111 |
112 | "local-image-sharp": {
113 | config: {
114 | maxAge: 31536000, // which corresponds to 1 year: 60 seconds × 60 minutes × 24 hours × 365 days = 31536000 seconds.
115 | },
116 | },
117 |
118 | // ...
119 | };
120 | ```
121 |
122 | ### `paths`
123 |
124 | Add custom paths to images if you use more than the default path '/uploads'.
125 |
126 | > _By default, '/uploads' is set, to cache all default upload routes._
127 |
128 | You can set the paths in `config/plugins.js`:
129 |
130 |
131 | ```js [config/plugins.js]
132 | "use strict";
133 |
134 | module.exports = {
135 | // ...
136 |
137 | "local-image-sharp": {
138 | config: {
139 | paths: ['/uploads','/custom'],
140 | },
141 | },
142 |
143 | // ...
144 | };
145 | ```
--------------------------------------------------------------------------------
/src/middleware.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const qs = require('qs');
4 | const { decode } = require('ufo');
5 | const { hash } = require('ohash');
6 | const { join } = require('path');
7 | const { createReadStream, existsSync } = require('fs');
8 | const { writeFile, readFile } = require('fs/promises');
9 | const getEtag = require('etag');
10 |
11 | function createMiddleware(ipx) {
12 | const config = strapi.config.get('plugin.local-image-sharp');
13 |
14 | return async function ipxMiddleware(ctx, next) {
15 | let path = null;
16 | config.paths.forEach(target => {
17 | if (ctx.req.url.includes(target)) {
18 | path = ctx.req.url.split(target).join('');
19 | }
20 | });
21 |
22 | if (!path) {
23 | const statusCode = 500;
24 | const statusMessage = 'No path found';
25 | strapi.log.debug(statusMessage);
26 | ctx.status = statusCode
27 | return;
28 | }
29 |
30 | const [url, query] = path.split('?');
31 | const [firstSegment = '', ...idSegments] = url
32 | .substr(1 /* leading slash */)
33 | .split('/');
34 | const allowedTypes = [
35 | 'JPEG',
36 | 'PNG',
37 | 'GIF',
38 | 'SVG',
39 | 'TIFF',
40 | 'ICO',
41 | 'DVU',
42 | 'JPG',
43 | 'WEBP',
44 | 'AVIF',
45 | ];
46 | let id;
47 | let modifiers;
48 |
49 | let tempFilePath;
50 | let tempTypePath;
51 | let tempEtagPath;
52 |
53 | // extract modifiers from query string
54 | if (!idSegments.length && firstSegment) {
55 | id = firstSegment;
56 | modifiers = qs.parse(query);
57 | } else {
58 | // extract modifiers from url segments
59 | id = decode(idSegments.join('/')); // decode is a shortend version of decodeURIComponent
60 | modifiers = Object.create(null);
61 | if (firstSegment !== '_') {
62 | for (const p of firstSegment.split(',')) {
63 | const [key, value = ''] = p.split('_');
64 | modifiers[key] = decode(value);
65 | }
66 | }
67 | }
68 |
69 | // if no id or no modifiers or not allowed type, skip
70 | if (
71 | !id ||
72 | !Object.keys(modifiers).length ||
73 | !allowedTypes.includes(id.split('.').pop().toUpperCase())
74 | ) {
75 | await next();
76 | return;
77 | }
78 |
79 | const objectHash = hash({ id, modifiers });
80 |
81 | // If cache enabled, check if file exists
82 | if (config.cacheDir) {
83 | tempFilePath = join(config.cacheDir, `${objectHash}.raw`);
84 | tempTypePath = join(config.cacheDir, `${objectHash}.mime`);
85 | tempEtagPath = join(config.cacheDir, `${objectHash}.etag`);
86 |
87 | if (existsSync(tempFilePath)) {
88 | try {
89 | const [type, etag] = await Promise.all([
90 | readFile(tempTypePath, 'utf-8'),
91 | readFile(tempEtagPath, 'utf-8'),
92 | ]);
93 | const stream = createReadStream(tempFilePath);
94 |
95 | ctx.set('ETag', etag);
96 | if (etag && ctx.req.headers['if-none-match'] === etag) {
97 | ctx.status = 304;
98 | return;
99 | }
100 |
101 | // Cache-Control
102 | if (config.maxAge) {
103 | ctx.set(
104 | 'Cache-Control',
105 | `max-age=${+config.maxAge}, public, s-maxage=${+config.maxAge}`
106 | );
107 | }
108 |
109 | // Mime
110 | if (type) {
111 | ctx.set('Content-Type', type);
112 | }
113 | ctx.body = stream;
114 | return;
115 | } catch {
116 | // file not found, continue to generate fresh image
117 | }
118 | }
119 | }
120 |
121 | // Create request
122 | const img = ipx(id, modifiers, ctx.req.options);
123 | // Get image meta from source
124 | try {
125 | const src = await img.src();
126 |
127 | // Caching headers
128 | if (src.mtime) {
129 | if (ctx.req.headers['if-modified-since']) {
130 | if (new Date(ctx.req.headers['if-modified-since']) >= src.mtime) {
131 | ctx.status = 304;
132 | return;
133 | }
134 | }
135 | ctx.set('Last-Modified', `${+src.mtime}`);
136 | }
137 |
138 | const maxAge = src.maxAge ?? config.maxAge;
139 |
140 | if (maxAge) {
141 | ctx.set(
142 | 'Cache-Control',
143 | `max-age=${+maxAge}, public, s-maxage=${+maxAge}`
144 | );
145 | }
146 |
147 | // Get converted image
148 | const { data, format } = await img.data();
149 |
150 | // ETag
151 | const etag = getEtag(data);
152 |
153 | // If cache enabled, write image to temp dir
154 | if (tempTypePath && tempFilePath) {
155 | Promise.all([
156 | writeFile(tempTypePath, `image/${format}`, 'utf-8'),
157 | writeFile(tempEtagPath, etag, 'utf-8'),
158 | writeFile(tempFilePath, data),
159 | ]).catch(() => {
160 | // console.error(error);
161 | });
162 | }
163 |
164 | ctx.set('ETag', etag);
165 | if (etag && ctx.req.headers['if-none-match'] === etag) {
166 | ctx.status = 304;
167 | return;
168 | }
169 |
170 | // Mime
171 | if (format) {
172 | ctx.set('Content-Type', `image/${format}`);
173 | }
174 |
175 | ctx.body = data;
176 | } catch (error) {
177 | const statusCode = parseInt(error.statusCode, 10) || 500;
178 | const statusMessage = error.message
179 | ? `IPX Error (${error.message})`
180 | : `IPX Error (${statusCode})`;
181 | strapi.log.debug(statusMessage);
182 | // console.error(error);
183 |
184 | ctx.status = statusCode;
185 | }
186 | };
187 | }
188 |
189 | module.exports = {
190 | createMiddleware,
191 | };
192 |
--------------------------------------------------------------------------------
/docs/public/logo-rest-cache-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/fluent-emoji-cloud-with-lightning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/logo-rest-cache-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/guide/modifiers.md:
--------------------------------------------------------------------------------
1 |
2 | # Modifiers
3 |
4 | ## `width` / `w`
5 |
6 | Resize the image to the specified width in pixels. The height is calculated with the same aspect ratio.
7 |
8 | **Example (Path modifier)**:
9 | `http://localhost:1337/uploads/width_200/buffalo.png`
10 |
11 | **Example (Query parameter)**:
12 | `http://localhost:1337/uploads/buffalo.png?width=200`
13 |
14 | **Sharp documentation**:
15 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize)
16 |
17 | ## `height` / `h`
18 |
19 | Resize the image to the specified height in pixels. The width is calculated with the same aspect ratio.
20 |
21 | **Example (Path modifier)**:
22 | `http://localhost:1337/uploads/height_200/buffalo.png`
23 |
24 | **Example (Query parameter)**:
25 | `http://localhost:1337/uploads/buffalo.png?height=200`
26 |
27 | **Sharp documentation**:
28 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize)
29 |
30 | ## `resize` / `s`
31 |
32 | Resize the image to the specified width and height in pixels. The aspect ratio is not preserved.
33 |
34 | **Example (Path modifier)**:
35 | `http://localhost:1337/uploads/s_200x200/buffalo.png`
36 |
37 | **Example (Query parameter)**:
38 | `http://localhost:1337/uploads/buffalo.png?resize=200x200`
39 |
40 | **Sharp documentation**:
41 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize)
42 |
43 | ## `fit`
44 |
45 | Resize the image to the specified width and height in pixels. The aspect ratio is preserved.
46 |
47 | **Example (Path modifier)**:
48 | `http://localhost:1337/uploads/s_200x200,fit_outside/buffalo.png`
49 |
50 | **Example (Query parameter)**:
51 | `http://localhost:1337/uploads/buffalo.png?resize=200x200&fit=outside`
52 |
53 | **Sharp documentation**:
54 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize)
55 |
56 | ## `position` / `pos`
57 |
58 | Sets `position` option for `resize`. Only works with `fit` and `resize` modifiers.
59 |
60 | **Example (Path modifier)**:
61 | `http://localhost:1337/uploads/s_200x200,pos_top/buffalo.png`
62 |
63 | **Example (Query parameter)**:
64 | `http://localhost:1337/uploads/buffalo.png?resize=200x200&position=top`
65 |
66 | **Sharp documentation**:
67 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize)
68 |
69 | ## `trim`
70 |
71 | Trim the image to remove any border that matches the specified background color.
72 |
73 | **Example (Path modifier)**:
74 | `http://localhost:1337/uploads/trim_ffffff/buffalo.png`
75 |
76 | **Example (Query parameter)**:
77 | `http://localhost:1337/uploads/buffalo.png?trim=ffffff`
78 |
79 | **Sharp documentation**:
80 | [https://sharp.pixelplumbing.com/api-operation#trim](https://sharp.pixelplumbing.com/api-operation#trim)
81 |
82 | ## `format` / `f`
83 |
84 | Convert the image to the specified format. The format must be one of the following: `jpeg`, `jpg`, `png`, `webp`, `tiff`, `raw`, `gif`, `svg`, `heif`, `heic`, `avif`.
85 |
86 | **Example (Path modifier)**:
87 | `http://localhost:1337/uploads/format_jpg/buffalo.png`
88 |
89 | **Example (Query parameter)**:
90 | `http://localhost:1337/uploads/buffalo.png?format=jpg`
91 |
92 | **Sharp documentation**:
93 | [https://sharp.pixelplumbing.com/api-output#toformat](https://sharp.pixelplumbing.com/api-output#toformat)
94 |
95 | ## `quality` / `q`
96 |
97 | Set the quality of the output image. This is only applicable to JPEG and WebP images.
98 |
99 | **Example (Path modifier)**:
100 | `http://localhost:1337/uploads/quality_50/buffalo.png`
101 |
102 | **Example (Query parameter)**:
103 | `http://localhost:1337/uploads/buffalo.png?quality=50`
104 |
105 | **Sharp documentation**:
106 | [https://sharp.pixelplumbing.com/api-output#jpeg](https://sharp.pixelplumbing.com/api-output#jpeg)
107 |
108 | ## `rotate` / `r`
109 |
110 | Rotate the image by the specified number of degrees.
111 |
112 | **Example (Path modifier)**:
113 | `http://localhost:1337/uploads/rotate_90/buffalo.png`
114 |
115 | **Example (Query parameter)**:
116 | `http://localhost:1337/uploads/buffalo.png?rotate=90`
117 |
118 | **Sharp documentation**:
119 | [https://sharp.pixelplumbing.com/api-operation#rotate](https://sharp.pixelplumbing.com/api-operation#rotate)
120 |
121 | ## `enlarge`
122 |
123 | Enlarge the output image to be at least as wide and as high as the specified width and height in pixels. The aspect ratio is not preserved.
124 |
125 | **Example (Path modifier)**:
126 | `http://localhost:1337/uploads/enlarge_200x200/buffalo.png`
127 |
128 | **Example (Query parameter)**:
129 | `http://localhost:1337/uploads/buffalo.png?enlarge=200x200`
130 |
131 | **Sharp documentation**:
132 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize)
133 |
134 | ## `flip`
135 |
136 | Flip the image about the vertical Y axis. This is equivalent to a horizontal flip if the image is displayed in the correct orientation.
137 |
138 | **Example (Path modifier)**:
139 | `http://localhost:1337/uploads/flip/buffalo.png`
140 |
141 | **Example (Query parameter)**:
142 | `http://localhost:1337/uploads/buffalo.png?flip`
143 |
144 | **Sharp documentation**:
145 | [https://sharp.pixelplumbing.com/api-operation#flip](https://sharp.pixelplumbing.com/api-operation#flip)
146 |
147 | ## `flop`
148 |
149 | Flip the image about the horizontal X axis. This is equivalent to a horizontal flip.
150 |
151 | **Example (Path modifier)**:
152 | `http://localhost:1337/uploads/flop/buffalo.png`
153 |
154 | **Example (Query parameter)**:
155 | `http://localhost:1337/uploads/buffalo.png?flop`
156 |
157 | **Sharp documentation**:
158 | [https://sharp.pixelplumbing.com/api-operation#flop](https://sharp.pixelplumbing.com/api-operation#flop)
159 |
160 | ## `sharpen`
161 |
162 | Sharpen the image. This can be used to increase the perceived sharpness of an image.
163 |
164 | **Example (Path modifier)**:
165 | `http://localhost:1337/uploads/sharpen/buffalo.png`
166 |
167 | **Example (Query parameter)**:
168 | `http://localhost:1337/uploads/buffalo.png?sharpen`
169 |
170 | **Sharp documentation**:
171 | [https://sharp.pixelplumbing.com/api-operation#sharpen](https://sharp.pixelplumbing.com/api-operation#sharpen)
172 |
173 | ## `median`
174 |
175 | Apply a median filter to the image. This can be used to reduce noise in an image.
176 |
177 | **Example (Path modifier)**:
178 | `http://localhost:1337/uploads/median/buffalo.png`
179 |
180 | **Example (Query parameter)**:
181 | `http://localhost:1337/uploads/buffalo.png?median`
182 |
183 | **Sharp documentation**:
184 | [https://sharp.pixelplumbing.com/api-operation#median](https://sharp.pixelplumbing.com/api-operation#median)
185 |
186 | ## `gamma`
187 |
188 | Apply gamma correction to the image. This can be used to adjust the overall brightness of an image.
189 |
190 | **Example (Path modifier)**:
191 | `http://localhost:1337/uploads/gamma_1.5/buffalo.png`
192 |
193 | **Example (Query parameter)**:
194 | `http://localhost:1337/uploads/buffalo.png?gamma=1.5`
195 |
196 | **Sharp documentation**:
197 | [https://sharp.pixelplumbing.com/api-operation#gamma](https://sharp.pixelplumbing.com/api-operation#gamma)
198 |
199 | ## `negate`
200 |
201 | Negate the image. This can be used to invert the colors of an image.
202 |
203 | **Example (Path modifier)**:
204 | `http://localhost:1337/uploads/negate/buffalo.png`
205 |
206 | **Example (Query parameter)**:
207 | `http://localhost:1337/uploads/buffalo.png?negate`
208 |
209 | **Sharp documentation**:
210 | [https://sharp.pixelplumbing.com/api-operation#negate](https://sharp.pixelplumbing.com/api-operation#negate)
211 |
212 | ## `normalize`
213 |
214 | Normalize the image. This can be used to improve the contrast in an image.
215 |
216 | **Example (Path modifier)**:
217 | `http://localhost:1337/uploads/normalize/buffalo.png`
218 |
219 | **Example (Query parameter)**:
220 | `http://localhost:1337/uploads/buffalo.png?normalize`
221 |
222 | **Sharp documentation**:
223 | [https://sharp.pixelplumbing.com/api-operation#normalize](https://sharp.pixelplumbing.com/api-operation#normalize)
224 |
225 | ## `threshold`
226 |
227 | Threshold the image. This can be used to convert an image to black and white.
228 |
229 | **Example (Path modifier)**:
230 | `http://localhost:1337/uploads/threshold_128/buffalo.png`
231 |
232 | **Example (Query parameter)**:
233 | `http://localhost:1337/uploads/buffalo.png?threshold=128`
234 |
235 | **Sharp documentation**:
236 | [https://sharp.pixelplumbing.com/api-operation#threshold](https://sharp.pixelplumbing.com/api-operation#threshold)
237 |
238 | ## `grayscale`
239 |
240 | Convert the image to grayscale. This can be used to remove color from an image.
241 |
242 | **Example (Path modifier)**:
243 | `http://localhost:1337/uploads/grayscale/buffalo.png`
244 |
245 | **Example (Query parameter)**:
246 | `http://localhost:1337/uploads/buffalo.png?grayscale`
247 |
248 | **Sharp documentation**:
249 | [https://sharp.pixelplumbing.com/api-operation#greyscale](https://sharp.pixelplumbing.com/api-operation#greyscale)
250 |
251 | ## `animated`
252 |
253 | **experimental**
--------------------------------------------------------------------------------