Skip to content

Commit 1da7341

Browse files
authoredApr 20, 2021
Enable cleanTransparent filter for mipmapping and improve its' algorithm (#11145)
1 parent 90a7bd6 commit 1da7341

File tree

6 files changed

+105
-32
lines changed

6 files changed

+105
-32
lines changed
 

‎builtin/settingtypes.txt

+5-6
Original file line numberDiff line numberDiff line change
@@ -504,18 +504,17 @@ bilinear_filter (Bilinear filtering) bool false
504504
trilinear_filter (Trilinear filtering) bool false
505505

506506
# Filtered textures can blend RGB values with fully-transparent neighbors,
507-
# which PNG optimizers usually discard, sometimes resulting in a dark or
508-
# light edge to transparent textures. Apply this filter to clean that up
509-
# at texture load time.
507+
# which PNG optimizers usually discard, often resulting in dark or
508+
# light edges to transparent textures. Apply a filter to clean that up
509+
# at texture load time. This is automatically enabled if mipmapping is enabled.
510510
texture_clean_transparent (Clean transparent textures) bool false
511511

512512
# When using bilinear/trilinear/anisotropic filters, low-resolution textures
513513
# can be blurred, so automatically upscale them with nearest-neighbor
514514
# interpolation to preserve crisp pixels. This sets the minimum texture size
515515
# for the upscaled textures; higher values look sharper, but require more
516-
# memory. Powers of 2 are recommended. Setting this higher than 1 may not
517-
# have a visible effect unless bilinear/trilinear/anisotropic filtering is
518-
# enabled.
516+
# memory. Powers of 2 are recommended. This setting is ONLY applies if
517+
# bilinear/trilinear/anisotropic filtering is enabled.
519518
# This is also used as the base node texture size for world-aligned
520519
# texture autoscaling.
521520
texture_min_size (Minimum texture size) int 64

‎src/client/guiscalingfilter.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
102102
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
103103
return src;
104104
srcimg = driver->createImageFromData(src->getColorFormat(),
105-
src->getSize(), src->lock(), false);
105+
src->getSize(), src->lock(video::ETLM_READ_ONLY), false);
106106
src->unlock();
107107
g_imgCache[origname] = srcimg;
108108
}

‎src/client/imagefilters.cpp

+89-18
Original file line numberDiff line numberDiff line change
@@ -19,63 +19,134 @@ with this program; if not, write to the Free Software Foundation, Inc.,
1919
#include "imagefilters.h"
2020
#include "util/numeric.h"
2121
#include <cmath>
22+
#include <cassert>
23+
#include <vector>
24+
25+
// Simple 2D bitmap class with just the functionality needed here
26+
class Bitmap {
27+
u32 linesize, lines;
28+
std::vector<u8> data;
29+
30+
static inline u32 bytepos(u32 index) { return index >> 3; }
31+
static inline u8 bitpos(u32 index) { return index & 7; }
32+
33+
public:
34+
Bitmap(u32 width, u32 height) : linesize(width), lines(height),
35+
data(bytepos(width * height) + 1) {}
36+
37+
inline bool get(u32 x, u32 y) const {
38+
u32 index = y * linesize + x;
39+
return data[bytepos(index)] & (1 << bitpos(index));
40+
}
41+
42+
inline void set(u32 x, u32 y) {
43+
u32 index = y * linesize + x;
44+
data[bytepos(index)] |= 1 << bitpos(index);
45+
}
46+
47+
inline bool all() const {
48+
for (u32 i = 0; i < data.size() - 1; i++) {
49+
if (data[i] != 0xff)
50+
return false;
51+
}
52+
// last byte not entirely filled
53+
for (u8 i = 0; i < bitpos(linesize * lines); i++) {
54+
bool value_of_bit = data.back() & (1 << i);
55+
if (!value_of_bit)
56+
return false;
57+
}
58+
return true;
59+
}
60+
61+
inline void copy(Bitmap &to) const {
62+
assert(to.linesize == linesize && to.lines == lines);
63+
to.data = data;
64+
}
65+
};
2266

