# AVSLib :: 'filters' package :: 'resize' 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", "resize")

global __load__ = Eval("""
	LoadModule(__lib__, "base", "core")
	LoadModule(__lib__, "clip", "core")
	LoadModule(__lib__, "array", "core")  # needed by resize plugin
	LoadModule("plugins", "resize", "core")
	""")

# ==== plugins::resize package's contents <begin> ====
#
# Function GetDefaultResizer(bool "with_params")
# Function SetDefaultResizer(string resizer, val "rsz_param1", val "rsz_param2")
#
# Resizes a clip with a specified 'resizer' function ("bilinear" being the 
# default). Accepts widths and heights down to zero. 
# Returns a clip with the right dimensions for its colorspace and 'interlaced' 
# properties; thus the later may deviate from what the caller has specified.
#
# Function Resize(clip c, int target_width, int target_height, \
#	float "src_left", float "src_top", float "src_width", float "src_height", \
#	string "resizer", val "rsz_param1", val "rsz_param2", bool "interlaced") 
#
# NOTE: for interlaced clips, set interlaced = true.
# ==== plugins::resize package's contents <end> ====

# ResizeToFit mode constants
global RTF_ZOOM = 0
global RTF_CROP = 1
global RTF_STRETCH = 2

# Resizes base clip to the same dimensions as the target clip, possibly 
# with a conversion to the target's colorspace (the later is done only if 
# calling the resizer will crash Avisynth (ver. < 2.5.6) or result in an 
# error dialog box (ver. >= 2.5.6).
# If even with colorspace conversion the target dimensions cannot be achieved
# (this may happen only for interlaced clips and targets with odd height 
# (RGB32, YUY2) or with not-properly cropped targets), the filter throws 
# an error.
# If 'base' is a non-CCIR YUV (ie 0..255 range) and is converted to RGB 
# (ie 'target' is RGB and has dimensions not compatible with YUV, such as 
# odd width ) it will be upscaled; take care that either 'base' is in 
# CCIR range or 'target' has dimensions appropriate for the YUV colorspace.
#
Function ResizeToTarget(clip base, clip target, \
	string "resizer", val "rsz_param1", val "rsz_param2", bool "interlaced")
{
	_FID = "ResizeToTarget: "
	resizer = LCase(Default(resizer, GetDefaultResizer(false)))
	interlaced = Default(interlaced, false)

	#$$ DebugLog(DBG_FILT_STD, "ResizeToTarget(b, t, %s, %v, %v, %b)", \
	#$$ 	resizer, rsz_param1, rsz_param2, interlaced)

	w = target.Width
	h = target.Height
	sw = base.SafeWidth(w)
	sh = base.SafeHeight(h, interlaced)
	tw = target.SafeWidth(w)
	th = target.SafeHeight(h, interlaced)
	# check conditions
	b_good_target = (tw == w) && (th == h)
	b_can_intlace = h.IsEven || !interlaced

	#$$ DebugLog(DBG_FILT_EXT, "\tb.colorspace=%s, b.width=%i, b.height=%i, " + \
	#$$ 	"t.colorspace=%s, t.width=%i, t.height=%i, b.safewidth=%i, " + \
	#$$ 	"b.safeheight=%i, t.safewidth=%i, t.safeheight=%i", b.ColorSpace, \
	#$$ 	b.Width, b.Height, t.ColorSpace, t.Width, t.Height, sw, sh, tw, th)

	Assert(b_can_intlace, _FID + \
		"'target' has odd height (not supported for interlaced clips)")
	Assert(b_good_target, _FID + \
		"'target' has width or height not supported" + \
		Chr(10) + "by its colorspace (possibly badly cropped?)")
	# decide actions to be performed
	b_need_resz = base.Width != w || base.Height != h
	b_need_conv = b_need_resz && (sw != w || sh != h)
	# Convert if necessary
	ret = b_need_conv \
		? Eval("base.ConvertTo" + target.ColorSpace + "()") \
		: base
	# exploit {crop param==zero <-> undefined crop param}
	ret = b_need_resz \
		? ret.Resize(w, h, 0, 0, 0, 0, resizer, \
			rsz_param1, rsz_param2, interlaced) \
		: ret

	#$$ DebugLog(DBG_FILT_STD, "\tresized_b=%b, converted_b=%b\n" + \
	#$$ 	"<End> ResizeToTarget", b_need_resz, b_need_conv)

	return ret
}

