Implement ability to override the canvas size, to render animations that overflow the canvas.
This commit is contained in:
parent
9dd3772380
commit
d39a660eab
@ -191,6 +191,10 @@ class PlacedClip(PlacedObject):
|
||||
self.requested_frame: Optional[int] = None
|
||||
self.visible_frame: int = -1
|
||||
|
||||
# Root clip resizing, which we don't really support.
|
||||
self.__width = 0
|
||||
self.__height = 0
|
||||
|
||||
@property
|
||||
def source(self) -> RegisteredClip:
|
||||
return self.__source
|
||||
@ -299,6 +303,30 @@ class PlacedClip(PlacedObject):
|
||||
def _visible(self, val: Any) -> None:
|
||||
self.visible = val != 0
|
||||
|
||||
@property
|
||||
def _width(self) -> int:
|
||||
calculated_width = self.__width
|
||||
for obj in self.placed_objects:
|
||||
if isinstance(obj, PlacedClip):
|
||||
calculated_width = max(calculated_width, obj._width)
|
||||
return calculated_width
|
||||
|
||||
@_width.setter
|
||||
def _width(self, val: Any) -> None:
|
||||
self.__width = val
|
||||
|
||||
@property
|
||||
def _height(self) -> int:
|
||||
calculated_height = self.__height
|
||||
for obj in self.placed_objects:
|
||||
if isinstance(obj, PlacedClip):
|
||||
calculated_height = max(calculated_height, obj._height)
|
||||
return calculated_height
|
||||
|
||||
@_height.setter
|
||||
def _height(self, val: Any) -> None:
|
||||
self.__height = val
|
||||
|
||||
|
||||
class PlacedImage(PlacedObject):
|
||||
# An image that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag
|
||||
@ -612,6 +640,8 @@ class AFPRenderer(VerboseOutput):
|
||||
only_depths: Optional[List[int]] = None,
|
||||
only_frames: Optional[List[int]] = None,
|
||||
movie_transform: Matrix = Matrix.identity(),
|
||||
overridden_width: Optional[float] = None,
|
||||
overridden_height: Optional[float] = None,
|
||||
verbose: bool = False,
|
||||
) -> Generator[Image.Image, None, None]:
|
||||
# Given a path to a SWF root animation, attempt to render it to a list of frames.
|
||||
@ -620,7 +650,7 @@ class AFPRenderer(VerboseOutput):
|
||||
# This is the SWF we care about.
|
||||
with self.debugging(verbose):
|
||||
swf.color = background_color or swf.color
|
||||
yield from self.__render(swf, only_depths, only_frames, movie_transform, background_image)
|
||||
yield from self.__render(swf, only_depths, only_frames, movie_transform, background_image, overridden_width, overridden_height)
|
||||
return
|
||||
|
||||
raise Exception(f'{path} not found in registered SWFs!')
|
||||
@ -1642,6 +1672,8 @@ class AFPRenderer(VerboseOutput):
|
||||
only_frames: Optional[List[int]],
|
||||
movie_transform: Matrix,
|
||||
background_image: Optional[List[Image.Image]],
|
||||
overridden_width: Optional[float],
|
||||
overridden_height: Optional[float],
|
||||
) -> Generator[Image.Image, None, None]:
|
||||
# First, let's attempt to resolve imports.
|
||||
self.__registered_objects = self.__handle_imports(swf)
|
||||
@ -1651,10 +1683,14 @@ class AFPRenderer(VerboseOutput):
|
||||
frameno: int = 0
|
||||
|
||||
# Calculate actual size based on given movie transform.
|
||||
resized_width, resized_height, _ = movie_transform.multiply_point(Point(swf.location.width, swf.location.height)).as_tuple()
|
||||
actual_width = overridden_width or swf.location.width
|
||||
actual_height = overridden_height or swf.location.height
|
||||
resized_width, resized_height, _ = movie_transform.multiply_point(Point(actual_width, actual_height)).as_tuple()
|
||||
|
||||
# TODO: If the location top/left is nonzero, we need move the root transform
|
||||
# so that the correct viewport is rendered.
|
||||
if round(swf.location.top, 2) != 0.0 or round(swf.location.left, 2) != 0.0:
|
||||
# TODO: If the location top/left is nonzero, we need move the root transform
|
||||
# so that the correct viewport is rendered.
|
||||
print("WARNING: Root clip requested to play not in top-left corner!")
|
||||
|
||||
# Create a root clip for the movie to play.
|
||||
root_clip = PlacedClip(
|
||||
@ -1674,6 +1710,10 @@ class AFPRenderer(VerboseOutput):
|
||||
swf.labels,
|
||||
),
|
||||
)
|
||||
root_clip._width = int(actual_width)
|
||||
root_clip._height = int(actual_height)
|
||||
last_width = actual_width
|
||||
last_height = actual_height
|
||||
self.__root = root_clip
|
||||
|
||||
# If we have a background image, add it to the root clip.
|
||||
@ -1686,10 +1726,10 @@ class AFPRenderer(VerboseOutput):
|
||||
imgwidth = background_image[0].width
|
||||
imgheight = background_image[0].height
|
||||
background_matrix = Matrix.affine(
|
||||
a=swf.location.width / imgwidth,
|
||||
a=actual_width / imgwidth,
|
||||
b=0,
|
||||
c=0,
|
||||
d=swf.location.height / imgheight,
|
||||
d=actual_height / imgheight,
|
||||
tx=0,
|
||||
ty=0,
|
||||
)
|
||||
@ -1772,6 +1812,12 @@ class AFPRenderer(VerboseOutput):
|
||||
continue
|
||||
|
||||
if changed or last_rendered_frame is None:
|
||||
if last_width != root_clip._width or last_height != root_clip._height:
|
||||
last_width = root_clip._width
|
||||
last_height = root_clip._height
|
||||
if root_clip._width > actual_width or root_clip._height > actual_height:
|
||||
print(f"WARNING: Root clip requested to resize to {last_width}x{last_height} which overflows root canvas!")
|
||||
|
||||
# Now, render out the placed objects.
|
||||
color = swf.color or Color(0.0, 0.0, 0.0, 0.0)
|
||||
curimage = Image.new("RGBA", (resized_width, resized_height), color=color.as_tuple())
|
||||
|
@ -567,6 +567,8 @@ def render_path(
|
||||
background_loop_start: Optional[int] = None,
|
||||
background_loop_end: Optional[int] = None,
|
||||
background_loop_offset: Optional[int] = None,
|
||||
override_width: Optional[int] = None,
|
||||
override_height: Optional[int] = None,
|
||||
force_width: Optional[int] = None,
|
||||
force_height: Optional[int] = None,
|
||||
force_aspect_ratio: Optional[str] = None,
|
||||
@ -688,8 +690,16 @@ def render_path(
|
||||
|
||||
# Calculate the size of the animation so we can apply scaling options.
|
||||
swf_location = renderer.compute_path_location(path)
|
||||
requested_width = force_width if force_width is not None else swf_location.width
|
||||
requested_height = force_height if force_height is not None else swf_location.height
|
||||
if override_width is not None:
|
||||
actual_width = float(override_width)
|
||||
else:
|
||||
actual_width = swf_location.width
|
||||
if override_height is not None:
|
||||
actual_height = float(override_height)
|
||||
else:
|
||||
actual_height = swf_location.height
|
||||
requested_width = force_width if force_width is not None else actual_width
|
||||
requested_height = force_height if force_height is not None else actual_height
|
||||
|
||||
# Allow overriding the aspect ratio.
|
||||
if force_aspect_ratio:
|
||||
@ -702,20 +712,20 @@ def render_path(
|
||||
raise Exception("Ratio must only include positive numbers!")
|
||||
|
||||
actual_ratio = rx / ry
|
||||
swf_ratio = swf_location.width / swf_location.height
|
||||
swf_ratio = actual_width / actual_height
|
||||
|
||||
if abs(swf_ratio - actual_ratio) > 0.0001:
|
||||
new_width = actual_ratio * swf_location.height
|
||||
new_height = swf_location.width / actual_ratio
|
||||
new_width = actual_ratio * actual_height
|
||||
new_height = actual_width / actual_ratio
|
||||
|
||||
if new_width < swf_location.width and new_height < swf_location.height:
|
||||
if new_width < actual_width and new_height < actual_height:
|
||||
raise Exception("Impossible aspect ratio!")
|
||||
if new_width > swf_location.width and new_height > swf_location.height:
|
||||
if new_width > actual_width and new_height > actual_height:
|
||||
raise Exception("Impossible aspect ratio!")
|
||||
|
||||
# We know that one is larger and one is smaller, pick the larger.
|
||||
# This way we always stretch instead of shrinking.
|
||||
if new_width > swf_location.width:
|
||||
if new_width > actual_width:
|
||||
requested_width = new_width
|
||||
else:
|
||||
requested_height = new_height
|
||||
@ -726,10 +736,10 @@ def render_path(
|
||||
|
||||
# Calculate the overall view matrix based on the requested width/height.
|
||||
transform = Matrix.affine(
|
||||
a=requested_width / swf_location.width,
|
||||
a=requested_width / actual_width,
|
||||
b=0.0,
|
||||
c=0.0,
|
||||
d=requested_height / swf_location.height,
|
||||
d=requested_height / actual_height,
|
||||
tx=0.0,
|
||||
ty=0.0,
|
||||
)
|
||||
@ -760,6 +770,8 @@ def render_path(
|
||||
only_depths=requested_depths,
|
||||
only_frames=requested_frames,
|
||||
movie_transform=transform,
|
||||
overridden_width=override_width,
|
||||
overridden_height=override_height,
|
||||
)
|
||||
):
|
||||
if show_progress:
|
||||
@ -1147,6 +1159,24 @@ def main() -> int:
|
||||
"the animation's original height it will be stretched vertically."
|
||||
),
|
||||
)
|
||||
render_parser.add_argument(
|
||||
"--override-width",
|
||||
type=int,
|
||||
default=None,
|
||||
help=(
|
||||
"Override the specified the width of the rendered animation to a specific pixel value, such as 640 or 800. Note that this performs "
|
||||
"no scaling whatsoever. It simply overrides the animation's root canvas size."
|
||||
),
|
||||
)
|
||||
render_parser.add_argument(
|
||||
"--override-height",
|
||||
type=int,
|
||||
default=None,
|
||||
help=(
|
||||
"Override the specified the height of the rendered animation to a specific pixel value, such as 640 or 800. Note that this performs "
|
||||
"no scaling whatsoever. It simply overrides the animation's root canvas size."
|
||||
),
|
||||
)
|
||||
render_parser.add_argument(
|
||||
"--force-aspect-ratio",
|
||||
type=str,
|
||||
@ -1247,6 +1277,8 @@ def main() -> int:
|
||||
background_loop_start=args.background_loop_start,
|
||||
background_loop_end=args.background_loop_end,
|
||||
background_loop_offset=args.background_loop_offset,
|
||||
override_width=args.override_width,
|
||||
override_height=args.override_height,
|
||||
force_width=args.force_width,
|
||||
force_height=args.force_height,
|
||||
force_aspect_ratio=args.force_aspect_ratio,
|
||||
|
Loading…
x
Reference in New Issue
Block a user