Add simple anti-aliasing to texture renderer functions.
This commit is contained in:
parent
4785b01132
commit
e6ffc983f7
@ -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,21 +182,59 @@ def affine_composite(
|
||||
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 + 0.5), float(imgy + 0.5)))
|
||||
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 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)
|
||||
|
||||
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(imgx + 0.5, imgy + 0.5))
|
||||
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)
|
||||
imgmap[imgoff] = blend_point(add_color, mult_color, texmap[texoff], imgmap[imgoff], blendfunc)
|
||||
|
||||
img.putdata(imgmap)
|
||||
else:
|
||||
@ -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,23 +371,62 @@ def pixel_renderer(
|
||||
if imgx < minx or imgx >= maxx:
|
||||
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
|
||||
continue
|
||||
|
||||
# Calculate what texture pixel data goes here.
|
||||
texloc = inverse.multiply_point(Point(float(imgx + 0.5), float(imgy + 0.5)))
|
||||
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:
|
||||
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
|
||||
continue
|
||||
|
||||
# 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))
|
||||
|
||||
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(imgx + 0.5, imgy + 0.5))
|
||||
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:
|
||||
result.append(imgbytes[(imgoff * 4):((imgoff + 1) * 4)])
|
||||
continue
|
||||
|
||||
# Blend it.
|
||||
texoff = texx + (texy * texwidth)
|
||||
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])
|
||||
results.put((imgy, linebytes))
|
||||
|
@ -12,5 +12,6 @@ def affine_composite(
|
||||
blendfunc: int,
|
||||
texture: Image.Image,
|
||||
single_threaded: bool = False,
|
||||
enable_aa: bool = True,
|
||||
) -> Image.Image:
|
||||
...
|
||||
|
@ -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++!")
|
||||
|
@ -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,23 +254,75 @@ extern "C"
|
||||
// Determine offset.
|
||||
unsigned int imgoff = imgx + (imgy * work->imgwidth);
|
||||
|
||||
// Calculate what texture pixel data goes here.
|
||||
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;
|
||||
|
||||
// If we're out of bounds, don't update.
|
||||
if (texx < 0 or texy < 0 or texx >= (int)work->texwidth or texy >= (int)work->texheight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Blend it.
|
||||
unsigned int texoff = texx + (texy * work->texwidth);
|
||||
// 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;
|
||||
}
|
||||
work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, work->texdata[texoff], work->imgdata[imgoff], work->blendfunc);
|
||||
|
||||
// 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;
|
||||
|
||||
// If we're out of bounds, don't update.
|
||||
if (texx < 0 or texy < 0 or texx >= (int)work->texwidth or texy >= (int)work->texheight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user