Move clipping to the rasterize protocol, make frame buffer generation lazy and composite colours.
This commit is contained in:
parent
d9ee9562d7
commit
14e3c0e430
10 changed files with 109 additions and 77 deletions
43
lib/enumerable/vivid/frame.ex
Normal file
43
lib/enumerable/vivid/frame.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defimpl Enumerable, for: Vivid.Frame do
|
||||
alias Vivid.Frame
|
||||
|
||||
@moduledoc """
|
||||
Implements the Enumerable protocol for %Frame{}
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns the number of Shapes in a Frame.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Frame.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)])
|
||||
...> |> Enum.count
|
||||
2
|
||||
"""
|
||||
def count(%Frame{shapes: shapes}), do: {:ok, Enum.count(shapes)}
|
||||
|
||||
@doc """
|
||||
Returns whether the shape is a member of a Frame.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Vivid.Frame.init([Vivid.Point.init(1,1)])
|
||||
...> |> Enum.member?(Vivid.Point.init(1,1))
|
||||
true
|
||||
|
||||
iex> Vivid.Frame.init([Vivid.Point.init(1,1)])
|
||||
...> |> Enum.member?(Vivid.Point.init(2,2))
|
||||
false
|
||||
"""
|
||||
def member?(%Frame{shapes: shapes}, shape), do: {:ok, Enum.member?(shapes, shape)}
|
||||
|
||||
@doc """
|
||||
Reduce's the Path's shapes into an accumulator
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Vivid.Frame.init([Vivid.Point.init(1,2), Vivid.Point.init(2,4)]) |> Enum.reduce(%{}, fn (%Vivid.Point{x: x, y: y}, acc) -> Map.put(acc, x, y) end)
|
||||
%{1 => 2, 2 => 4}
|
||||
"""
|
||||
def reduce(%Frame{shapes: shapes}, acc, fun), do: Enumerable.MapSet.reduce(shapes, acc, fun)
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Vivid.Frame do
|
||||
alias Vivid.{Frame, Point, RGBA}
|
||||
defstruct ~w(width height background_colour buffer)a
|
||||
defstruct ~w(width height background_colour shapes)a
|
||||
|
||||
@moduledoc """
|
||||
A frame buffer or something.
|
||||
|
@ -19,7 +19,7 @@ defmodule Vivid.Frame do
|
|||
#Vivid.Frame<[width: 4, height: 4, background_colour: #Vivid.RGBA<{0, 0, 0, 0}>]>
|
||||
"""
|
||||
def init(width \\ 128, height \\ 64, colour \\ RGBA.init(0,0,0,0)) do
|
||||
%Frame{width: width, height: height, background_colour: colour, buffer: allocate_buffer(width * height, colour)}
|
||||
%Frame{width: width, height: height, background_colour: colour, shapes: []}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -93,18 +93,8 @@ defmodule Vivid.Frame do
|
|||
" @ \n" <>
|
||||
"@ \n"
|
||||
"""
|
||||
def push(%Frame{buffer: buffer, width: w}=frame, shape, colour) do
|
||||
points = Vivid.Rasterize.rasterize(shape)
|
||||
buffer = Enum.reduce(points, buffer, fn(point, buffer) ->
|
||||
if point_inside_bounds?(point, frame) do
|
||||
x = point |> Point.x
|
||||
y = point |> Point.y
|
||||
List.replace_at(buffer, (x * w) + y, colour)
|
||||
else
|
||||
buffer
|
||||
end
|
||||
end)
|
||||
%{frame | buffer: buffer}
|
||||
def push(%Frame{shapes: shapes}=frame, shape, colour) do
|
||||
%{frame | shapes: [{shape, colour} | shapes]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -137,6 +127,19 @@ defmodule Vivid.Frame do
|
|||
"""
|
||||
def background_colour(%Frame{background_colour: c}), do: c
|
||||
|
||||
def buffer(%Frame{shapes: shapes, width: w, height: h, background_colour: bg}) do
|
||||
Enum.reduce(shapes, allocate_buffer(w * h, bg), fn({shape, colour}, buffer)->
|
||||
points = Vivid.Rasterize.rasterize(shape, {0, 0, w-1, h-1})
|
||||
Enum.reduce(points, buffer, fn(point, buffer) ->
|
||||
x = point |> Point.x
|
||||
y = point |> Point.y
|
||||
pos = (x * w) + y
|
||||
existing = Enum.at(buffer, pos)
|
||||
List.replace_at(buffer, pos, RGBA.over(existing, colour))
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Convert a frame buffer to a string for debugging.
|
||||
|
||||
|
@ -148,14 +151,15 @@ defmodule Vivid.Frame do
|
|||
" \n" <>
|
||||
" \n"
|
||||
"""
|
||||
def to_string(%Frame{buffer: buffer, width: width}) do
|
||||
s = buffer
|
||||
def to_string(%Frame{width: width}=frame) do
|
||||
s = frame
|
||||
|> buffer
|
||||
|> Enum.reverse
|
||||
|> Enum.chunk(width)
|
||||
|> Enum.map(fn (row) ->
|
||||
row
|
||||
|> Enum.reverse
|
||||
|> Enum.map(fn(colour) -> RGBA.to_ascii(colour) end)
|
||||
|> Enum.map(&RGBA.to_ascii(&1))
|
||||
|> Enum.join
|
||||
end)
|
||||
|> Enum.join("\n")
|
||||
|
@ -172,13 +176,9 @@ defmodule Vivid.Frame do
|
|||
:ok
|
||||
end
|
||||
|
||||
|
||||
|
||||
defp allocate_buffer(size, colour) do
|
||||
Enum.map((1..size), fn(_) -> colour end)
|
||||
end
|
||||
|
||||
defp point_inside_bounds?(%Point{x: x}, _frame) when x < 0, do: false
|
||||
defp point_inside_bounds?(%Point{y: y}, _frame) when y < 0, do: false
|
||||
defp point_inside_bounds?(%Point{x: x}, %Frame{width: w}) when x >= w, do: false
|
||||
defp point_inside_bounds?(%Point{y: y}, %Frame{height: h}) when y >= h, do: false
|
||||
defp point_inside_bounds?(_point, _frame), do: true
|
||||
end
|
|
@ -1,3 +1,3 @@
|
|||
defprotocol Vivid.Rasterize do
|
||||
def rasterize(shape)
|
||||
def rasterize(shape, bounds)
|
||||
end
|
|
@ -12,15 +12,11 @@ defimpl Vivid.Rasterize, for: Vivid.Arc do
|
|||
## Example
|
||||
|
||||
iex> Vivid.Arc.init(Vivid.Point.init(5,5), 5, 270, 90, 3)
|
||||
...> |> Vivid.Rasterize.rasterize
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 0, y: 5}, %Vivid.Point{x: 1, y: 3},
|
||||
%Vivid.Point{x: 1, y: 4}, %Vivid.Point{x: 2, y: 2},
|
||||
%Vivid.Point{x: 3, y: 1}, %Vivid.Point{x: 4, y: 1},
|
||||
%Vivid.Point{x: 5, y: 0}
|
||||
])
|
||||
...> |> Vivid.Rasterize.rasterize({0, 0, 5, 5})
|
||||
#MapSet<[#Vivid.Point<{0, 5}>, #Vivid.Point<{1, 3}>, #Vivid.Point<{1, 4}>, #Vivid.Point<{2, 2}>, #Vivid.Point<{3, 1}>, #Vivid.Point<{4, 1}>, #Vivid.Point<{5, 0}>]>
|
||||
|
||||
"""
|
||||
def rasterize(%Arc{center: center, radius: radius, start_angle: start_angle, range: range, steps: steps}) do
|
||||
def rasterize(%Arc{center: center, radius: radius, start_angle: start_angle, range: range, steps: steps}, bounds) do
|
||||
h = center |> Point.x
|
||||
k = center |> Point.y
|
||||
|
||||
|
@ -37,7 +33,7 @@ defimpl Vivid.Rasterize, for: Vivid.Arc do
|
|||
Point.init(x, y)
|
||||
end)
|
||||
|> Path.init
|
||||
|> Rasterize.rasterize
|
||||
|> Rasterize.rasterize(bounds)
|
||||
end
|
||||
|
||||
defp degrees_to_radians(degrees), do: degrees / 360.0 * 2.0 * pi
|
||||
|
|
|
@ -17,7 +17,7 @@ defimpl Vivid.Rasterize, for: Vivid.Circle do
|
|||
## Example
|
||||
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4)
|
||||
...> |> Vivid.Rasterize.rasterize
|
||||
...> |> Vivid.Rasterize.rasterize({0, 0, 10, 10})
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 1, y: 4}, %Vivid.Point{x: 1, y: 5},
|
||||
%Vivid.Point{x: 1, y: 6}, %Vivid.Point{x: 2, y: 2},
|
||||
|
@ -34,7 +34,7 @@ defimpl Vivid.Rasterize, for: Vivid.Circle do
|
|||
])
|
||||
"""
|
||||
|
||||
def rasterize(%Circle{center: point, radius: radius}) do
|
||||
def rasterize(%Circle{center: point, radius: radius}, bounds) do
|
||||
x_center = point |> Point.x
|
||||
y_center = point |> Point.y
|
||||
r_squared = pow(radius, 2)
|
||||
|
@ -53,6 +53,6 @@ defimpl Vivid.Rasterize, for: Vivid.Circle do
|
|||
|
||||
points0 ++ points1
|
||||
|> Polygon.init
|
||||
|> Rasterize.rasterize
|
||||
|> Rasterize.rasterize(bounds)
|
||||
end
|
||||
end
|
|
@ -11,21 +11,12 @@ defimpl Vivid.Rasterize, for: Vivid.Group do
|
|||
## Example
|
||||
|
||||
iex> path = Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(1,3), Vivid.Point.init(3,3), Vivid.Point.init(3,1)])
|
||||
...> Vivid.Group.init([path])
|
||||
...> |> Vivid.Rasterize.rasterize
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 1, y: 1},
|
||||
%Vivid.Point{x: 1, y: 2},
|
||||
%Vivid.Point{x: 1, y: 3},
|
||||
%Vivid.Point{x: 2, y: 3},
|
||||
%Vivid.Point{x: 3, y: 1},
|
||||
%Vivid.Point{x: 3, y: 2},
|
||||
%Vivid.Point{x: 3, y: 3}
|
||||
])
|
||||
...> Vivid.Group.init([path]) |> Vivid.Rasterize.rasterize({0, 0, 3, 3})
|
||||
#MapSet<[#Vivid.Point<{1, 1}>, #Vivid.Point<{1, 2}>, #Vivid.Point<{1, 3}>, #Vivid.Point<{2, 3}>, #Vivid.Point<{3, 1}>, #Vivid.Point<{3, 2}>, #Vivid.Point<{3, 3}>]>
|
||||
"""
|
||||
def rasterize(%Group{shapes: shapes}) do
|
||||
def rasterize(%Group{shapes: shapes}, bounds) do
|
||||
Enum.reduce(shapes, MapSet.new, fn(shape, acc) ->
|
||||
MapSet.union(acc, Rasterize.rasterize(shape))
|
||||
MapSet.union(acc, Rasterize.rasterize(shape, bounds))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,14 +11,10 @@ defimpl Vivid.Rasterize, for: Vivid.Line do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(3,3)) |> Vivid.Rasterize.rasterize
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 1, y: 1},
|
||||
%Vivid.Point{x: 2, y: 2},
|
||||
%Vivid.Point{x: 3, y: 3}
|
||||
])
|
||||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(3,3)) |> Vivid.Rasterize.rasterize({0, 0, 3, 3})
|
||||
#MapSet<[#Vivid.Point<{1, 1}>, #Vivid.Point<{2, 2}>, #Vivid.Point<{3, 3}>]>
|
||||
|
||||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(4,2)) |> Vivid.Rasterize.rasterize
|
||||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(4,2)) |> Vivid.Rasterize.rasterize({0, 0, 4, 4})
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 1, y: 1},
|
||||
%Vivid.Point{x: 2, y: 1},
|
||||
|
@ -26,7 +22,7 @@ defimpl Vivid.Rasterize, for: Vivid.Line do
|
|||
%Vivid.Point{x: 4, y: 2}
|
||||
])
|
||||
|
||||
iex> Vivid.Line.init(Vivid.Point.init(4,4), Vivid.Point.init(4,1)) |> Vivid.Rasterize.rasterize
|
||||
iex> Vivid.Line.init(Vivid.Point.init(4,4), Vivid.Point.init(4,1)) |> Vivid.Rasterize.rasterize({0, 0, 4, 4})
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 4, y: 4},
|
||||
%Vivid.Point{x: 4, y: 3},
|
||||
|
@ -35,7 +31,7 @@ defimpl Vivid.Rasterize, for: Vivid.Line do
|
|||
])
|
||||
|
||||
"""
|
||||
def rasterize(%Line{}=line) do
|
||||
def rasterize(%Line{}=line, bounds) do
|
||||
origin = line |> Line.origin
|
||||
dx = line |> Line.x_distance
|
||||
dy = line |> Line.y_distance
|
||||
|
@ -53,18 +49,22 @@ defimpl Vivid.Rasterize, for: Vivid.Line do
|
|||
current_x = origin |> Point.x
|
||||
current_y = origin |> Point.y
|
||||
|
||||
reduce_points(points, steps, current_x, current_y, x_increment, y_increment)
|
||||
reduce_points(points, steps, current_x, current_y, x_increment, y_increment, bounds)
|
||||
end
|
||||
end
|
||||
|
||||
defp reduce_points(points, 0, _, _, _, _), do: points
|
||||
defp reduce_points(points, 0, _, _, _, _, _), do: points
|
||||
|
||||
defp reduce_points(points, steps, current_x, current_y, x_increment, y_increment) do
|
||||
defp reduce_points(points, steps, current_x, current_y, x_increment, y_increment, {x0, y0, x1, y1}=bounds) do
|
||||
next_x = current_x + x_increment
|
||||
next_y = current_y + y_increment
|
||||
steps = steps - 1
|
||||
points = MapSet.put(points, Point.init(round(next_x), round(next_y)))
|
||||
reduce_points(points, steps, next_x, next_y, x_increment, y_increment)
|
||||
points = if (next_x >= x0) && (next_x <= x1) && (next_y >= y0) && (next_y <= y1) do
|
||||
MapSet.put(points, Point.init(round(next_x), round(next_y)))
|
||||
else
|
||||
points
|
||||
end
|
||||
reduce_points(points, steps, next_x, next_y, x_increment, y_increment, bounds)
|
||||
end
|
||||
|
||||
defp choose_largest_of(a, b) when a > b, do: a
|
||||
|
|
|
@ -10,7 +10,7 @@ defimpl Vivid.Rasterize, for: Vivid.Path do
|
|||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(1,3), Vivid.Point.init(3,3), Vivid.Point.init(3,1)]) |> Vivid.Rasterize.rasterize
|
||||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(1,3), Vivid.Point.init(3,3), Vivid.Point.init(3,1)]) |> Vivid.Rasterize.rasterize({0, 0, 3, 3})
|
||||
MapSet.new([
|
||||
%Vivid.Point{x: 1, y: 1},
|
||||
%Vivid.Point{x: 1, y: 2},
|
||||
|
@ -21,11 +21,11 @@ defimpl Vivid.Rasterize, for: Vivid.Path do
|
|||
%Vivid.Point{x: 3, y: 3}
|
||||
])
|
||||
"""
|
||||
def rasterize(%Path{}=path) do
|
||||
def rasterize(%Path{}=path, bounds) do
|
||||
lines = path |> Path.to_lines
|
||||
|
||||
Enum.reduce(lines, MapSet.new, fn(line, acc) ->
|
||||
MapSet.union(acc, Rasterize.rasterize(line))
|
||||
MapSet.union(acc, Rasterize.rasterize(line, bounds))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,10 +10,12 @@ defimpl Vivid.Rasterize, for: Vivid.Point do
|
|||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Rasterize.rasterize(Vivid.Point.init(3,3)) |> Enum.to_list
|
||||
iex> Vivid.Rasterize.rasterize(Vivid.Point.init(3,3), {0, 0, 3, 3}) |> Enum.to_list
|
||||
[%Vivid.Point{x: 3, y: 3}]
|
||||
"""
|
||||
def rasterize(%Point{}=point) do
|
||||
def rasterize(%Point{x: x, y: y}=point, {x0, y0, x1, y1}) when x >= x0 and x <= x1 and y >= y0 and y <= y1 do
|
||||
MapSet.new([point])
|
||||
end
|
||||
|
||||
def rasterize(_point, _bounds), do: MapSet.new
|
||||
end
|
|
@ -23,11 +23,11 @@ defimpl Vivid.Rasterize, for: Vivid.Polygon do
|
|||
%Vivid.Point{x: 3, y: 3}
|
||||
])
|
||||
"""
|
||||
def rasterize(%Polygon{}=polygon) do
|
||||
def rasterize(%Polygon{}=polygon, bounds) do
|
||||
lines = polygon |> Polygon.to_lines
|
||||
|
||||
Enum.reduce(lines, MapSet.new, fn(line, acc) ->
|
||||
MapSet.union(acc, Rasterize.rasterize(line))
|
||||
MapSet.union(acc, Rasterize.rasterize(line, bounds))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue