Examples :: Create an expanding rotating circle of rotating stars

This series of examples script demonstrates the (large) improvement in capacity and speed of the animation filters supplied by AVSLib in version 1.1.0, compared to previous versions.

The first example script performs the same animation as the sixth animation example of version's 1.0.0 documentation - that is twelve rectangles that move spirally from the center to the border of the screen while in parallel they enlarge and overlayed in each animation interval with a different overlay mode setting; the final visual result being a rotating circle of rectangles that grows in radius while the rectangles grow in size - but in a single script instead of a six-scripts chain.

The improvement in capacity is more than 6 to 1, since now the overal process size to run the script is now well below 1 GB, while the process size for running the previous version's scripts was a bit lower than 2 GB for each script in the chain.

The second example script makes the same animation but with clips showing a rotating star, thus producing an expanding rotating circle of rotating stars.

Lets now look closer at the first script:

LoadPackage("avslib", "base")
LoadPackage("avslib", "array")
LoadModule("avslib", "filters", "animate")

global _thetas = ArrayRange(0, 2*Pi(), npoints=21)
global _aparam = 40
# r,theta correspond to Archimedes spiral (a*theta, theta)
global _rs = _thetas.ArrayOpValue(_aparam, "*")

Function xpolar(float r, float theta) { return Float(r)*Cos(theta) }
Function ypolar(float r, float theta) { return Float(r)*Sin(theta) }

global xy_path = ArrayPlex( \
	ArrayOpArrayFunc(_rs, _thetas, "xpolar").ArrayOpFunc("Round"), \
	ArrayOpArrayFunc(_rs, _thetas, "ypolar").ArrayOpFunc("Round") \
	)
global wh_dims = ArrayRange(12, 92, 4)
global o_modes = \
	"blend,add,blend,subtract,blend,chroma,blend,blend," + \
	"luma,blend,blend,lighten,blend,blend,softlight,blend," + \
	"blend,hardlight,blend,blend"	# one less than xy_path

Function xrot(int x, int y, float angle) { return Round(x*Cos(angle) - y*Sin(angle)) }
Function yrot(int x, int y, float angle) { return Round(x*Sin(angle) + y*Cos(angle)) }

# rotates a plexed (x,y) path by angle radians
Function rotate_path(string path, float angle) {
	path_x = path.ArrayDeplex(0, 2)
	path_y = path.ArrayDeplex(1, 2)
	new_x = ArrayOpArrayFunc(path_x, path_y, "xrot", String(angle))
	new_y = ArrayOpArrayFunc(path_x, path_y, "yrot", String(angle))
	return ArrayPlex(new_x, new_y)
}

Up to this point the script loads needed modules and plugins and constructs the spiral path for the movement of the rectangles. Since the path is defined in polar coordinates, a couple of polar functions are defined to calculate the x,y cartesian coordinates of the path points.

The x and y arrays of coordinates are then multiplexed, by calling ArrayPlex, in a (x,y) array (the equivalent of a 2D matrix) in order to have one variable to hold all the path info.

Two additional arrays, wh_dims to hold the rectangle size at each path point, and o_modes, to hold the overlay mode in each path segment between two path points are defined.

Then a function to rotate a (x,y) path by a given angle, rotate_path, is defined. Inside it the path is de-multiplexed in separate x and y arrays by calling ArrayDeplex; then an appropriate coordinates-rotation function is applied to them and they are again multiplexed and returned as the new, rotated, path.

# the base effect function
Function rect_effect(clip base, string path, int rect_color) {
	rect = BlankClip(base, color=rect_color)
	frames = ArrayRange(0, base.Framecount - 1, npoints=21).ArrayOpFunc("Round")
	# relocate path to center of base clip
	xp = path.ArrayDeplex(0,2).ArrayOpValue(Round(base.Width / 2), "+")
	yp = path.ArrayDeplex(1,2).ArrayOpValue(Round(base.Height / 2), "+")
	# not specifying a mask == full white mask
	return PolygonAnim(base, rect, frames, xp, yp, 1.0, \
		wh_dims, wh_dims, mode=o_modes)
}

# the angle step to rotate base (x,y) path ( 2pi/{number of curves} ) the last being 12
global theta = Pi()/6 		# 30 degrees

# since color constants are globals creating an array with their names 
# will return their value
global in_colors = "color_gold,color_ivory,color_mediumorchid,color_beige," + \
	"color_aquamarine,color_blue,color_darkorange,color_white,color_darkred," + \
	"color_crimson,color_olivedrab,color_chocolate"

Function MakeEffect006(clip base) {
	c = base
	c = c.rect_effect(xy_path, in_colors.ArrayGet( 0))
	c = c.rect_effect(xy_path.rotate_path(   theta), in_colors.ArrayGet( 1))
	c = c.rect_effect(xy_path.rotate_path( 2*theta), in_colors.ArrayGet( 2))
	c = c.rect_effect(xy_path.rotate_path( 3*theta), in_colors.ArrayGet( 3))
	c = c.rect_effect(xy_path.rotate_path( 4*theta), in_colors.ArrayGet( 4))
	c = c.rect_effect(xy_path.rotate_path( 5*theta), in_colors.ArrayGet( 5))
	c = c.rect_effect(xy_path.rotate_path( 6*theta), in_colors.ArrayGet( 6))
	c = c.rect_effect(xy_path.rotate_path( 7*theta), in_colors.ArrayGet( 7))
	c = c.rect_effect(xy_path.rotate_path( 8*theta), in_colors.ArrayGet( 8))
	c = c.rect_effect(xy_path.rotate_path( 9*theta), in_colors.ArrayGet( 9))
	c = c.rect_effect(xy_path.rotate_path(10*theta), in_colors.ArrayGet(10))
	c = c.rect_effect(xy_path.rotate_path(11*theta), in_colors.ArrayGet(11))
	return c
}

