Skip to content

Commit 7c36ecf

Browse files
committed
Add Image.enhance/2 (luminance equalisation + saturation + sharpen)
1 parent 9a5f545 commit 7c36ecf

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ This is the changelog for Image version 0.67.0 released on ______, 2026. For ol
3030

3131
* Adds `Image.minimize_metadata/2` with a `:keep` option — caller-controlled list of EXIF fields to preserve when minimising metadata. The 1-arity variant continues to default to `[:copyright, :artist]`; the 2-arity variant lets callers pass `keep: [:copyright]` (preserve only copyright), `keep: []` (strip everything), or any other subset.
3232

33+
* Adds `Image.enhance/2` — content-aware automatic enhancement composed of luminance equalisation + mild saturation boost + mild sharpen. Approximates the CDN-style "improve" / "auto-enhance" calls used by Cloudinary, imgix, and ImageKit. Tunable via `:saturation` and `:sharpen_sigma` options.
34+
3335
### Bug Fixes
3436

3537
* `Image.add_alpha/2`'s `:opaque` and `:transparent` atoms now produce alpha = 255 and alpha = 0 respectively, matching the standard libvips / RGBA convention. The previous values were inverted relative to their names; integer values pass through unchanged.

lib/image.ex

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9111,6 +9111,97 @@ defmodule Image do
91119111
end
91129112
end
91139113

9114+
@doc """
9115+
Applies a content-aware automatic enhancement to an image.
9116+
9117+
This is a sensible-defaults stack of three operations
9118+
intended to mimic the behaviour of CDN-style "improve" /
9119+
"auto-enhance" calls (Cloudinary `e_improve`, imgix
9120+
`auto=enhance`, ImageKit `e-retouch`):
9121+
9122+
1. **Luminance equalisation** via `equalize/2` with `:luminance`
9123+
mode — stretches the tone range so the darkest pixel
9124+
becomes black and the brightest becomes white.
9125+
9126+
2. **Mild saturation boost** via `saturation/2 * 1.1` — adds
9127+
a small amount of vibrance to compensate for the
9128+
desaturating effect of luminance equalisation on
9129+
low-contrast inputs.
9130+
9131+
3. **Mild sharpening** via `sharpen/2` with a small sigma —
9132+
adds perceived clarity without ringing.
9133+
9134+
The hosted CDN equivalents are content-aware ML models that
9135+
this function does not attempt to replicate byte-for-byte;
9136+
results are visually similar but not identical.
9137+
9138+
### Arguments
9139+
9140+
* `image` is any `t:Vix.Vips.Image.t/0`.
9141+
9142+
* `options` is a keyword list of options.
9143+
9144+
### Options
9145+
9146+
* `:saturation` is the saturation multiplier applied after
9147+
luminance equalisation. Default `1.1`. Pass `1.0` to skip
9148+
the saturation step.
9149+
9150+
* `:sharpen_sigma` is the libvips Gaussian sigma used for
9151+
the final sharpen. Default `0.5`. Pass `0.0` to skip the
9152+
sharpen step.
9153+
9154+
### Returns
9155+
9156+
* `{:ok, enhanced_image}` or
9157+
9158+
* `{:error, reason}`.
9159+
9160+
### Example
9161+
9162+
iex> image = Image.open!("./test/support/images/Hong-Kong-2015-07-1998.jpg")
9163+
iex> {:ok, _enhanced} = Image.enhance(image)
9164+
9165+
"""
9166+
@doc since: "0.67.0"
9167+
@doc subject: "Operation"
9168+
9169+
@spec enhance(image :: Vimage.t(), options :: Keyword.t()) ::
9170+
{:ok, Vimage.t()} | {:error, error()}
9171+
def enhance(%Vimage{} = image, options \\ []) do
9172+
saturation_mult = Keyword.get(options, :saturation, 1.1)
9173+
sharpen_sigma = Keyword.get(options, :sharpen_sigma, 0.5)
9174+
9175+
with {:ok, equalised} <- equalize(image, :luminance),
9176+
{:ok, saturated} <- maybe_saturation(equalised, saturation_mult),
9177+
{:ok, _final} = success <- maybe_sharpen(saturated, sharpen_sigma) do
9178+
success
9179+
end
9180+
end
9181+
9182+
@doc """
9183+
Applies a content-aware automatic enhancement to an image,
9184+
or raises on error. See `enhance/2` for argument and option
9185+
documentation.
9186+
9187+
"""
9188+
@doc since: "0.67.0"
9189+
@doc subject: "Operation"
9190+
9191+
@spec enhance!(image :: Vimage.t(), options :: Keyword.t()) :: Vimage.t() | no_return()
9192+
def enhance!(%Vimage{} = image, options \\ []) do
9193+
case enhance(image, options) do
9194+
{:ok, image} -> image
9195+
{:error, reason} -> raise Image.Error, reason
9196+
end
9197+
end
9198+
9199+
defp maybe_saturation(image, mult) when mult == 1.0, do: {:ok, image}
9200+
defp maybe_saturation(image, mult), do: saturation(image, mult)
9201+
9202+
defp maybe_sharpen(image, sigma) when sigma in [0, +0.0], do: {:ok, image}
9203+
defp maybe_sharpen(image, sigma), do: sharpen(image, sigma: sigma)
9204+
91149205
@doc """
91159206
Applies a tone curve to an image.
91169207

0 commit comments

Comments
 (0)