From f8c3f758838c38bafefe8212cf63be290d7dc17b Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Sun, 13 Jun 2021 03:15:03 +0000 Subject: [PATCH] Fix issue with anti-aliasing artifacts around borders with transparent pixels. --- bemani/format/afp/blend/blend.py | 29 +++++++++++---- bemani/format/afp/blend/blendcppimpl.cxx | 46 +++++++++++++++++------- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/bemani/format/afp/blend/blend.py b/bemani/format/afp/blend/blend.py index 089d314..d4cc893 100644 --- a/bemani/format/afp/blend/blend.py +++ b/bemani/format/afp/blend/blend.py @@ -427,6 +427,7 @@ def pixel_renderer( b = 0 a = 0 count = 0 + denom = 0 # Essentially what we're doing here is calculating the scale, clamping it at 1.0 as the # minimum and then setting the AA sample swing accordingly. This has the effect of anti-aliasing @@ -442,15 +443,21 @@ def pixel_renderer( texloc = inverse.multiply_point(Point(imgx + addx, imgy + addy)) aax, aay = texloc.as_tuple() - # If we're out of bounds, don't update. + # If we're out of bounds, don't update. Factor this in, however, so we can get partial + # transparency to the pixel that is already there. + denom += 1 if aax < 0 or aay < 0 or aax >= texwidth or aay >= texheight: continue - # Grab the values to average, for SSAA. + # Grab the values to average, for SSAA. Make sure to factor in alpha as a poor-man's + # blend to ensure that partial transparency pixel values don't unnecessarily factor + # into average calculations. texoff = (aax + (aay * texwidth)) * 4 - r += texbytes[texoff] - g += texbytes[texoff + 1] - b += texbytes[texoff + 2] + apercent = texbytes[texoff + 3] / 255.0 + + r += int(texbytes[texoff] * apercent) + g += int(texbytes[texoff + 1] * apercent) + b += int(texbytes[texoff + 2] * apercent) a += texbytes[texoff + 3] count += 1 @@ -458,8 +465,16 @@ def pixel_renderer( # None of the samples existed in-bounds. return imgbytes[imgoff:(imgoff + 4)] - # Average the pixels. - average = [r // count, g // count, b // count, a // count] + # Average the pixels. Make sure to divide out the alpha in preparation for blending. + alpha = a // denom + + if alpha == 0: + average = [255, 255, 255, alpha] + else: + apercent = alpha / 255.0 + average = [int((r / denom) / apercent), int((g / denom) / apercent), int((b / denom) / apercent), alpha] + + # Finally, blend it with the destination. return blend_point(add_color, mult_color, average, imgbytes[imgoff:(imgoff + 4)], blendfunc) else: # Calculate what texture pixel data goes here. diff --git a/bemani/format/afp/blend/blendcppimpl.cxx b/bemani/format/afp/blend/blendcppimpl.cxx index a6c3dd3..ee86316 100644 --- a/bemani/format/afp/blend/blendcppimpl.cxx +++ b/bemani/format/afp/blend/blendcppimpl.cxx @@ -275,6 +275,7 @@ extern "C" int b = 0; int a = 0; int count = 0; + int denom = 0; 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) { @@ -282,16 +283,22 @@ extern "C" int aax = texloc.x; int aay = texloc.y; - // If we're out of bounds, don't update. + // If we're out of bounds, don't update. Factor this in, however, so we can get partial + // transparency to the pixel that is already there. + denom ++; if (aax < 0 or aay < 0 or aax >= (int)work->texwidth or aay >= (int)work->texheight) { continue; } - // Grab the values to average, for SSAA. + // Grab the values to average, for SSAA. Make sure to factor in alpha as a poor-man's + // blend to ensure that partial transparency pixel values don't unnecessarily factor + // into average calculations. unsigned int texoff = aax + (aay * work->texwidth); - r += work->texdata[texoff].r; - g += work->texdata[texoff].g; - b += work->texdata[texoff].b; + float apercent = work->texdata[texoff].a / 255.0; + + r += (int)(work->texdata[texoff].r * apercent); + g += (int)(work->texdata[texoff].g * apercent); + b += (int)(work->texdata[texoff].b * apercent); a += work->texdata[texoff].a; count ++; } @@ -302,13 +309,28 @@ extern "C" 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), - }; + // Average the pixels. Make sure to divide out the alpha in preparation for blending. + unsigned char alpha = (unsigned char)(a / denom); + intcolor_t average; + + if (alpha == 0) { + // Samples existed in bounds, but with zero alpha. + average = (intcolor_t){ + 255, + 255, + 255, + alpha, + }; + } else { + // Samples existed in bounds, with some alpha component, un-premultiply it. + float apercent = alpha / 255.0; + average = (intcolor_t){ + (unsigned char)((r / denom) / apercent), + (unsigned char)((g / denom) / apercent), + (unsigned char)((b / denom) / apercent), + alpha, + }; + } // Blend it. work->imgdata[imgoff] = blend_point(work->add_color, work->mult_color, average, work->imgdata[imgoff], work->blendfunc);