+-- Find the transformation that changes the first rectangle to the second one.
+function find_affine_param(a, b)
+ local sx = (b.x1 - b.x0) / (a.x1 - a.x0)
+ local sy = (b.y1 - b.y0) / (a.y1 - a.y0)
+ return {
+ sx = sx,
+ sy = sy,
+ tx = b.x0 - a.x0 * sx,
+ ty = b.y0 - a.y0 * sy
+ }
+end
+
+function place_rectangle_with_affine(input, pos, aff, screen_width, screen_height, input_width, input_height, hq)
+ local x0 = pos.x0 * aff.sx + aff.tx
+ local x1 = pos.x1 * aff.sx + aff.tx
+ local y0 = pos.y0 * aff.sy + aff.ty
+ local y1 = pos.y1 * aff.sy + aff.ty
+
+ place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height, hq)
+end
+
+function place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height, hq)
+ input.padding_effect:set_int("width", screen_width)
+ input.padding_effect:set_int("height", screen_height)
+
+ -- Cull.
+ if x0 > screen_width or x1 < 0.0 or y0 > screen_height or y1 < 0.0 then
+ input.resample_effect:choose(ResizeEffect) -- Low-quality resizing.
+ input.resample_effect:set_int("width", 1)
+ input.resample_effect:set_int("height", 1)
+ input.padding_effect:set_int("left", screen_width + 100)
+ input.padding_effect:set_int("top", screen_height + 100)
+ return
+ end
+
+ local srcx0 = 0.0
+ local srcx1 = 1.0
+ local srcy0 = 0.0
+ local srcy1 = 1.0
+
+ -- Clip.
+ if x0 < 0 then
+ srcx0 = -x0 / (x1 - x0)
+ x0 = 0
+ end
+ if y0 < 0 then
+ srcy0 = -y0 / (y1 - y0)
+ y0 = 0
+ end
+ if x1 > screen_width then
+ srcx1 = (screen_width - x0) / (x1 - x0)
+ x1 = screen_width
+ end
+ if y1 > screen_height then
+ srcy1 = (screen_height - y0) / (y1 - y0)
+ y1 = screen_height
+ end
+
+ if hq then
+ -- High-quality resampling.
+ local resample_effect = input.resample_effect:choose(ResampleEffect)
+
+ local x_subpixel_offset = x0 - math.floor(x0)
+ local y_subpixel_offset = y0 - math.floor(y0)
+
+ -- Resampling must be to an integral number of pixels. Round up,
+ -- and then add an extra pixel so we have some leeway for the border.
+ local width = math.ceil(x1 - x0) + 1
+ local height = math.ceil(y1 - y0) + 1
+ input.resample_effect:set_int("width", width)
+ input.resample_effect:set_int("height", height)
+
+ -- Correct the discrepancy with zoom. (This will leave a small
+ -- excess edge of pixels and subpixels, which we'll correct for soon.)
+ local zoom_x = (x1 - x0) / (width * (srcx1 - srcx0))
+ local zoom_y = (y1 - y0) / (height * (srcy1 - srcy0))
+ resample_effect:set_float("zoom_x", zoom_x) -- Use the actual effect specialization; specific to ResampleEffect.
+ resample_effect:set_float("zoom_y", zoom_y)
+ resample_effect:set_float("zoom_center_x", 0.0)
+ resample_effect:set_float("zoom_center_y", 0.0)
+
+ -- Padding must also be to a whole-pixel offset.
+ input.padding_effect:set_int("left", math.floor(x0))
+ input.padding_effect:set_int("top", math.floor(y0))
+
+ -- Correct _that_ discrepancy by subpixel offset in the resampling.
+ resample_effect:set_float("left", srcx0 * input_width - x_subpixel_offset / zoom_x)
+ resample_effect:set_float("top", srcy0 * input_height - y_subpixel_offset / zoom_y)
+
+ -- Finally, adjust the border so it is exactly where we want it.
+ input.padding_effect:set_float("border_offset_left", x_subpixel_offset)
+ input.padding_effect:set_float("border_offset_right", x1 - (math.floor(x0) + width))
+ input.padding_effect:set_float("border_offset_top", y_subpixel_offset)
+ input.padding_effect:set_float("border_offset_bottom", y1 - (math.floor(y0) + height))
+ else
+ -- Lower-quality simple resizing.
+ input.resample_effect:choose(ResizeEffect)
+
+ local width = round(x1 - x0)
+ local height = round(y1 - y0)
+ input.resample_effect:set_int("width", width)
+ input.resample_effect:set_int("height", height)
+
+ -- Padding must also be to a whole-pixel offset.
+ input.padding_effect:set_int("left", math.floor(x0))
+ input.padding_effect:set_int("top", math.floor(y0))
+
+ -- No subpixel stuff.
+ input.padding_effect:set_float("border_offset_left", 0.0)
+ input.padding_effect:set_float("border_offset_right", 0.0)
+ input.padding_effect:set_float("border_offset_top", 0.0)
+ input.padding_effect:set_float("border_offset_bottom", 0.0)