1
0
mirror of synced 2024-11-24 14:30:11 +01:00

Add simple anti-aliasing to texture renderer functions.

This commit is contained in:
Jennifer Taylor 2021-05-30 04:16:25 +00:00
parent 4785b01132
commit e6ffc983f7
6 changed files with 196 additions and 44 deletions

View File

@ -127,6 +127,7 @@ def affine_composite(
blendfunc: int,
texture: Image.Image,
single_threaded: bool = False,
enable_aa: bool = True,
) -> Image.Image:
# Calculate the inverse so we can map canvas space back to texture space.
try:
@ -181,9 +182,50 @@ def affine_composite(
for imgx in range(minx, maxx):
# Determine offset
imgoff = imgx + (imgy * imgwidth)
if maskmap is not None and maskmap[imgoff] == 0:
# This pixel is masked off!
continue
if enable_aa:
r = 0
g = 0
b = 0
a = 0
count = 0
xswing = abs(0.5 / inverse.a)
yswing = abs(0.5 / inverse.d)
xpoints = [0.5 - xswing, 0.5 - (xswing / 2.0), 0.5, 0.5 + (xswing / 2.0), 0.5 + xswing]
ypoints = [0.5 - yswing, 0.5 - (yswing / 2.0), 0.5, 0.5 + (yswing / 2.0), 0.5 + yswing]
for addy in ypoints:
for addx in xpoints:
texloc = inverse.multiply_point(Point(imgx + addx, imgy + addy))
aax, aay = texloc.as_tuple()
# If we're out of bounds, don't update.
if aax < 0 or aay < 0 or aax >= texwidth or aay >= texheight:
continue
# Grab the values to average, for SSAA.
texoff = aax + (aay * texwidth)
r += texmap[texoff][0]
g += texmap[texoff][1]
b += texmap[texoff][2]
a += texmap[texoff][3]
count += 1
if count == 0:
# None of the samples existed in-bounds.
continue
# Average the pixels.
average = [r // count, g // count, b // count, a // count]
imgmap[imgoff] = blend_point(add_color, mult_color, average, imgmap[imgoff], blendfunc)
else:
# Calculate what texture pixel data goes here.
texloc = inverse.multiply_point(Point(float(imgx + 0.5), float(imgy + 0.5)))
texloc = inverse.multiply_point(Point(imgx + 0.5, imgy + 0.5))
texx, texy = texloc.as_tuple()
# If we're out of bounds, don't update.
@ -192,9 +234,6 @@ def affine_composite(
# Blend it.
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)
img.putdata(imgmap)
@ -239,6 +278,7 @@ def affine_composite(
imgbytes,
texbytes,
maskbytes,
enable_aa,
),
)
procs.append(proc)
@ -317,6 +357,7 @@ def pixel_renderer(
imgbytes: bytes,
texbytes: bytes,
maskbytes: Optional[bytes],
enable_aa: bool,
) -> None:
while True:
imgy = work.get()
@ -330,9 +371,52 @@ def pixel_renderer(
if imgx < minx or imgx >= maxx:
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
continue
if maskbytes is not None and maskbytes[imgoff] == 0:
# This pixel is masked off!
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
continue
if enable_aa:
r = 0
g = 0
b = 0
a = 0
count = 0
xswing = abs(0.5 / inverse.a)
yswing = abs(0.5 / inverse.d)
xpoints = [0.5 - xswing, 0.5 - (xswing / 2.0), 0.5, 0.5 + (xswing / 2.0), 0.5 + xswing]
ypoints = [0.5 - yswing, 0.5 - (yswing / 2.0), 0.5, 0.5 + (yswing / 2.0), 0.5 + yswing]
for addy in ypoints:
for addx in xpoints:
texloc = inverse.multiply_point(Point(imgx + addx, imgy + addy))
aax, aay = texloc.as_tuple()
# If we're out of bounds, don't update.
if aax < 0 or aay < 0 or aax >= texwidth or aay >= texheight:
continue
# Grab the values to average, for SSAA.
texoff = (aax + (aay * texwidth)) * 4
r += texbytes[texoff]
g += texbytes[texoff + 1]
b += texbytes[texoff + 2]
a += texbytes[texoff + 3]
count += 1
if count == 0:
# None of the samples existed in-bounds.
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
continue
# Average the pixels.
average = [r // count, g // count, b // count, a // count]
result.append(blend_point(add_color, mult_color, average, imgbytes[(imgoff * 4):((imgoff + 1) * 4)], blendfunc))
else:
# Calculate what texture pixel data goes here.
texloc = inverse.multiply_point(Point(float(imgx + 0.5), float(imgy + 0.5)))
texloc = inverse.multiply_point(Point(imgx + 0.5, imgy + 0.5))
texx, texy = texloc.as_tuple()
# If we're out of bounds, don't update.
@ -342,10 +426,6 @@ def pixel_renderer(
# Blend it.
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))
linebytes = bytes([channel for pixel in result for channel in pixel])

View File

@ -12,5 +12,6 @@ def affine_composite(
blendfunc: int,
texture: Image.Image,
single_threaded: bool = False,
enable_aa: bool = True,
) -> Image.Image:
...

View File

@ -38,7 +38,8 @@ cdef extern int affine_composite_fast(
unsigned char *texdata,
unsigned int texwidth,
unsigned int texheight,
unsigned int threads
unsigned int threads,
unsigned int enable_aa,
)
def affine_composite(
@ -50,6 +51,7 @@ def affine_composite(
blendfunc: int,
texture: Image.Image,
single_threaded: bool = False,
enable_aa: bool = True,
) -> Image.Image:
# Calculate the inverse so we can map canvas space back to texture space.
try:
@ -125,6 +127,7 @@ def affine_composite(
texwidth,
texheight,
threads,
1 if enable_aa else 0,
)
if errors != 0:
raise Exception("Error raised in C++!")

View File

@ -65,6 +65,7 @@ extern "C"
floatcolor_t mult_color;
int blendfunc;
pthread_t *thread;
int enable_aa;
} work_t;
inline unsigned char clamp(float color) {
@ -253,7 +254,62 @@ extern "C"
// Determine offset.
unsigned int imgoff = imgx + (imgy * work->imgwidth);
// If we are masked off, don't do any other calculations.
if (work->maskdata != NULL && work->maskdata[imgoff] == 0) {
// This pixel is masked off!
continue;
}
// Blend for simple anti-aliasing.
if (work->enable_aa) {
// Calculate what texture pixel data goes here.
int r = 0;
int g = 0;
int b = 0;
int a = 0;
int count = 0;
float xswing = fabs(0.5 / work->inverse.a);
float yswing = fabs(0.5 / work->inverse.d);
for (float addy = 0.5 - yswing; addy <= 0.5 + yswing; addy += yswing / 2.0) {
for (float addx = 0.5 - xswing; addx <= 0.5 + xswing; addx += xswing / 2.0) {
point_t texloc = work->inverse.multiply_point((point_t){(float)imgx + addx, (float)imgy + addy});
int aax = texloc.x;
int aay = texloc.y;
// If we're out of bounds, don't update.
if (aax < 0 or aay < 0 or aax >= (int)work->texwidth or aay >= (int)work->texheight) {
continue;
}
// Grab the values to average, for SSAA.
unsigned int texoff = aax + (aay * work->texwidth);
r += work->texdata[texoff].r;
g += work->texdata[texoff].g;
b += work->texdata[texoff].b;
a += work->texdata[texoff].a;
count ++;
}
}
if (count == 0) {
// None of the samples existed in-bounds.
continue;
}
// Average the pixels.
intcolor_t average = (intcolor_t){
(unsigned char)(r / count),
(unsigned char)(g / count),
(unsigned char)(b / count),
(unsigned char)(a / count),
};
// Blend it.
work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, average, work->imgdata[imgoff], work->blendfunc);
} else {
// Grab the center of the pixel to get the color.
point_t texloc = work->inverse.multiply_point((point_t){(float)imgx + (float)0.5, (float)imgy + (float)0.5});
int texx = texloc.x;
int texy = texloc.y;
@ -265,14 +321,11 @@ extern "C"
// Blend it.
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);
}
}
}
}
void *chunk_composite_worker(void *arg) {
work_t *work = (work_t *)arg;
@ -296,7 +349,8 @@ extern "C"
unsigned char *texbytes,
unsigned int texwidth,
unsigned int texheight,
unsigned int threads
unsigned int threads,
unsigned int enable_aa
) {
// Cast to a usable type.
intcolor_t *imgdata = (intcolor_t *)imgbytes;
@ -319,6 +373,7 @@ extern "C"
work.add_color = add_color;
work.mult_color = mult_color;
work.blendfunc = blendfunc;
work.enable_aa = enable_aa;
chunk_composite_fast(&work);
} else {
@ -362,6 +417,7 @@ extern "C"
work->mult_color = mult_color;
work->blendfunc = blendfunc;
work->thread = thread;
work->enable_aa = enable_aa;
if (me)
{

View File

@ -397,11 +397,12 @@ MissingThis = object()
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, enable_aa: bool = True) -> None:
super().__init__()
# Options for rendering
self.__single_threaded = single_threaded
self.__enable_aa = enable_aa
# Library of shapes (draw instructions), textures (actual images) and swfs (us and other files for imports).
self.shapes: Dict[str, Shape] = shapes
@ -958,6 +959,7 @@ class AFPRenderer(VerboseOutput):
257,
mask.rectangle,
single_threaded=self.__single_threaded,
enable_aa=False,
)
# Composite it onto the current mask.
@ -970,6 +972,7 @@ class AFPRenderer(VerboseOutput):
256,
calculated_mask,
single_threaded=self.__single_threaded,
enable_aa=False,
)
def __render_object(
@ -1041,11 +1044,13 @@ class AFPRenderer(VerboseOutput):
print("WARNING: Unhandled UV coordinate color!")
texture = None
enable_aa = False
if params.flags & 0x2:
# We need to look up the texture for this.
if params.region not in self.textures:
raise Exception(f"Cannot find texture reference {params.region}!")
texture = self.textures[params.region]
enable_aa = self.__enable_aa
if params.flags & 0x8:
# TODO: This texture gets further blended somehow? Not sure this is ever used.
@ -1082,7 +1087,7 @@ class AFPRenderer(VerboseOutput):
texture = shape.rectangle
if texture is not None:
img = affine_composite(img, add_color, mult_color, transform, mask, blend, texture, single_threaded=self.__single_threaded)
img = affine_composite(img, add_color, mult_color, transform, mask, blend, texture, single_threaded=self.__single_threaded, enable_aa=enable_aa)
elif isinstance(renderable, PlacedImage):
if only_depths is not None and renderable.depth not in only_depths:
# Not on the correct depth plane.
@ -1090,7 +1095,7 @@ class AFPRenderer(VerboseOutput):
# This is a shape draw reference.
texture = self.textures[renderable.source.reference]
img = affine_composite(img, add_color, mult_color, transform, mask, blend, texture, single_threaded=self.__single_threaded)
img = affine_composite(img, add_color, mult_color, transform, mask, blend, texture, single_threaded=self.__single_threaded, enable_aa=self.__enable_aa)
elif isinstance(renderable, PlacedDummy):
# Nothing to do!
pass

View File

@ -523,6 +523,7 @@ def render_path(
output: str,
*,
disable_threads: bool = False,
disable_anti_aliasing: bool = False,
background_color: Optional[str] = None,
background_image: Optional[str] = None,
force_aspect_ratio: Optional[str] = None,
@ -532,7 +533,7 @@ def render_path(
only_frames: Optional[str] = None,
verbose: bool = False,
) -> int:
renderer = AFPRenderer(single_threaded=disable_threads)
renderer = AFPRenderer(single_threaded=disable_threads, enable_aa=not disable_anti_aliasing)
load_containers(renderer, containers, need_extras=True, verbose=verbose)
# Verify the correct params.
@ -918,6 +919,11 @@ def main() -> int:
action="store_true",
help="Disable multi-threaded rendering.",
)
render_parser.add_argument(
"--disable-anti-aliasing",
action="store_true",
help="Disable anti-aliased rendering.",
)
render_parser.add_argument(
"-v",
"--verbose",
@ -982,6 +988,7 @@ def main() -> int:
args.path,
args.output,
disable_threads=args.disable_threads,
disable_anti_aliasing=args.disable_anti_aliasing,
background_color=args.background_color,
background_image=args.background_image,
force_aspect_ratio=args.force_aspect_ratio,