# Resizes a clip to target dimensions according to 'mode', taking also 
# into account the clip's colorspace constrains.
# mode == 0 (Zoom), default:
#     Enlarge/shrink keeping aspect ratio; uncovered space is made pure black
# mode == 1 (Crop):
#     Try to fit exactly, keeping aspect ratio; crop if necessary
# mode == 2 (Stretch):
#     Try to fit exactly; do not care to retain aspect ratio
# If the target dimensions are not compatible with the clip's colorspace, 
# the filter throws an error.

	Function __resize_tofit_zoom(clip c, int target_w, int target_h, \
		string "resizer", val "rsz_param1", val "rsz_param2", \
		bool "interlaced")
	{
		black = c.BlankClip(width=target_w, height=target_h)
		c_aspect = Float(c.Width) / Float(c.Height)
		t_aspect = Float(target_w) / Float(target_h)
		# calculate zoom dimensions (they won't exceed target_w or target_h)
		zoom_w = c_aspect >= t_aspect \
			? target_w \
			: __min2(c.SafeWidth(Round(c_aspect * target_h)), target_w)
		zoom_h = c_aspect <= t_aspect \
			? target_h \
			: __min2(c.SafeHeight(Round(target_w / c_aspect), interlaced), \
				target_h)

		#$$ DebugLog(DBG_FILT_EXT, "\tc.aspect_ratio=%f, target.aspect_ratio=%f, " + \
		#$$ 	"target_w=%i, target_h=%i", c_aspect, t_aspect, target_w, target_h)

		rsz = c.Resize(zoom_w, zoom_h, 0, 0, 0, 0, resizer, \
			rsz_param1, rsz_param2, interlaced)
		# one of them is quaranteed to be zero
		rest_w = target_w - zoom_w
		rest_h = target_h - zoom_h
		# halves; use 2 because base quantities may be odd 
		r1w = Int(rest_w / 2) # also overlay x offset
		r2w = rest_w - r1w
		r1h = Int(rest_h / 2) # also overlay y offset
		r2h = rest_h - r1h
		b_can_crop = rest_w == 0 \
			? c.SafeHeight(r1h, interlaced) == r1h && \
				c.SafeHeight(r2h, interlaced) == r2h \
			: c.SafeWidth(r1w) == r1w && \
				c.SafeWidth(r2w) == r2w

		#$$ DebugLog(DBG_FILT_EXT, "\tmode=zoom, zoom_w=%i, border_w=%i, zoom_h=%i, border_h=%i, " + \
		#$$ 	"cropping_instead_overlaying=%b", zoom_w, rest_w, zoom_h, rest_h, b_can_crop)

		# If cropping of black bars gives sizes not supported by the clip's
		# colorspace, use Overlay to avoid artifacts in later processing;
		# else use Crop + StackH/V to avoid Overlay's memory overhead.
		return rest_w != 0 || rest_h != 0 \
			? ( b_can_crop ? \
				(rest_w == 0 ? \
					StackVertical( \
						black.Crop(0, 0, target_w, r1h), \
						rsz, \
						black.Crop(0, 0, target_w, r2h) \
					) : \
					StackHorizontal( \
						black.Crop(0, 0, r1w, target_h), \
						rsz, \
						black.Crop(0, 0, r2w, target_h)) \
					) \
				: Overlay(c, rsz, x=r1w, y=r1h, mode="blend", opacity=1.0) \
			  ) \
			: rsz	# if aspect ratios match simply return the full-zoomed clip
	}

	Function __resize_tofit_crop(clip c, int target_w, int target_h, \
		string "resizer", val "rsz_param1", val "rsz_param2", \
		bool "interlaced")
	{
		c_aspect = Float(c.Width) / Float(c.Height)
		t_aspect = Float(target_w) / Float(target_h)
		# calculate zoom dimensions (they will exceed target_w or target_h)
		zoom_w = c_aspect <= t_aspect \
			? target_w \
			: __max2(c.SafeWidth(Round(c_aspect * target_h)), target_w)
		zoom_h = c_aspect >= t_aspect \
			? target_h \
			: __max2(c.SafeHeight(Round(target_w / c_aspect), interlaced), \
				target_h)

		#$$ DebugLog(DBG_FILT_EXT, "\tc.aspect_ratio=%f, target.aspect_ratio=%f, " + \
		#$$ 	"target_w=%i, target_h=%i", c_aspect, t_aspect, target_w, target_h)

		rsz = c.Resize(zoom_w, zoom_h, 0, 0, 0, 0, resizer, \
			rsz_param1, rsz_param2, interlaced)		
		# one of them is quaranteed to be zero
		excess_w = zoom_w - target_w 
		excess_h = zoom_h - target_h
		# halves; use 2 because base quantities may be odd 
		e1w = Int(excess_w / 2)
		e2w = excess_w - e1w
		e1h = Int(excess_h / 2)
		e2h = excess_h - e1h

		#$$ DebugLog(DBG_FILT_EXT, "\tmode=crop, zoom_w=%i, excess_w=%i, zoom_h=%i, excess_h=%i" + \
		#$$ 	zoom_w, excess_w, zoom_h, excess_h)

		# here we do not check, since we *can* crop in valid dimensions
		return rsz.Crop(e1w, e1h, -e2w, -e2h)
	}

