From 6e99a79b0377786953db42375499ec59c3621922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Wed, 3 May 2023 16:41:29 +0200 Subject: [PATCH 1/9] add alpha channel to sprites commands --- lib/scenic/graph/bounds.ex | 2 +- lib/scenic/primitive/sprites.ex | 8 +++++--- lib/scenic/script.ex | 8 +++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/scenic/graph/bounds.ex b/lib/scenic/graph/bounds.ex index f531f583..d275dc19 100644 --- a/lib/scenic/graph/bounds.ex +++ b/lib/scenic/graph/bounds.ex @@ -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 diff --git a/lib/scenic/primitive/sprites.ex b/lib/scenic/primitive/sprites.ex index fc385619..0aaf7e45 100644 --- a/lib/scenic/primitive/sprites.ex +++ b/lib/scenic/primitive/sprites.ex @@ -81,7 +81,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()] @@ -223,11 +224,12 @@ defmodule Scenic.Primitive.Sprites do _, {:error, _} = error -> error - {{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}}, acc + {{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}, alpha}, 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) -> + is_number(dst_w) and is_number(dst_h) and + is_number(alpha) -> acc cmd, _ -> diff --git a/lib/scenic/script.ex b/lib/scenic/script.ex index 984c6a5f..b2af51af 100644 --- a/lib/scenic/script.ex +++ b/lib/scenic/script.ex @@ -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} -> { [ << @@ -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 ], @@ -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) From 7c33eac208fe673e0a873611f831ec2cc108c1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Wed, 3 May 2023 16:42:03 +0200 Subject: [PATCH 2/9] adjust tests --- test/scenic/graph/bounds_test.exs | 6 +++--- test/scenic/graph/compile_test.exs | 6 +++--- test/scenic/primitive/sprites_test.exs | 4 ++-- test/scenic/script_test.exs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/scenic/graph/bounds_test.exs b/test/scenic/graph/bounds_test.exs index 96deebfc..3051e9ac 100644 --- a/test/scenic/graph/bounds_test.exs +++ b/test/scenic/graph/bounds_test.exs @@ -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 @@ -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} ]} ) diff --git a/test/scenic/graph/compile_test.exs b/test/scenic/graph/compile_test.exs index c65f7ed7..93045f65 100644 --- a/test/scenic/graph/compile_test.exs +++ b/test/scenic/graph/compile_test.exs @@ -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} = @@ -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} = diff --git a/test/scenic/primitive/sprites_test.exs b/test/scenic/primitive/sprites_test.exs index e24e1424..6e0f73b8 100644 --- a/test/scenic/primitive/sprites_test.exs +++ b/test/scenic/primitive/sprites_test.exs @@ -11,8 +11,8 @@ defmodule Scenic.Primitive.SpritesTest do alias Scenic.Primitive.Sprites @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} ] # ============================================================================ diff --git a/test/scenic/script_test.exs b/test/scenic/script_test.exs index 2de9ec4b..6e6b6577 100644 --- a/test/scenic/script_test.exs +++ b/test/scenic/script_test.exs @@ -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() From ced9d389c12d1982cee9dbb952163a28f9a3c2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Wed, 3 May 2023 16:45:32 +0200 Subject: [PATCH 3/9] rename file to reflect module name --- test/scenic/graph/{compile_test.exs => compiler_test.exs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/scenic/graph/{compile_test.exs => compiler_test.exs} (100%) diff --git a/test/scenic/graph/compile_test.exs b/test/scenic/graph/compiler_test.exs similarity index 100% rename from test/scenic/graph/compile_test.exs rename to test/scenic/graph/compiler_test.exs From 2afef45009f0a64e24341d09ad823f47fe71d30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Thu, 4 May 2023 13:10:10 +0200 Subject: [PATCH 4/9] sprite commands: make alpha channel optional to avoid breaking existing API --- lib/scenic/primitive/sprites.ex | 50 ++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/scenic/primitive/sprites.ex b/lib/scenic/primitive/sprites.ex index 0aaf7e45..ebc0bb5f 100644 --- a/lib/scenic/primitive/sprites.ex +++ b/lib/scenic/primitive/sprites.ex @@ -218,23 +218,41 @@ 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}, alpha}, 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) and - is_number(alpha) -> - acc - - cmd, _ -> - {:error, :command, cmd} - end) + validate = + Enum.reduce(commands, {:ok, []}, fn + _, {:error, _} = error -> + error + + {{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) -> + {: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) -> + {:ok, [cmd | cmds]} + + cmd, _ -> + {:error, :command, cmd} + end) + + case validate do + {:ok, cmds} -> {:ok, Enum.reverse(cmds)} + error -> error + end end # -------------------------------------------------------- From 11cba587665a6e1b7ef192beb7be7ad6517ef81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Thu, 4 May 2023 13:13:20 +0200 Subject: [PATCH 5/9] use reduce_while to avoid useless recursion after an error occurs --- lib/scenic/primitive/sprites.ex | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/scenic/primitive/sprites.ex b/lib/scenic/primitive/sprites.ex index ebc0bb5f..d308a9d3 100644 --- a/lib/scenic/primitive/sprites.ex +++ b/lib/scenic/primitive/sprites.ex @@ -222,20 +222,18 @@ defmodule Scenic.Primitive.Sprites do defp validate_commands(commands) do validate = - Enum.reduce(commands, {:ok, []}, fn - _, {:error, _} = error -> - error - + 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) -> - {:ok, - [ - {{src_x, src_y}, {src_w, src_h}, {dst_x, dst_y}, {dst_w, dst_h}, @default_alpha} - | cmds - ]} + {: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 @@ -243,10 +241,10 @@ defmodule Scenic.Primitive.Sprites do is_number(dst_x) and is_number(dst_y) and is_number(dst_w) and is_number(dst_h) and is_number(alpha) -> - {:ok, [cmd | cmds]} + {:cont, {:ok, [cmd | cmds]}} cmd, _ -> - {:error, :command, cmd} + {:halt, {:error, :command, cmd}} end) case validate do From 641e58b7393f34538d1a4dc13067ed53c35aca24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Thu, 4 May 2023 13:21:12 +0200 Subject: [PATCH 6/9] adjust doc --- lib/scenic/primitive/sprites.ex | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/scenic/primitive/sprites.ex b/lib/scenic/primitive/sprites.ex index d308a9d3..608e8661 100644 --- a/lib/scenic/primitive/sprites.ex +++ b/lib/scenic/primitive/sprites.ex @@ -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. @@ -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} ]}) ``` """ From 601c2453d30dc3fc45e653a695a2a376bc529e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Thu, 4 May 2023 13:29:49 +0200 Subject: [PATCH 7/9] adjust tests to use both types of sprites commands --- test/scenic/primitive/sprites_test.exs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/scenic/primitive/sprites_test.exs b/test/scenic/primitive/sprites_test.exs index 6e0f73b8..b9fb887b 100644 --- a/test/scenic/primitive/sprites_test.exs +++ b/test/scenic/primitive/sprites_test.exs @@ -11,8 +11,13 @@ defmodule Scenic.Primitive.SpritesTest do alias Scenic.Primitive.Sprites @cmds [ + {{0, 1}, {10, 11}, {2, 3}, {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}, 1} + {{2, 3}, {10, 11}, {4, 5}, {12, 13}, 0.8} ] # ============================================================================ @@ -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 @@ -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 From 39351f483919057dc7db2abc66c5a0e6ce86891f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Wed, 10 May 2023 13:00:43 +0200 Subject: [PATCH 8/9] fix doc --- lib/scenic/primitives.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scenic/primitives.ex b/lib/scenic/primitives.ex index 40081143..02779d85 100644 --- a/lib/scenic/primitives.ex +++ b/lib/scenic/primitives.ex @@ -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(), From ded8f2919829ba7e78b6a446548028256e384395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Saint-Sevin?= Date: Wed, 10 May 2023 17:21:36 +0200 Subject: [PATCH 9/9] fix doc --- lib/scenic/primitive/style/paint/radial_gradient.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scenic/primitive/style/paint/radial_gradient.ex b/lib/scenic/primitive/style/paint/radial_gradient.ex index ce627528..a845cf90 100644 --- a/lib/scenic/primitive/style/paint/radial_gradient.ex +++ b/lib/scenic/primitive/style/paint/radial_gradient.ex @@ -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}} ) ``` """