Implement what I think are the correct blending modes. Fixes some older Pop'n animations.
This commit is contained in:
parent
5a499a0f2c
commit
81c4496269
11
README.md
11
README.md
@ -27,6 +27,17 @@ I think there are more complete and more accurate programs out there. However,
|
|||||||
they all lack source as far as I could tell, so I developed this. Run it like
|
they all lack source as far as I could tell, so I developed this. Run it like
|
||||||
`./2dxutils --help` to see help output and determine how to use this.
|
`./2dxutils --help` to see help output and determine how to use this.
|
||||||
|
|
||||||
|
## afputils
|
||||||
|
|
||||||
|
Utilities for working with several animation formats found across a vast range
|
||||||
|
of games. This includes a TXP2 container parser, a GE2D shape parser and an
|
||||||
|
AFP/BSI parser. Together, they make a set of utilities that attempts to render
|
||||||
|
animations out of IFS and TXP2 files as found in various games. Note that this
|
||||||
|
format is similar to SWF and thus very complicated. Therefore, it is unlikely
|
||||||
|
that these tools will correctly render all animations from all games that it
|
||||||
|
encounters. Run it like `./afputils --help` to see help output and determine
|
||||||
|
how to use it.
|
||||||
|
|
||||||
## api
|
## api
|
||||||
|
|
||||||
Development version of this repository's BEMAPI implementation. Run it like
|
Development version of this repository's BEMAPI implementation. Run it like
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, List, Tuple, Optional
|
from typing import Dict, List, Tuple, Optional, Union
|
||||||
from PIL import Image # type: ignore
|
from PIL import Image # type: ignore
|
||||||
|
|
||||||
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag
|
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag
|
||||||
@ -285,6 +285,7 @@ class AFPRenderer(VerboseOutput):
|
|||||||
# Calculate add color if it is present.
|
# Calculate add color if it is present.
|
||||||
add_color = (tag.add_color or Color(0.0, 0.0, 0.0, 0.0)).as_tuple()
|
add_color = (tag.add_color or Color(0.0, 0.0, 0.0, 0.0)).as_tuple()
|
||||||
mult_color = tag.mult_color or Color(1.0, 1.0, 1.0, 1.0)
|
mult_color = tag.mult_color or Color(1.0, 1.0, 1.0, 1.0)
|
||||||
|
blend = tag.blend or 0
|
||||||
|
|
||||||
# Now, render out shapes.
|
# Now, render out shapes.
|
||||||
for params in shape.draw_params:
|
for params in shape.draw_params:
|
||||||
@ -320,7 +321,8 @@ class AFPRenderer(VerboseOutput):
|
|||||||
transform.b == 0.0 and
|
transform.b == 0.0 and
|
||||||
transform.c == 0.0 and
|
transform.c == 0.0 and
|
||||||
transform.a == 1.0 and
|
transform.a == 1.0 and
|
||||||
transform.d == 1.0
|
transform.d == 1.0 and
|
||||||
|
blend == 0
|
||||||
):
|
):
|
||||||
# We can!
|
# We can!
|
||||||
cutin = transform.multiply_point(Point.identity().subtract(origin))
|
cutin = transform.multiply_point(Point.identity().subtract(origin))
|
||||||
@ -365,39 +367,119 @@ class AFPRenderer(VerboseOutput):
|
|||||||
|
|
||||||
# Blend it.
|
# Blend it.
|
||||||
texoff = texx + (texy * texture.width)
|
texoff = texx + (texy * texture.width)
|
||||||
imgmap[imgoff] = self.__blend(imgmap[imgoff], texmap[texoff], mult_color, add_color)
|
|
||||||
|
if blend == 0:
|
||||||
|
imgmap[imgoff] = self.__blend_normal(imgmap[imgoff], texmap[texoff], mult_color, add_color)
|
||||||
|
elif blend == 8:
|
||||||
|
imgmap[imgoff] = self.__blend_additive(imgmap[imgoff], texmap[texoff], mult_color, add_color)
|
||||||
|
elif blend == 9:
|
||||||
|
imgmap[imgoff] = self.__blend_subtractive(imgmap[imgoff], texmap[texoff], mult_color, add_color)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unsupported blend {blend}")
|
||||||
|
|
||||||
img.putdata(imgmap)
|
img.putdata(imgmap)
|
||||||
|
|
||||||
def __blend(
|
def __clamp(self, color: Union[float, int]) -> int:
|
||||||
|
return min(max(0, round(color)), 255)
|
||||||
|
|
||||||
|
def __blend_normal(
|
||||||
self,
|
self,
|
||||||
bg: Tuple[int, int, int, int],
|
# RGBA color tuple representing what's already at the dest.
|
||||||
fg: Tuple[int, int, int, int],
|
dest: Tuple[int, int, int, int],
|
||||||
|
# RGBA color tuple representing the source we want to blend to the dest.
|
||||||
|
src: Tuple[int, int, int, int],
|
||||||
|
# A pre-scaled color where all values are 0.0-1.0, used to calculate the final color.
|
||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
|
# A RGBA color tuple where all values are 0-255, used to calculate the final color.
|
||||||
add_color: Tuple[int, int, int, int],
|
add_color: Tuple[int, int, int, int],
|
||||||
) -> Tuple[int, int, int, int]:
|
) -> Tuple[int, int, int, int]:
|
||||||
# Calculate multiplicative and additive colors.
|
# Calculate multiplicative and additive colors against the source.
|
||||||
fg = (
|
src = (
|
||||||
min(int(fg[0] * mult_color.r) + add_color[0], 255),
|
self.__clamp((src[0] * mult_color.r) + add_color[0]),
|
||||||
min(int(fg[1] * mult_color.g) + add_color[1], 255),
|
self.__clamp((src[1] * mult_color.g) + add_color[1]),
|
||||||
min(int(fg[2] * mult_color.b) + add_color[2], 255),
|
self.__clamp((src[2] * mult_color.b) + add_color[2]),
|
||||||
min(int(fg[3] * mult_color.a) + add_color[3], 255),
|
self.__clamp((src[3] * mult_color.a) + add_color[3]),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Short circuit for speed.
|
# Short circuit for speed.
|
||||||
if fg[3] == 0:
|
if src[3] == 0:
|
||||||
return bg
|
return dest
|
||||||
if fg[3] == 255:
|
if src[3] == 255:
|
||||||
return fg
|
return src
|
||||||
|
|
||||||
# Calculate alpha blending.
|
# Calculate alpha blending.
|
||||||
fgpercent = (float(fg[3]) / 255.0)
|
srcpercent = (float(src[3]) / 255.0)
|
||||||
bgpercent = 1.0 - fgpercent
|
destpercent = (float(dest[3]) / 255.0)
|
||||||
|
destremainder = 1.0 - srcpercent
|
||||||
return (
|
return (
|
||||||
int(float(bg[0]) * bgpercent + float(fg[0]) * fgpercent),
|
self.__clamp((float(dest[0]) * destpercent * destremainder) + (float(src[0]) * srcpercent)),
|
||||||
int(float(bg[1]) * bgpercent + float(fg[1]) * fgpercent),
|
self.__clamp((float(dest[1]) * destpercent * destremainder) + (float(src[1]) * srcpercent)),
|
||||||
int(float(bg[2]) * bgpercent + float(fg[2]) * fgpercent),
|
self.__clamp((float(dest[2]) * destpercent * destremainder) + (float(src[2]) * srcpercent)),
|
||||||
255,
|
self.__clamp(255 * (srcpercent + destpercent * destremainder)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __blend_additive(
|
||||||
|
self,
|
||||||
|
# RGBA color tuple representing what's already at the dest.
|
||||||
|
dest: Tuple[int, int, int, int],
|
||||||
|
# RGBA color tuple representing the source we want to blend to the dest.
|
||||||
|
src: Tuple[int, int, int, int],
|
||||||
|
# A pre-scaled color where all values are 0.0-1.0, used to calculate the final color.
|
||||||
|
mult_color: Color,
|
||||||
|
# A RGBA color tuple where all values are 0-255, used to calculate the final color.
|
||||||
|
add_color: Tuple[int, int, int, int],
|
||||||
|
) -> Tuple[int, int, int, int]:
|
||||||
|
# Calculate multiplicative and additive colors against the source.
|
||||||
|
src = (
|
||||||
|
self.__clamp((src[0] * mult_color.r) + add_color[0]),
|
||||||
|
self.__clamp((src[1] * mult_color.g) + add_color[1]),
|
||||||
|
self.__clamp((src[2] * mult_color.b) + add_color[2]),
|
||||||
|
self.__clamp((src[3] * mult_color.a) + add_color[3]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Short circuit for speed.
|
||||||
|
if src[3] == 0:
|
||||||
|
return dest
|
||||||
|
|
||||||
|
# Calculate alpha blending.
|
||||||
|
srcpercent = (float(src[3]) / 255.0)
|
||||||
|
return (
|
||||||
|
self.__clamp(dest[0] + (float(src[0]) * srcpercent)),
|
||||||
|
self.__clamp(dest[1] + (float(src[1]) * srcpercent)),
|
||||||
|
self.__clamp(dest[2] + (float(src[2]) * srcpercent)),
|
||||||
|
self.__clamp(dest[3] + (255 * srcpercent)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __blend_subtractive(
|
||||||
|
self,
|
||||||
|
# RGBA color tuple representing what's already at the dest.
|
||||||
|
dest: Tuple[int, int, int, int],
|
||||||
|
# RGBA color tuple representing the source we want to blend to the dest.
|
||||||
|
src: Tuple[int, int, int, int],
|
||||||
|
# A pre-scaled color where all values are 0.0-1.0, used to calculate the final color.
|
||||||
|
mult_color: Color,
|
||||||
|
# A RGBA color tuple where all values are 0-255, used to calculate the final color.
|
||||||
|
add_color: Tuple[int, int, int, int],
|
||||||
|
) -> Tuple[int, int, int, int]:
|
||||||
|
# Calculate multiplicative and additive colors against the source.
|
||||||
|
src = (
|
||||||
|
self.__clamp((src[0] * mult_color.r) + add_color[0]),
|
||||||
|
self.__clamp((src[1] * mult_color.g) + add_color[1]),
|
||||||
|
self.__clamp((src[2] * mult_color.b) + add_color[2]),
|
||||||
|
self.__clamp((src[3] * mult_color.a) + add_color[3]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Short circuit for speed.
|
||||||
|
if src[3] == 0:
|
||||||
|
return dest
|
||||||
|
|
||||||
|
# Calculate alpha blending.
|
||||||
|
srcpercent = (float(src[3]) / 255.0)
|
||||||
|
return (
|
||||||
|
self.__clamp(dest[0] - (float(src[0]) * srcpercent)),
|
||||||
|
self.__clamp(dest[1] - (float(src[1]) * srcpercent)),
|
||||||
|
self.__clamp(dest[2] - (float(src[2]) * srcpercent)),
|
||||||
|
self.__clamp(dest[3] - (255 * srcpercent)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __render(self, swf: SWF, export_tag: Optional[str]) -> Tuple[int, List[Image.Image]]:
|
def __render(self, swf: SWF, export_tag: Optional[str]) -> Tuple[int, List[Image.Image]]:
|
||||||
|
@ -576,7 +576,7 @@ def main() -> int:
|
|||||||
if fmt in ["GIF", "WEBP"]:
|
if fmt in ["GIF", "WEBP"]:
|
||||||
# Write all the frames out in one file.
|
# Write all the frames out in one file.
|
||||||
with open(args.output, "wb") as bfp:
|
with open(args.output, "wb") as bfp:
|
||||||
images[0].save(bfp, format=fmt, save_all=True, append_images=images[1:], duration=[duration] * len(images))
|
images[0].save(bfp, format=fmt, save_all=True, append_images=images[1:], duration=duration, optimize=True)
|
||||||
|
|
||||||
print(f"Wrote animation to {args.output}")
|
print(f"Wrote animation to {args.output}")
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user