Function ResizeToFit(clip c, int target_w, int target_h, int "mode", \
	string "resizer", val "rsz_param1", val "rsz_param2", bool "interlaced")
{
	_FID = "ResizeToFit: "
	mode = Default(mode, RTF_ZOOM)
	resizer = LCase(Default(resizer, GetDefaultResizer(false)))
	interlaced = Default(interlaced, false)

	#$$ DebugLog(DBG_FILT_STD, "ResizeToFit(c, %i, %i, %i, %s, %v, %v, %b)", \
	#$$ 	target_w, target_h, mode, resizer, rsz_param1, rsz_param2, interlaced)

	Assert(mode >= RTF_ZOOM && mode <= RTF_STRETCH, _FID + \
		"invalid value for 'mode' (should be 0, 1, or 2)")
	Assert(target_w >= 0, _FID + \
		"'target_w' must be a positive number or zero")
	Assert(target_h >= 0, _FID + \
		"'target_h' must be a positive number or zero")
	target_w = target_w == 0 ? c.Width : target_w
	target_h = target_h == 0 ? c.Height : target_h
	target_sw = c.SafeWidth(target_w)
	target_sh = c.SafeHeight(target_h, interlaced)

	#$$ DebugLog(DBG_FILT_EXT, "\tc.colorspace=%s, c.width=%i, c.height=%i, c.safewidth=%i, " + \
	#$$ 	"c.safeheight=%i", c.ColorSpace, c.Width, c.Height, target_sw, target_sh)

	# check conditions
	b_good_target = (target_w == target_sw) && (target_h == target_sh)
	b_can_intlace = target_h.IsEven || !interlaced
	Assert(b_can_intlace, _FID + \
		"'target_h' is odd (not supported for interlaced clips)")
	Assert(b_good_target, _FID + \
		"'target_w' or 'target_h' or both are not" + \
		Chr(10) + "supported by clip's colorspace")
	ret = mode == RTF_ZOOM ? \
			__resize_tofit_zoom(c, target_w, target_h, \
			resizer, rsz_param1, rsz_param2, interlaced) : ( \
		mode == RTF_CROP ? \
			__resize_tofit_crop(c, target_w, target_h, \
			resizer, rsz_param1, rsz_param2, interlaced) \
		: \
			c.Resize(target_w, target_h, 0, 0, 0, 0, resizer, \
			rsz_param1, rsz_param2, interlaced) )

	#$$ dummy = (mode != RTF_ZOOM) && (mode != RTF_CROP) \
	#$$ 	? DebugLog(DBG_FILT_EXT, "\tmode=stretch") : NOP
	
	#$$ DebugLog(DBG_FILT_STD, "<End> ResizeToFit")

	return ret
}