The base effect function, rect_effect, demultiplexes the supplied path, translates, using ArrayOpValue, all coordinates so that they correspond to the center of the overlay clip and passes the arrays to PolygonAnim filter to draw the animation accross the supplied path. Frame numbers are calculated dynamically, based on base clip's length, with the aid of ArrayRange function.

A small note here: the excersized reader may have already noticed that if separate x and y arrays where used instead of a combined (x,y) array we would avoid the costs of uneeded multiplexing and demultiplexing operations with only the small added complexity of passing one more parameter; however this is a demonstration script, thus we can afford to be a bit more un-optimized in order to show in addition the way to construct a combined (x,y) path for the case someone needs it at the future!

The rest is the definition of rotation angle, of rectangles' colors and of the overall effect function, which simply calls the base effect function for all elements of the colors array with increasing rotation angle.

# create a 8sec animation in PAL format
base = BlankClip(color=$020060, length=200, fps=FRATE_PAL, width=WSIZE_PAL, \
	height=HSIZE_PAL).ConvertToYV12()
return MakeEffect006(base)

Finally, a base source is created and the overall animation effect function is called.

The second script example adds a level of complexity and visual appealence to the basic idea, simply because it can now afford to do so due to the increased capacity of AVSLib version 1.1.0 animation filters: instead of animating rectangles, it animates clips showing a rotating star. The overal effect is now an expanding rotating circle of rotating stars.

The result of running the script is presented below.

 

Figure 1: Clip produced by second example script (top-left quarter: frame 12, bottom-right: frame 199)

example script's output clip (4 frames sample)

 

In order to keep the text short, only the differences with the previous script are presented:

LoadModule("avslib", "filters", "resize")
LoadModule("avslib", "filters", "utility")

...

# the base effect function
Function rect_effect(clip base, clip effmask, string path, int rect_color) {
	rect = BlankClip(base, color=rect_color)
	frames = ArrayRange(0, base.Framecount - 1, npoints=21).ArrayOpFunc("Round")
	# relocate path to center of base clip
	xp = path.ArrayDeplex(0,2).ArrayOpValue(Round(base.Width / 2), "+")
	yp = path.ArrayDeplex(1,2).ArrayOpValue(Round(base.Height / 2), "+")
	return PolygonAnim(base, rect, frames, xp, yp, 1.0, wh_dims, wh_dims, \
		mode=o_modes, mask=effmask)
}

...

Function MakeEffect006(clip base, clip effmask) {
	c = base
	c = c.rect_effect(effmask, xy_path,                       in_colors.ArrayGet( 0))
	c = c.rect_effect(effmask, xy_path.rotate_path(   theta), in_colors.ArrayGet( 1))
	c = c.rect_effect(effmask, xy_path.rotate_path( 2*theta), in_colors.ArrayGet( 2))
	c = c.rect_effect(effmask, xy_path.rotate_path( 3*theta), in_colors.ArrayGet( 3))
	c = c.rect_effect(effmask, xy_path.rotate_path( 4*theta), in_colors.ArrayGet( 4))
	c = c.rect_effect(effmask, xy_path.rotate_path( 5*theta), in_colors.ArrayGet( 5))
	c = c.rect_effect(effmask, xy_path.rotate_path( 6*theta), in_colors.ArrayGet( 6))
	c = c.rect_effect(effmask, xy_path.rotate_path( 7*theta), in_colors.ArrayGet( 7))
	c = c.rect_effect(effmask, xy_path.rotate_path( 8*theta), in_colors.ArrayGet( 8))
	c = c.rect_effect(effmask, xy_path.rotate_path( 9*theta), in_colors.ArrayGet( 9))
	c = c.rect_effect(effmask, xy_path.rotate_path(10*theta), in_colors.ArrayGet(10))
	c = c.rect_effect(effmask, xy_path.rotate_path(11*theta), in_colors.ArrayGet(11))
	return c
}

# create a 8sec animation in PAL format
base = BlankClip(color=$020060, length=200, fps=FRATE_PAL, \
	width=WSIZE_PAL, height=HSIZE_PAL)
base = base.ConvertToYV12()
effmask = ImageSource("star%06d.jpg", 0, 48, 25.0).Reverse().Loop(10).Trim(0,199)
effmask = effmask.ResizeToTarget(base).ConvertToTarget(base).ScaleToPC()
return MakeEffect006(base, effmask)

The base effect function now accepts one more argument, a clip mask, which is passed to the PolygonAnim filter. The same is true for the overall effect function, since it must pass the mask to the base effect function.

In addition, two more modules are loaded, to provide the filters (ResizeToTarget and ConvertToTarget) that convert the mask's source image sequence into a clip with the same characteristics as the base clip.

The top-level section of the script loads the image sequence and (with the filters above) converts it to a mask clip compatible with the base clip. ScaleToPC ensures that the mask is in full [0..255] range.

The image sequence is provided here.