Implement masking support.
This commit is contained in:
parent
9df33fcec3
commit
56498f6154
@ -1,7 +1,7 @@
|
|||||||
import multiprocessing
|
import multiprocessing
|
||||||
import signal
|
import signal
|
||||||
from PIL import Image # type: ignore
|
from PIL import Image # type: ignore
|
||||||
from typing import Any, List, Sequence
|
from typing import Any, List, Optional, Sequence
|
||||||
|
|
||||||
from .types.generic import Color, Matrix, Point
|
from .types.generic import Color, Matrix, Point
|
||||||
|
|
||||||
@ -122,6 +122,7 @@ except ImportError:
|
|||||||
add_color: Color,
|
add_color: Color,
|
||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
transform: Matrix,
|
transform: Matrix,
|
||||||
|
mask: Optional[Image.Image],
|
||||||
blendfunc: int,
|
blendfunc: int,
|
||||||
texture: Image.Image,
|
texture: Image.Image,
|
||||||
single_threaded: bool = False,
|
single_threaded: bool = False,
|
||||||
@ -136,7 +137,7 @@ except ImportError:
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
# Warn if we have an unsupported blend.
|
# Warn if we have an unsupported blend.
|
||||||
if blendfunc not in {0, 1, 2, 3, 8, 9, 70}:
|
if blendfunc not in {0, 1, 2, 3, 8, 9, 70, 256, 257}:
|
||||||
print(f"WARNING: Unsupported blend {blendfunc}")
|
print(f"WARNING: Unsupported blend {blendfunc}")
|
||||||
return img
|
return img
|
||||||
|
|
||||||
@ -168,6 +169,11 @@ except ImportError:
|
|||||||
# Get the data in an easier to manipulate and faster to update fashion.
|
# Get the data in an easier to manipulate and faster to update fashion.
|
||||||
imgmap = list(img.getdata())
|
imgmap = list(img.getdata())
|
||||||
texmap = list(texture.getdata())
|
texmap = list(texture.getdata())
|
||||||
|
if mask:
|
||||||
|
alpha = mask.split()[-1]
|
||||||
|
maskmap = alpha.tobytes('raw', 'L')
|
||||||
|
else:
|
||||||
|
maskmap = None
|
||||||
|
|
||||||
# We don't have enough CPU cores to bother multiprocessing.
|
# We don't have enough CPU cores to bother multiprocessing.
|
||||||
for imgy in range(miny, maxy):
|
for imgy in range(miny, maxy):
|
||||||
@ -185,12 +191,20 @@ except ImportError:
|
|||||||
|
|
||||||
# Blend it.
|
# Blend it.
|
||||||
texoff = texx + (texy * texwidth)
|
texoff = texx + (texy * texwidth)
|
||||||
|
if maskmap is not None and maskmap[imgoff] == 0:
|
||||||
|
# This pixel is masked off!
|
||||||
|
continue
|
||||||
imgmap[imgoff] = blend_point(add_color, mult_color, texmap[texoff], imgmap[imgoff], blendfunc)
|
imgmap[imgoff] = blend_point(add_color, mult_color, texmap[texoff], imgmap[imgoff], blendfunc)
|
||||||
|
|
||||||
img.putdata(imgmap)
|
img.putdata(imgmap)
|
||||||
else:
|
else:
|
||||||
imgbytes = img.tobytes('raw', 'RGBA')
|
imgbytes = img.tobytes('raw', 'RGBA')
|
||||||
texbytes = texture.tobytes('raw', 'RGBA')
|
texbytes = texture.tobytes('raw', 'RGBA')
|
||||||
|
if mask:
|
||||||
|
alpha = mask.split()[-1]
|
||||||
|
maskbytes = alpha.tobytes('raw', 'L')
|
||||||
|
else:
|
||||||
|
maskbytes = None
|
||||||
|
|
||||||
# Let's spread the load across multiple processors.
|
# Let's spread the load across multiple processors.
|
||||||
procs: List[multiprocessing.Process] = []
|
procs: List[multiprocessing.Process] = []
|
||||||
@ -223,6 +237,7 @@ except ImportError:
|
|||||||
blendfunc,
|
blendfunc,
|
||||||
imgbytes,
|
imgbytes,
|
||||||
texbytes,
|
texbytes,
|
||||||
|
maskbytes,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
procs.append(proc)
|
procs.append(proc)
|
||||||
@ -256,6 +271,33 @@ except ImportError:
|
|||||||
img = Image.frombytes('RGBA', (imgwidth, imgheight), b''.join(lines))
|
img = Image.frombytes('RGBA', (imgwidth, imgheight), b''.join(lines))
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
def blend_mask_create(
|
||||||
|
# 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]:
|
||||||
|
# Mask creating just allows a pixel to be drawn if the source image has a nonzero
|
||||||
|
# alpha, according to the SWF spec.
|
||||||
|
if src[3] != 0:
|
||||||
|
return (255, 0, 0, 255)
|
||||||
|
else:
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
|
||||||
|
def blend_mask_combine(
|
||||||
|
# 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]:
|
||||||
|
# Mask blending just takes the source and destination and ands them together, making
|
||||||
|
# a final mask that is the intersection of the original mask and the new mask. The
|
||||||
|
# reason we even have a color component to this is for debugging visibility.
|
||||||
|
if dest[3] != 0 and src[3] != 0:
|
||||||
|
return (255, 0, 0, 255)
|
||||||
|
else:
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
|
||||||
def pixel_renderer(
|
def pixel_renderer(
|
||||||
work: multiprocessing.Queue,
|
work: multiprocessing.Queue,
|
||||||
results: multiprocessing.Queue,
|
results: multiprocessing.Queue,
|
||||||
@ -270,6 +312,7 @@ except ImportError:
|
|||||||
blendfunc: int,
|
blendfunc: int,
|
||||||
imgbytes: bytes,
|
imgbytes: bytes,
|
||||||
texbytes: bytes,
|
texbytes: bytes,
|
||||||
|
maskbytes: Optional[bytes],
|
||||||
) -> None:
|
) -> None:
|
||||||
while True:
|
while True:
|
||||||
imgy = work.get()
|
imgy = work.get()
|
||||||
@ -295,6 +338,10 @@ except ImportError:
|
|||||||
|
|
||||||
# Blend it.
|
# Blend it.
|
||||||
texoff = texx + (texy * texwidth)
|
texoff = texx + (texy * texwidth)
|
||||||
|
if maskbytes is not None and maskbytes[imgoff] == 0:
|
||||||
|
# This pixel is masked off!
|
||||||
|
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
|
||||||
|
continue
|
||||||
result.append(blend_point(add_color, mult_color, texbytes[(texoff * 4):((texoff + 1) * 4)], imgbytes[(imgoff * 4):((imgoff + 1) * 4)], blendfunc))
|
result.append(blend_point(add_color, mult_color, texbytes[(texoff * 4):((texoff + 1) * 4)], imgbytes[(imgoff * 4):((imgoff + 1) * 4)], blendfunc))
|
||||||
|
|
||||||
linebytes = bytes([channel for pixel in result for channel in pixel])
|
linebytes = bytes([channel for pixel in result for channel in pixel])
|
||||||
@ -335,5 +382,11 @@ except ImportError:
|
|||||||
return blend_subtraction(dest_color, src_color)
|
return blend_subtraction(dest_color, src_color)
|
||||||
# TODO: blend mode 75, which is not in the SWF spec and appears to have the equation
|
# TODO: blend mode 75, which is not in the SWF spec and appears to have the equation
|
||||||
# Src * (1 - Dst) + Dst * (1 - Src).
|
# Src * (1 - Dst) + Dst * (1 - Src).
|
||||||
|
elif blendfunc == 256:
|
||||||
|
# Dummy blend function for calculating masks.
|
||||||
|
return blend_mask_combine(dest_color, src_color)
|
||||||
|
elif blendfunc == 257:
|
||||||
|
# Dummy blend function for calculating masks.
|
||||||
|
return blend_mask_create(dest_color, src_color)
|
||||||
else:
|
else:
|
||||||
return blend_normal(dest_color, src_color)
|
return blend_normal(dest_color, src_color)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from PIL import Image # type: ignore
|
from PIL import Image # type: ignore
|
||||||
from typing import Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from .types.generic import Color, Matrix, Point
|
from .types.generic import Color, Matrix, Point
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ def affine_composite(
|
|||||||
add_color: Color,
|
add_color: Color,
|
||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
transform: Matrix,
|
transform: Matrix,
|
||||||
|
mask: Optional[Image.Image],
|
||||||
blendfunc: int,
|
blendfunc: int,
|
||||||
texture: Image.Image,
|
texture: Image.Image,
|
||||||
single_threaded: bool = False,
|
single_threaded: bool = False,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import multiprocessing
|
import multiprocessing
|
||||||
from PIL import Image # type: ignore
|
from PIL import Image # type: ignore
|
||||||
from typing import Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from .types.generic import Color, Matrix, Point
|
from .types.generic import Color, Matrix, Point
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ cdef extern struct point_t:
|
|||||||
|
|
||||||
cdef extern int affine_composite_fast(
|
cdef extern int affine_composite_fast(
|
||||||
unsigned char *imgdata,
|
unsigned char *imgdata,
|
||||||
|
unsigned char *maskdata,
|
||||||
unsigned int imgwidth,
|
unsigned int imgwidth,
|
||||||
unsigned int imgheight,
|
unsigned int imgheight,
|
||||||
unsigned int minx,
|
unsigned int minx,
|
||||||
@ -45,6 +46,7 @@ def affine_composite(
|
|||||||
add_color: Color,
|
add_color: Color,
|
||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
transform: Matrix,
|
transform: Matrix,
|
||||||
|
mask: Optional[Image.Image],
|
||||||
blendfunc: int,
|
blendfunc: int,
|
||||||
texture: Image.Image,
|
texture: Image.Image,
|
||||||
single_threaded: bool = False,
|
single_threaded: bool = False,
|
||||||
@ -58,7 +60,7 @@ def affine_composite(
|
|||||||
# be drawn.
|
# be drawn.
|
||||||
return img
|
return img
|
||||||
|
|
||||||
if blendfunc not in {0, 1, 2, 3, 8, 9, 70}:
|
if blendfunc not in {0, 1, 2, 3, 8, 9, 70, 256, 257}:
|
||||||
print(f"WARNING: Unsupported blend {blendfunc}")
|
print(f"WARNING: Unsupported blend {blendfunc}")
|
||||||
return img
|
return img
|
||||||
|
|
||||||
@ -89,6 +91,16 @@ def affine_composite(
|
|||||||
imgbytes = img.tobytes('raw', 'RGBA')
|
imgbytes = img.tobytes('raw', 'RGBA')
|
||||||
texbytes = texture.tobytes('raw', 'RGBA')
|
texbytes = texture.tobytes('raw', 'RGBA')
|
||||||
|
|
||||||
|
# Grab the mask data.
|
||||||
|
if mask is not None:
|
||||||
|
alpha = mask.split()[-1]
|
||||||
|
maskdata = alpha.tobytes('raw', 'L')
|
||||||
|
else:
|
||||||
|
maskdata = None
|
||||||
|
cdef unsigned char *maskbytes = NULL
|
||||||
|
if maskdata is not None:
|
||||||
|
maskbytes = maskdata
|
||||||
|
|
||||||
# Convert classes to C structs.
|
# Convert classes to C structs.
|
||||||
cdef floatcolor_t c_addcolor = floatcolor_t(r=add_color.r, g=add_color.g, b=add_color.b, a=add_color.a)
|
cdef floatcolor_t c_addcolor = floatcolor_t(r=add_color.r, g=add_color.g, b=add_color.b, a=add_color.a)
|
||||||
cdef floatcolor_t c_multcolor = floatcolor_t(r=mult_color.r, g=mult_color.g, b=mult_color.b, a=mult_color.a)
|
cdef floatcolor_t c_multcolor = floatcolor_t(r=mult_color.r, g=mult_color.g, b=mult_color.b, a=mult_color.a)
|
||||||
@ -98,6 +110,7 @@ def affine_composite(
|
|||||||
# Call the C++ function.
|
# Call the C++ function.
|
||||||
errors = affine_composite_fast(
|
errors = affine_composite_fast(
|
||||||
imgbytes,
|
imgbytes,
|
||||||
|
maskbytes,
|
||||||
imgwidth,
|
imgwidth,
|
||||||
imgheight,
|
imgheight,
|
||||||
minx,
|
minx,
|
||||||
|
@ -51,6 +51,7 @@ extern "C"
|
|||||||
|
|
||||||
typedef struct work {
|
typedef struct work {
|
||||||
intcolor_t *imgdata;
|
intcolor_t *imgdata;
|
||||||
|
unsigned char *maskdata;
|
||||||
unsigned int imgwidth;
|
unsigned int imgwidth;
|
||||||
unsigned int minx;
|
unsigned int minx;
|
||||||
unsigned int maxx;
|
unsigned int maxx;
|
||||||
@ -174,6 +175,33 @@ extern "C"
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intcolor_t blend_mask_create(
|
||||||
|
intcolor_t dest,
|
||||||
|
intcolor_t src
|
||||||
|
) {
|
||||||
|
// Mask creating just allows a pixel to be drawn if the source image has a nonzero
|
||||||
|
// alpha, according to the SWF spec.
|
||||||
|
if (src.a != 0) {
|
||||||
|
return (intcolor_t){255, 0, 0, 255};
|
||||||
|
} else {
|
||||||
|
return (intcolor_t){0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intcolor_t blend_mask_combine(
|
||||||
|
intcolor_t dest,
|
||||||
|
intcolor_t src
|
||||||
|
) {
|
||||||
|
// Mask blending just takes the source and destination and ands them together, making
|
||||||
|
// a final mask that is the intersection of the original mask and the new mask. The
|
||||||
|
// reason we even have a color component to this is for debugging visibility.
|
||||||
|
if (dest.a != 0 && src.a != 0) {
|
||||||
|
return (intcolor_t){255, 0, 0, 255};
|
||||||
|
} else {
|
||||||
|
return (intcolor_t){0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
intcolor_t blend_point(
|
intcolor_t blend_point(
|
||||||
floatcolor_t add_color,
|
floatcolor_t add_color,
|
||||||
floatcolor_t mult_color,
|
floatcolor_t mult_color,
|
||||||
@ -208,6 +236,12 @@ extern "C"
|
|||||||
if (blendfunc == 9 || blendfunc == 70) {
|
if (blendfunc == 9 || blendfunc == 70) {
|
||||||
return blend_subtraction(dest_color, src_color);
|
return blend_subtraction(dest_color, src_color);
|
||||||
}
|
}
|
||||||
|
if (blendfunc == 256) {
|
||||||
|
return blend_mask_combine(dest_color, src_color);
|
||||||
|
}
|
||||||
|
if (blendfunc == 257) {
|
||||||
|
return blend_mask_create(dest_color, src_color);
|
||||||
|
}
|
||||||
// TODO: blend mode 75, which is not in the SWF spec and appears to have the equation
|
// TODO: blend mode 75, which is not in the SWF spec and appears to have the equation
|
||||||
// Src * (1 - Dst) + Dst * (1 - Src).
|
// Src * (1 - Dst) + Dst * (1 - Src).
|
||||||
return blend_normal(dest_color, src_color);
|
return blend_normal(dest_color, src_color);
|
||||||
@ -231,6 +265,10 @@ extern "C"
|
|||||||
|
|
||||||
// Blend it.
|
// Blend it.
|
||||||
unsigned int texoff = texx + (texy * work->texwidth);
|
unsigned int texoff = texx + (texy * work->texwidth);
|
||||||
|
if (work->maskdata != NULL && work->maskdata[imgoff] == 0) {
|
||||||
|
// This pixel is masked off!
|
||||||
|
continue;
|
||||||
|
}
|
||||||
work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, work->texdata[texoff], work->imgdata[imgoff], work->blendfunc);
|
work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, work->texdata[texoff], work->imgdata[imgoff], work->blendfunc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,6 +282,7 @@ extern "C"
|
|||||||
|
|
||||||
int affine_composite_fast(
|
int affine_composite_fast(
|
||||||
unsigned char *imgbytes,
|
unsigned char *imgbytes,
|
||||||
|
unsigned char *maskbytes,
|
||||||
unsigned int imgwidth,
|
unsigned int imgwidth,
|
||||||
unsigned int imgheight,
|
unsigned int imgheight,
|
||||||
unsigned int minx,
|
unsigned int minx,
|
||||||
@ -267,6 +306,7 @@ extern "C"
|
|||||||
// Just create a local work structure so we can call the common function.
|
// Just create a local work structure so we can call the common function.
|
||||||
work_t work;
|
work_t work;
|
||||||
work.imgdata = imgdata;
|
work.imgdata = imgdata;
|
||||||
|
work.maskdata = maskbytes;
|
||||||
work.imgwidth = imgwidth;
|
work.imgwidth = imgwidth;
|
||||||
work.minx = minx;
|
work.minx = minx;
|
||||||
work.maxx = maxx;
|
work.maxx = maxx;
|
||||||
@ -308,6 +348,7 @@ extern "C"
|
|||||||
|
|
||||||
// Pass to it all of the params it needs.
|
// Pass to it all of the params it needs.
|
||||||
work->imgdata = imgdata;
|
work->imgdata = imgdata;
|
||||||
|
work->maskdata = maskbytes;
|
||||||
work->imgwidth = imgwidth;
|
work->imgwidth = imgwidth;
|
||||||
work->minx = minx;
|
work->minx = minx;
|
||||||
work->maxx = maxx;
|
work->maxx = maxx;
|
||||||
|
@ -4,7 +4,7 @@ from PIL import Image # type: ignore
|
|||||||
from .blend import affine_composite
|
from .blend import affine_composite
|
||||||
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag
|
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag
|
||||||
from .decompile import ByteCode
|
from .decompile import ByteCode
|
||||||
from .types import Color, Matrix, Point, Rectangle, AP2Trigger, AP2Action, PushAction, StoreRegisterAction, StringConstant, Register, THIS, UNDEFINED, GLOBAL
|
from .types import Color, Matrix, Point, Rectangle, AP2Trigger, AP2Action, PushAction, StoreRegisterAction, StringConstant, Register, NULL, UNDEFINED, GLOBAL, ROOT, PARENT, THIS, CLIP
|
||||||
from .geo import Shape, DrawParams
|
from .geo import Shape, DrawParams
|
||||||
from .util import VerboseOutput
|
from .util import VerboseOutput
|
||||||
|
|
||||||
@ -46,9 +46,15 @@ class RegisteredDummy:
|
|||||||
return f"RegisteredDummy(tag_id={self.tag_id})"
|
return f"RegisteredDummy(tag_id={self.tag_id})"
|
||||||
|
|
||||||
|
|
||||||
|
class Mask:
|
||||||
|
def __init__(self, bounds: Rectangle) -> None:
|
||||||
|
self.bounds = bounds
|
||||||
|
self.rectangle: Optional[Image.Image] = None
|
||||||
|
|
||||||
|
|
||||||
class PlacedObject:
|
class PlacedObject:
|
||||||
# An object that occupies the screen at some depth.
|
# An object that occupies the screen at some depth.
|
||||||
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, mask: Optional[Rectangle]) -> None:
|
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, mask: Optional[Mask]) -> None:
|
||||||
self.__object_id = object_id
|
self.__object_id = object_id
|
||||||
self.__depth = depth
|
self.__depth = depth
|
||||||
self.rotation_offset = rotation_offset
|
self.rotation_offset = rotation_offset
|
||||||
@ -86,7 +92,7 @@ class PlacedShape(PlacedObject):
|
|||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
add_color: Color,
|
add_color: Color,
|
||||||
blend: int,
|
blend: int,
|
||||||
mask: Optional[Rectangle],
|
mask: Optional[Mask],
|
||||||
source: RegisteredShape,
|
source: RegisteredShape,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
||||||
@ -112,7 +118,7 @@ class PlacedClip(PlacedObject):
|
|||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
add_color: Color,
|
add_color: Color,
|
||||||
blend: int,
|
blend: int,
|
||||||
mask: Optional[Rectangle],
|
mask: Optional[Mask],
|
||||||
source: RegisteredClip,
|
source: RegisteredClip,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
||||||
@ -193,7 +199,7 @@ class PlacedDummy(PlacedObject):
|
|||||||
mult_color: Color,
|
mult_color: Color,
|
||||||
add_color: Color,
|
add_color: Color,
|
||||||
blend: int,
|
blend: int,
|
||||||
mask: Optional[Rectangle],
|
mask: Optional[Mask],
|
||||||
source: RegisteredDummy,
|
source: RegisteredDummy,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
||||||
@ -206,7 +212,7 @@ class PlacedDummy(PlacedObject):
|
|||||||
|
|
||||||
class Movie:
|
class Movie:
|
||||||
def __init__(self, root: PlacedClip) -> None:
|
def __init__(self, root: PlacedClip) -> None:
|
||||||
self.__root = root
|
self.root = root
|
||||||
|
|
||||||
def getInstanceAtDepth(self, depth: Any) -> Any:
|
def getInstanceAtDepth(self, depth: Any) -> Any:
|
||||||
if not isinstance(depth, int):
|
if not isinstance(depth, int):
|
||||||
@ -216,7 +222,7 @@ class Movie:
|
|||||||
# stored added to -0x4000, so let's reverse that.
|
# stored added to -0x4000, so let's reverse that.
|
||||||
depth = depth + 0x4000
|
depth = depth + 0x4000
|
||||||
|
|
||||||
for obj in self.__root.placed_objects:
|
for obj in self.root.placed_objects:
|
||||||
if obj.depth == depth:
|
if obj.depth == depth:
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@ -225,29 +231,30 @@ class Movie:
|
|||||||
|
|
||||||
|
|
||||||
class AEPLib:
|
class AEPLib:
|
||||||
def __init__(self, this: PlacedObject, movie: Movie) -> None:
|
|
||||||
self.__this = this
|
|
||||||
self.__movie = movie
|
|
||||||
|
|
||||||
def aep_set_rect_mask(self, thisptr: Any, left: Any, right: Any, top: Any, bottom: Any) -> None:
|
def aep_set_rect_mask(self, thisptr: Any, left: Any, right: Any, top: Any, bottom: Any) -> None:
|
||||||
if not isinstance(left, (int, float)) or not isinstance(right, (int, float)) or not isinstance(top, (int, float)) or not isinstance(bottom, (int, float)):
|
if not isinstance(left, (int, float)) or not isinstance(right, (int, float)) or not isinstance(top, (int, float)) or not isinstance(bottom, (int, float)):
|
||||||
print("WARNING: Ignoring aeplib.aep_set_rect_mask call with invalid parameters!")
|
print(f"WARNING: Ignoring aeplib.aep_set_rect_mask call with invalid parameters {left}, {right}, {top}, {bottom}!")
|
||||||
return
|
return
|
||||||
if thisptr is THIS:
|
if isinstance(thisptr, PlacedObject):
|
||||||
self.__this.mask = Rectangle(
|
thisptr.mask = Mask(
|
||||||
|
Rectangle(
|
||||||
left=float(left),
|
left=float(left),
|
||||||
right=float(right),
|
right=float(right),
|
||||||
top=float(top),
|
top=float(top),
|
||||||
bottom=float(bottom),
|
bottom=float(bottom),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print("WARNING: Ignoring aeplib.aep_set_rect_mask call with unrecognized target!")
|
print(f"WARNING: Ignoring aeplib.aep_set_rect_mask call with unrecognized target {thisptr}!")
|
||||||
|
|
||||||
def aep_set_set_frame(self, thisptr: Any, frame: Any) -> None:
|
def aep_set_set_frame(self, thisptr: Any, frame: Any) -> None:
|
||||||
# I have no idea what this should do, so let's ignore it.
|
# I have no idea what this should do, so let's ignore it.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
MissingThis = object()
|
||||||
|
|
||||||
|
|
||||||
class AFPRenderer(VerboseOutput):
|
class AFPRenderer(VerboseOutput):
|
||||||
def __init__(self, shapes: Dict[str, Shape] = {}, textures: Dict[str, Image.Image] = {}, swfs: Dict[str, SWF] = {}, single_threaded: bool = False) -> None:
|
def __init__(self, shapes: Dict[str, Shape] = {}, textures: Dict[str, Image.Image] = {}, swfs: Dict[str, SWF] = {}, single_threaded: bool = False) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -319,15 +326,15 @@ class AFPRenderer(VerboseOutput):
|
|||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def __execute_bytecode(self, bytecode: ByteCode, clip: PlacedClip) -> None:
|
def __execute_bytecode(self, bytecode: ByteCode, clip: PlacedClip, thisptr: Optional[Any] = MissingThis) -> None:
|
||||||
if self.__movie is None:
|
if self.__movie is None:
|
||||||
raise Exception("Logic error, executing bytecode outside of a rendering movie clip!")
|
raise Exception("Logic error, executing bytecode outside of a rendering movie clip!")
|
||||||
|
|
||||||
|
this = clip if (thisptr is MissingThis) else thisptr
|
||||||
location: int = 0
|
location: int = 0
|
||||||
stack: List[Any] = []
|
stack: List[Any] = []
|
||||||
variables: Dict[str, Any] = {
|
variables: Dict[str, Any] = {
|
||||||
'aeplib': AEPLib(clip, self.__movie),
|
'aeplib': AEPLib(),
|
||||||
'GLOBAL': self.__movie,
|
|
||||||
}
|
}
|
||||||
registers: List[Any] = [UNDEFINED] * 256
|
registers: List[Any] = [UNDEFINED] * 256
|
||||||
|
|
||||||
@ -386,7 +393,7 @@ class AFPRenderer(VerboseOutput):
|
|||||||
funcname = stack.pop()
|
funcname = stack.pop()
|
||||||
|
|
||||||
# Grab the object to perform the call on.
|
# Grab the object to perform the call on.
|
||||||
obj = variables['GLOBAL']
|
obj = self.__movie
|
||||||
|
|
||||||
# Grab the parameters to pass to the function.
|
# Grab the parameters to pass to the function.
|
||||||
num_params = stack.pop()
|
num_params = stack.pop()
|
||||||
@ -415,10 +422,34 @@ class AFPRenderer(VerboseOutput):
|
|||||||
stack.append(obj.alias)
|
stack.append(obj.alias)
|
||||||
else:
|
else:
|
||||||
stack.append(StringConstant.property_to_name(obj.const))
|
stack.append(StringConstant.property_to_name(obj.const))
|
||||||
|
elif obj is NULL:
|
||||||
|
stack.append(None)
|
||||||
elif obj is THIS:
|
elif obj is THIS:
|
||||||
stack.append(clip)
|
stack.append(this)
|
||||||
elif obj is GLOBAL:
|
elif obj is GLOBAL:
|
||||||
stack.append(self.__movie)
|
stack.append(self.__movie)
|
||||||
|
elif obj is ROOT:
|
||||||
|
stack.append(self.__movie.root)
|
||||||
|
elif obj is CLIP:
|
||||||
|
# I am not sure this is correct? Maybe it works out
|
||||||
|
# in circumstances where "THIS" is pointed at something
|
||||||
|
# else, such as defined function calls maybe?
|
||||||
|
stack.append(clip)
|
||||||
|
elif obj is PARENT:
|
||||||
|
# Find the parent of this clip.
|
||||||
|
def find_parent(parent: PlacedClip, child: PlacedClip) -> Any:
|
||||||
|
for obj in parent.placed_objects:
|
||||||
|
if obj is child:
|
||||||
|
# This is us, so the parent is our parent.
|
||||||
|
return parent
|
||||||
|
if isinstance(obj, PlacedClip):
|
||||||
|
maybe_parent = find_parent(obj, child)
|
||||||
|
if maybe_parent is not None:
|
||||||
|
return maybe_parent
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
stack.append(find_parent(self.__movie.root, clip) or UNDEFINED)
|
||||||
else:
|
else:
|
||||||
stack.append(obj)
|
stack.append(obj)
|
||||||
elif isinstance(action, StoreRegisterAction):
|
elif isinstance(action, StoreRegisterAction):
|
||||||
@ -683,11 +714,49 @@ class AFPRenderer(VerboseOutput):
|
|||||||
else:
|
else:
|
||||||
raise Exception(f"Failed to process tag: {tag}")
|
raise Exception(f"Failed to process tag: {tag}")
|
||||||
|
|
||||||
|
def __apply_mask(
|
||||||
|
self,
|
||||||
|
parent_mask: Image.Image,
|
||||||
|
transform: Matrix,
|
||||||
|
mask: Mask,
|
||||||
|
) -> Image.Image:
|
||||||
|
if mask.rectangle is None:
|
||||||
|
# Calculate the new mask rectangle.
|
||||||
|
mask.rectangle = Image.new('RGBA', (int(mask.bounds.width), int(mask.bounds.height)), (255, 0, 0, 255))
|
||||||
|
|
||||||
|
# Offset it by its top/left.
|
||||||
|
transform = transform.translate(Point(mask.bounds.left, mask.bounds.top))
|
||||||
|
|
||||||
|
# Draw the mask onto a new image.
|
||||||
|
calculated_mask = affine_composite(
|
||||||
|
Image.new('RGBA', (parent_mask.width, parent_mask.height), (0, 0, 0, 0)),
|
||||||
|
Color(0.0, 0.0, 0.0, 0.0),
|
||||||
|
Color(1.0, 1.0, 1.0, 1.0),
|
||||||
|
transform,
|
||||||
|
None,
|
||||||
|
257,
|
||||||
|
mask.rectangle,
|
||||||
|
single_threaded=self.__single_threaded,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Composite it onto the current mask.
|
||||||
|
return affine_composite(
|
||||||
|
parent_mask.copy(),
|
||||||
|
Color(0.0, 0.0, 0.0, 0.0),
|
||||||
|
Color(1.0, 1.0, 1.0, 1.0),
|
||||||
|
Matrix.identity(),
|
||||||
|
None,
|
||||||
|
256,
|
||||||
|
calculated_mask,
|
||||||
|
single_threaded=self.__single_threaded,
|
||||||
|
)
|
||||||
|
|
||||||
def __render_object(
|
def __render_object(
|
||||||
self,
|
self,
|
||||||
img: Image.Image,
|
img: Image.Image,
|
||||||
renderable: PlacedObject,
|
renderable: PlacedObject,
|
||||||
parent_transform: Matrix,
|
parent_transform: Matrix,
|
||||||
|
parent_mask: Image.Image,
|
||||||
parent_mult_color: Color,
|
parent_mult_color: Color,
|
||||||
parent_add_color: Color,
|
parent_add_color: Color,
|
||||||
parent_blend: int,
|
parent_blend: int,
|
||||||
@ -707,7 +776,9 @@ class AFPRenderer(VerboseOutput):
|
|||||||
blend = parent_blend
|
blend = parent_blend
|
||||||
|
|
||||||
if renderable.mask:
|
if renderable.mask:
|
||||||
print(f"WARNING: Unsupported mask Rectangle({renderable.mask})!")
|
mask = self.__apply_mask(parent_mask, transform, renderable.mask)
|
||||||
|
else:
|
||||||
|
mask = parent_mask
|
||||||
|
|
||||||
# Render individual shapes if this is a sprite.
|
# Render individual shapes if this is a sprite.
|
||||||
if isinstance(renderable, PlacedClip):
|
if isinstance(renderable, PlacedClip):
|
||||||
@ -729,7 +800,7 @@ class AFPRenderer(VerboseOutput):
|
|||||||
for obj in renderable.placed_objects:
|
for obj in renderable.placed_objects:
|
||||||
if obj.depth != depth:
|
if obj.depth != depth:
|
||||||
continue
|
continue
|
||||||
img = self.__render_object(img, obj, transform, mult_color, add_color, blend, only_depths=new_only_depths, prefix=prefix + " ")
|
img = self.__render_object(img, obj, transform, mask, mult_color, add_color, blend, only_depths=new_only_depths, prefix=prefix + " ")
|
||||||
elif isinstance(renderable, PlacedShape):
|
elif isinstance(renderable, PlacedShape):
|
||||||
if only_depths is not None and renderable.depth not in only_depths:
|
if only_depths is not None and renderable.depth not in only_depths:
|
||||||
# Not on the correct depth plane.
|
# Not on the correct depth plane.
|
||||||
@ -790,7 +861,7 @@ class AFPRenderer(VerboseOutput):
|
|||||||
texture = shape.rectangle
|
texture = shape.rectangle
|
||||||
|
|
||||||
if texture is not None:
|
if texture is not None:
|
||||||
img = affine_composite(img, add_color, mult_color, transform, blend, texture, single_threaded=self.__single_threaded)
|
img = affine_composite(img, add_color, mult_color, transform, mask, blend, texture, single_threaded=self.__single_threaded)
|
||||||
elif isinstance(renderable, PlacedDummy):
|
elif isinstance(renderable, PlacedDummy):
|
||||||
# Nothing to do!
|
# Nothing to do!
|
||||||
pass
|
pass
|
||||||
@ -968,6 +1039,9 @@ class AFPRenderer(VerboseOutput):
|
|||||||
)
|
)
|
||||||
self.__movie = Movie(root_clip)
|
self.__movie = Movie(root_clip)
|
||||||
|
|
||||||
|
# Create the root mask for where to draw the root clip.
|
||||||
|
movie_mask = Image.new("RGBA", actual_size, color=(255, 0, 0, 255))
|
||||||
|
|
||||||
# These could possibly be overwritten from an external source of we wanted.
|
# These could possibly be overwritten from an external source of we wanted.
|
||||||
actual_mult_color = Color(1.0, 1.0, 1.0, 1.0)
|
actual_mult_color = Color(1.0, 1.0, 1.0, 1.0)
|
||||||
actual_add_color = Color(0.0, 0.0, 0.0, 0.0)
|
actual_add_color = Color(0.0, 0.0, 0.0, 0.0)
|
||||||
@ -987,7 +1061,7 @@ class AFPRenderer(VerboseOutput):
|
|||||||
if changed or frameno == 0:
|
if changed or frameno == 0:
|
||||||
# Now, render out the placed objects.
|
# Now, render out the placed objects.
|
||||||
curimage = Image.new("RGBA", actual_size, color=color.as_tuple())
|
curimage = Image.new("RGBA", actual_size, color=color.as_tuple())
|
||||||
curimage = self.__render_object(curimage, root_clip, movie_transform, actual_mult_color, actual_add_color, actual_blend, only_depths=only_depths)
|
curimage = self.__render_object(curimage, root_clip, movie_transform, movie_mask, actual_mult_color, actual_add_color, actual_blend, only_depths=only_depths)
|
||||||
else:
|
else:
|
||||||
# Nothing changed, make a copy of the previous render.
|
# Nothing changed, make a copy of the previous render.
|
||||||
self.vprint(" Using previous frame render")
|
self.vprint(" Using previous frame render")
|
||||||
|
Loading…
Reference in New Issue
Block a user