Skip to content

Unify background handling under a single :background option#213

Open
hlindset wants to merge 1 commit into
elixir-image:mainfrom
hlindset:feat/background-option-refactor
Open

Unify background handling under a single :background option#213
hlindset wants to merge 1 commit into
elixir-image:mainfrom
hlindset:feat/background-option-refactor

Conversation

@hlindset

@hlindset hlindset commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

First of all: Very open for input/suggestions/discussions on the choices made in this PR!

In an earlier iteration of the PR, I removed libvips' default fill (the zero-fill) and instead leaned on using background: :transparent + extend_mode: :background, but that caused some inconsistency with e.g. CMYK on transform ops vs flatten/write, so I reversed course and instead standardized on default/background: nil meaning libvips' default zero-fill, which resolved the inconsistency. The only wrinkle is that libvips' default is only accessible by not setting :background (or explicitly setting it to nil).


Generally

  • Collapse the :background_color/:background_transparency options into a single :background option, resolved via Image.BackgroundColor.resolve/2 everywhere (embed, flatten, join, rotate, affine, translate, shear, warp_perspective, write)
  • Transparency can be set/overridden by wrapping any color (including :average) in a tuple, like so: {color, alpha: a}, where a is 0..255 | 0.0..1.0 | :opaque | :transparent (the Image.Pixel.transparency type)
  • background: nil means "unset" everywhere (falls back to the op's default).

Defaults

  • An omitted (or nil) :background defers to libvips' native all-zeros fill everywhere (embed, join, rotate, affine, translate, shear, flatten, write): transparent on images with an alpha band, black otherwise (white in CMYK). Keeps flatten() |> write() and write() consistent.
  • Exception: warp_perspective defaults to :black (it flattens before warping)

extend_mode

embed

  • Reduced to :copy | :repeat | :mirror | :background
  • :black/:white removed: use an explicit :background instead. They're somewhat confusing anyway, e.g. :black actually being white in CMYK.
  • A content mode (:copy/:repeat/:mirror) consumes no color, so combining one with a non-nil :background is contradictory and returns an error

affine/shear/translate/warp_perspective

Extend here only affects the antialiased fringe at the content edge. The uncovered canvas is always filled with :background. extend_mode: :background is the option that makes sense most of the time.

  • Reduced to :background | :copy
    • extend_mode: :background (default) blends the fringe into the fill, which is right whenever canvas is exposed
    • :copy clamps to content, which can be right e.g. when the content fills the whole canvas, where using extend_mode: :background would leave a faint border along the edge
  • Removed :repeat/:mirror modes
    • :repeat bleeds the opposite edge into the fringe
    • :mirror is pixel-identical to :copy for the fringe
  • rotate doesn't accept :extend_mode. Both of its internal paths blend the fringe toward :background.

Breaking changes

  • :background_color / :background_transparency:background (embed, flatten, join)
  • embed on an alpha image now defaults to a transparent border (was opaque black)
  • embed's :black/:white extend modes removed
  • affine-family :extend_mode narrowed to :background | :copy
  • flatten with no :background now defers to libvips' native fill instead of :black: identical for sRGB, paper-white instead of ink-black for CMYK (aligning flatten() |> write() with write())
  • embed with content :extend_mode (:copy/:repeat/:mirror) plus a non-nil :background now errors

P.S. warp_perspective/straighten_perspective currently flatten. I can have a look at solving it similarly to affine with premultiply/unpremultiply to support transparency through the transform in a follow-up PR, resolving the one exception left after this PR

@hlindset hlindset force-pushed the feat/background-option-refactor branch 2 times, most recently from 1fcdda0 to 9068de1 Compare July 9, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant