1
0
mirror of synced 2024-11-14 09:57:36 +01:00

Move affine transform function out into blend for slight cPython boost and also preparation for threading.

This commit is contained in:
Jennifer Taylor 2021-05-16 00:20:23 +00:00
parent 187783696b
commit 85606fece9
2 changed files with 101 additions and 71 deletions

View File

@ -1,6 +1,7 @@
from typing import Tuple from PIL import Image # type: ignore
from typing import List, Tuple
from .types.generic import Color from .types.generic import Color, Matrix, Point
def clamp(color: float) -> int: def clamp(color: float) -> int:
@ -153,3 +154,98 @@ def blend_multiply(
clamp(255 * ((dest[2] / 255.0) * (src[2] / 255.0))), clamp(255 * ((dest[2] / 255.0) * (src[2] / 255.0))),
clamp(255 * ((dest[3] / 255.0) * (src[3] / 255.0))), clamp(255 * ((dest[3] / 255.0) * (src[3] / 255.0))),
) )
def affine_composite(
img: Image.Image,
add_color: Tuple[int, int, int, int],
mult_color: Color,
transform: Matrix,
inverse: Matrix,
origin: Point,
blendfunc: int,
texture: Image.Image,
) -> List[Tuple[int, int, int, int]]:
# Get the data in an easier to manipulate and faster to update fashion.
imgmap = list(img.getdata())
texmap = list(texture.getdata())
# Warn if we have an unsupported blend.
if blendfunc not in {0, 2, 3, 8, 9, 70}:
print(f"WARNING: Unsupported blend {blendfunc}")
# These are calculated properties and caching them outside of the loop
# speeds things up a bit.
imgwidth = img.width
imgheight = img.height
texwidth = texture.width
texheight = texture.height
# Calculate the maximum range of update this texture can possibly reside in.
pix1 = transform.multiply_point(Point.identity().subtract(origin))
pix2 = transform.multiply_point(Point.identity().subtract(origin).add(Point(texwidth, 0)))
pix3 = transform.multiply_point(Point.identity().subtract(origin).add(Point(0, texheight)))
pix4 = transform.multiply_point(Point.identity().subtract(origin).add(Point(texwidth, texheight)))
# Map this to the rectangle we need to sweep in the rendering image.
minx = max(int(min(pix1.x, pix2.x, pix3.x, pix4.x)), 0)
maxx = min(int(max(pix1.x, pix2.x, pix3.x, pix4.x)) + 1, imgwidth)
miny = max(int(min(pix1.y, pix2.y, pix3.y, pix4.y)), 0)
maxy = min(int(max(pix1.y, pix2.y, pix3.y, pix4.y)) + 1, imgheight)
for imgy in range(miny, maxy):
for imgx in range(minx, maxx):
# Determine offset
imgoff = imgx + (imgy * imgwidth)
# Blit this pixel.
imgmap[imgoff] = affine_blend_point(imgx, imgy, imgwidth, imgheight, add_color, mult_color, imgmap[imgoff], inverse, origin, blendfunc, texwidth, texheight, texmap)
return imgmap
def affine_blend_point(
imgx: int,
imgy: int,
imgwidth: int,
imgheight: int,
add_color: Tuple[int, int, int, int],
mult_color: Color,
dest_color: Tuple[int, int, int, int],
inverse: Matrix,
origin: Point,
blendfunc: int,
texwidth: int,
texheight: int,
texmap: List[Tuple[int, int, int, int]],
) -> Tuple[int, int, int, int]:
# Calculate what texture pixel data goes here.
texloc = inverse.multiply_point(Point(float(imgx), float(imgy))).add(origin)
texx, texy = texloc.as_tuple()
# If we're out of bounds, don't update.
if texx < 0 or texy < 0 or texx >= texwidth or texy >= texheight:
return dest_color
# Blend it.
texoff = texx + (texy * texwidth)
if blendfunc == 3:
return blend_multiply(dest_color, texmap[texoff], mult_color, add_color)
# TODO: blend mode 4, which is "screen" blending according to SWF references. I've only seen this
# in Jubeat and it implements it using OpenGL equation Src * (1 - Dst) + Dst * 1.
# TODO: blend mode 5, which is "lighten" blending according to SWF references. Jubeat does not
# 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, texmap[texoff], mult_color, add_color)
elif blendfunc == 9 or blendfunc == 70:
return blend_subtraction(dest_color, texmap[texoff], mult_color, add_color)
# TODO: blend mode 75, which is not in the SWF spec and appears to have the equation
# Src * (1 - Dst) + Dst * (1 - Src).
else:
return blend_normal(dest_color, texmap[texoff], mult_color, add_color)

