1
0
mirror of synced 2025-02-20 12:40:59 +01:00

Add support for animated backgrounds, including previously rendered output.

This commit is contained in:
Jennifer Taylor 2021-08-11 21:40:01 +00:00
parent ed5b5beda5
commit c2fa122a25
2 changed files with 92 additions and 45 deletions

View File

@ -514,7 +514,7 @@ class AFPRenderer(VerboseOutput):
self,
path: str,
background_color: Optional[Color] = None,
background_image: Optional[Image.Image] = None,
background_image: Optional[List[Image.Image]] = None,
only_depths: Optional[List[int]] = None,
only_frames: Optional[List[int]] = None,
movie_transform: Matrix = Matrix.identity(),
@ -1543,7 +1543,7 @@ class AFPRenderer(VerboseOutput):
only_depths: Optional[List[int]],
only_frames: Optional[List[int]],
movie_transform: Matrix,
background_image: Optional[Image.Image],
background_image: Optional[List[Image.Image]],
) -> Generator[Image.Image, None, None]:
# First, let's attempt to resolve imports.
self.__registered_objects = self.__handle_imports(swf)
@ -1579,10 +1579,13 @@ class AFPRenderer(VerboseOutput):
self.__root = root_clip
# If we have a background image, add it to the root clip.
background_object = RegisteredImage(-1, "INVALID_REFERENCE_NAME")
background_frames = 0
if background_image:
# Stretch the image to make sure it fits the entire frame.
imgwidth = float(background_image.width)
imgheight = float(background_image.height)
# Stretch the images to make sure they fit the entire frame.
imgwidth = background_image[0].width
imgheight = background_image[0].height
background_matrix = Matrix.affine(
a=swf.location.width / imgwidth,
b=0,
@ -1591,14 +1594,18 @@ class AFPRenderer(VerboseOutput):
tx=0,
ty=0,
)
background_frames = len(background_image)
# Register the background image with the texture library.
name = f"{swf.exported_name}_inserted_background"
self.textures[name] = background_image.convert("RGBA")
# Register the background images with the texture library.
for background_frame in range(background_frames):
if background_image[background_frame].width != imgwidth or background_image[background_frame].height != imgheight:
raise Exception(f"Frame {background_frame + 1} of background image sequence has different dimensions than others!")
name = f"{swf.exported_name}_inserted_background_{background_frame}"
self.textures[name] = background_image[background_frame].convert("RGBA")
# Place an instance of this background on the root clip.
root_clip.placed_objects.append(
PlacedShape(
PlacedImage(
-1,
-1,
Point.identity(),
@ -1608,37 +1615,7 @@ class AFPRenderer(VerboseOutput):
Color(0.0, 0.0, 0.0, 0.0),
0,
None,
RegisteredShape(
-1,
# The coordinates of the rectangle of the shape in screen space.
[
Point(0, 0),
Point(imgwidth, 0),
Point(imgwidth, imgheight),
Point(0, imgheight),
],
# The coordinates of the original texture in UV space (we don't use this).
[
Point(0.0, 0.0),
Point(1.0, 0.0),
Point(1.0, 1.0),
Point(0.0, 1.0),
],
# No texture colors.
[],
[
DrawParams(
# Instantiable, includes texture.
0x3,
# The texture this should use for drawing.
name,
# The coordinates of the triangles that get drawn (we don't use this).
[0, 1, 2, 2, 1, 3],
# The blend color.
None,
),
],
),
background_object
),
)
@ -1665,6 +1642,14 @@ class AFPRenderer(VerboseOutput):
while self.__is_dirty(root_clip):
changed = self.__process_tags(root_clip, True) or changed
# Calculate a new background frame if needed.
if background_frames > 0:
background_frame = frameno % background_frames
name = f"{swf.exported_name}_inserted_background_{background_frame}"
if background_object.reference != name:
background_object.reference = name
changed = True
# Adjust camera based on the movie's scaling.
if self.__camera is not None and not self.__camera.adjusted:
self.__camera.center = movie_transform.multiply_point(self.__camera.center)

View File

@ -8,7 +8,7 @@ import os.path
import sys
import textwrap
from PIL import Image, ImageDraw # type: ignore
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple
from bemani.format.afp import TXP2File, Shape, SWF, Frame, Tag, AP2DoActionTag, AP2PlaceObjectTag, AP2DefineSpriteTag, AFPRenderer, Color, Matrix
from bemani.format import IFS
@ -572,9 +572,65 @@ def render_path(
else:
color = None
# Allow inserting a background image.
# Allow inserting a background image, series of images or animation.
if background_image:
background = Image.open(background_image)
background_image = os.path.abspath(background_image)
background: List[Image.Image] = []
if os.path.isfile(background_image):
# This is a direct reference.
bgimg = Image.open(background_image)
frames = getattr(bgimg, "n_frames", 1)
if frames == 1:
background.append(bgimg)
elif frames > 1:
for frame in range(frames):
bgimg.seek(frame)
background.append(bgimg.copy())
else:
raise Exception("Invalid image specified as background!")
else:
# This is probably a reference to a list of images.
dirof, fileof = os.path.split(background_image)
startof, endof = os.path.splitext(fileof)
if len(startof) == 0 or len(endof) == 0:
raise Exception("Invalid image specified as background!")
startof = startof + '-'
# Gather up the sequence of files so we can make frames out of them.
seqdict: Dict[int, str] = {}
for filename in os.listdir(dirof):
if filename.startswith(startof) and filename.endswith(endof):
seqno = filename[len(startof):(-len(endof))]
if seqno.isdigit():
seqint = int(seqno)
if seqint in seqdict:
raise Exception(f"{filename} specifies the same background frame number as {seqdict[seqint]}!")
seqdict[seqint] = filename
# Now, order the sequence by the integer of the sequence number so we can load the images.
seqtuple: List[Tuple[int, str]] = sorted(
[(s, p) for (s, p) in seqdict.items()],
key=lambda e: e[0],
)
# Finally, get the filenames from this sequence.
filenames: List[str] = [os.path.join(dirof, filename) for (_, filename) in seqtuple]
# Now that we have the list, lets load the images!
for filename in filenames:
bgimg = Image.open(filename)
frames = getattr(bgimg, "n_frames", 1)
if frames == 1:
background.append(bgimg)
elif frames > 1:
for frame in range(frames):
bgimg.seek(frame)
background.append(bgimg.copy())
else:
raise Exception("Invalid image specified as background!")
else:
background = None
@ -900,7 +956,10 @@ def main() -> int:
metavar="IMAGE",
type=str,
default="out.gif",
help='The output file (ending either in .gif, .webp or .png) where the render should be saved.',
help=(
'The output file (ending either in .gif, .webp or .png) where the render should be saved. If .png is chosen then the '
'output will be a series of png files for each rendered frame.'
),
)
render_parser.add_argument(
"--background-color",
@ -912,7 +971,10 @@ def main() -> int:
"--background-image",
type=str,
default=None,
help="Set a background image to be placed behind the animation. Note that it will be stretched to fit the animation.",
help=(
"Set a background image or animation to be placed behind the animation. Note that the background will be stretched to fit "
"the animation. If a .png is specified and multiple rendered frames are present, it will use that series as an animation."
),
)
render_parser.add_argument(
"--only-depths",