diff --git a/bemani/format/afp/blend/blend.py b/bemani/format/afp/blend/blend.py index b7f044c..8465e57 100644 --- a/bemani/format/afp/blend/blend.py +++ b/bemani/format/afp/blend/blend.py @@ -121,6 +121,26 @@ def blend_multiply( ) +def blend_overlay( + # RGBA color tuple representing what's already at the dest. + dest: Sequence[int], + # RGBA color tuple representing the source we want to blend to the dest. + src: Sequence[int], +) -> Sequence[int]: + # "Overlay" blend mode. Various games use the DX equation Src * Dst + Dst * Src. It appears that + # jubeat uses the alternative formula Src * Dst + Dst * (1 - As). + + # Calculate final color blending. + src_alpha = src[3] / 255.0 + src_remainder = 1.0 - src_alpha + return ( + clamp((255 * (2.0 * (dest[0] / 255.0) * (src[0] / 255.0) * src_alpha)) + (dest[0] * src_remainder)), + clamp((255 * (2.0 * (dest[1] / 255.0) * (src[1] / 255.0) * src_alpha)) + (dest[1] * src_remainder)), + clamp((255 * (2.0 * (dest[2] / 255.0) * (src[2] / 255.0) * src_alpha)) + (dest[2] * src_remainder)), + dest[3], + ) + + def blend_mask_create( # RGBA color tuple representing what's already at the dest. dest: Sequence[int], @@ -175,14 +195,14 @@ def blend_point( # premultiply by alpha, but the GL/DX equation is max(Src * As, Dst * 1). # TODO: blend mode 6, which is "darken" blending according to SWF references. Jubeat does not # premultiply by alpha, but the GL/DX equation is min(Src * As, Dst * 1). - # TODO: blend mode 10, which is "invert" according to SWF references. The only game I could find - # that implemented this had equation Src * (1 - Dst) + Dst * (1 - As). - # TODO: blend mode 13, which is "overlay" according to SWF references. The equation seems to be - # Src * Dst + Dst * Src but Jubeat thinks it should be Src * Dst + Dst * (1 - As). elif blendfunc == 8: return blend_addition(dest_color, src_color) elif blendfunc == 9 or blendfunc == 70: return blend_subtraction(dest_color, src_color) + # TODO: blend mode 10, which is "invert" according to SWF references. The only game I could find + # that implemented this had equation Src * (1 - Dst) + Dst * (1 - As). + if blendfunc == 13: + return blend_overlay(dest_color, src_color) # TODO: blend mode 75, which is not in the SWF spec and appears to have the equation # Src * (1 - Dst) + Dst * (1 - Src). elif blendfunc == 256: @@ -440,7 +460,7 @@ def affine_composite( return img # Warn if we have an unsupported blend. - if blendfunc not in {0, 1, 2, 3, 8, 9, 70, 256, 257}: + if blendfunc not in {0, 1, 2, 3, 8, 9, 13, 70, 256, 257}: print(f"WARNING: Unsupported blend {blendfunc}") return img @@ -656,7 +676,7 @@ def perspective_composite( aa_mode: int = AAMode.SSAA_ONLY, ) -> Image.Image: # Warn if we have an unsupported blend. - if blendfunc not in {0, 1, 2, 3, 8, 9, 70, 256, 257}: + if blendfunc not in {0, 1, 2, 3, 8, 9, 13, 70, 256, 257}: print(f"WARNING: Unsupported blend {blendfunc}") return img diff --git a/bemani/format/afp/blend/blendcpp.pyx b/bemani/format/afp/blend/blendcpp.pyx index 776eedc..707d476 100644 --- a/bemani/format/afp/blend/blendcpp.pyx +++ b/bemani/format/afp/blend/blendcpp.pyx @@ -59,6 +59,10 @@ def affine_composite( single_threaded: bool = False, aa_mode: int = AAMode.SSAA_OR_BILINEAR, ) -> Image.Image: + if blendfunc not in {0, 1, 2, 3, 8, 9, 13, 70, 256, 257}: + print(f"WARNING: Unsupported blend {blendfunc}") + return img + # Calculate the inverse so we can map canvas space back to texture space. try: inverse = transform.inverse() @@ -68,10 +72,6 @@ def affine_composite( # be drawn. return img - if blendfunc not in {0, 1, 2, 3, 8, 9, 70, 256, 257}: - print(f"WARNING: Unsupported blend {blendfunc}") - return img - # These are calculated properties and caching them outside of the loop # speeds things up a bit. imgwidth = img.width @@ -166,7 +166,7 @@ def perspective_composite( single_threaded: bool = False, aa_mode: int = AAMode.SSAA_ONLY, ) -> Image.Image: - if blendfunc not in {0, 1, 2, 3, 8, 9, 70, 256, 257}: + if blendfunc not in {0, 1, 2, 3, 8, 9, 13, 70, 256, 257}: print(f"WARNING: Unsupported blend {blendfunc}") return img diff --git a/bemani/format/afp/blend/blendcppimpl.cxx b/bemani/format/afp/blend/blendcppimpl.cxx index fff057d..8d6a8fe 100644 --- a/bemani/format/afp/blend/blendcppimpl.cxx +++ b/bemani/format/afp/blend/blendcppimpl.cxx @@ -196,6 +196,24 @@ extern "C" }; } + intcolor_t blend_overlay( + intcolor_t dest, + intcolor_t src + ) { + // "Overlay" blend mode. Various games use the DX equation Src * Dst + Dst * Src. It appears that + // jubeat uses the alternative formula Src * Dst + Dst * (1 - As). + + // Calculate final color blending. + double src_alpha = src.a / 255.0; + double src_remainder = 1.0 - src_alpha; + return (intcolor_t){ + clamp((255 * (2.0 * (dest.r / 255.0) * (src.r / 255.0) * src_alpha)) + (dest.r * src_remainder)), + clamp((255 * (2.0 * (dest.g / 255.0) * (src.g / 255.0) * src_alpha)) + (dest.g * src_remainder)), + clamp((255 * (2.0 * (dest.b / 255.0) * (src.b / 255.0) * src_alpha)) + (dest.b * src_remainder)), + dest.a, + }; + } + intcolor_t blend_mask_create( intcolor_t dest, intcolor_t src @@ -247,16 +265,17 @@ extern "C" // premultiply by alpha, but the GL/DX equation is max(Src * As, Dst * 1). // TODO: blend mode 6, which is "darken" blending according to SWF references. Jubeat does not // premultiply by alpha, but the GL/DX equation is min(Src * As, Dst * 1). - // TODO: blend mode 10, which is "invert" according to SWF references. The only game I could find - // that implemented this had equation Src * (1 - Dst) + Dst * (1 - As). - // TODO: blend mode 13, which is "overlay" according to SWF references. The equation seems to be - // Src * Dst + Dst * Src but Jubeat thinks it should be Src * Dst + Dst * (1 - As). if (blendfunc == 8) { return blend_addition(dest_color, src_color); } if (blendfunc == 9 || blendfunc == 70) { return blend_subtraction(dest_color, src_color); } + // TODO: blend mode 10, which is "invert" according to SWF references. The only game I could find + // that implemented this had equation Src * (1 - Dst) + Dst * (1 - As). + if (blendfunc == 13) { + return blend_overlay(dest_color, src_color); + } if (blendfunc == 256) { return blend_mask_combine(dest_color, src_color); }