{"id":16551598,"url":"https://github.com/ambroisemaupate/intervention-request","last_synced_at":"2025-04-05T17:09:03.642Z","repository":{"id":32404928,"uuid":"35981666","full_name":"ambroisemaupate/intervention-request","owner":"ambroisemaupate","description":"A customizable Intervention Image wrapper to use simple image re-sampling features over urls and a configurable cache.","archived":false,"fork":false,"pushed_at":"2025-03-29T12:43:15.000Z","size":20168,"stargazers_count":38,"open_issues_count":3,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-29T16:11:06.313Z","etag":null,"topics":["gd","image-intervention","image-manipulation","image-processing","imagemagick","intervention-request"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ambroisemaupate.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-05-21T00:34:07.000Z","updated_at":"2025-03-29T12:43:15.000Z","dependencies_parsed_at":"2024-06-19T17:13:11.128Z","dependency_job_id":"bb571c09-5756-430a-8a87-3a9350b5d05f","html_url":"https://github.com/ambroisemaupate/intervention-request","commit_stats":{"total_commits":151,"total_committers":5,"mean_commits":30.2,"dds":0.09933774834437081,"last_synced_commit":"f12304f9df67d96c6ff281850e92a2ed899fe127"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ambroisemaupate%2Fintervention-request","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ambroisemaupate%2Fintervention-request/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ambroisemaupate%2Fintervention-request/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ambroisemaupate%2Fintervention-request/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ambroisemaupate","download_url":"https://codeload.github.com/ambroisemaupate/intervention-request/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369952,"owners_count":20927928,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gd","image-intervention","image-manipulation","image-processing","imagemagick","intervention-request"],"created_at":"2024-10-11T19:37:54.529Z","updated_at":"2025-04-05T17:09:03.603Z","avatar_url":"https://github.com/ambroisemaupate.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Intervention Request\n\n**A customizable *Intervention Image* wrapper to use image simple re-sampling features over urls and a configurable cache.**\n\n[![Static analysis and code style](https://github.com/ambroisemaupate/intervention-request/actions/workflows/run-test.yml/badge.svg)](https://github.com/ambroisemaupate/intervention-request/actions/workflows/run-test.yml)\n[![Packagist](https://img.shields.io/packagist/v/ambroisemaupate/intervention-request.svg)](https://packagist.org/packages/ambroisemaupate/intervention-request)\n[![Packagist](https://img.shields.io/packagist/dt/ambroisemaupate/intervention-request.svg)](https://packagist.org/packages/ambroisemaupate/intervention-request)\n\n- [Ready-to-go *Docker* image](#ready-to-go-docker-image)\n    * [Use local file-system or a distant one](#use-local-file-system-or-a-distant-one)\n    * [Docker Compose example](#docker-compose-example)\n- [Install](#install)\n- [Configuration](#configuration)\n- [Available operations](#available-operations)\n    * [Fit position](#fit-position)\n- [Using standalone entry point](#using-standalone-entry-point)\n- [Using as a library inside your projects](#using-as-a-library-inside-your-projects)\n- [Use URL rewriting](#use-url-rewriting)\n    * [Shortcuts](#shortcuts)\n- [Use pass-through cache](#use-pass-through-cache)\n- [Convert to webp](#convert-to-webp)\n- [Force garbage collection](#force-garbage-collection)\n    * [Using command-line](#using-command-line)\n- [Extend Intervention Request](#extend-intervention-request)\n    * [Add custom event subscribers](#add-custom-event-subscribers)\n        + [Available events](#available-events)\n        + [Listener examples](#listener-examples)\n- [Performances](#performances)\n- [Optimization](#optimization)\n    * [jpegoptim](#jpegoptim)\n    * [pngquant](#pngquant)\n    * [oxipng](#oxipng)\n    * [pingo](#pingo)\n    * [kraken.io](#krakenio)\n    * [tinyjpg.com](#tinyjpgcom)\n    * [jpegtran](#jpegtran)\n    * [Optimization benchmark](#optimization-benchmark)\n- [License](#license)\n- [Testing](#testing)\n\n## Ready-to-go *Docker* image\n\nIntervention Request is now available as a standalone Docker server to use with whatever CMS or language you need.\nIt declares two volumes: one for your images storage and one for cached files.\n\n- `/var/www/html/web/images`: you can set it as read-only to prevent any write operation\n- `/var/www/html/web/assets`: you must set it as read-write to allow cache files to be written\n\n```php\ndocker pull ambroisemaupate/intervention-request;\n# Make sure to share your volume with READ-ONLY flag\ndocker run -v \"/my/images/folder:/var/www/html/web/images:ro\" -p 8080:80/tcp ambroisemaupate/intervention-request;\n```\n\n- Create a `/my/images/folder/your-image.png`\n- Then try http://localhost:8080/assets/f300x300-s5/images/your-image.png.webp\n\nGarbage collector runs every hour as a `crontab` job and will purge cache files created more than `$IR_GC_TTL` seconds ago.\nYou still can execute it manually using `docker compose exec -u www-data intervention bin/intervention gc:launch` command.\n\n### Use local file-system or a distant one\n\n_InterventionRequest_ is built on [*Flysystem*](https://flysystem.thephpleague.com/docs/) library to abstract access to your native images.\nThen you can store all your images on an _AWS_ bucket or a _Scaleway_ Object Storage and process them on the fly. **Processed images are still\ncached on _InterventionRequest_ local storage.** This new file system abstraction layer allows multiple *InterventionRequest* docker\ncontainers to run in parallel but still use the same storage as image backend, or simply detach your media storage logic from\n_InterventionRequest_ application.\n\n`InterventionRequest` object requires a `FileResolverInterface` which can be a `LocalFileResolver` or `FlysystemFileResolver`, then\n`FlysystemFileResolver` must be provided a `League\\Flysystem\\Filesystem` object configured with any _Flysystem_ adapter.\n\nIf you prefer to use `ambroisemaupate/intervention-request` Docker image, environment variables are available for\n_AWS_ adapter only.\n\n### Docker Compose example\n\n```yaml\nversion: '3'\nservices:\n    intervention:\n        image: ambroisemaupate/intervention-request:latest\n        volumes:\n            - cache:/var/www/html/web/assets\n            ## If using local storage file system\n            - ./my/images/folder:/var/www/html/web/images:ro\n        # You can override some defaults below\n        environment:\n            IR_DEBUG: 0\n            IR_DEFAULT_QUALITY: 80\n            ## If using local storage file system\n            IR_IMAGES_PATH: /var/www/html/web/images\n            ## If using an AWS or Scaleway Object storage file system \n            IR_AWS_ACCESS_KEY_ID: 'changeme'\n            IR_AWS_ACCESS_KEY_SECRET: 'changeme'\n            IR_AWS_ENDPOINT: 'https://s3.fr-par.scw.cloud'\n            IR_AWS_REGION: 'fr-par'\n            IR_AWS_BUCKET: 'my-bucket'\n            IR_AWS_PATH_PREFIX: 'images'\n        ports:\n            - 8080:80/tcp\n        # Uncomment lines below for Traefik usage\n        #labels:\n        #    - \"traefik.enable=true\"\n        #    - \"traefik.http.services.intervention.loadbalancer.server.scheme=http\"\n        #    - \"traefik.http.services.intervention.loadbalancer.server.port=80\"\n        #    - \"traefik.http.services.intervention.loadbalancer.passhostheader=true\"\n        #    # Listen HTTP\n        #    - \"traefik.http.routers.intervention.entrypoints=http\"\n        #    - \"traefik.http.routers.intervention.rule=Host(`intervention.test`)\"\n        #    - \"traefik.http.routers.intervention.service=intervention\"\n        #networks:\n        #    - default\n        #    - frontproxynet\nvolumes:\n    cache:\n```\n\nYou don’t need to read further if you do not plan to embed this library in your PHP application.\n\n## Install\n\n```shell\ncomposer require ambroisemaupate/intervention-request\n```\n\nIntervention Request is based on *symfony/http-foundation* component for handling\nHTTP request, response and basic file operations. It wraps [*Intervention/image*](https://github.com/Intervention/image)\nfeature with a simple file cache managing.\n\n## Configuration\n\nIntervention request use a dedicated class to configure your image request\nparameters. Before creating `InterventionRequest` object, you must instantiate\na new `AM\\InterventionRequest\\Configuration` object and set cache and images paths.\n\n```php\n$conf-\u003esetCachePath(APP_ROOT.'/cache');\n$conf-\u003esetImagesPath(APP_ROOT.'/images');\n```\n\nThis code will create a configuration with *cache* and *images* folders in the\nsame folder as your PHP script (`APP_ROOT`). **Notice that in the default `index.php` file, *images* path is defined to `/test` folder in order to use the testing images**. You should always set this path against your website images folder to prevent processing other files.\n\nYou can edit each configuration parameters using their corresponding *setters*:\n\n- `setCaching(true|false)`: use or not request cache to store generated images on filesystem (default: `true`);\n- `setCachePath(string)`: image cache folder path;\n- `setUsePassThroughCache(true|false)`: use or not *pass-through* cache to by-pass PHP processing once image is generated;\n- `setDefaultQuality(int)`: default 90, set the quality amount when user does not specify it;\n- `setImagesPath(string)`: requested images root path;\n- `setTtl(integer)`: cache images time to live for internal garbage collector (default: `1 week`);\n- `setResponseTtl(integer)`: image HTTP responses time to live for browser and proxy caches (default: `1 year`);\n- `setDriver('gd'|'imagick')`: choose an available *Image Intervention* driver;\n- `setTimezone(string)`: PHP timezone to build \\DateTime object used for caching. Set it here if you have not set it in your `php.ini` file;\n- `setGcProbability(integer)`: Garbage collector probability divisor. Garbage collection launch probability is 1/$gcProbability where a probability of 1/1 will launch GC at every request.\n- `setUseFileChecksum(true|false)`: Use file checksum to test if file is different even if its name does not change. This option can be greedy on large files. (default: `false`).\n- `setJpegoptimPath(string)`: *Optional* — Tells where `jpegoptim` binary is for JPEG post-processing (not useful unless you need to stick to 100 quality).\n- `setPngquantPath(string)`: *Optional* — Tells where `pngquant` binary is for PNG post-processing. **This post-processing tool is highly recommended** as PNG won’t be optimized without it.\n- `setLossyPng(true|false)`: *Optional* — Tells `pngquant`/`pingo` binaries to use *palette* lossy compression (default: `false`).\n\n\n## Available operations\n\n|  Query attribute  |  Description  |  Usage  |\n| ----------------- | ------------- | ------------- |\n| image | Native image path relative to your configuration `imagePath` | `?image=path/to/image.jpg` |\n| fit | [Crop and resize combined](http://image.intervention.io/api/fit) It needs a `width` and a `height` in pixels, this filter can be combined with `align` to choose which part of your image to fit | `…\u0026fit=300x300` |\n| align | [Crop and resize combined](http://image.intervention.io/api/fit) Choose which part of your image to fit. | `…\u0026align=c` |\n| flip | [Mirror image horizontal or vertical](http://image.intervention.io/api/flip) You can set `h` for horizontal or `v` for vertical flip. | `…\u0026flip=h` |\n| crop | [Crop an image](http://image.intervention.io/api/crop) It needs a `width` and a `height` in pixels | `…\u0026crop=300x300` |\n| width | [Resize image proportionally to given width](http://image.intervention.io/api/widen) It needs a `width` in pixels | `…\u0026width=300` |\n| height | [Resize image proportionally to given height](http://image.intervention.io/api/heighten) It needs a `height` in pixels | `…\u0026height=300` |\n| crop + height/width | Do the same as *fit* using width or height as final size | `…\u0026crop=300x300\u0026width=200`: This will output a 200 x 200px image |\n| background | [Matte a png file with a background color](http://image.intervention.io/api/limitColors) | `…\u0026background=ff0000` |\n| greyscale/grayscale | [Turn an image into a greyscale version](http://image.intervention.io/api/greyscale) | `…\u0026greyscale=1` |\n| blur | [Blurs an image](http://image.intervention.io/api/blur) | `…\u0026blur=20` |\n| quality | Set the exporting quality (1 - 100), default to 90 | `…\u0026quality=95` |\n| progressive | [Toggle progressive mode](http://image.intervention.io/api/interlace) | `…\u0026progressive=1` |\n| interlace | [Toggle interlaced mode](http://image.intervention.io/api/interlace) | `…\u0026interlace=1` |\n| sharpen | [Sharpen image](http://image.intervention.io/api/sharpen) (1 - 100) | `…\u0026sharpen=10` |\n| contrast | [Change image contrast](http://image.intervention.io/api/contrast) (-100 to 100, 0 means no changes) | `…\u0026contrast=10` |\n| no_process | **Disable all image processing by PHP**, this does not load image in memory but executes any post-process optimizers (such as *pngquant*, *jpegoptim*…) | `…\u0026no_process=1` |\n\n### Fit position\n\nDue to URL rewriting, `align` filter can only takes one or two letters as a value. When no align filter is specified, `center` is used:\n\n| URL value | Alignment |\n| --- | --- |\n| `tl` | top-left |\n| `t` | top |\n| `tr` | top-right |\n| `l` | left |\n| `c` | center |\n| `r` | right |\n| `bl` | bottom-left |\n| `b` | bottom |\n| `br` | bottom-right |\n\n## Using standalone entry point\n\nAn `index.php` file enables you to use this tool as a standalone app. You can\nadjust your configuration to set your native images folder or enable/disable cache.\n\nSetup it on your webserver root, in a `intervention-request` folder\nand call this url (for example using MAMP/LAMP on your computer with included test images):\n`http://localhost:8888/intervention-request/?image=images/testPNG.png\u0026fit=100x100`\n\n## Using as a library inside your projects\n\n`InterventionRequest` class works seamlessly with *Symfony* `Request` and `Response`. It’s\nvery easy to integrate it in your *Symfony* controller scheme:\n\n```php\nuse AM\\InterventionRequest\\Configuration;\nuse AM\\InterventionRequest\\InterventionRequest;\nuse AM\\InterventionRequest\\LocalFileResolver;\nuse Monolog\\Logger;\n\n/*\n * A test configuration\n */\n$conf = new Configuration();\n$conf-\u003esetCachePath(APP_ROOT.'/cache');\n$conf-\u003esetImagesPath(APP_ROOT.'/files');\n// Comment this line if jpegoptim is not available on your server\n$conf-\u003esetJpegoptimPath('/usr/local/bin/jpegoptim');\n// Comment this line if pngquant is not available on your server\n$conf-\u003esetPngquantPath('/usr/local/bin/pngquant');\n\n$fileResolver = new LocalFileResolver($conf-\u003egetImagesPath());\n\n/*\n * InterventionRequest constructor asks 2 objects:\n *\n * - AM\\InterventionRequest\\Configuration\n * - AM\\InterventionRequest\\FileResolverInterface\n */\n$intRequest = new InterventionRequest($conf, $fileResolver, new Logger('InterventionRequest'));\n// Handle request and process image\n$intRequest-\u003ehandleRequest($request);\n\n// getResponse returns a Symfony\\Component\\HttpFoundation\\Response object\n// with image mime-type and data. All you need is to send it!\nreturn $intRequest-\u003egetResponse($request);\n```\n\n## Use URL rewriting\n\nIf you want to use clean URL. You can add `ShortUrlExpander` class to listen\nto shorten URL like: `http://localhost:8888/intervention-request/f100x100-g/images/testPNG.png`.\n\nFirst, add an `.htaccess` file (or its *Nginx* equivalent) to activate rewriting:\n\n```apache\n# .htaccess\n# Pretty URLs\n\u003cIfModule mod_rewrite.c\u003e\nRewriteEngine On\nRewriteCond %{REQUEST_FILENAME} -f [OR]\nRewriteCond %{REQUEST_FILENAME} -d\nRewriteRule ^.*$ - [S=40]\nRewriteRule . index.php [L]\n\u003c/IfModule\u003e\n```\n\nThen add these lines to your application before handling `InterventionRequest`.\n`ShortUrlExpander` will work on your existing `$request` object.\n\n```php\nuse AM\\InterventionRequest\\ShortUrlExpander;\n/*\n * Handle short url with Url rewriting\n */\n$expander = new ShortUrlExpander($request);\n// Enables using /cache in request path to mimic a pass-through file serve.\n//$expander-\u003esetIgnorePath('/cache');\n$params = $expander-\u003eparsePathInfo();\nif (null !== $params) {\n    // this will convert rewritten path to request with query params\n    $expander-\u003einjectParamsToRequest($params['queryString'], $params['filename']);\n}\n```\n\n### Shortcuts\n\nURL shortcuts can be combined using `-` (dash) character.\nFor example `f100x100-q50-g1-p0` stands for `fit=100x100\u0026quality=50\u0026greyscale=1\u0026progressive=0`.\n\n|  Query attribute  |  Shortcut letter  |\n| ----------------- | ------------- |\n| align | a |\n| fit | f |\n| flip | m |\n| crop | c |\n| width | w |\n| height | h |\n| background | b |\n| greyscale | g |\n| blur | l |\n| quality | q |\n| progressive | p |\n| interlace | i |\n| sharpen | s |\n| contrast *(only from 0 to 100)* | k |\n| no_process *(do not process and load image in memory, allows optimizers)* | n |\n\n\n## Use pass-through cache\n\nIntervention request can save your images in a public folder to let *Apache* or *Nginx* serve them once they’ve been generated. This can reduce *time-to-first-byte* as PHP is not called any more.\n\n- Make sure you have configured *Apache* or *Nginx* to serve real files **before** proxying your request to PHP. Otherwise this could lead to file overwriting!\n- Pass-through cache is only available if you are using `ShortUrlExpander` to mimic a real image path without any query-string.\n- Your cache folder **must** be public (in your document root), so your documents will be visible to anyone. If your images must be protected behind a PHP firewall, you should not activate *pass-through* cache.\n- **Garbage collector won’t be called** because cached image won’t be served by your PHP server anymore but *Apache* or *Nginx*\n- Pass-through cache will save image for the first time at the real path used in your request, make sure it won’t overwrite any application file.\n\nDefine your configuration cache path to a public folder:\n\n```php\n$conf = new Configuration();\n$conf-\u003esetCachePath(APP_ROOT . '/cache');\n$conf-\u003esetUsePassThroughCache(true);\n```\n\nThen enable the `ShortUrlExpander` and **ignore your cache path** to process only path info after it.\n```php\n$expander = new ShortUrlExpander($request);\n// Enables using /cache in request path to mimic a pass-through file serve.\n$expander-\u003esetIgnorePath('/cache');\n```\n\n## Convert to webp\n\n**Make sure your PHP is compiled with WebP image format.**\n\nIntervention Request can automatically generated webp images by appending `.webp` to an existing image file.\n\nUse `/image.jpg.webp` for `/image.jpg` file.\n\nIntervention Request will look for a image file without `.webp` extension and throw a 404 error if it does not exist.\n\n## Force garbage collection\n\n### Using command-line\n\n```shell\nbin/intervention gc:launch /path/to/my/cache/folder --log /path/to/my/log/file.log\n```\n\n## Extend Intervention Request\n\nIntervention Request uses *Processors* to alter original images. By default, each\navailable operation is handled by one `AbstractProcessor` inheriting class (look at\nthe `src/Processor` folder).\n\nYou can create your own *Processors* and override default ones by injecting an array\nto your `InterventionRequest` object.\n\n```php\n/*\n * Handle main image request with a\n * custom list of Processors.\n */\n$iRequest = new InterventionRequest(\n    $conf,\n    $fileResolver,\n    $log,\n    [\n        new Processor\\WidenProcessor(),\n        // add or replace with your own Processors\n    ],\n    $debug\n);\n```\n\nBe careful, *Processors* position in this array is very important, please look at\nthe default one in `InterventionRequest.php` class. Resizing processors should be\nthe first, and quality processors should be the last as image operations will be done\nfollowing your processors ordering.\n\n### Add custom event subscribers\n\nYou can create custom actions if you need to optimize/alter your images before they get served\nusing `ImageSavedEvent` and *Symfony* event system :\n\nCreate a class implementing `ImageEventSubscriberInterface` and, for example, listen to `ImageSavedEvent::NAME`\n\n```php\npublic static function getSubscribedEvents()\n{\n    return [\n        ImageSavedEvent::class =\u003e 'onImageSaved',\n    ];\n}\n```\n\nThis event will carry a `ImageSavedEvent` object with all you need to optimize/alter it. \nThen, use `$interventionRequest-\u003eaddSubscriber($yourSubscriber)` method to register it.\n\n#### Available events\n\n| Event name | Description |\n| ---------- | ----------- |\n| `RequestEvent::class` | Main request handling event which handles `quality` and image processing and caching. |\n| `ImageBeforeProcessEvent::class` | Before `Image` is being processed. |\n| `ImageAfterProcessEvent::class` | After `Image` has been processed. |\n| `ImageSavedEvent::class` | After `Image` has been saved to filesystem with a physical file-path. **This event is only dispatched if *caching* is enabled.** |\n| `ResponseEvent::class` | After Symfony’s response has been built with image data. (Useful to alter headers) |\n\n#### Listener examples\n\n- `WatermarkListener` will print text on your image\n- `KrakenListener` will optimize your image file using *kraken.io* external service\n- `TinifyListener` will optimize your image file using *tinyjpg.com* external service\n- `JpegTranListener` will optimize your image file using local `jpegtran` binary\n\nOf course, you can build your own listeners and share them with us!\n\n## Performances\n\nIf your *Intervention-request* throws errors like that one:\n\n```\nFatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 5184 bytes).\n```\n\nIt’s because you are trying to process a too large image. The solution is too increase your `memory_limit`\nPHP setting over `256M`. You can edit this file in your server `php.ini` file.\n\nYou can use `ini_set('memory_limit', '256M');` in your `index.php` file if your hosting plan\nallows you to dynamically change PHP configuration.\n\nIn general, we encourage to always downscale your native images before using them with\n*Intervention-request*. Raw jpeg images coming from your DSLR camera will give your\nPHP server a very hard time to process.\n\n## Optimization\n\n### jpegoptim\n\nIf you have `jpegoptim` installed on your server, you can add it to your configuration\n\n```php\n$conf-\u003esetJpegoptimPath('/usr/local/bin/jpegoptim');\n```\n\n### pngquant\n\nIf you have `pngquant` installed on your server, you can add it to your configuration\n\n```php\n$conf-\u003esetPngquantPath('/usr/local/bin/pngquant');\n$conf-\u003esetLossyPng(true); // use palette lossy png compression - default: false\n```\n\n### oxipng\n\nIf you have [`oxipng`](https://github.com/shssoichiro/oxipng) installed on your server, you can add it to your configuration\n\n```php\n$conf-\u003esetOxipngPath('/usr/local/bin/oxipng');\n```\n\n### pingo\n\nIf you have [`pingo`](https://css-ig.net/pingo) installed on your server and `Wine`, you can add it to your configuration\n\n```php\n$conf-\u003esetPingoPath('/usr/local/bin/pingo.exe');\n$conf-\u003esetLossyPng(true); // use palette lossy png compression - default: false\n$conf-\u003esetNoAlphaPingo(true); // Remove png transparency to compress more - default: false\n```\n\n### kraken.io\n\nIf you have subscribed to a paid [kraken.io](https://kraken.io) plan, you can add the dedicated \n`KrakenListener` to send your resized images over the external service.\n\n```php\n$iRequest-\u003eaddSubscriber(new \\AM\\InterventionRequest\\Listener\\KrakenListener(\n    'your-api-key', \n    'your-api-secret', \n    true,\n    $log\n));\n```\n\nPay attention, that images will be sent over *kraken.io* API, it will take some additional time. \n\n### tinyjpg.com\n\nIf you have subscribed to a paid [tinyjpg.com](https://tinyjpg.com) plan, you can add the dedicated \n`TinifyListener` to send your resized images over the external service.\n\n```php\n$iRequest-\u003eaddSubscriber(new \\AM\\InterventionRequest\\Listener\\TinifyListener(\n    'your-api-key',\n    $log\n));\n```\n\nPay attention, that images will be sent over *kraken.io* API, it will take some additional time. \n\n### jpegtran\n\nIf you want to use your system `jpegtran` or the *Mozjpeg* one, you can use the `JpegTranListener`.\n\n```php\n$iRequest-\u003eaddSubscriber(new \\AM\\InterventionRequest\\Listener\\JpegTranListener(\n    '/usr/local/opt/mozjpeg/bin/jpegtran',\n    $log\n));\n```\n\n### Optimization benchmark\n\nWith default quality to 90%. \\\nAVIF conversion only supports custom compiled *ImageMagick* and only support lossless encoding.\n\n| Url       | PHP raw | *tinyjpg.com*  | *Kraken.io* + lossy | jpegoptim | mozjpeg (jpegtran) | WebP (90%) | WebP (85%) | AVIF (100%) |\n| ----------------------------------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |\n| /test/images/testUHD.jpg?width=2300 | 405 kB | 168 kB | 187 kB | 395 kB | 390 kB | 235 kB | 155 kB |  94 kB |\n| /test/images/testUHD.jpg?width=1920 | 294 kB | 132 kB | 134 kB | 285 kB | 282 kB | 176 kB | 115 kB |  71 kB |\n| /test/images/rhino.jpg?width=1920   | 642 kB | 278 kB | 534 kB | 598 kB | 596 kB | 564 kB | 429 kB | 398 kB |\n| /test/images/rhino.jpg?width=1280   | 325 kB | 203 kB | 278 kB | 303 kB | 301 kB | 295 kB | 229 kB | 227 kB |\n\n\n| Url                                 | PHP raw   | pngquant  | oxipng    | *Kraken.io* + lossy    | WebP (100%) | WebP (85%) | AVIF (100%) |\n| ----------------------------------- | --------- | --------- | --------- | --------- | ----------- | --------- | --------- |\n| /test/images/testPNG.png            | 292 kB | 167 kB | 288 kB | 142 kB | 186 kB   | 28 kB | 11.7 kB |\n\n## License\n\n*Intervention Request* is handcrafted by *Ambroise Maupate* under **MIT license**.\n\nHave fun!\n\n## Testing \n\nThis project uses Docker for development environment.\nCopy `compose.override.yml` to `compose.override.yml`, use `php-dev` target and declare a volume on your project root folder.\n\n```yaml\nservices:\n    intervention:\n        build:\n            context: .\n            target: php-dev\n        volumes:\n            - ./:/var/www/html\n        ports:\n            -   \"8080:80/tcp\"\n\n```\n\n```bash\ndocker compose build\ndocker compose up\n```\n\nThen open `http://0.0.0.0:8080/assets/w300/rhino.jpg` in your browser. \nYou should be able to test *intervention-request* with Passthrough cache and *ShortUrl* enabled.\nSet `IR_USE_PASSTHROUGH_CACHE=0` if you don't want cache to be served by *Nginx*.\n\n### Disable cache during development    \n\nSet these variables to avoid caching images during development:\n\n```dotenv\nIR_DEBUG: 1\nIR_USE_PASSTHROUGH_CACHE: 0\nIR_GC_PROBABILITY: 1\nIR_GC_TTL: 0\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fambroisemaupate%2Fintervention-request","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fambroisemaupate%2Fintervention-request","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fambroisemaupate%2Fintervention-request/lists"}