Skip to content

Unify the opacity vocabulary + fix alpha scaling for 16-bit and scRGB images - #231

Open
hlindset wants to merge 1 commit into
elixir-image:mainfrom
hlindset:fix/opacity-naming-and-alpha-scaling
Open

Unify the opacity vocabulary + fix alpha scaling for 16-bit and scRGB images#231
hlindset wants to merge 1 commit into
elixir-image:mainfrom
hlindset:fix/opacity-naming-and-alpha-scaling

Conversation

@hlindset

@hlindset hlindset commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

This work unifies the naming for setting and operating on opacities. Previously transparency, alpha and opacity were all used. The naming is now split in two: opacity is the value the caller sets, unscaled and independent of the image's interpretation, e.g. Image.add_alpha(image, opacity), background: {:red, opacity: 0.5}. alpha is kept for operations and values that touch the alpha band directly. On the way there, I also fixed three bugs, and tightened the error contract of Image.Pixel.

All functions that take an opacity now accept the t:Image.Pixel.opacity/0 type: 8-bit notation 0..255, float notation 0.0..1.0, :transparent, and :opaque.

Bugs fixed

  • Breaking: Image.add_alpha/2 now correctly scales the alpha band it adds to the image's interpretation. Previously wrote an 8-bit band into float and 16-bit alpha bands. Interpretations with 8-bit alpha bands are unaffected, including Lab and LCH.
  • Breaking: Image.Pixel.to_pixel/3 no longer quantizes a float :opacity to a byte before scaling it. Changes alpha values for 16-bit and scRGB images given a float opacity that is not representable in the 8-bit range.
  • Image.Pixel.to_pixel/3 now applies the :opacity option to a color given as a list of numbers.

Added

  • Image.Pixel.put_alpha/3, Image.Pixel.put_alpha!/3: Returns a pixel with its alpha component set to a given opacity, scaled to the image's alpha band. Returns an unchanged pixel when no alpha band is present.
  • Image.Pixel.alpha_for/2, Image.Pixel.alpha_for!/2: Scales an opacity to the alpha band of a given image, whose range depends on the interpretation.
  • Image.Pixel.opacity_fraction/1, Image.Pixel.opacity_fraction!/1: Takes t:Image.Pixel.opacity/0 and returns a fraction of full opacity.

Changed

  • The :opacity options of Image.Shape and the :background_fill_opacity / :background_stroke_opacity options of Image.Text.text/2 accept any t:Image.Pixel.opacity/0 rather than only a float.
  • Breaking: The :opacity option of Image.drop_shadow/2 accepts any t:Image.Pixel.opacity/0 rather than any number in 0.0..1.0. opacity: 1 previously meant fully opaque and is now 8-bit notation for 1/255, as it is everywhere else. Use 1.0 instead. Only 1 changes meaning, since 0 remains fully transparent.
  • Breaking: Image.Pixel returns an Image.Error on every error path. An unsupported interpretation was {:error, binary()} and is now reason: :unsupported_interpretation. Invalid colors were the Color library's own exceptions, and are now reason: :invalid_color with the color in value and the original text as the message. Reaches every function that resolves a color, including the :background options.
  • Breaking: An invalid opacity reports reason: :invalid_opacity instead of :invalid_transparency.
  • Image.add_alpha/2 now accepts the full t:Image.Pixel.opacity/0 type, whereas previously it only accepted 0..255, :transparent, and :opaque.
  • Image.add_alpha/2 returns {:error, %Image.Error{reason: :invalid_opacity}} for an invalid opacity, where it previously raised FunctionClauseError.

Removed

  • Breaking: Removes Image.Pixel.transparency/1, Image.Pixel.max_opacity/0 and Image.Pixel.min_opacity/0. Use Image.Pixel.alpha_for/2 for a value to write into an image's alpha band, or Image.Pixel.opacity_fraction/1 for the opacity itself.
  • Breaking: Removes the t:Image.transparency/0 and t:Image.Pixel.transparency/0 types. Replaced by t:Image.Pixel.opacity/0.
  • Breaking: Removes the :alpha option of Image.Pixel.to_pixel/3 and the {color, alpha: alpha} background form. Use :opacity and {color, opacity: opacity}, which are the same options under new names.
  • Breaking: Removes :none as an opacity, though not as a color. Use :transparent, which it was an exact synonym for.

Open questions

  • The only naming wrinkle left, where opacity means something else, is Image.opacity/2. One possibility is to rename it to e.g. Image.multiply_alpha/2?
  • It's possible to make a variant of Image.Pixel.alpha_for/2 that accepts an interpretation atom, so alpha resolution can be done for an interpretation without having an image at hand. Not needed by any of the internal call sites, so I didn't add that. Easy to add, if you think it'll be useful?

@hlindset
hlindset force-pushed the fix/opacity-naming-and-alpha-scaling branch from 9684609 to 67f0fe8 Compare August 11, 2026 10:32
@hlindset
hlindset force-pushed the fix/opacity-naming-and-alpha-scaling branch from 67f0fe8 to 120b2cc Compare August 11, 2026 10:34
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