Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

* Adds `Image.chroma_color!/1` as a companion to `Image.chroma_color/1`. ([#219](https://github.com/elixir-image/image/pull/219))

* Adds `Image.Pixel.strip_alpha/2` which returns a resolved pixel without its alpha component. It consolidates the truncation that `Image.flatten/2`, `Image.chroma_mask/2`, `Image.Options.Trim` and `Image.Options.Write` each did separately before. ([#222](https://github.com/elixir-image/image/pull/222))

### Changed

* **Breaking:** `Image.average/1` and `Image.chroma_color/1` now return `{:ok, [number()]} | {:error, Image.Error.t()}` instead of a bare list on success. The previous success type was documented as `Pixel.t()` but was always a list of numbers. ([#219](https://github.com/elixir-image/image/pull/219))
Expand Down
5 changes: 2 additions & 3 deletions lib/image.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2390,7 +2390,7 @@ defmodule Image do
{color_image, _alpha} = split_alpha(image)

with {:ok, color} <- maybe_calculate_color(image, color) do
color = Enum.take(color, bands(color_image))
color = Pixel.strip_alpha(color, image)

color_image
|> Math.subtract!(color)
Expand Down Expand Up @@ -6377,8 +6377,7 @@ defmodule Image do
with {:ok, resolved} <- Image.BackgroundColor.resolve(image, background) do
# Flatten strips alpha and replaces it with the background, so the
# background must be the opaque color part only.
bands = Vix.Vips.Image.bands(image) - 1
Operation.flatten(image, background: Enum.take(resolved, bands))
Operation.flatten(image, background: Pixel.strip_alpha(resolved, image))
end
end
end
Expand Down
10 changes: 1 addition & 9 deletions lib/image/options/trim.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ defmodule Image.Options.Trim do
{:ok, pixel} ->
# libvips find_trim compares against the color bands only, so
# any alpha band value is dropped from the resolved pixel.
{:cont, Keyword.put(options, :background, strip_alpha(pixel, image))}
{:cont, Keyword.put(options, :background, Pixel.strip_alpha(pixel, image))}

_other ->
{:halt, {:error, invalid_option(option)}}
Expand All @@ -59,14 +59,6 @@ defmodule Image.Options.Trim do
{:halt, {:error, invalid_option(option)}}
end

defp strip_alpha(pixel, image) do
if Image.has_alpha?(image) and length(pixel) == Image.bands(image) do
Enum.take(pixel, length(pixel) - 1)
else
pixel
end
end

defp invalid_option(option) do
%Image.Error{
reason: :invalid_option,
Expand Down
10 changes: 1 addition & 9 deletions lib/image/options/write.ex
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ defmodule Image.Options.Write do
defp validate_option({:background, background}, options, image, _image_type) do
case BackgroundColor.resolve(image, background) do
{:ok, pixel} ->
{:cont, Keyword.put(options, :background, strip_alpha(pixel, image))}
{:cont, Keyword.put(options, :background, Pixel.strip_alpha(pixel, image))}

# The resolve error is already an %Image.Error{} with a more
# specific message than invalid_option/1 would produce.
Expand Down Expand Up @@ -442,14 +442,6 @@ defmodule Image.Options.Write do
Enum.reduce(@suffix_values, options, &Keyword.delete(&2, &1))
end

defp strip_alpha(pixel, image) do
if Image.has_alpha?(image) do
Enum.take(pixel, length(pixel) - 1)
else
pixel
end
end

# Range 1..10
defp conform_effort(effort, ".png"), do: effort

Expand Down
47 changes: 47 additions & 0 deletions lib/image/pixel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,53 @@ defmodule Image.Pixel do
end
end

@doc """
Returns a resolved pixel without its alpha component, if it
has one.

### Arguments

* `pixel` is a list of numbers in `image`'s band layout, such as
the output of `to_pixel/3`.

* `image` is any `t:Vix.Vips.Image.t/0`.

### Returns

* `pixel` without its last element if `image` has an alpha band
and `pixel` spans all of `image`'s bands, or

* `pixel` unchanged. A pixel that does not span the image's
bands exactly has no identifiable alpha component, so it is
left alone.

### Notes

* The band count is the only check, so a pixel resolved against
another image of the same band count is truncated just the same.

### Examples

iex> {:ok, image} = Image.new(2, 2, color: [0, 0, 0, 255])
iex> {:ok, pixel} = Image.Pixel.to_pixel(image, :red)
iex> Image.Pixel.strip_alpha(pixel, image)
[255, 0, 0]

iex> {:ok, image} = Image.new(2, 2, color: :black)
iex> {:ok, pixel} = Image.Pixel.to_pixel(image, :red)
iex> Image.Pixel.strip_alpha(pixel, image)
[255, 0, 0]

"""
@spec strip_alpha(pixel :: [number()], image :: Vimage.t()) :: [number()]
def strip_alpha(pixel, %Vimage{} = image) when is_list(pixel) do
if Vimage.has_alpha?(image) and length(pixel) == Vimage.bands(image) do
Enum.take(pixel, length(pixel) - 1)
else
pixel
end
end

@doc """
Resolves a color input to an sRGB pixel `[r, g, b]` (or
`[r, g, b, a]`) with channels in `0..255`, regardless of any
Expand Down
20 changes: 20 additions & 0 deletions test/pixel_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,26 @@ defmodule Image.PixelTest do
end
end

describe "strip_alpha/2" do
test "drops the last band of a full pixel on an alpha image" do
{:ok, image} = Image.new(2, 2, color: [0, 0, 0, 255])

assert [255, 0, 0] == Pixel.strip_alpha([255, 0, 0, 128], image)
end

test "returns the pixel unchanged on an image without alpha" do
{:ok, image} = Image.new(2, 2, color: [0, 0, 0])

assert [255, 0, 0] == Pixel.strip_alpha([255, 0, 0], image)
end

test "returns a pixel shorter than the image's bands unchanged" do
{:ok, image} = Image.new(2, 2, color: [0, 0, 0, 255])

assert [255, 0, 0] == Pixel.strip_alpha([255, 0, 0], image)
end
end

describe "transparency/1" do
test "atoms" do
assert {:ok, 0} = Pixel.transparency(:none)
Expand Down
Loading