Move clipping to the rasterize protocol, make frame buffer generation lazy and composite colours.

This commit is contained in:
James Harton 2016-12-29 14:27:22 +13:00
parent d9ee9562d7
commit 14e3c0e430
10 changed files with 109 additions and 77 deletions

View 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

View file

@ -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

View file

@ -1,3 +1,3 @@
defprotocol Vivid.Rasterize do
def rasterize(shape)
def rasterize(shape, bounds)
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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