1
0
mirror of synced 2024-11-12 01:00:46 +01:00

Implement bilinear AA for scaled up sprites to get rid of boxy artifacting.

This commit is contained in:
Jennifer Taylor 2021-06-13 18:24:18 +00:00
parent 8e8fa77d36
commit 092c4b6972
2 changed files with 216 additions and 89 deletions

View File

@ -432,52 +432,109 @@ def pixel_renderer(
# 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
# scaled up images a bit softer than would otherwise be achieved.
xswing = 0.5 * max(1.0, 1.0 / math.sqrt(inverse.a * inverse.a + inverse.b * inverse.b))
yswing = 0.5 * max(1.0, 1.0 / math.sqrt(inverse.c * inverse.c + inverse.d * inverse.d))
xscale = 1.0 / math.sqrt(inverse.a * inverse.a + inverse.b * inverse.b)
yscale = 1.0 / math.sqrt(inverse.c * inverse.c + inverse.d * inverse.d)
# These are used for picking the various sample points for SSAA method below.
xswing = 0.5 * max(1.0, xscale)
yswing = 0.5 * max(1.0, yscale)
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()
# First, figure out if we can use bilinear resampling.
bilinear = False
if xscale >= 1.0 and yscale >= 1.0:
aaloc = inverse.multiply_point(Point(imgx + 0.5, imgy + 0.5))
aax, aay = aaloc.as_tuple()
if not (aax <= 0 or aay <= 0 or aax >= (texwidth - 1) or aay >= (texheight - 1)):
bilinear = True
# 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
# Now perform the desired AA operation.
if bilinear:
# Calculate the pixel we're after, and what percentage into the pixel we are.
texloc = inverse.multiply_point(Point(imgx + 0.5, imgy + 0.5))
aax, aay = texloc.as_tuple()
aaxrem = texloc.x - aax
aayrem = texloc.y - aay
# 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
# Find the four pixels that we can interpolate from. The first number is the x, and second is y.
tex00 = (aax + (aay * texwidth)) * 4
tex10 = tex00 + 4
tex01 = (aax + ((aay + 1) * texwidth)) * 4
tex11 = tex01 + 4
# If this is a fully transparent pixel, the below formulas work out to adding nothing
# so we should skip this altogether.
if texbytes[texoff + 3] == 0:
continue
# Calculate various scaling factors based on alpha and percentage.
tex00percent = texbytes[tex00 + 3] / 255.0
tex10percent = texbytes[tex10 + 3] / 255.0
tex01percent = texbytes[tex01 + 3] / 255.0
tex11percent = texbytes[tex11 + 3] / 255.0
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
y0percent = (tex00percent * (1.0 - aaxrem)) + (tex10percent * aaxrem)
y1percent = (tex01percent * (1.0 - aaxrem)) + (tex11percent * aaxrem)
finalpercent = (y0percent * (1.0 - aayrem)) + (y1percent * aayrem)
if count == 0:
# None of the samples existed in-bounds.
return imgbytes[imgoff:(imgoff + 4)]
if finalpercent <= 0.0:
# This pixel would be blank, so we avoid dividing by zero.
average = [255, 255, 255, 0]
else:
# Interpolate in the X direction on both Y axis.
y0r = ((texbytes[tex00] * tex00percent * (1.0 - aaxrem)) + (texbytes[tex10] * tex10percent * aaxrem))
y0g = ((texbytes[tex00 + 1] * tex00percent * (1.0 - aaxrem)) + (texbytes[tex10 + 1] * tex10percent * aaxrem))
y0b = ((texbytes[tex00 + 2] * tex00percent * (1.0 - aaxrem)) + (texbytes[tex10 + 2] * tex10percent * aaxrem))
# Average the pixels. Make sure to divide out the alpha in preparation for blending.
alpha = a // denom
y1r = ((texbytes[tex01] * tex01percent * (1.0 - aaxrem)) + (texbytes[tex11] * tex11percent * aaxrem))
y1g = ((texbytes[tex01 + 1] * tex01percent * (1.0 - aaxrem)) + (texbytes[tex11 + 1] * tex11percent * aaxrem))
y1b = ((texbytes[tex01 + 2] * tex01percent * (1.0 - aaxrem)) + (texbytes[tex11 + 2] * tex11percent * aaxrem))
if alpha == 0:
average = [255, 255, 255, alpha]
# Now interpolate the Y direction to get the final pixel value.
average = [
int(((y0r * (1.0 - aayrem)) + (y1r * aayrem)) / finalpercent),
int(((y0g * (1.0 - aayrem)) + (y1g * aayrem)) / finalpercent),
int(((y0b * (1.0 - aayrem)) + (y1b * aayrem)) / finalpercent),
int(finalpercent * 255),
]
else:
apercent = alpha / 255.0
average = [int((r / denom) / apercent), int((g / denom) / apercent), int((b / denom) / apercent), alpha]
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. 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. 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
# If this is a fully transparent pixel, the below formulas work out to adding nothing
# so we should skip this altogether.
if texbytes[texoff + 3] == 0:
continue
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
if count == 0:
# None of the samples existed in-bounds.
return imgbytes[imgoff:(imgoff + 4)]
# 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)

View File

@ -253,8 +253,12 @@ extern "C"
// costs us almost nothing. 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 scaled up images
// a bit softer than would otherwise be achieved.
float xswing = 0.5 * fmax(1.0, 1.0 / sqrt(work->inverse.a * work->inverse.a + work->inverse.b * work->inverse.b));
float yswing = 0.5 * fmax(1.0, 1.0 / sqrt(work->inverse.c * work->inverse.c + work->inverse.d * work->inverse.d));
float xscale = 1.0 / sqrt(work->inverse.a * work->inverse.a + work->inverse.b * work->inverse.b);
float yscale = 1.0 / sqrt(work->inverse.c * work->inverse.c + work->inverse.d * work->inverse.d);
// These are used for picking the various sample points for SSAA method below.
float xswing = 0.5 * fmax(1.0, xscale);
float yswing = 0.5 * fmax(1.0, yscale);
for (unsigned int imgy = work->miny; imgy < work->maxy; imgy++) {
for (unsigned int imgx = work->minx; imgx < work->maxx; imgx++) {
@ -277,65 +281,131 @@ extern "C"
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) {
point_t texloc = work->inverse.multiply_point((point_t){(float)imgx + addx, (float)imgy + addy});
int aax = texloc.x;
int aay = texloc.y;
// First, figure out if we can use bilinear resampling.
int bilinear = 0;
if (xscale >= 1.0 && yscale >= 1.0) {
point_t aaloc = work->inverse.multiply_point((point_t){(float)(imgx + 0.5), (float)(imgy + 0.5)});
int aax = aaloc.x;
int aay = aaloc.y;
// 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. 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);
// If this is a fully transparent pixel, the below formulas work out to adding nothing
// so we should skip this altogether.
if (work->texdata[texoff].a == 0) {
continue;
}
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 ++;
if (!(aax <= 0 || aay <= 0 || aax >= ((int)work->texwidth - 1) || aay >= ((int)work->texheight - 1))) {
bilinear = 1;
}
}
if (count == 0) {
// None of the samples existed in-bounds.
continue;
}
// Average the pixels. Make sure to divide out the alpha in preparation for blending.
unsigned char alpha = (unsigned char)(a / denom);
// Now perform the desired AA operation.
intcolor_t average;
if (bilinear) {
// Calculate the pixel we're after, and what percentage into the pixel we are.
point_t texloc = work->inverse.multiply_point((point_t){(float)(imgx + 0.5), (float)(imgy + 0.5)});
int aax = texloc.x;
int aay = texloc.y;
float aaxrem = texloc.x - (float)aax;
float aayrem = texloc.y - (float)aay;
if (alpha == 0) {
// Samples existed in bounds, but with zero alpha.
average = (intcolor_t){
255,
255,
255,
alpha,
};
// Find the four pixels that we can interpolate from. The first number is the x, and second is y.
unsigned int tex00 = aax + (aay * work->texwidth);
unsigned int tex10 = tex00 + 1;
unsigned int tex01 = aax + ((aay + 1) * work->texwidth);
unsigned int tex11 = tex01 + 1;
// Calculate various scaling factors based on alpha and percentage.
float tex00percent = work->texdata[tex00].a / 255.0;
float tex10percent = work->texdata[tex10].a / 255.0;
float tex01percent = work->texdata[tex01].a / 255.0;
float tex11percent = work->texdata[tex11].a / 255.0;
float y0percent = (tex00percent * (1.0 - aaxrem)) + (tex10percent * aaxrem);
float y1percent = (tex01percent * (1.0 - aaxrem)) + (tex11percent * aaxrem);
float finalpercent = (y0percent * (1.0 - aayrem)) + (y1percent * aayrem);
if (finalpercent <= 0.0) {
// This pixel would be blank, so we avoid dividing by zero.
average = (intcolor_t){
255,
255,
255,
0,
};
} else {
// Interpolate in the X direction on both Y axis.
float y0r = ((work->texdata[tex00].r * tex00percent * (1.0 - aaxrem)) + (work->texdata[tex10].r * tex10percent * aaxrem));
float y0g = ((work->texdata[tex00].g * tex00percent * (1.0 - aaxrem)) + (work->texdata[tex10].g * tex10percent * aaxrem));
float y0b = ((work->texdata[tex00].b * tex00percent * (1.0 - aaxrem)) + (work->texdata[tex10].b * tex10percent * aaxrem));
float y1r = ((work->texdata[tex01].r * tex01percent * (1.0 - aaxrem)) + (work->texdata[tex11].r * tex11percent * aaxrem));
float y1g = ((work->texdata[tex01].g * tex01percent * (1.0 - aaxrem)) + (work->texdata[tex11].g * tex11percent * aaxrem));
float y1b = ((work->texdata[tex01].b * tex01percent * (1.0 - aaxrem)) + (work->texdata[tex11].b * tex11percent * aaxrem));
// Now interpolate the Y direction to get the final pixel value.
average = (intcolor_t){
(unsigned char)(((y0r * (1.0 - aayrem)) + (y1r * aayrem)) / finalpercent),
(unsigned char)(((y0g * (1.0 - aayrem)) + (y1g * aayrem)) / finalpercent),
(unsigned char)(((y0b * (1.0 - aayrem)) + (y1b * aayrem)) / finalpercent),
(unsigned char)(finalpercent * 255),
};
}
} 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,
};
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. Factor this in, however, so we can get partial
// transparency to the pixel that is already there.
denom ++;
if (aax < 0 || aay < 0 || aax >= (int)work->texwidth || aay >= (int)work->texheight) {
continue;
}
// 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);
// If this is a fully transparent pixel, the below formulas work out to adding nothing
// so we should skip this altogether.
if (work->texdata[texoff].a == 0) {
continue;
}
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 ++;
}
}
if (count == 0) {
// None of the samples existed in-bounds.
continue;
}
// Average the pixels. Make sure to divide out the alpha in preparation for blending.
unsigned char alpha = (unsigned char)(a / denom);
if (alpha == 0) {
// Samples existed in bounds, but with zero alpha.
average = (intcolor_t){
255,
255,
255,
0,
};
} 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.
@ -347,7 +417,7 @@ extern "C"
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) {
if (texx < 0 || texy < 0 || texx >= (int)work->texwidth || texy >= (int)work->texheight) {
continue;
}