Add support for animated backgrounds, including previously rendered output.
This commit is contained in:
parent
ed5b5beda5
commit
c2fa122a25
@ -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)
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user