diff --git a/docs/make.jl b/docs/make.jl index 74e24a06..35ab4184 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -28,7 +28,7 @@ makedocs( "Work with polygons" => "howto/polygons.md", "Add text" => "howto/text.md", "Clip graphics" => "howto/clipping.md", - "Placing images" => "howto/images.md", + "Placing images" => "howto/images.md", "Turtle graphics" => "howto/turtle.md", "Make animations" => "howto/animation.md", "Live graphics and snapshots" => "howto/livegraphics.md", @@ -36,6 +36,7 @@ makedocs( "Explanations" => [ "Basic concepts" => "explanation/basics.md", "Image matrix" => "explanation/imagematrix.md", + "Perfect pixels and antialising" => "explanation/perfectpixels.md", "Transforms and matrices" => "explanation/transforms.md", "Contributing" => "explanation/contributing.md", ], diff --git a/docs/src/example/examples.md b/docs/src/example/examples.md index 14d2da6b..ac8fa116 100644 --- a/docs/src/example/examples.md +++ b/docs/src/example/examples.md @@ -189,3 +189,70 @@ using Luxor # hide arrow(O - (220, 0), O + (220, 0)) end 800 200 ``` + +Another way you could approach tasks like this is to use the [`arrow`](@ref) functions, which let you decorate lines. Here's how a curved number line could be made: + +```@example +using Luxor # hide +@drawsvg begin + background("antiquewhite") + _counter() = (a = -1; () -> a += 1) + counter = _counter() # closure + fontsize(15) + arrow(O + (0, 100), 200, π, 2π, + arrowheadlength=0, + decoration=range(0, 1, length=61), + decorate = () -> begin + d = counter() + if d % 5 == 0 + text(string(d), O + (0, -20), halign=:center) + setline(3) + end + line(O - (0, 5), O + (0, 5), :stroke) + end + ) +end 800 300 +``` + +## Draw a matrix + +To draw a matrix, you can use a Table to generate the +positions. + +It's sometimes useful to be able to highight particular +cells. Here, numbers that have already been used once +are drawn in orange. + +```@example +using Luxor + +function drawmatrix(A::Matrix; + cellsize = (10, 10)) + table = Table(size(A)..., cellsize...) + used = Set() + for i in CartesianIndices(A) + r, c = Tuple(i) + if A[r, c] ∈ used + sethue("orange") + else + sethue("purple") + push!(used, A[r, c]) + end + text(string(A[r, c]), table[r, c], + halign=:center, + valign=:middle) + sethue("white") + box(table, r, c, :stroke) + end +end + +A = rand(1:99, 5, 8) + +@drawsvg begin + background("black") + fontsize(30) + setline(0.5) + sethue("white") + drawmatrix(A, cellsize = 10 .* size(A)) +end +``` diff --git a/docs/src/explanation/perfectpixels.md b/docs/src/explanation/perfectpixels.md new file mode 100644 index 00000000..af6d7f1e --- /dev/null +++ b/docs/src/explanation/perfectpixels.md @@ -0,0 +1,156 @@ +# Perfect pixels and anti-aliasing + +## Antialiasing + +The process of converting smooth graphic shapes to a grid of pixels is automatically performed by Luxor/Cairo when you save the drawing as a PNG file. If you make an SVG or PDF drawing, this process is carried out by the application you use to view or display the file. It's usually better to defer the conversion as long as possible: eventually - unless you're using a pen plotter or laser cutter - your smooth outlines will have to be converted ("rasterized") to a grid of colored pixels. + +The smoothing process includes "anti-aliasing". You can - to some extent - adjust the amount of anti-aliasing used when you make drawings in Luxor. + +```@setup draw_matrix +using Luxor, Colors +function make_matrix(; + antialias=0) + d = Drawing(20, 20, :image) + setantialias(antialias) + origin() + setcolor("red") + circle(Point(0, 0), 5, :fill) + mat = image_as_matrix() + finish() + return mat +end + +function drawmatrix(A; + cellsize = (10, 10)) + table = Table(size(A)..., cellsize...) + for i in CartesianIndices(A) + r, c = Tuple(i) + sethue(A[r, c]) + box(table, r, c, :fill) + sethue("black") + box(table, r, c, :stroke) + end +end + +function draw(antialias) + mat = make_matrix(antialias=antialias) + @drawsvg begin + background("black") + drawmatrix(mat, cellsize = 1 .* size(mat)) + c = length(unique(color.(mat))) + sethue("white") + fontsize(8) + text("number of colors used: $c", boxbottomcenter(BoundingBox() * 0.9), halign=:center) + end 300 300 +end +``` + +The [`setantialias`](@ref) function lets you set the antialiasing amount to a constant between 0 and 6. The Cairo documentation describes the different values as follows: + +| Value | Name | Description | +|:----- |:---- |:---- | +|0 |`CAIRO_ANTIALIAS_DEFAULT` |Use the default antialiasing for the subsystem and target device| +|1 |`CAIRO_ANTIALIAS_NONE` |Use a bilevel alpha mask| +|2 |`CAIRO_ANTIALIAS_GRAY` |Perform single-color antialiasing (using shades of gray for black text on a white background, for example)| +|3 |`CAIRO_ANTIALIAS_SUBPIXEL` |Perform antialiasing by taking advantage of the order of subpixel elements on devices such as LCD panels| +|4 |`CAIRO_ANTIALIAS_FAST` |Hint that the backend should perform some antialiasing but prefer speed over quality| +|5 |`CAIRO_ANTIALIAS_GOOD` |The backend should balance quality against performance| +|6 |`CAIRO_ANTIALIAS_BEST` |Hint that the backend should render at the highest quality, sacrificing speed if necessary| + +To show the antialiasing in action, the following code draws a red circle: + +``` +Drawing(20, 20, :image) +setantialias(0) +origin() +setcolor("red") +circle(Point(0, 0), 5, :fill) +mat = image_as_matrix() +finish() +``` + +Enlarging the matrix shows the default value of 0: + +```@example draw_matrix +draw(0) # hide +``` + +Luxor used 18 colors to render this red circle. + +Here's the bilevel mask (value 1 or "none"): + +```@example draw_matrix +draw(1) # hide +``` + +and Luxor used two colors to draw the circle. + +The other values are the same as the default (0), apart from 4 ("speed over quality"): + +```@example draw_matrix +draw(4) # hide +``` + +which uses 12 rather than 16 colors. + +The anti-aliasing process can vary according to the OS and device you're using. The [Cairo documentation](https://www.cairographics.org/manual/cairo-cairo-t.html) stresses this more than once: + +> The value is a hint, and a particular backend may or may not support a particular value. [...] The values make no guarantee on how the backend will perform its rasterisation (if it even rasterises!) [...] The interpretation of CAIRO_ANTIALIAS_DEFAULT is left entirely up to the backend [...] + +## Text + +The antialiasing described above does not apply to text. + +Text rendering is much more platform-dependent than graphics; Windows, MacOS, and Linux all have their own methods for rendering and rasterizing fonts, and currently Cairo.jl doesn't currently provide an interface to any font rendering APIs. + +```@setup draw_text +using Luxor, Colors +function make_matrix(; + antialias=0) + d = Drawing(20, 20, :image) + setantialias(antialias) + origin() + setcolor("red") + fontsize(20) + text("a", halign=:center, valign=:middle) + mat = image_as_matrix() + finish() + return mat +end + +function drawmatrix(A; + cellsize = (10, 10)) + table = Table(size(A)..., cellsize...) + for i in CartesianIndices(A) + r, c = Tuple(i) + sethue(A[r, c]) + box(table, r, c, :fill) + sethue("black") + box(table, r, c, :stroke) + end +end + +function draw(antialias) + mat = make_matrix(antialias=antialias) + @drawsvg begin + background("black") + drawmatrix(mat, cellsize = 1 .* size(mat)) + c = length(unique(color.(mat))) + sethue("white") + fontsize(8) + text("number of colors used: $c", boxbottomcenter(BoundingBox() * 0.9), halign=:center) + end 300 300 +end +``` + +On some platforms you'll see anti-aliased fonts rendered like this: + +```@example draw_text +draw(1) # hide +``` + +On Windows systems, text might be displayed using Microsoft’s Cleartype subpixel rendering process, and variously colored pixels are drawn around the edges of text in order to provide a “smoother” appearance. + +In addition, Windows uses font-hinting, a process in which the outlines of text glyphs are shifted so as to align better on the rectangular grid of pixels. + +If you want text to be aligned precisely on Windows, it might be worth investigating Luxor’s [`textoutlines`](@ref) function, which converts text to vector-based outlines. diff --git a/docs/src/howto/simplegraphics.md b/docs/src/howto/simplegraphics.md index 076ef2c4..bce9a7fc 100644 --- a/docs/src/howto/simplegraphics.md +++ b/docs/src/howto/simplegraphics.md @@ -587,10 +587,10 @@ nothing # hide ### Decoration The [`arrow`](@ref) functions allow you to specify -decorations - graphics at a point somewhere along the shaft. +decorations - graphics at one or more points somewhere along the shaft. For example, say you want to draw a number and a circle at -the midpoint of an arrow, you can define a function that -draws text `t` in a circle of radius `r` : +the midpoint of an arrow's shaft, you can define a function that +draws text `t` in a circle of radius `r` like this: ``` function marker(r, t) @@ -605,7 +605,7 @@ end ``` and then pass this to the `decorate` keyword argument of -`arrrow`. By default, the graphics origin when the function +`arrow`. By default, the graphics origin when the function is called is placed at the midpoint (0.5) of the arrow's shaft. @@ -645,7 +645,9 @@ nothing # hide Use the `decoration` keyword to specify one or more locations other than the default 0.5. -The graphics environment provided by the `decorate` function is centered at each decoration point, and rotated to the slope of the shaft at that point. +The graphics environment provided by the `decorate` function +is centered at each decoration point in turn, and rotated to the +slope of the shaft at that point. ```@example using Luxor @@ -655,30 +657,25 @@ function fletcher() line(O, polar(30, deg2rad(140)), :stroke) end -function customarrowhead(shaftendpoint, endpoint, shaftangle) - @layer begin - sidept1 = shaftendpoint - polar(40, shaftangle + π/5) - sidept2 = shaftendpoint - polar(40, shaftangle - π/5) - sethue("red") - poly([sidept1, endpoint, sidept2], :fill) - sethue("black") - poly([sidept1, endpoint, sidept2], :stroke, close=true) - end -end - @drawsvg begin background("antiquewhite") arrow(O, 150, 0, π + π/3, linewidth=5, + arrowheadlength=50, decorate=fletcher, - arrowheadfunction = customarrowhead, decoration=range(0., .1, length=3)) -end +end 800 350 ``` ### Custom arrowheads -To make custom arrowheads, you can define a three-argument function that draws them to your own design. This function should accept three arguments: the point at the end of the arrow's shaft, the point where the tip of the arrowhead would be, and the angle of the shaft at the end. You can then use any code to draw the arrow. Pass this function to the `arrow` function's `arrowheadfunction` keyword. +To make custom arrowheads, you can define a three-argument +function that draws them to your own design. This function +takes: the point at the end of the +arrow's shaft; the point where the tip of the arrowhead +would be; and the angle of the shaft at the end. You can +then use any code to draw the arrow. Pass this function to +the [`arrow`](@ref) function's `arrowheadfunction` keyword. ```@example using Luxor # hide diff --git a/docs/src/howto/tables-grids.md b/docs/src/howto/tables-grids.md index 6c319d63..89566fba 100644 --- a/docs/src/howto/tables-grids.md +++ b/docs/src/howto/tables-grids.md @@ -18,7 +18,7 @@ These are types which act as iterators. Their job is to provide you with centerp The drawing area (or any other area) can be divided into rectangular tiles (as rows and columns) using the `Tiler` and `Partition` iterators. -The `Tiler` iterator returns the center point and tile number of each tile in turn. +The [`Tiler`](@ref) iterator returns the center point and tile number of each tile in turn. In this example, every third tile is divided up into subtiles and colored: @@ -52,7 +52,7 @@ nothing # hide ![tiler](../assets/figures/tiler.png) -`Partition` is like `Tiler`, but you specify the width and height of the tiles, rather than +[`Partition`])(@ref) is like `Tiler`, but you specify the width and height of the tiles, rather than how many rows and columns of tiles you want. You can obtain the centerpoints of all the tiles in one go with: @@ -69,11 +69,17 @@ tiles[1:2:end] ## Tables -The `Table` iterator can be used to define tables: rectangular grids with a specific number of rows and columns. +The [`Table`](@ref) iterator can be used to define tables: rectangular grids with a specific number of rows and columns. -Unlike a Tiler, the Table iterator lets you have columns can have different widths, and rows with different heights. +Unlike a Tiler, the Table iterator lets you have columns with different widths, and rows with different heights. -(Luxor generally tries to keep to the Julia convention of 'width' -> 'height', 'row' -> 'column'. This flavour of consistency can sometimes be confusing if you're expecting other kinds of consistency, such as 'x before y'...) +!!! note + + Luxor generally tries to keep to the Julia convention of + ‘width’ -> ‘height’, ‘row’ -> ‘column’. This flavour of + consistency can sometimes be confusing if you’re + expecting other kinds of consistency, such as ‘x before + y’ or ‘column major’...) Tables don't store data, of course, but are designed to help you draw tabular data. diff --git a/docs/src/tutorial/design-a-logo.md b/docs/src/tutorial/design-a-logo.md index 0c327aba..30017e42 100644 --- a/docs/src/tutorial/design-a-logo.md +++ b/docs/src/tutorial/design-a-logo.md @@ -5,7 +5,12 @@ DocTestSetup = quote ``` # Tutorial: design a logo -The new (and currently fictitious) organization JuliaFission has just asked you to design a new logo for them. They're something to do with atoms and particles, perhaps? So we'll design a new logo for them using some basic shapes; perhaps colored circles in some kind of spiral formation would look suitably "atomic". +The new (and currently fictitious) organization JuliaFission +has just asked you to design a new logo for them. They're +something to do with atoms and particles, perhaps? So we'll +design a new logo for them using some basic shapes; perhaps +colored circles in some kind of spiral formation would look +suitably "atomic". Let's try out some ideas. @@ -35,23 +40,23 @@ svgimage # hide This short piece of code does the following things: -- Makes a new drawing 500 units square, and saves it in "my-drawing.svg" in SVG format. +- makes a new drawing 500 units square, and saves it in "my-drawing.svg" in SVG format. -- Moves the zero point from the top left to the center. Graphics applications often start measuring from the top left (or bottom left), but it's easier to work out the positions of things if you start at the center.) +- moves the zero point from the top left to the center. Graphics applications usually start measuring from the top left (occasionally from the bottom left), but it's easier to work out the positions of things if you start at the center. The `origin` function moves the `0/0` point to the center of the drawing. -- Selects one of the 200 or so named colors (defined in Colors.jl [here](http://juliagraphics.github.io/Colors.jl/stable/)) +- selects one of the 200 or so named colors (defined in [Colors.jl](http://juliagraphics.github.io/Colors.jl/stable/)) -- Draws a circle at x = 0, y = 0, with radius 100 units, and fills it with the current color.) +- draws a circle at x = 0, y = 0, with radius 100 units, and fills it with the current color -- Finishes the drawing and displays it on the screen. +- finishes the drawing and displays it on the screen -In case you're wondering, the units are *points* (as in font sizes), and there are 72 points in an inch, just over 3 per millimeter. The y-axis points down the page, by the way. If you want to be reminded of where the x and y axes are, uses the [`rulers`](@ref) function. +In case you're wondering, the units are *points* (as in font sizes), and there are 72 points in an inch, just over 3 per millimeter. The y-axis points down the page. If you want to be reminded of where the x and y axes are, uses the [`rulers`](@ref) function. -The `:fill` at the end of [`circle`](@ref) is one of a set of symbols that lets you use the shape in different ways. There's `:stroke`, which draws around the edges but doesn't fill the shape with color, and you might also meet `:path`, `:fillstroke`, `:fillpreserve`, `:strokepreserve`, `:clip`, and `:none`. +The `:fill` at the end of [`circle`](@ref) is one of a set of symbols that lets you use the shape in different ways. There's the `:stroke` action, which draws around the edges but doesn't fill the shape with color, and you might also meet the `:path`, `:fillstroke`, `:fillpreserve`, `:strokepreserve`, `:clip`, and `:none` actions. ## Circles in a spiral -We want more than just one circle. We'll define a triangular shape, and place circles at each corner. The [`ngon`](@ref) function creates regular polygon (eg triangles, squares, etc.), and the `vertices=true` keyword returns the corner points - just what we want. +We want more than just one circle. We'll define a triangular shape, and place a circle at each corner. The [`ngon`](@ref) function creates regular polygon (eg triangles, squares, etc.), and the `vertices=true` keyword returns the corner points rather than draws the shape - just what we want. ```@setup example_2 using Luxor @@ -78,11 +83,11 @@ preview() svgimage # hide ``` -Notice the "." after `circle`. This broadcasts the `circle()` function over the `corners`, drawing a red filled circle at every point. +Notice the "." after `circle`. This broadcasts the `circle()` function over the `corners`, drawing a 10-unit red filled circle at every point. -The arguments to `ngon` are centerpoint, radius, and number of sides. Try changing the third argument from 3 to 4 or 33. +The arguments to `ngon` are centerpoint, radius, and number of sides. Try changing the third argument from 3 (triangle) to 4 (square) or 31 (traikontagon?). -To create a spiral of circles, we want to repeat this `ngon`...`circle` part more than once. A simple loop will do: we'll rotate everything by `i * ` 5° (`deg2rad(5)` radians) each time (so 5°, 10°, 15°, 20°, 25°, and 30°), and increase the size of the shape by multiples of 10: +To create a spiral of circles, we want to repeat this "draw a circle at each vertex of a triangle" procedure more than once. A simple loop will do: we'll rotate the drawing by `i * ` 5° (`deg2rad(5)` radians) each time (so 5°, 10°, 15°, 20°, 25°, and 30°), and at the same time increase the size of the polygon by multiples of 10: ```@setup example_3 using Luxor @@ -121,7 +126,7 @@ svgimage # hide ## Just add color -The Julia colors are available as constants in Luxor, so we can make two changes that cycle through them. The first line creates the set of colors; the [`setcolor`](@ref) function then works through them. `mod1` is the 1-based version of the `mod` function, essential for working with Julia and its 1-based indexing. +The Julia colors are available as constants in Luxor, so we can make two changes that cycle through them. The first line creates the set of Julia colors; the [`setcolor`](@ref) function then works through them. `mod1` (get the `nth` element of an array) is the 1-based version of the `mod` function, essential for working with Julia and its 1-based indexing, such that `mod1(4, end)` gets the first value of a four element array (whereas `mod(4, end)` would fail, since it returns 0, and `colors[0]` is an error). ```@setup example_4 using Luxor, Colors @@ -154,7 +159,6 @@ for i in 1:6 circle.(corners, 10, :fill) end - finish() preview() @@ -166,7 +170,17 @@ svgimage # hide ## Taking particles seriously -The flat circles are a bit dull, so let's write a function that takes circles seriously. The `drawcircle()` function draws lots of circles, but each one is drawn with a slightly smaller radius and a slightly lighter shade of the incoming color. The [`rescale`](@ref) function in Luxor provides an easy way to map or adjust values from one range to another; here, numbers between 5 and 1 are mapped to numbers between 0.5 and 3. And the radius is scaled to run between `radius` and `radius/6`. Also, let's make them get larger as they spiral outwards, by adding `4i` to the radius when called by `drawcircle()`. +The flat circles are a bit dull, so let's write a function +that takes circles seriously. The `drawcircle()` function +draws lots of circles, but each one is drawn with a slightly +smaller radius and a slightly lighter shade of the incoming +color. The [`rescale`](@ref) function in Luxor provides an +easy way to map or adjust values from one range to another; +here, numbers between 5 and 1 are mapped to numbers between +0.5 and 3. And the radius is scaled to run between `radius` +and `radius/6`. Also, let's make them get larger as they +spiral outwards, by adding `4i` to the radius when called by +`drawcircle()`. ```@setup example_5 using Luxor, Colors @@ -216,7 +230,14 @@ preview() svgimage # hide ``` -This is looking quite promising. But here's the thing: in a parallel universe, you might already have made this in no time at all using Adobe Illustrator or Inkscape. But with this Luxor code, you can try all kinds of different variations with almost immediate results - just walk through the parameter space, either manually or via code, and see what effects you get. You don't have to redraw everything with different angles and radii... +This is looking quite promising. But here’s the thing: in a +parallel universe, you might already have made this in no +time at all using Adobe Illustrator or Inkscape. But with +this Luxor code, you can try all kinds of different +variations with almost immediate results - you can “walk through +the parameter space”, either manually or via code, and see +what effects you get. You don’t have to redraw everything +with different angles and radii... So here's what a pentagonal theme with more circles looks like: @@ -264,7 +285,7 @@ svgimage # hide To tidy up, it's a good idea to move the code into functions (to avoid running too much in global scope), and do a bit of housekeeping. -Also, a background for the icon would look good. [`squircle`](@ref) is useful for drawing shapes that occupy the space between pointy rectangles and inefficient circles. +Also, a background for the icon would look good. [`squircle`](@ref) is useful for drawing shapes that occupy the space between pointy dull rectangles and space-inefficient circles. The final script looks like this: @@ -332,14 +353,10 @@ So, did the JuliaFission organization like their logo? Who knows!? But if not, w ## Extra credit -### 1. Remember the random values - -Using random numbers is a great way to find new patterns and shapes; but unless you know what the values are, you can't reproduce them. So modify the code so that the random numbers are remembered, and drawn on the screen (you can use the `text(str, position)` function), - -### 2. Backgrounds +### 1. Randomize -Because there's no background, the SVG or PNG image created will have a transparent background. This is usually what you want for an icon or logo. But this design might look good against a darker colored background. Use `background()` or `paint()` and experiment. +Try refactoring your code so that you can automatically run through various parameter ranges. You could create many drawings with automatically-generated filenames... -### 3. Randomize +### 2. Remember the random values -Try refactoring your code so that you can automatically run through various parameter ranges. +Using random numbers is a great way to find new patterns and shapes; but unless you know what values were used, you can't easily reproduce them. It's frustrating to produce something really good but not know what values were used to make it. So modify the code so that the random numbers are remembered, and drawn on the screen (you can use the `text(str, position)` function), diff --git a/docs/src/tutorial/simple-animation.md b/docs/src/tutorial/simple-animation.md index 756ad561..b7959b45 100644 --- a/docs/src/tutorial/simple-animation.md +++ b/docs/src/tutorial/simple-animation.md @@ -1,6 +1,8 @@ # Make simple animations -Luxor.jl can help you build simple animations, by assembling a series of PNG images. To make richer animations, you should use [Javis.jl](https://github.com/Wikunia/Javis.jl). +Luxor.jl can help you build simple animations, by assembling +a series of PNG images. To make richer animations, you +should use [Javis.jl](https://github.com/Wikunia/Javis.jl) instead. ## A Julia spinner @@ -12,7 +14,16 @@ mymovie = Movie(400, 400, "mymovie") The resulting animation will be 400×400 pixels. -The `frame()` function (it doesn't have to be called that, but it's a good name), should accept two arguments, a Scene object, and a framenumber (integer). +To make the graphics, use a function called `frame()` (it doesn't have to be called that, but it's a good name) which accepts two arguments, a Scene object, and a framenumber (integer). + +A movie consists of one or more scenes. A scene is an object which determines how many drawings should be made into a sequence. The framenumber lets you keep track of where you are in a scene. + +Here's a simple `frame` function which creates a drawing. + +This function is responsible for drawing all the graphics for a single frame. +The incoming frame number is converted (normalized) to lie between 0 and 1 - ie. between the first frame and the last frame of the scene. It's multiplied by 2π and used as input to `rotate`. + +A sequence of drawings will be made, and as the framenumber goes from 1 to `n`, each drawing will be rotated by an increasing angle. For example, for a scene with 60 frames, framenumber 30 will set a rotation value of about `2π * 0.5`. ```julia function frame(scene::Scene, framenumber::Int64) @@ -26,19 +37,9 @@ function frame(scene::Scene, framenumber::Int64) end ``` -This function is responsible for drawing all the graphics for a single frame. The Scene object has details about the number of frames for this scene, and those are used to rescale (normalize) the framenumber to be a value between 0 and 1. - -```julia -julia> rescale(1, 1, 60) -0.0 - -julia> rescale(60, 1, 60) -1.0 -``` - -So for a scene with 60 frames, framenumber 30 will make a value of about 0.5. +The Scene object has details about the number of frames for this scene, the number of times the `frame` function is called. -The [`animate`](@ref) function takes an array of scenes and builds a GIF. +To actually build the animation, the [`animate`](@ref) function takes an array of one or more scenes and builds a GIF. ```julia animate(mymovie, @@ -51,20 +52,29 @@ animate(mymovie, ![julia spinner](../assets/figures/juliaspinner.gif) +Obviously, if you increase the range from 1:60 to, say, +1:300, you'll generate 300 drawings rather than 60, and the +rotation of the drawing will take longer and will be much +smoother. Unless, of course, you change the framerate to be +something other than the default `30`. + ## Combining scenes -In the next example, we'll construct an animation that organizes frames into different scenes. -Consider this animation showing the sun for each hour of a 24 hour day. +In the next example, we'll construct an animation that uses different scenes. + +Consider this animation, showing the sun's position for each hour of a 24 hour day. (It's only a model...) ![sun24 animation](../assets/figures/sun24.gif) -Start by creating a movie. This acts as a useful handle that we can pass from function to function. The arguments are width, height, name, and a range that determines how many frames are used. +Again, start by creating a movie, a useful handle that we can pass from function to function. We'll specify 24 frames for the entire animation. ``` sun24demo = Movie(400, 400, "sun24", 0:23) ``` -We'll define a `backgroundfunction` function that draws a background used for all frames (animated GIFs like constant backgrounds): +We'll define a simple `backgroundfunction` function that draws a +background that will be used for all frames (since animated +GIFs like constant backgrounds): ``` function backgroundfunction(scene::Scene, framenumber) @@ -72,7 +82,7 @@ function backgroundfunction(scene::Scene, framenumber) end ``` -A `nightskyfunction` draws the night sky: +A `nightskyfunction` draws the night sky, covering the entire drawing: ``` function nightskyfunction(scene::Scene, framenumber) @@ -90,19 +100,19 @@ function dayskyfunction(scene::Scene, framenumber) end ``` -The `sunfunction` draws a sun at 24 positions during the day. The framenumber will be a number between 0 and 23, which can be easily converted to lie between 0 and 2π. +The `sunfunction` draws a sun at 24 positions during the day. Since the framenumber will be a number between 0 and 23, this can be easily converted to lie between 0 and 2π. ``` function sunfunction(scene::Scene, framenumber) - i = rescale(framenumber, 0, 23, 2pi, 0) + t = rescale(framenumber, 0, 23, 2pi, 0) gsave() sethue("yellow") - circle(polar(150, i), 20, :fill) + circle(polar(150, t), 20, :fill) grestore() end ``` -Here's a `groundfunction` that draws the ground: +And finally, tere's a `groundfunction` that draws the ground, the lower half of the drawing: ``` function groundfunction(scene::Scene, framenumber) @@ -114,7 +124,9 @@ function groundfunction(scene::Scene, framenumber) end ``` -To combine these together, define a group of Scenes that make up the movie. The scenes specify which functions are to be used, and for which frames: +To combine these together, we'll define a group of Scenes +that make up the movie. The scenes specify which functions +are to be used, and for which frames: ``` backdrop = Scene(sun24demo, backgroundfunction, 0:23) # every frame @@ -125,7 +137,9 @@ sun = Scene(sun24demo, sunfunction, 6:18) # 06:00 to 18:00 ground = Scene(sun24demo, groundfunction, 0:23) # every frame ``` -Finally, the `animate` function scans the scenes in the scenelist for a movie, and calls the functions for each frame to build the animation: +Finally, the `animate` function scans all the scenes in the +scenelist for the movie, and calls the specified functions for each +frame to build the animation: ``` animate(sun24demo, [ @@ -141,7 +155,11 @@ Notice that for some frames, such as frame 0, 1, or 23, three of the functions a ## An alternative -An alternative approach is to use the incoming framenumber as the master parameter that determines the position and appearance of all the graphics. +As this is a very simple example, there is of course an easier wayto make this particular animation. + +We can use the incoming framenumber, rescaled, as the master +parameter that determines the position and appearance of all +the graphics. ``` function frame(scene, framenumber)