# AVSLib :: 'filters' package :: 'animate' module
# Copyright (c) 2005 George Zarkadas (gzarkadas@users.sourceforge.net)

# This program is free software; you can redistribute it and/or modify it under the terms of 
# the GNU General Public License as published by the Free Software Foundation; either 
# version 2 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program; 
# if not, write to the "Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
# MA  02111-1307  USA"

__lib__ = "avslib" #$$ [__lib__ = "avslib-debug"]

DefineModule(__lib__, "filters", "animate")

global __load__ = IsPackageLoaded(__lib__, "base") ? NOP : Eval("""
	LoadModule(__lib__, "base", "core")
	LoadModule(__lib__, "base", "conversion")
	""")
global __load__ = IsPackageLoaded(__lib__, "array") ? NOP : Eval("""
	LoadModule(__lib__, "array", "core")
	LoadModule(__lib__, "array", "operators")
	""")
global __load__ = Eval("""
	LoadModule(__lib__, "filters", "resize") # needed by plugins/animate
	LoadModule("plugins", "animate", "core")
	""")

# ==== plugins::animate package's contents <begin> ====

# Function MoveOverlay(clip base, clip ovl, int "f_start", int "f_end", int "x_start", \
#     int "x_end", int "y_start", int "y_end", float "opac_start", float "opac_end", \
#     clip "mask", string "mode", bool "greymask", string "output", bool "ignore_conditional", \
#     bool "pc_range")
# Function ResizeOverlay(clip base, clip ovl, int "f_start", int "f_end", int "w_start", \
#     int "w_end", int "h_start", int "h_end", bool "pcrange")
# Function LineAnim(clip base, clip ovl, int "f_start", int "f_end", int "x_start", int "x_end", \
#     int "y_start", int "y_end", float "op_start", float "op_end", int "w_start", int "w_end", \
#     int "h_start", int "h_end", clip "mask", string "mode", bool "greymask", string "output", \
#     bool "ignore_conditional", bool "pc_range")
	
# ==== plugins::animate package's contents <end> ====

# PolygonAnim:
# Most of the arguments are declared as variable type (except where this does 
# not make sense) in order to allow different settings for each 'line' segment
# and thus the maximum of flexibility. 
# When they are strings they are interpreted as arrays containing one value 
# for each segment. When the are *not* strings it is assumed that they contain 
# a constant value of the correct type to be used during the entire animation.
# NOTE: f_array, x_array, y_array, op_array, w_array, h_array define segment's
# end-points' values, while all the other arrays (if supplied) define segment's
# properties and thus they should have ONE element LESS than the above arrays.

	Function __anim_white_mask(clip base) {
		mask = BlankClip(base, color=$ffffff)
		return IsYUV(mask) ? mask.ColorYUV(levels="tv->pc") : mask
	}

	# Array length check helper functions

	Function __anim_len_check(int reflen, val "arr") {
		return Defined(arr) && arr.IsString ? reflen == arr.ArrayLen() : true
	}
	
	Function __anim_select_map(bool flag, string label) {
		return (!flag) ? label : ""
	}
	
	Function __anim_sum_map(string x1, string x2) {
		return x1 + ((x1 != "" && x2 != "") ? ", " : "") + x2
	}
	
	Function __anim_info_map(string checks) {
		smap = "x_array,y_array,op_array,w_array,h_array,mask,mode,scale,greymask,ignore_conditional,pc_range"
		return ArrayOpArrayFunc(checks, smap, "__anim_select_map").ArraySum(sum_func="__anim_sum_map")
	}

	# Array retrieve values and manipulation helper functions 
	
	# If arr is defined checks if it is an array and 
	# if yes returns arr[index] else coerce_func(arr).
	# If arr is not defined, returns the supplied defval.
	#
	Function __anim_get_value(int index, val defval, val "arr", string "coerce_func") {
		coerce_func = Default(coerce_func, "Int")
		return Defined(arr) \
			? (IsString(arr) ? arr.ArrayGet(index) : Apply(coerce_func, arr)) \
			: defval
	}
	
	Function __anim_trunc_array(val "arr") {
		return Defined(arr) && arr.IsString ? arr.ArrayDelete(0) : arr
	}

# Output is not made an val/array choice pair since the format 
# of clips must be identical in order to add them