View File

@ -1,7 +1,7 @@
from typing import Dict, List, Tuple, Optional, Union from typing import Dict, List, Tuple, Optional, Union
from PIL import Image # type: ignore from PIL import Image # type: ignore
from .blend import blend_normal, blend_addition, blend_subtraction, blend_multiply from .blend import affine_composite
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
from .types import Color, Matrix, Point from .types import Color, Matrix, Point
from .geo import Shape, DrawParams from .geo import Shape, DrawParams
@ -448,74 +448,8 @@ class AFPRenderer(VerboseOutput):
img.alpha_composite(texture, cutin.as_tuple(), cutoff.as_tuple()) img.alpha_composite(texture, cutin.as_tuple(), cutoff.as_tuple())
else: else:
# Now, render out the texture. # We can't, so do the slow render that's correct.
imgmap = list(img.getdata()) img.putdata(affine_composite(img, add_color, mult_color, transform, inverse, origin, blend, texture))
texmap = list(texture.getdata())
# These are calculated properties and caching them outside of the loop
# speeds things up a bit.
imgwidth = img.width
imgheight = img.height
texwidth = texture.width
texheight = texture.height
# Calculate the maximum range of update this texture can possibly reside in.
pix1 = transform.multiply_point(Point.identity().subtract(origin))
pix2 = transform.multiply_point(Point.identity().subtract(origin).add(Point(texwidth, 0)))
pix3 = transform.multiply_point(Point.identity().subtract(origin).add(Point(0, texheight)))
pix4 = transform.multiply_point(Point.identity().subtract(origin).add(Point(texwidth, texheight)))
# Map this to the rectangle we need to sweep in the rendering image.
minx = max(int(min(pix1.x, pix2.x, pix3.x, pix4.x)), 0)
maxx = min(int(max(pix1.x, pix2.x, pix3.x, pix4.x)) + 1, imgwidth)
miny = max(int(min(pix1.y, pix2.y, pix3.y, pix4.y)), 0)
maxy = min(int(max(pix1.y, pix2.y, pix3.y, pix4.y)) + 1, imgheight)
announced = False
for imgy in range(miny, maxy):
for imgx in range(minx, maxx):
# Determine offset
imgoff = imgx + (imgy * imgwidth)
# Calculate what texture pixel data goes here.
texloc = inverse.multiply_point(Point(float(imgx), float(imgy))).add(origin)
texx, texy = texloc.as_tuple()
# If we're out of bounds, don't update.
if texx < 0 or texy < 0 or texx >= texwidth or texy >= texheight:
continue
# Blend it.
texoff = texx + (texy * texwidth)
if blend == 0 or blend == 2:
imgmap[imgoff] = blend_normal(imgmap[imgoff], texmap[texoff], mult_color, add_color)
elif blend == 3:
imgmap[imgoff] = blend_multiply(imgmap[imgoff], texmap[texoff], mult_color, add_color)
# TODO: blend mode 4, which is "screen" blending according to SWF references. I've only seen this
# in Jubeat and it implements it using OpenGL equation Src * (1 - Dst) + Dst * 1.
# TODO: blend mode 5, which is "lighten" blending according to SWF references. Jubeat does not
# 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 blend == 8:
imgmap[imgoff] = blend_addition(imgmap[imgoff], texmap[texoff], mult_color, add_color)
elif blend == 9 or blend == 70:
imgmap[imgoff] = blend_subtraction(imgmap[imgoff], texmap[texoff], mult_color, add_color)
# TODO: blend mode 75, which is not in the SWF spec and appears to have the equation
# Src * (1 - Dst) + Dst * (1 - Src).
else:
if not announced:
# Don't print it for every pixel.
print(f"WARNING: Unsupported blend {blend}")
announced = True
imgmap[imgoff] = blend_normal(imgmap[imgoff], texmap[texoff], mult_color, add_color)
img.putdata(imgmap)
else: else:
raise Exception(f"Unknown placed object type to render {renderable}!") raise Exception(f"Unknown placed object type to render {renderable}!")