2367
/* Fill in RGB values for transparent pixels, to correct for odd colors
2468
* appearing at borders when blending. This is because many PNG optimizers
2569
* like to discard RGB values of transparent pixels, but when blending then
26-
* with non-transparent neighbors, their RGB values will shpw up nonetheless.
70+
* with non-transparent neighbors, their RGB values will show up nonetheless.
2771
*
2872
* This function modifies the original image in-place.
2973
*
3074
* Parameter "threshold" is the alpha level below which pixels are considered
31-
* transparent. Should be 127 for 3d where alpha is threshold, but 0 for
32-
* 2d where alpha is blended.
75+
* transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
76+
* 0 when alpha blending is used.
3377
*/
3478
void imageCleanTransparent(video::IImage *src, u32 threshold)
3579
{
3680
core::dimension2d<u32> dim = src->getDimension();
3781

38-
// Walk each pixel looking for fully transparent ones.
82+
Bitmap bitmap(dim.Width, dim.Height);
83+
84+
// First pass: Mark all opaque pixels
3985
// Note: loop y around x for better cache locality.
4086
for (u32 ctry = 0; ctry < dim.Height; ctry++)
4187
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
88+
if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
89+
bitmap.set(ctrx, ctry);
90+
}
91+
92+
// Exit early if all pixels opaque
93+
if (bitmap.all())
94+
return;
95+
96+
Bitmap newmap = bitmap;
97+
98+
// Then repeatedly look for transparent pixels, filling them in until
99+
// we're finished (capped at 50 iterations).
100+
for (u32 iter = 0; iter < 50; iter++) {
42101

43-
// Ignore opaque pixels.
44-
irr::video::SColor c = src->getPixel(ctrx, ctry);
45-
if (c.getAlpha() > threshold)
102+
for (u32 ctry = 0; ctry < dim.Height; ctry++)
103+
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
104+
// Skip pixels we have already processed
105+
if (bitmap.get(ctrx, ctry))
46106
continue;
47107

48-
// Sample size and total weighted r, g, b values.
108+
video::SColor c = src->getPixel(ctrx, ctry);
109+
110+
// Sample size and total weighted r, g, b values
49111
u32 ss = 0, sr = 0, sg = 0, sb = 0;
50112

51-
// Walk each neighbor pixel (clipped to image bounds).
113+
// Walk each neighbor pixel (clipped to image bounds)
52114
for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
53115
sy <= (ctry + 1) && sy < dim.Height; sy++)
54116
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
55117
sx <= (ctrx + 1) && sx < dim.Width; sx++) {
56-
57-
// Ignore transparent pixels.
58-
irr::video::SColor d = src->getPixel(sx, sy);
59-
if (d.getAlpha() <= threshold)
118+
// Ignore pixels we haven't processed
119+
if (!bitmap.get(sx, sy))
60120
continue;
61-
62-
// Add RGB values weighted by alpha.
63-
u32 a = d.getAlpha();
121+
122+
// Add RGB values weighted by alpha IF the pixel is opaque, otherwise
123+
// use full weight since we want to propagate colors.
124+
video::SColor d = src->getPixel(sx, sy);
125+
u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
64126
ss += a;
65127
sr += a * d.getRed();
66128
sg += a * d.getGreen();
67129
sb += a * d.getBlue();
68130
}
69131

70-
// If we found any neighbor RGB data, set pixel to average
71-
// weighted by alpha.
132+
// Set pixel to average weighted by alpha
72133
if (ss > 0) {
73134
c.setRed(sr / ss);
74135
c.setGreen(sg / ss);
75136
c.setBlue(sb / ss);
76137
src->setPixel(ctrx, ctry, c);
138+
newmap.set(ctrx, ctry);
77139
}
78140
}
141+
142+
if (newmap.all())
143+
return;
144+
145+
// Apply changes to bitmap for next run. This is done so we don't introduce
146+
// a bias in color propagation in the direction pixels are processed.
147+
newmap.copy(bitmap);
148+
149+
}
79150
}
80151

81152
/* Scale a region of an image into another image, using nearest-neighbor with

‎src/client/imagefilters.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2323
/* Fill in RGB values for transparent pixels, to correct for odd colors
2424
* appearing at borders when blending. This is because many PNG optimizers
2525
* like to discard RGB values of transparent pixels, but when blending then
26-
* with non-transparent neighbors, their RGB values will shpw up nonetheless.
26+
* with non-transparent neighbors, their RGB values will show up nonetheless.
2727
*
2828
* This function modifies the original image in-place.
2929
*
3030
* Parameter "threshold" is the alpha level below which pixels are considered
31-
* transparent. Should be 127 for 3d where alpha is threshold, but 0 for
32-
* 2d where alpha is blended.
31+
* transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
32+
* 0 when alpha blending is used.
3333
*/
3434
void imageCleanTransparent(video::IImage *src, u32 threshold);
3535

‎src/client/minimap.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,8 @@ video::ITexture *Minimap::getMinimapTexture()
491491
// Want to use texture source, to : 1 find texture, 2 cache it
492492
video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
493493
video::IImage* image = driver->createImageFromData(
494-
texture->getColorFormat(), texture->getSize(), texture->lock(), true, false);
494+
texture->getColorFormat(), texture->getSize(),
495+
texture->lock(video::ETLM_READ_ONLY), true, false);
495496
texture->unlock();
496497

497498
auto dim = image->getDimension();

‎src/client/tile.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ class TextureSource : public IWritableTextureSource
427427
std::unordered_map<std::string, Palette> m_palettes;
428428

429429
// Cached settings needed for making textures from meshes
430+
bool m_setting_mipmap;
430431
bool m_setting_trilinear_filter;
431432
bool m_setting_bilinear_filter;
432433
};
@@ -447,6 +448,7 @@ TextureSource::TextureSource()
447448
// Cache some settings
448449
// Note: Since this is only done once, the game must be restarted
449450
// for these settings to take effect
451+
m_setting_mipmap = g_settings->getBool("mip_map");
450452
m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
451453
m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
452454
}
@@ -667,7 +669,7 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
667669
video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
668670
{
669671
static thread_local bool filter_needed =
670-
g_settings->getBool("texture_clean_transparent") ||
672+
g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
671673
((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
672674
g_settings->getS32("texture_min_size") > 1);
673675
// Avoid duplicating texture if it won't actually change
@@ -1636,8 +1638,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
16361638
return false;
16371639
}
16381640

1639-
// Apply the "clean transparent" filter, if configured.
1640-
if (g_settings->getBool("texture_clean_transparent"))
1641+
// Apply the "clean transparent" filter, if needed
1642+
if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
16411643
imageCleanTransparent(baseimg, 127);
16421644

16431645
/* Upscale textures to user's requested minimum size. This is a trick to make

0 commit comments

Comments
 (0)
Please sign in to comment.