Function PolygonAnim(clip base, clip ovl, string f_array, val "x_array", val "y_array", \
	val "op_array", val "w_array", val "h_array", val "mask", val "mode", val "greymask", \
	string "output", val "ignore_conditional", val "pc_range")
{
	# check arrays lengths
	arrlen = f_array.ArrayLen()
	Assert(arrlen > 1, \
		"PolygonAnim: At least one segment must be specified ('f_array' length must be > 1).")
	checks = ArrayCreate( \
		__anim_len_check(arrlen, x_array), \
		__anim_len_check(arrlen, y_array), \
		__anim_len_check(arrlen, op_array), \
		__anim_len_check(arrlen, w_array), \
		__anim_len_check(arrlen, h_array), \
		__anim_len_check(arrlen - 1, mask), \
		__anim_len_check(arrlen - 1, mode), \
		__anim_len_check(arrlen - 1, greymask), \
		__anim_len_check(arrlen - 1, ignore_conditional), \
		__anim_len_check(arrlen - 1, pc_range) )
	b_len_ok = checks.ArraySum(sum_func="__and2")
	# print an info map if an error occures
	smap = b_len_ok ? "" : __anim_info_map(checks)
	Assert(b_len_ok, \
		"PolygonAnim: Some argument arrays have number of elements " + \
		"incompatible with 'f_array':" + Chr(10) + smap + ".")
	# get frame numbers and ensure that they are in bounds and increase monotonically 
	fs  = f_array.ArrayGet(0)
	fe  = f_array.ArrayGet(1)
	bfc = base.Framecount
	Assert(fs >= 0 && fs < bfc, \
		"PolygonAnim: Segment's start-frame outside of valid frame range.")
	Assert(fe >= 0 && fe < bfc, \
		"PolygonAnim: Segment's end-frame outside of valid frame range.")
	Assert(fs < fe, \
		"PolygonAnim: Segment's end-frame must always be greater than start-frame.")
	# most of the arguments get default values from LineAnim, 
	# so supplying Undef() as default is appropriate 
	# segment's end-points arrays
	xs  = __anim_get_value(0, Undef(), x_array)
	xe  = __anim_get_value(1, Undef(), x_array)
	ys  = __anim_get_value(0, Undef(), y_array)
	ye  = __anim_get_value(1, Undef(), y_array)
	os  = __anim_get_value(0, 1.0, op_array, "Float")
	oe  = __anim_get_value(1, 1.0, op_array, "Float")
	ws  = __anim_get_value(0, Undef(), w_array)
	we  = __anim_get_value(1, Undef(), w_array)
	hs  = __anim_get_value(0, Undef(), h_array)
	he  = __anim_get_value(1, Undef(), h_array)
	# entire segment's properties arrays
	msk = __anim_get_value(0, __anim_white_mask(ovl), mask, "__self")
	mod = __anim_get_value(0, "Blend", mode, "String")
	gre = __anim_get_value(0, true, greymask, "CBool")
	igc = __anim_get_value(0, true, ignore_conditional, "CBool")
	pcr = __anim_get_value(0, false, pc_range, "CBool")
	# Subtract fe from all remaining frame array elements (since base will be trimmed).
	f_array = f_array.ArrayOpValue(fe, "-")
	# Delete first element of each array, so that the recursion eventually ends
	f_array  = __anim_trunc_array(f_array)
	x_array  = __anim_trunc_array(x_array)
	y_array  = __anim_trunc_array(y_array)
	op_array = __anim_trunc_array(op_array)
	w_array  = __anim_trunc_array(w_array)
	h_array  = __anim_trunc_array(h_array)
	mask     = __anim_trunc_array(mask)
	mode     = __anim_trunc_array(mode)
	greymask = __anim_trunc_array(greymask)
	ignore_conditional = __anim_trunc_array(ignore_conditional)
	pc_range = __anim_trunc_array(pc_range)
	# The recursion must stop when 2 array elements have left (calling LineAnim on 
	# the last two array elements). This gives our function the desired behavior.
	do_recurse = (arrlen > 2) ? true : false
	loclip = LineAnim(base, ovl, fs, fe, xs, xe, ys, ye, os, oe, \
		ws, we, hs, he, msk, mod, gre, output, igc, pcr)
	loclip = do_recurse ? loclip.Trim(0, -fe) : loclip
	# In order to get proper results all clips including mask must be trimmed the 
	# same way, else ovl and/or mask will be repeated during each frame set. However 
	# if mask is an array do not trim it; assume the user has properly setup masks.
	mask = Defined(mask) \
		? (IsString(mask) ? mask : mask.Trim(fe, 0)) \
		: Undef()
	poclip = do_recurse \
		? PolygonAnim(base.Trim(fe, 0), ovl.Trim(fe, 0), f_array, \
			x_array, y_array, op_array, w_array, h_array, mask, mode, \
			greymask, output, ignore_conditional, pc_range) \
		: Null(base)
	return do_recurse ? loclip + poclip : loclip
}
