1
0
mirror of synced 2025-01-18 22:24:04 +01:00

Add hue/saturation/lightness shift support to AFP renderer to fix neo generator seven boss animation rendering.

This commit is contained in:
Jennifer Taylor 2022-10-16 19:30:02 +00:00
parent 6937473d0a
commit 3b10423955
9 changed files with 315 additions and 12 deletions

View File

@ -3,7 +3,7 @@ import signal
from PIL import Image # type: ignore
from typing import Any, Callable, List, Optional, Sequence, Union
from ..types import Color, Matrix, Point, AAMode
from ..types import Color, HSL, Matrix, Point, AAMode
from .perspective import perspective_calculate
@ -186,6 +186,7 @@ def blend_mask_combine(
def blend_point(
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
# This should be a sequence of exactly 4 values, either bytes or a tuple.
src_color: Sequence[int],
# This should be a sequence of exactly 4 values, either bytes or a tuple.
@ -200,6 +201,23 @@ def blend_point(
clamp((src_color[3] * mult_color.a) + (255 * add_color.a)),
)
# Only add in HSL shift effects if they exist, since its expensive to
# convert and shift. Also I'm not sure if this should be done before or
# after the add and multiply.
if not hsl_shift.is_identity:
hslcolor = Color(
src_color[0] / 255, src_color[1] / 255, src_color[2] / 255, 1.0
).as_hsl()
hslcolor = hslcolor.add(hsl_shift)
newcolor = hslcolor.as_rgb()
src_color = (
clamp(newcolor.r * 255),
clamp(newcolor.g * 255),
clamp(newcolor.b * 255),
src_color[3],
)
if blendfunc == 3:
return blend_multiply(dest_color, src_color)
# TODO: blend mode 4, which is "screen" blending according to SWF references. I've only seen this
@ -240,6 +258,7 @@ def pixel_renderer(
callback: Callable[[Point], Optional[Point]],
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
blendfunc: int,
imgbytes: Union[bytes, bytearray],
texbytes: Union[bytes, bytearray],
@ -422,7 +441,12 @@ def pixel_renderer(
# Finally, blend it with the destination.
return blend_point(
add_color, mult_color, average, imgbytes[imgoff : (imgoff + 4)], blendfunc
add_color,
mult_color,
hsl_shift,
average,
imgbytes[imgoff : (imgoff + 4)],
blendfunc,
)
else:
# Calculate what texture pixel data goes here.
@ -441,6 +465,7 @@ def pixel_renderer(
return blend_point(
add_color,
mult_color,
hsl_shift,
texbytes[texoff : (texoff + 4)],
imgbytes[imgoff : (imgoff + 4)],
blendfunc,
@ -459,6 +484,7 @@ def affine_line_renderer(
inverse: Matrix,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
blendfunc: int,
imgbytes: Union[bytes, bytearray],
texbytes: Union[bytes, bytearray],
@ -491,6 +517,7 @@ def affine_line_renderer(
lambda point: inverse.multiply_point(point),
add_color,
mult_color,
hsl_shift,
blendfunc,
imgbytes,
texbytes,
@ -505,6 +532,7 @@ def affine_composite(
img: Image.Image,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
transform: Matrix,
mask: Optional[Image.Image],
blendfunc: int,
@ -577,6 +605,7 @@ def affine_composite(
lambda point: inverse.multiply_point(point),
add_color,
mult_color,
hsl_shift,
blendfunc,
imgbytes,
texbytes,
@ -623,6 +652,7 @@ def affine_composite(
inverse,
add_color,
mult_color,
hsl_shift,
blendfunc,
imgbytes,
texbytes,
@ -676,6 +706,7 @@ def perspective_line_renderer(
inverse: Matrix,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
blendfunc: int,
imgbytes: Union[bytes, bytearray],
texbytes: Union[bytes, bytearray],
@ -716,6 +747,7 @@ def perspective_line_renderer(
perspective_inverse,
add_color,
mult_color,
hsl_shift,
blendfunc,
imgbytes,
texbytes,
@ -730,6 +762,7 @@ def perspective_composite(
img: Image.Image,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
transform: Matrix,
camera: Point,
focal_length: float,
@ -804,6 +837,7 @@ def perspective_composite(
perspective_inverse,
add_color,
mult_color,
hsl_shift,
blendfunc,
imgbytes,
texbytes,
@ -852,6 +886,7 @@ def perspective_composite(
inverse_matrix,
add_color,
mult_color,
hsl_shift,
blendfunc,
imgbytes,
texbytes,

View File

@ -1,13 +1,14 @@
from PIL import Image # type: ignore
from typing import Optional
from ..types import Color, Point, Matrix
from ..types import Color, HSL, Point, Matrix
def affine_composite(
img: Image.Image,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
transform: Matrix,
mask: Optional[Image.Image],
blendfunc: int,
@ -22,6 +23,7 @@ def perspective_composite(
img: Image.Image,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
transform: Matrix,
camera: Point,
focal_length: float,

View File

@ -2,7 +2,7 @@ import multiprocessing
from PIL import Image # type: ignore
from typing import Optional, Tuple
from ..types import Color, Matrix, Point, AAMode
from ..types import Color, HSL, Matrix, Point, AAMode
from .perspective import perspective_calculate
cdef extern struct floatcolor_t:
@ -11,6 +11,11 @@ cdef extern struct floatcolor_t:
double b;
double a;
cdef extern struct hslcolor_t:
double h;
double s;
double l;
cdef extern struct matrix_t:
double a11;
double a12;
@ -36,6 +41,7 @@ cdef extern int composite_fast(
unsigned int maxy,
floatcolor_t add_color,
floatcolor_t mult_color,
hslcolor_t hsl_shift,
double xscale,
double yscale,
matrix_t inverse,
@ -52,6 +58,7 @@ def affine_composite(
img: Image.Image,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
transform: Matrix,
mask: Optional[Image.Image],
blendfunc: int,
@ -112,6 +119,7 @@ def affine_composite(
# 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_multcolor = floatcolor_t(r=mult_color.r, g=mult_color.g, b=mult_color.b, a=mult_color.a)
cdef hslcolor_t c_hslcolor = hslcolor_t(h=hsl_shift.h, s=hsl_shift.s, l=hsl_shift.l)
cdef matrix_t c_inverse = matrix_t(
a11=inverse.a11, a12=inverse.a12, a13=inverse.a13,
a21=inverse.a21, a22=inverse.a22, a23=inverse.a23,
@ -132,6 +140,7 @@ def affine_composite(
maxy,
c_addcolor,
c_multcolor,
c_hslcolor,
transform.xscale,
transform.yscale,
c_inverse,
@ -157,6 +166,7 @@ def perspective_composite(
img: Image.Image,
add_color: Color,
mult_color: Color,
hsl_shift: HSL,
transform: Matrix,
camera: Point,
focal_length: float,
@ -200,6 +210,7 @@ def perspective_composite(
# 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_multcolor = floatcolor_t(r=mult_color.r, g=mult_color.g, b=mult_color.b, a=mult_color.a)
cdef hslcolor_t c_hslcolor = hslcolor_t(h=hsl_shift.h, s=hsl_shift.s, l=hsl_shift.l)
cdef matrix_t c_inverse = matrix_t(
a11=inverse_matrix.a11, a12=inverse_matrix.a12, a13=inverse_matrix.a13,
a21=inverse_matrix.a21, a22=inverse_matrix.a22, a23=inverse_matrix.a23,
@ -220,6 +231,7 @@ def perspective_composite(
maxy,
c_addcolor,
c_multcolor,
c_hslcolor,
transform.xscale,
transform.yscale,
c_inverse,

View File

@ -26,6 +26,12 @@ extern "C"
double a;
} floatcolor_t;
typedef struct hslcolor {
double h;
double s;
double l;
} hslcolor_t;
typedef struct point {
double x;
double y;
@ -81,6 +87,7 @@ extern "C"
int use_perspective;
floatcolor_t add_color;
floatcolor_t mult_color;
hslcolor_t hsl_shift;
int blendfunc;
pthread_t *thread;
int aa_mode;
@ -90,6 +97,101 @@ extern "C"
return fmin(fmax(0.0, roundf(color)), 255.0);
}
inline unsigned int min(unsigned int x, unsigned int y) {
return x < y ? x : y;
}
inline unsigned int max(unsigned int x, unsigned int y) {
return x > y ? x : y;
}
void rgb_to_hsl(int r, int g, int b, double *h, double *s, double *l) {
int cmax = max(max(r, g), b);
int cmin = min(min(r, g), b);
double sum = (double)(cmin + cmax);
// First, calculate luminance, which is the sum divided by 2. We
// also need to scale down by 255 since RGB values are integers!
*l = sum / (2.0 * 255.0);
if (cmax == cmin) {
// No point in calculating anything else, its just luminance.
*h = 0.0;
*s = 0.0;
return;
}
// Second, calculate saturation.
double delta = (double)(cmax - cmin);
if (*l <= 0.5) {
// 255 scaling appears on both sides, so no need to handle it.
*s = delta / sum;
} else {
// We need to remember to scale by 255 here, so let's factor it out.
*s = delta / ((2.0 * 255) - sum);
}
// Finaly, calculate hue. This can theoretically go above 1.0 or below
// 0.0 and most equations show it being clamped, but we need to clamp
// again when converting back so don't bother wasting time.
if (r == cmax) {
*h = ((double)(g - b) / 6.0) / delta;
} else if (g == cmax) {
*h = (1.0 / 3.0) + ((double)(b - r) / 6.0) / delta;
} else {
*h = (2.0 / 3.0) + ((double)(r - g) / 6.0) / delta;
}
}
inline double hue_to_rgb(double v1, double v2, double vh) {
// Clamp hue value to 0.0/1.0, respecting the fact that 361 degrees is
// equivalent to 1 degree, and negative 1 degree is equivalent to 359.
if (vh < 0.0) {
vh += 1.0;
}
if (vh >= 1.0) {
vh -= 1.0;
}
// Split back into 3 quadrants since RGB isn't linear with in these,
// there's a step function where at some point the slope goes from positive
// to negative non-continuously.
if ((6.0 * vh) < 1.0) {
return v1 + ((v2 - v1) * 6.0 * vh);
}
if ((2.0 * vh) < 1.0) {
return v2;
}
if ((3.0 * vh) < 2.0) {
return v1 + ((v2 - v1) * ((2.0 / 3.0) - vh) * 6.0);
}
return v1;
}
void hsl_to_rgb(double h, double s, double l, unsigned char *r, unsigned char *g, unsigned char *b) {
// Clamp hue value to 0.0/1.0, respecting the fact that 361 degrees is
// equivalent to 1 degree, and negative 1 degree is equivalent to 359.
while (h < 0.0) {
h += 1.0;
}
while (h >= 1.0) {
h -= 1.0;
}
s = fmin(fmax(s, 0.0), 1.0);
l = fmin(fmax(l, 0.0), 1.0);
if (s == 0.0) {
*r = *g = *b = (int)(l * 255.0);
} else {
double v2 = (l < 0.5) ? (l * (1.0 + s)) : ((l + s) - (l * s));
double v1 = (2.0 * l) - v2;
*r = (unsigned char)(255.0 * hue_to_rgb(v1, v2, h + (1.0 / 3.0)));
*g = (unsigned char)(255.0 * hue_to_rgb(v1, v2, h));
*b = (unsigned char)(255.0 * hue_to_rgb(v1, v2, h - (1.0 / 3.0)));
}
}
intcolor_t blend_normal(
intcolor_t dest,
intcolor_t src
@ -242,6 +344,7 @@ extern "C"
intcolor_t blend_point(
floatcolor_t add_color,
floatcolor_t mult_color,
hslcolor_t hsl_shift,
intcolor_t src_color,
intcolor_t dest_color,
int blendfunc
@ -254,6 +357,32 @@ extern "C"
clamp((src_color.a * mult_color.a) + (255 * add_color.a)),
};
// Add in hsl shift if there is anything to do.
if (hsl_shift.h != 0.0 || hsl_shift.s != 0.0 || hsl_shift.l != 0.0) {
hslcolor_t hslcolor;
rgb_to_hsl(
src_color.r,
src_color.g,
src_color.b,
&hslcolor.h,
&hslcolor.s,
&hslcolor.l
);
hslcolor.h += hsl_shift.h;
hslcolor.s += hsl_shift.s;
hslcolor.l += hsl_shift.l;
hsl_to_rgb(
hslcolor.h,
hslcolor.s,
hslcolor.l,
&src_color.r,
&src_color.g,
&src_color.b
);
}
if (blendfunc == 3) {
return blend_multiply(dest_color, src_color);
}
@ -485,7 +614,7 @@ extern "C"
}
// Blend it.
work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, average, work->imgdata[imgoff], work->blendfunc);
work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, work->hsl_shift, average, work->imgdata[imgoff], work->blendfunc);
} else {
// Grab the center of the pixel to get the color.
int texx = -1;
@ -510,7 +639,7 @@ extern "C"
// Blend it.
unsigned int texoff = texx + (texy * work->texwidth);
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->hsl_shift, work->texdata[texoff], work->imgdata[imgoff], work->blendfunc);
}
}
}
@ -533,6 +662,7 @@ extern "C"
unsigned int maxy,
floatcolor_t add_color,
floatcolor_t mult_color,
hslcolor_t hsl_shift,
double xscale,
double yscale,
matrix_t inverse,
@ -567,6 +697,7 @@ extern "C"
work.inverse = inverse;
work.add_color = add_color;
work.mult_color = mult_color;
work.hsl_shift = hsl_shift;
work.blendfunc = blendfunc;
work.aa_mode = aa_mode;
work.use_perspective = use_perspective;
@ -614,6 +745,7 @@ extern "C"
work->inverse = inverse;
work->add_color = add_color;
work->mult_color = mult_color;
work->hsl_shift = hsl_shift;
work->blendfunc = blendfunc;
work->thread = thread;
work->aa_mode = aa_mode;

View File

@ -20,6 +20,7 @@ from .swf import (
from .decompile import ByteCode
from .types import (
Color,
HSL,
Matrix,
Point,
Rectangle,
@ -120,6 +121,7 @@ class PlacedObject:
projection: int,
mult_color: Color,
add_color: Color,
hsl_shift: HSL,
blend: int,
mask: Optional[Mask],
) -> None:
@ -130,6 +132,7 @@ class PlacedObject:
self.projection = projection
self.mult_color = mult_color
self.add_color = add_color
self.hsl_shift = hsl_shift
self.blend = blend
self.mask = mask
self.visible: bool = True
@ -164,6 +167,7 @@ class PlacedShape(PlacedObject):
projection: int,
mult_color: Color,
add_color: Color,
hsl_shift: HSL,
blend: int,
mask: Optional[Mask],
source: RegisteredShape,
@ -176,6 +180,7 @@ class PlacedShape(PlacedObject):
projection,
mult_color,
add_color,
hsl_shift,
blend,
mask,
)
@ -201,6 +206,7 @@ class PlacedClip(PlacedObject):
projection: int,
mult_color: Color,
add_color: Color,
hsl_shift: HSL,
blend: int,
mask: Optional[Mask],
source: RegisteredClip,
@ -213,6 +219,7 @@ class PlacedClip(PlacedObject):
projection,
mult_color,
add_color,
hsl_shift,
blend,
mask,
)
@ -375,6 +382,7 @@ class PlacedImage(PlacedObject):
projection: int,
mult_color: Color,
add_color: Color,
hsl_shift: HSL,
blend: int,
mask: Optional[Mask],
source: RegisteredImage,
@ -387,6 +395,7 @@ class PlacedImage(PlacedObject):
projection,
mult_color,
add_color,
hsl_shift,
blend,
mask,
)
@ -411,6 +420,7 @@ class PlacedDummy(PlacedObject):
projection: int,
mult_color: Color,
add_color: Color,
hsl_shift: HSL,
blend: int,
mask: Optional[Mask],
source: RegisteredDummy,
@ -423,6 +433,7 @@ class PlacedDummy(PlacedObject):
projection,
mult_color,
add_color,
hsl_shift,
blend,
mask,
)
@ -1059,6 +1070,7 @@ class AFPRenderer(VerboseOutput):
if obj.object_id == tag.object_id and obj.depth == tag.depth:
new_mult_color = tag.mult_color or obj.mult_color
new_add_color = tag.add_color or obj.add_color
new_hsl_shift = tag.hsl_shift or obj.hsl_shift
new_transform = (
obj.transform.update(tag.transform)
if tag.transform is not None
@ -1092,6 +1104,7 @@ class AFPRenderer(VerboseOutput):
new_projection,
new_mult_color,
new_add_color,
new_hsl_shift,
new_blend,
obj.mask,
newobj,
@ -1108,6 +1121,7 @@ class AFPRenderer(VerboseOutput):
new_projection,
new_mult_color,
new_add_color,
new_hsl_shift,
new_blend,
obj.mask,
newobj,
@ -1124,6 +1138,7 @@ class AFPRenderer(VerboseOutput):
new_projection,
new_mult_color,
new_add_color,
new_hsl_shift,
new_blend,
obj.mask,
newobj,
@ -1141,6 +1156,7 @@ class AFPRenderer(VerboseOutput):
new_projection,
new_mult_color,
new_add_color,
new_hsl_shift,
new_blend,
obj.mask,
newobj,
@ -1160,6 +1176,7 @@ class AFPRenderer(VerboseOutput):
)
obj.mult_color = new_mult_color
obj.add_color = new_add_color
obj.hsl_shift = new_hsl_shift
obj.transform = new_transform
obj.rotation_origin = new_rotation_origin
obj.projection = new_projection
@ -1194,6 +1211,7 @@ class AFPRenderer(VerboseOutput):
tag.projection,
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.hsl_shift or HSL(0.0, 0.0, 0.0),
tag.blend or 0,
None,
newobj,
@ -1212,6 +1230,7 @@ class AFPRenderer(VerboseOutput):
tag.projection,
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.hsl_shift or HSL(0.0, 0.0, 0.0),
tag.blend or 0,
None,
newobj,
@ -1229,6 +1248,7 @@ class AFPRenderer(VerboseOutput):
tag.projection,
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.hsl_shift or HSL(0.0, 0.0, 0.0),
tag.blend or 0,
None,
newobj,
@ -1258,6 +1278,7 @@ class AFPRenderer(VerboseOutput):
tag.projection,
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.hsl_shift or HSL(0.0, 0.0, 0.0),
tag.blend or 0,
None,
newobj,
@ -1386,6 +1407,7 @@ class AFPRenderer(VerboseOutput):
),
Color(0.0, 0.0, 0.0, 0.0),
Color(1.0, 1.0, 1.0, 1.0),
HSL(0.0, 0.0, 0.0),
Matrix.identity().translate(Point(mask.bounds.left, mask.bounds.top)),
None,
0,
@ -1406,6 +1428,7 @@ class AFPRenderer(VerboseOutput):
),
Color(0.0, 0.0, 0.0, 0.0),
Color(1.0, 1.0, 1.0, 1.0),
HSL(0.0, 0.0, 0.0),
transform,
None,
257,
@ -1424,6 +1447,7 @@ class AFPRenderer(VerboseOutput):
),
Color(0.0, 0.0, 0.0, 0.0),
Color(1.0, 1.0, 1.0, 1.0),
HSL(0.0, 0.0, 0.0),
transform,
None,
257,
@ -1438,6 +1462,7 @@ class AFPRenderer(VerboseOutput):
),
Color(0.0, 0.0, 0.0, 0.0),
Color(1.0, 1.0, 1.0, 1.0),
HSL(0.0, 0.0, 0.0),
transform,
self.__camera.center,
self.__camera.focal_length,
@ -1453,6 +1478,7 @@ class AFPRenderer(VerboseOutput):
parent_mask.copy(),
Color(0.0, 0.0, 0.0, 0.0),
Color(1.0, 1.0, 1.0, 1.0),
HSL(0.0, 0.0, 0.0),
Matrix.identity(),
None,
256,
@ -1470,6 +1496,7 @@ class AFPRenderer(VerboseOutput):
parent_mask: Image.Image,
parent_mult_color: Color,
parent_add_color: Color,
parent_hsl_shift: HSL,
parent_blend: int,
only_depths: Optional[List[int]] = None,
prefix: str = "",
@ -1505,6 +1532,7 @@ class AFPRenderer(VerboseOutput):
.multiply(parent_mult_color)
.add(parent_add_color)
)
hsl_shift = renderable.hsl_shift or HSL(0.0, 0.0, 0.0).add(parent_hsl_shift)
blend = renderable.blend or 0
if parent_blend not in {0, 1, 2} and blend in {0, 1, 2}:
blend = parent_blend
@ -1541,6 +1569,7 @@ class AFPRenderer(VerboseOutput):
mask,
mult_color,
add_color,
hsl_shift,
blend,
only_depths=new_only_depths,
prefix=prefix + " ",
@ -1631,6 +1660,7 @@ class AFPRenderer(VerboseOutput):
img,
add_color,
mult_color,
hsl_shift,
transform,
mask,
blend,
@ -1656,6 +1686,7 @@ class AFPRenderer(VerboseOutput):
img,
add_color,
mult_color,
hsl_shift,
transform,
mask,
blend,
@ -1677,6 +1708,7 @@ class AFPRenderer(VerboseOutput):
img,
add_color,
mult_color,
hsl_shift,
transform,
self.__camera.center,
self.__camera.focal_length,
@ -1699,6 +1731,7 @@ class AFPRenderer(VerboseOutput):
img,
add_color,
mult_color,
hsl_shift,
transform,
mask,
blend,
@ -1717,6 +1750,7 @@ class AFPRenderer(VerboseOutput):
img,
add_color,
mult_color,
hsl_shift,
transform,
mask,
blend,
@ -1731,6 +1765,7 @@ class AFPRenderer(VerboseOutput):
img,
add_color,
mult_color,
hsl_shift,
transform,
self.__camera.center,
self.__camera.focal_length,
@ -2051,6 +2086,7 @@ class AFPRenderer(VerboseOutput):
AP2PlaceObjectTag.PROJECTION_AFFINE,
Color(1.0, 1.0, 1.0, 1.0),
Color(0.0, 0.0, 0.0, 0.0),
HSL(0.0, 0.0, 0.0),
0,
None,
RegisteredClip(
@ -2106,6 +2142,7 @@ class AFPRenderer(VerboseOutput):
AP2PlaceObjectTag.PROJECTION_AFFINE,
Color(1.0, 1.0, 1.0, 1.0),
Color(0.0, 0.0, 0.0, 0.0),
HSL(0.0, 0.0, 0.0),
0,
None,
background_object,
@ -2120,6 +2157,7 @@ class AFPRenderer(VerboseOutput):
# 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_add_color = Color(0.0, 0.0, 0.0, 0.0)
actual_hsl_shift = HSL(0.0, 0.0, 0.0)
actual_blend = 0
max_frame: Optional[int] = None
@ -2206,6 +2244,7 @@ class AFPRenderer(VerboseOutput):
movie_mask,
actual_mult_color,
actual_add_color,
actual_hsl_shift,
actual_blend,
only_depths=only_depths,
)

View File

@ -8,6 +8,7 @@ from .decompile import ByteCode
from .types import (
Matrix,
Color,
HSL,
Point,
Rectangle,
AP2Action,
@ -323,6 +324,7 @@ class AP2PlaceObjectTag(Tag):
projection: int,
mult_color: Optional[Color],
add_color: Optional[Color],
hsl_shift: Optional[HSL],
triggers: Dict[int, List[ByteCode]],
unrecognized_options: bool,
) -> None:
@ -364,6 +366,9 @@ class AP2PlaceObjectTag(Tag):
# If there is a color to add with the sprite/shape when drawing.
self.add_color = add_color
# If there is a hue/saturation/lightness shift effect when drawing.
self.hsl_shift = hsl_shift
# List of triggers for this object, and their respective bytecodes to execute when the trigger
# fires.
self.triggers = triggers
@ -398,6 +403,9 @@ class AP2PlaceObjectTag(Tag):
"add_color": self.add_color.as_dict(*args, **kwargs)
if self.add_color is not None
else None,
"hsl_shift": self.hsl_shift.as_dict(*args, **kwargs)
if self.hsl_shift
else None,
"triggers": {
i: [b.as_dict(*args, **kwargs) for b in t]
for (i, t) in self.triggers.items()
@ -2007,18 +2015,29 @@ class SWF(VerboseOutput, TrackedCoverage):
component="tags",
)
# HSL shift data.
hue: Optional[int] = None
saturation: Optional[int] = None
lightness: Optional[int] = None
if flags & 0x20000000:
# TODO: Again, absolutely no idea, gets passed into a function and I don't see how its used.
# Looks like Hue/Lightness/Saturation shift, matching after effects in the limits.
# First value is degrees to shift the hue, second and third values I'm not sure if
# its saturation then lightness or lightness then saturation but both have limits of
# -100 to 100 in after effects and the file that I found with this option chooses
# 0 for each.
unhandled_flags &= ~0x20000000
unk_a, unk_b, unk_c = struct.unpack(
hue, saturation, lightness = struct.unpack(
"<hbb", datachunk[running_pointer : (running_pointer + 4)]
)
self.add_coverage(dataoffset + running_pointer, 4)
running_pointer += 4
unrecognized_options = True
# TODO: Need to confirm whether 2 and 3 options are saturation and lightness or
# lightness and saturation. Should be easy if we ever find an animation using either
# of these values.
self.vprint(
f"{prefix} Unknown Data: {unk_a}, {unk_b}, {unk_c}",
f"{prefix} HSL Shift: {hue}, {saturation}, {lightness}",
component="tags",
)
@ -2186,6 +2205,9 @@ class SWF(VerboseOutput, TrackedCoverage):
projection=projection,
mult_color=multcolor if color_information else None,
add_color=addcolor if color_information else None,
hsl_shift=HSL(hue / 360.0, saturation / 100.0, lightness / 100.0)
if hue is not None
else None,
triggers=bytecodes,
unrecognized_options=unrecognized_options,
)

View File

@ -1,4 +1,4 @@
from .generic import Matrix, Color, Point, Rectangle
from .generic import Matrix, Color, HSL, Point, Rectangle
from .ap2 import (
AP2Tag,
AP2Action,
@ -93,6 +93,7 @@ from .aa import AAMode
__all__ = [
"Matrix",
"Color",
"HSL",
"Point",
"Rectangle",
"AP2Tag",

View File

@ -1,3 +1,4 @@
import colorsys
import math
from typing import Any, Dict, List, Tuple
@ -37,6 +38,10 @@ class Color:
a=self.a + other.a,
)
def as_hsl(self) -> "HSL":
h, l, s = colorsys.rgb_to_hls(self.r, self.g, self.b)
return HSL(h, s, l)
def as_tuple(self) -> Tuple[int, int, int, int]:
return (
int(self.r * 255),
@ -49,6 +54,61 @@ class Color:
return f"r: {round(self.r, 5)}, g: {round(self.g, 5)}, b: {round(self.b, 5)}, a: {round(self.a, 5)}"
class HSL:
# A hue/saturation/lightness color shift, represented as a series of floats between
# -1.0 and 1.0. The hue represents a percentage along the polar coordinates,
# 0.0 being 0 degrees, -1.0 being -360 degrees and 1.0 being 360 degrees. The
# saturation and lightness values representing actual normalized percentages where
# a lightness of 100 would be written as 1.0.
def __init__(self, h: float, s: float, l: float) -> None:
self.h = h
self.s = s
self.l = l
@property
def is_identity(self) -> bool:
return self.h == 0.0 and self.s == 0.0 and self.l == 0.0
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
return {
"h": self.h,
"s": self.s,
"l": self.l,
}
def add(self, other: "HSL") -> "HSL":
# Not entirely sure this is correct, but we don't have any animations to compare to.
# Basically, not sure if HSL colorspace is linear in this way, but as long as no
# animations try to stack multiple HSL shift effects this shouldn't matter.
return HSL(h=self.h + other.h, s=self.s + other.s, l=self.l + other.l)
def as_rgb(self) -> "Color":
h = self.h
while h < 0.0:
h += 1.0
while h > 1.0:
h -= 1.0
s = min(max(self.s, 0.0), 1.0)
l = min(max(self.l, 0.0), 1.0)
r, g, b = colorsys.hls_to_rgb(h, l, s)
return Color(r, g, b, 1.0)
def as_tuple(self) -> Tuple[int, int, int]:
h = int(self.h * 360)
while h < 0:
h += 360
while h > 360:
h -= 360
s = min(max(int(self.s), -100), 100)
l = min(max(int(self.l), -100), 100)
return (h, s, l)
def __repr__(self) -> str:
return f"h: {round(self.h, 5)}, s: {round(self.s, 5)}, l: {round(self.l, 5)}"
class Point:
# A simple 3D point. For ease of construction, the Z can be left out
# at which point it is assumed to be zero.

View File

@ -1,3 +1,3 @@
#! /bin/bash
flake8 bemani/ --ignore E203,E501,E252,W503,W504,B006,B008,B009 | grep -v "migrations\/"
flake8 bemani/ --ignore E203,E501,E252,E741,W503,W504,B006,B008,B009 | grep -v "migrations\/"