Implement forced aspect ratio for renderer.
This commit is contained in:
parent
3922535555
commit
35c53c3b6c
@ -3,7 +3,7 @@ from PIL import Image # type: ignore
|
||||
|
||||
from .blend import affine_composite
|
||||
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag
|
||||
from .types import Color, Matrix, Point
|
||||
from .types import Color, Matrix, Point, Rectangle
|
||||
from .geo import Shape, DrawParams
|
||||
from .util import VerboseOutput
|
||||
|
||||
@ -154,14 +154,33 @@ class AFPRenderer(VerboseOutput):
|
||||
data.parse()
|
||||
self.swfs[name] = data
|
||||
|
||||
def render_path(self, path: str, background_color: Optional[Color] = None, verbose: bool = False, only_depths: Optional[List[int]] = None) -> Tuple[int, List[Image.Image]]:
|
||||
def render_path(
|
||||
self,
|
||||
path: str,
|
||||
background_color: Optional[Color] = None,
|
||||
only_depths: Optional[List[int]] = None,
|
||||
movie_transform: Matrix = Matrix.identity(),
|
||||
verbose: bool = False,
|
||||
) -> Tuple[int, List[Image.Image]]:
|
||||
# Given a path to a SWF root animation, attempt to render it to a list of frames.
|
||||
for name, swf in self.swfs.items():
|
||||
if swf.exported_name == path:
|
||||
# This is the SWF we care about.
|
||||
with self.debugging(verbose):
|
||||
swf.color = background_color or swf.color
|
||||
return self.__render(swf, only_depths=only_depths)
|
||||
return self.__render(swf, only_depths, movie_transform)
|
||||
|
||||
raise Exception(f'{path} not found in registered SWFs!')
|
||||
|
||||
def compute_path_location(
|
||||
self,
|
||||
path: str,
|
||||
) -> Rectangle:
|
||||
# Given a path to a SWF root animation, find its bounding rectangle.
|
||||
for name, swf in self.swfs.items():
|
||||
if swf.exported_name == path:
|
||||
# This is the SWF we care about.
|
||||
return swf.location
|
||||
|
||||
raise Exception(f'{path} not found in registered SWFs!')
|
||||
|
||||
@ -636,21 +655,25 @@ class AFPRenderer(VerboseOutput):
|
||||
# We didn't find the tag we were after.
|
||||
return None
|
||||
|
||||
def __render(self, swf: SWF, only_depths: Optional[List[int]] = None) -> Tuple[int, List[Image.Image]]:
|
||||
def __render(self, swf: SWF, only_depths: Optional[List[int]], movie_transform: Matrix) -> Tuple[int, List[Image.Image]]:
|
||||
# First, let's attempt to resolve imports.
|
||||
self.__registered_objects = self.__handle_imports(swf)
|
||||
|
||||
# Now, let's go through each frame, performing actions as necessary.
|
||||
# Initialize overall frame advancement stuff.
|
||||
spf = 1.0 / swf.fps
|
||||
frames: List[Image.Image] = []
|
||||
frameno: int = 0
|
||||
|
||||
# Calculate actual size based on given movie transform.
|
||||
actual_size = movie_transform.multiply_point(Point(swf.location.width, swf.location.height)).as_tuple()
|
||||
print(actual_size)
|
||||
|
||||
# Create a root clip for the movie to play.
|
||||
root_clip = PlacedClip(
|
||||
-1,
|
||||
-1,
|
||||
Point.identity(),
|
||||
Matrix.identity(),
|
||||
movie_transform,
|
||||
Color(1.0, 1.0, 1.0, 1.0),
|
||||
Color(0.0, 0.0, 0.0, 0.0),
|
||||
0,
|
||||
@ -676,7 +699,7 @@ class AFPRenderer(VerboseOutput):
|
||||
# Now, render out the placed objects. We sort by depth so that we can
|
||||
# get the layering correct, but its important to preserve the original
|
||||
# insertion order for delete requests.
|
||||
curimage = Image.new("RGBA", (int(swf.location.width), int(swf.location.height)), color=color.as_tuple())
|
||||
curimage = Image.new("RGBA", actual_size, color=color.as_tuple())
|
||||
curimage = self.__render_object(curimage, root_clip, root_clip.transform, root_clip.rotation_offset, only_depths=only_depths)
|
||||
else:
|
||||
# Nothing changed, make a copy of the previous render.
|
||||
@ -688,6 +711,6 @@ class AFPRenderer(VerboseOutput):
|
||||
frameno += 1
|
||||
except KeyboardInterrupt:
|
||||
# Allow ctrl-c to end early and render a partial animation.
|
||||
print(f"WARNING: Interrupted early, will render only {len(frames)} of animation!")
|
||||
print(f"WARNING: Interrupted early, will render only {len(frames)}/{len(root_clip.source.frames)} frames of animation!")
|
||||
|
||||
return int(spf * 1000.0), frames
|
||||
|
@ -9,7 +9,7 @@ import textwrap
|
||||
from PIL import Image, ImageDraw # type: ignore
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from bemani.format.afp import TXP2File, Shape, SWF, Frame, Tag, AP2DoActionTag, AP2PlaceObjectTag, AP2DefineSpriteTag, AFPRenderer, Color
|
||||
from bemani.format.afp import TXP2File, Shape, SWF, Frame, Tag, AP2DoActionTag, AP2PlaceObjectTag, AP2DefineSpriteTag, AFPRenderer, Color, Matrix
|
||||
from bemani.format import IFS
|
||||
|
||||
|
||||
@ -273,6 +273,12 @@ def main() -> int:
|
||||
default=None,
|
||||
help="Only render objects on these depth planes. Can provide either a number or a comma-separated list of numbers.",
|
||||
)
|
||||
render_parser.add_argument(
|
||||
"--force-aspect-ratio",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Force the aspect ratio of the rendered image, as a colon-separated aspect ratio such as 16:9 or 4:3.",
|
||||
)
|
||||
render_parser.add_argument(
|
||||
"--disable-threads",
|
||||
action="store_true",
|
||||
@ -714,12 +720,57 @@ def main() -> int:
|
||||
else:
|
||||
color = None
|
||||
|
||||
# Calculate the size of the SWF so we can apply scaling options.
|
||||
swf_location = renderer.compute_path_location(args.path)
|
||||
requested_width = swf_location.width
|
||||
requested_height = swf_location.height
|
||||
|
||||
# Allow overriding the aspect ratio.
|
||||
if args.force_aspect_ratio:
|
||||
ratio = args.force_aspect_ratio.split(":")
|
||||
if len(ratio) != 2:
|
||||
raise Exception("Invalid aspect ratio, specify a ratio such as 16:9 or 4:3!")
|
||||
|
||||
rx, ry = [float(r.strip()) for r in ratio]
|
||||
if rx <= 0 or ry <= 0:
|
||||
raise Exception("Ratio must only include positive numbers!")
|
||||
|
||||
actual_ratio = rx / ry
|
||||
swf_ratio = swf_location.width / swf_location.height
|
||||
|
||||
if abs(swf_ratio - actual_ratio) > 0.0001:
|
||||
new_width = actual_ratio * swf_location.height
|
||||
new_height = swf_location.width / actual_ratio
|
||||
|
||||
if new_width < swf_location.width and new_height < swf_location.height:
|
||||
raise Exception("Impossible aspect ratio!")
|
||||
if new_width > swf_location.width and new_height > swf_location.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:
|
||||
requested_width = new_width
|
||||
else:
|
||||
requested_height = new_height
|
||||
|
||||
# Calculate the overall view matrix based on the requested width/height.
|
||||
transform = Matrix(
|
||||
a=requested_width / swf_location.width,
|
||||
b=0.0,
|
||||
c=0.0,
|
||||
d=requested_height / swf_location.height,
|
||||
tx=0.0,
|
||||
ty=0.0,
|
||||
)
|
||||
print(transform)
|
||||
|
||||
# Render the gif/webp frames.
|
||||
if args.only_depths is not None:
|
||||
depths = [int(d.strip()) for d in args.only_depths.split(",")]
|
||||
else:
|
||||
depths = None
|
||||
duration, images = renderer.render_path(args.path, verbose=args.verbose, background_color=color, only_depths=depths)
|
||||
duration, images = renderer.render_path(args.path, verbose=args.verbose, background_color=color, only_depths=depths, movie_transform=transform)
|
||||
|
||||
if len(images) == 0:
|
||||
raise Exception("Did not render any frames!")
|
||||
|
Loading…
x
Reference in New Issue
Block a user