Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alpha channel to sprites #315

Merged
merged 9 commits into from May 5, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/scenic/graph/bounds.ex
Expand Up @@ -226,7 +226,7 @@ defmodule Scenic.Graph.Bounds do
end

defp points(Primitive.Sprites, {_id, cmds}, _st) do
Enum.reduce(cmds, [], fn {_, _, {x, y}, {w, h}}, acc ->
Enum.reduce(cmds, [], fn {_, _, {x, y}, {w, h}, _alpha}, acc ->
[[{x, y}, {x + w, y}, {x + w, y + h}, {x, y + h}, {x, y}] | acc]
end)
end
Expand Down
61 changes: 43 additions & 18 deletions lib/scenic/primitive/sprites.ex
Expand Up @@ -25,11 +25,16 @@ defmodule Scenic.Primitive.Sprites do
are executed in order when the primitive renders.

`[ {{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}} ]`
or
`[ {{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}}, alpha ]`

Each draw command is an x/y position and width/height of a rectangle in
the source image, followed by the x/y position and width/height
rectangle in the destination space.

An optional alpha channel can be set in last position to apply a transparency
effect on the sprite.

In other words, This copies rectangular images from the source
indicated by image_id and draws them in the coordinate space of
the graph.
Expand Down Expand Up @@ -59,14 +64,16 @@ defmodule Scenic.Primitive.Sprites do
You should add/modify primitives via the helper functions in
[`Scenic.Primitives`](Scenic.Primitives.html#sprites/3)

This example draws the same source rectangle twice in different locations.
The first is at full size, the second is expanded 10x.
This example draws the same source rectangle three times in different locations.
The first is at full size, the second is expanded 10x, the third is with a
50% transparency effect.

```elixir
graph
|> sprites( { "images/my_sprites.png", [
{{0,0}, {10, 20}, {10, 10}, {10, 20}},
{{0,0}, {10, 20}, {100, 100}, {100, 200}},
{{0,0}, {10, 20}, {100, 100}, {100, 200}, 0.5}
]})
```
"""
Expand All @@ -81,7 +88,8 @@ defmodule Scenic.Primitive.Sprites do
{sx :: number, sy :: number},
{sw :: number, sh :: number},
{dx :: number, dy :: number},
{dw :: number, dh :: number}
{dw :: number, dh :: number},
alpha :: number
}
@type draw_cmds :: [draw_cmd()]

Expand Down Expand Up @@ -217,22 +225,39 @@ defmodule Scenic.Primitive.Sprites do
end
end

@default_alpha 1

defp validate_commands(commands) do
commands
|> Enum.reduce({:ok, commands}, fn
_, {:error, _} = error ->
error

{{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}}, acc
when is_number(src_x) and is_number(src_y) and
is_number(src_w) and is_number(src_h) and
is_number(dst_x) and is_number(dst_y) and
is_number(dst_w) and is_number(dst_h) ->
acc

cmd, _ ->
{:error, :command, cmd}
end)
validate =
Enum.reduce_while(commands, {:ok, []}, fn
{{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}}, {:ok, cmds}
when is_number(src_x) and is_number(src_y) and
is_number(src_w) and is_number(src_h) and
is_number(dst_x) and is_number(dst_y) and
is_number(dst_w) and is_number(dst_h) ->
{:cont,
{:ok,
[
{{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}, @default_alpha}
| cmds
]}}

cmd = {{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}, alpha}, {:ok, cmds}
when is_number(src_x) and is_number(src_y) and
is_number(src_w) and is_number(src_h) and
is_number(dst_x) and is_number(dst_y) and
is_number(dst_w) and is_number(dst_h) and
is_number(alpha) ->
{:cont, {:ok, [cmd | cmds]}}

cmd, _ ->
{:halt, {:error, :command, cmd}}
end)

case validate do
{:ok, cmds} -> {:ok, Enum.reverse(cmds)}
error -> error
end
end

# --------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion lib/scenic/primitive/style/paint/radial_gradient.ex
Expand Up @@ -16,7 +16,7 @@ defmodule Scenic.Primitive.Style.Paint.RadialGradient do

```elixir
Graph.build()
|> rect( {100, 50}, fill: {:linear, {50, 25, 10, 45, :blue, :yellow}} )
|> rect( {100, 50}, fill: {:radial, {50, 25, 10, 45, :blue, :yellow}} )
```
"""

Expand Down
2 changes: 1 addition & 1 deletion lib/scenic/primitives.ex
Expand Up @@ -1419,7 +1419,7 @@ defmodule Scenic.Primitives do

# --------------------------------------------------------
@doc """
Add a sprites list a graph.
Add a sprites list to a graph.
"""
@spec sprites(
source :: Graph.t() | Primitive.t(),
Expand Down
8 changes: 5 additions & 3 deletions lib/scenic/script.ex
Expand Up @@ -1451,7 +1451,7 @@ defmodule Scenic.Script do

{cmds, count} =
Enum.reduce(cmds, {[], 0}, fn
{{sx, sy}, {sw, sh}, {dx, dy}, {dw, dh}}, {cmds, count} ->
{{sx, sy}, {sw, sh}, {dx, dy}, {dw, dh}, alpha}, {cmds, count} ->
{
[
<<
Expand All @@ -1462,7 +1462,8 @@ defmodule Scenic.Script do
dx::float-32-big,
dy::float-32-big,
dw::float-32-big,
dh::float-32-big
dh::float-32-big,
alpha::float-32-big
>>
| cmds
],
Expand Down Expand Up @@ -2204,10 +2205,11 @@ defmodule Scenic.Script do
dy::float-32-big,
dw::float-32-big,
dh::float-32-big,
alpha::float-32-big,
bin::binary
>> = bin

{[{{sx, sy}, {sw, sh}, {dx, dy}, {dw, dh}} | cmds], bin}
{[{{sx, sy}, {sw, sh}, {dx, dy}, {dw, dh}, alpha} | cmds], bin}
end)

cmds = Enum.reverse(cmds)
Expand Down
6 changes: 3 additions & 3 deletions test/scenic/graph/bounds_test.exs
Expand Up @@ -155,7 +155,7 @@ defmodule Scenic.Graph.BoundsTest do
end

test "finds the natural bounds of a single sprite" do
graph = Graph.build() |> sprites({:parrot, [{{0, 0}, {10, 10}, {10, 20}, {30, 15}}]})
graph = Graph.build() |> sprites({:parrot, [{{0, 0}, {10, 10}, {10, 20}, {30, 15}, 1}]})
{10.0, 20.0, 40.0, 35.0} = Graph.bounds(graph)
end

Expand All @@ -165,8 +165,8 @@ defmodule Scenic.Graph.BoundsTest do
|> sprites(
{:parrot,
[
{{0, 0}, {10, 10}, {10, 20}, {30, 15}},
{{0, 0}, {10, 10}, {40, -3}, {30, 15}}
{{0, 0}, {10, 10}, {10, 20}, {30, 15}, 1},
{{0, 0}, {10, 10}, {40, -3}, {30, 15}, 1}
]}
)

Expand Down
Expand Up @@ -193,8 +193,8 @@ defmodule Scenic.Graph.CompilerTest do
# ---------------------------------------------------------
test "graph with sprites works" do
cmds = [
{{0, 1}, {10, 11}, {2, 3}, {12, 13}},
{{2, 3}, {10, 11}, {4, 5}, {12, 13}}
{{0, 1}, {10, 11}, {2, 3}, {12, 13}, 1},
{{2, 3}, {10, 11}, {4, 5}, {12, 13}, 1}
]

{:ok, list} =
Expand Down Expand Up @@ -250,7 +250,7 @@ defmodule Scenic.Graph.CompilerTest do
end

# ---------------------------------------------------------
# Should correctly compile fill and stroke. Note that
# Should correctly compile fill and stroke. Note that
# primitives with neither fill nor stroke are eliminated completely
test "fill and stroke are compiled correctly" do
{:ok, list} =
Expand Down
15 changes: 10 additions & 5 deletions test/scenic/primitive/sprites_test.exs
Expand Up @@ -12,7 +12,12 @@ defmodule Scenic.Primitive.SpritesTest do

@cmds [
{{0, 1}, {10, 11}, {2, 3}, {12, 13}},
{{2, 3}, {10, 11}, {4, 5}, {12, 13}}
{{2, 3}, {10, 11}, {4, 5}, {12, 13}, 0.8}
]

@enhanced_cmds [
{{0, 1}, {10, 11}, {2, 3}, {12, 13}, 1},
{{2, 3}, {10, 11}, {4, 5}, {12, 13}, 0.8}
]

# ============================================================================
Expand All @@ -21,16 +26,16 @@ defmodule Scenic.Primitive.SpritesTest do
test "build works" do
p = Sprites.build({:parrot, @cmds})
assert p.module == Sprites
assert Primitive.get(p) == {:parrot, @cmds}
assert Primitive.get(p) == {:parrot, @enhanced_cmds}
end

# ============================================================================

test "validate accepts valid data" do
assert Sprites.validate({:parrot, @cmds}) == {:ok, {:parrot, @cmds}}
assert Sprites.validate({:parrot, @cmds}) == {:ok, {:parrot, @enhanced_cmds}}

assert Sprites.validate({{:test_assets, "images/parrot.png"}, @cmds}) ==
{:ok, {{:test_assets, "images/parrot.png"}, @cmds}}
{:ok, {{:test_assets, "images/parrot.png"}, @enhanced_cmds}}
end

test "validate rejects bad data" do
Expand Down Expand Up @@ -65,7 +70,7 @@ defmodule Scenic.Primitive.SpritesTest do
p = Sprites.build({:parrot, @cmds})

assert Sprites.compile(p, %{stroke_fill: :blue}) == [
{:draw_sprites, {"VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns", @cmds}}
{:draw_sprites, {"VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns", @enhanced_cmds}}
]
end
end
2 changes: 1 addition & 1 deletion test/scenic/script_test.exs
Expand Up @@ -238,7 +238,7 @@ defmodule Scenic.ScriptTest do
end

test "draw_sprites works" do
cmds = [{{10, 11}, {30, 40}, {2, 3}, {60, 70}}]
cmds = [{{10, 11}, {30, 40}, {2, 3}, {60, 70}, 1}]
expected = [{:draw_sprites, {"VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns", cmds}}]
assert Script.draw_sprites([], :parrot, cmds) == expected
assert expected == Script.serialize(expected) |> Script.deserialize()
Expand Down