# PhoenixImage
An on-demand image optimization library for Phoenix applications.
`phx_image` provides a Plug that fetches source images, resizes/transcodes
them, and serves them with caching headers. It also provides a Next.js-like
`<.image />` function component for Phoenix templates.
## Features
- **On-Demand Optimization:** Fetch, resize, and convert images on the fly via HTTP.
- **Aspect-Preserving Resizing:** Keep original aspect ratio while fitting requested dimensions.
- **Multiple Formats:** Supports outputting as `webp`, `avif`, `jpg`, and `png`.
- **High Performance:** Powered by `libvips` via the `image` library for extremely fast processing.
- **Built-in Caching Headers:** Automatically sets `Cache-Control` headers for long-term browser caching.
## Installation
The package can be installed by adding `phx_image` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:phx_image, "~> 0.1.0"}
]
end
```
`PhoenixImage.Component` depends on `:phoenix_live_view`. Add it explicitly if
you use the component:
```elixir
def deps do
[
{:phx_image, "~> 0.1.0"},
{:phoenix_live_view, "~> 1.0"}
]
end
```
## Usage
### Demo App
This repo includes a Phoenix demo app in `demo/` for manual testing.
```bash
cd demo
mix deps.get
mix phx.server
```
Open `http://localhost:4000` and use the form to test `/images/optimize`.
### As a Plug
You can mount the `PhoenixImage.Plug` in your Phoenix router or as part of a standalone Plug pipeline:
```elixir
# In your router.ex
scope "/images" do
pipe_through :browser
get "/optimize", PhoenixImage.Plug, []
end
```
### As a Component (`<.image />`)
Add to your Phoenix components module:
```elixir
defmodule MyAppWeb.CoreComponents do
use Phoenix.Component
import PhoenixImage.Component, only: [image: 1]
end
```
Use in HEEx:
```heex
<.image
src="https://example.com/photo.jpg"
alt="Scenic view"
width={1200}
height={800}
sizes="100vw"
/>
```
Required sizing rule:
- `fill={true}` OR both `width` and `height`.
Configure component defaults:
```elixir
config :phx_image, :image_component,
optimize_path: "/images/optimize",
device_sizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
image_sizes: [16, 32, 48, 64, 96, 128, 256, 384],
allowed_hosts: ["example.com", "cdn.example.com"]
```
### Plug Options
`PhoenixImage.Plug` supports:
- `:cache_control` (default: `public, max-age=31536000, immutable`)
- `:allowed_hosts` (default: `[]`): extra hosts allowed for absolute `src`
URLs in addition to the request host.
### Query Parameters
- `src` (required): source image location.
- Absolute `http://` or `https://` URL, or
- Root-relative path like `/images/logo.png` (resolved against request host).
- `w` (optional): positive integer width, max `8192`.
- `h` (optional): positive integer height, max `8192`.
- `q` (optional): quality from `1` to `100`.
- `f` (optional): output format `webp|avif|jpg|png`. Default: `webp`.
- `upscale` (optional): `true|false`. Default: `false`.
### Resize Behavior
- `w` only: scales to width, preserves aspect ratio.
- `h` only: scales to height, preserves aspect ratio.
- `w` + `h`: fits inside the box while preserving aspect ratio
(does not crop to exact dimensions).
- By default, requests that would enlarge the image are clamped to source size.
- With `upscale=true`, enlarge is allowed up to `2x` source size.
### Example
Requesting a 400px wide WebP version of an external image:
```text
GET /images/optimize?src=https://example.com/large-photo.jpg&w=400&f=webp
```
The response will include:
- The processed image binary.
- `Content-Type: image/webp`.
- `Cache-Control: public, max-age=31536000, immutable`.
- `x-phoenix-image-upscale: skipped` when a requested enlargement was clamped.
### Relative `src` Example
```text
GET /images/optimize?src=/images/logo.png&w=320
```
### Error Semantics
- `400 Bad Request`: missing/invalid parameters.
- `403 Forbidden`: `src` host is not same-host and not in `:allowed_hosts`.
- `404 Not Found`: upstream source returned 404.
- `500 Internal Server Error`: upstream/image-processing failures.
## Requirements
- **libvips:** This library requires `libvips` to be installed on your system.
- macOS: `brew install vips`
- Linux: `apt install libvips-dev` (or equivalent for your distribution)
## Compatibility
- Elixir `~> 1.15`
- Plug `~> 1.19`
- Optional component dependency: `phoenix_live_view ~> 1.0`
- System dependency: `libvips` with desired output codecs (`webp`, `avif`, etc.)
## Security Notes
- Validate `src` inputs and restrict with `:allowed_hosts` for remote fetches.
- Avoid exposing unrestricted optimizer endpoints on untrusted networks.
## License
Apache-2.0. See [LICENSE](LICENSE).
Documentation can be generated locally with:
```bash
mix docs
```
Docs are published automatically to GitLab Pages from the default branch.
## Releasing
Use the custom task to run preflight checks, tag, push, and publish:
```bash
mix release.publish --yes
```
Useful flags:
- `--no-publish`: run checks and create/push tag without publishing to Hex.
- `--no-tag`: publish without creating a tag.
- `--no-push`: avoid pushing `HEAD` and tag.
- `--skip-precommit`: skip `mix precommit`.
- `--remote origin`: choose push remote.