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 .blend import affine_composite
|
||||||
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag
|
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 .geo import Shape, DrawParams
|
||||||
from .util import VerboseOutput
|
from .util import VerboseOutput
|
||||||
|
|
||||||
@ -154,14 +154,33 @@ class AFPRenderer(VerboseOutput):
|
|||||||
data.parse()
|
data.parse()
|
||||||
self.swfs[name] = data
|
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.
|
# Given a path to a SWF root animation, attempt to render it to a list of frames.
|
||||||
for name, swf in self.swfs.items():
|
for name, swf in self.swfs.items():
|
||||||
if swf.exported_name == path:
|
if swf.exported_name == path:
|
||||||
# This is the SWF we care about.
|
# This is the SWF we care about.
|
||||||
with self.debugging(verbose):
|
with self.debugging(verbose):
|
||||||
swf.color = background_color or swf.color
|
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!')
|
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.
|
# We didn't find the tag we were after.
|
||||||
return None
|
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.
|
# First, let's attempt to resolve imports.
|
||||||
self.__registered_objects = self.__handle_imports(swf)
|
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
|
spf = 1.0 / swf.fps
|
||||||
frames: List[Image.Image] = []
|
frames: List[Image.Image] = []
|
||||||
frameno: int = 0
|
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.
|
# Create a root clip for the movie to play.
|
||||||
root_clip = PlacedClip(
|
root_clip = PlacedClip(
|
||||||
-1,
|
-1,
|
||||||
-1,
|
-1,
|
||||||
Point.identity(),
|
Point.identity(),
|
||||||
Matrix.identity(),
|
movie_transform,
|
||||||
Color(1.0, 1.0, 1.0, 1.0),
|
Color(1.0, 1.0, 1.0, 1.0),
|
||||||
Color(0.0, 0.0, 0.0, 0.0),
|
Color(0.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
|
# 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
|
# get the layering correct, but its important to preserve the original
|
||||||
# insertion order for delete requests.
|
# 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)
|
curimage = self.__render_object(curimage, root_clip, root_clip.transform, root_clip.rotation_offset, only_depths=only_depths)
|
||||||
else:
|
else:
|
||||||
# Nothing changed, make a copy of the previous render.
|
# Nothing changed, make a copy of the previous render.
|
||||||
@ -688,6 +711,6 @@ class AFPRenderer(VerboseOutput):
|
|||||||
frameno += 1
|
frameno += 1
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
# Allow ctrl-c to end early and render a partial animation.
|
# 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
|
return int(spf * 1000.0), frames
|
||||||
|
@ -9,7 +9,7 @@ import textwrap
|
|||||||
from PIL import Image, ImageDraw # type: ignore
|
from PIL import Image, ImageDraw # type: ignore
|
||||||
from typing import Any, Dict, List
|
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
|
from bemani.format import IFS
|
||||||
|
|
||||||
|
|
||||||
@ -273,6 +273,12 @@ def main() -> int:
|
|||||||
default=None,
|
default=None,
|
||||||
help="Only render objects on these depth planes. Can provide either a number or a comma-separated list of numbers.",
|
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(
|
render_parser.add_argument(
|
||||||
"--disable-threads",
|
"--disable-threads",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -714,12 +720,57 @@ def main() -> int:
|
|||||||
else:
|
else:
|
||||||
color = None
|
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.
|
# Render the gif/webp frames.
|
||||||
if args.only_depths is not None:
|
if args.only_depths is not None:
|
||||||
depths = [int(d.strip()) for d in args.only_depths.split(",")]
|
depths = [int(d.strip()) for d in args.only_depths.split(",")]
|
||||||
else:
|
else:
|
||||||
depths = None
|
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:
|
if len(images) == 0:
|
||||||
raise Exception("Did not render any frames!")
|
raise Exception("Did not render any frames!")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user