aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorsfan5 <sfan5@live.de>2021-04-20 19:50:19 +0200
committerGitHub <noreply@github.com>2021-04-20 19:50:19 +0200
commit1da73418cd2ea0e03e8289f54a47dededcf8b331 (patch)
tree384bd9f7680f9444fc8c6f95c56400f0f22487bb /src/client
parent90a7bd6a0afb6509e96bcb373f95b448ee7f3b1d (diff)
downloadminetest-1da73418cd2ea0e03e8289f54a47dededcf8b331.tar.gz
minetest-1da73418cd2ea0e03e8289f54a47dededcf8b331.tar.bz2
minetest-1da73418cd2ea0e03e8289f54a47dededcf8b331.zip
Enable cleanTransparent filter for mipmapping and improve its' algorithm (#11145)
Diffstat (limited to 'src/client')
-rw-r--r--src/client/guiscalingfilter.cpp2
-rw-r--r--src/client/imagefilters.cpp107
-rw-r--r--src/client/imagefilters.h6
-rw-r--r--src/client/minimap.cpp3
-rw-r--r--src/client/tile.cpp8
5 files changed, 100 insertions, 26 deletions
diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp
index 8c565a52f..cf371d8ba 100644
--- a/src/client/guiscalingfilter.cpp
+++ b/src/client/guiscalingfilter.cpp
@@ -102,7 +102,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
return src;
srcimg = driver->createImageFromData(src->getColorFormat(),
- src->getSize(), src->lock(), false);
+ src->getSize(), src->lock(video::ETLM_READ_ONLY), false);
src->unlock();
g_imgCache[origname] = srcimg;
}
diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp
index 0fa501410..7b2ef9822 100644
--- a/src/client/imagefilters.cpp
+++ b/src/client/imagefilters.cpp
@@ -19,63 +19,134 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "imagefilters.h"
#include "util/numeric.h"
#include <cmath>
+#include <cassert>
+#include <vector>
+
+// Simple 2D bitmap class with just the functionality needed here
+class Bitmap {
+ u32 linesize, lines;
+ std::vector<u8> data;
+
+ static inline u32 bytepos(u32 index) { return index >> 3; }
+ static inline u8 bitpos(u32 index) { return index & 7; }
+
+public:
+ Bitmap(u32 width, u32 height) : linesize(width), lines(height),
+ data(bytepos(width * height) + 1) {}
+
+ inline bool get(u32 x, u32 y) const {
+ u32 index = y * linesize + x;
+ return data[bytepos(index)] & (1 << bitpos(index));
+ }
+
+ inline void set(u32 x, u32 y) {
+ u32 index = y * linesize + x;
+ data[bytepos(index)] |= 1 << bitpos(index);
+ }
+
+ inline bool all() const {
+ for (u32 i = 0; i < data.size() - 1; i++) {
+ if (data[i] != 0xff)
+ return false;
+ }
+ // last byte not entirely filled
+ for (u8 i = 0; i < bitpos(linesize * lines); i++) {
+ bool value_of_bit = data.back() & (1 << i);
+ if (!value_of_bit)
+ return false;
+ }
+ return true;
+ }
+
+ inline void copy(Bitmap &to) const {
+ assert(to.linesize == linesize && to.lines == lines);
+ to.data = data;
+ }
+};
/* Fill in RGB values for transparent pixels, to correct for odd colors
* appearing at borders when blending. This is because many PNG optimizers
* like to discard RGB values of transparent pixels, but when blending then
- * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ * with non-transparent neighbors, their RGB values will show up nonetheless.
*
* This function modifies the original image in-place.
*
* Parameter "threshold" is the alpha level below which pixels are considered
- * transparent. Should be 127 for 3d where alpha is threshold, but 0 for
- * 2d where alpha is blended.
+ * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
+ * 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold)
{
core::dimension2d<u32> dim = src->getDimension();
- // Walk each pixel looking for fully transparent ones.
+ Bitmap bitmap(dim.Width, dim.Height);
+
+ // First pass: Mark all opaque pixels
// Note: loop y around x for better cache locality.
for (u32 ctry = 0; ctry < dim.Height; ctry++)
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
+ if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
+ bitmap.set(ctrx, ctry);
+ }
+
+ // Exit early if all pixels opaque
+ if (bitmap.all())
+ return;
+
+ Bitmap newmap = bitmap;
+
+ // Then repeatedly look for transparent pixels, filling them in until
+ // we're finished (capped at 50 iterations).
+ for (u32 iter = 0; iter < 50; iter++) {
- // Ignore opaque pixels.
- irr::video::SColor c = src->getPixel(ctrx, ctry);
- if (c.getAlpha() > threshold)
+ for (u32 ctry = 0; ctry < dim.Height; ctry++)
+ for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
+ // Skip pixels we have already processed
+ if (bitmap.get(ctrx, ctry))
continue;
- // Sample size and total weighted r, g, b values.
+ video::SColor c = src->getPixel(ctrx, ctry);
+
+ // Sample size and total weighted r, g, b values
u32 ss = 0, sr = 0, sg = 0, sb = 0;
- // Walk each neighbor pixel (clipped to image bounds).
+ // Walk each neighbor pixel (clipped to image bounds)
for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
sy <= (ctry + 1) && sy < dim.Height; sy++)
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
sx <= (ctrx + 1) && sx < dim.Width; sx++) {
-
- // Ignore transparent pixels.
- irr::video::SColor d = src->getPixel(sx, sy);
- if (d.getAlpha() <= threshold)
+ // Ignore pixels we haven't processed
+ if (!bitmap.get(sx, sy))
continue;
-
- // Add RGB values weighted by alpha.
- u32 a = d.getAlpha();
+
+ // Add RGB values weighted by alpha IF the pixel is opaque, otherwise
+ // use full weight since we want to propagate colors.
+ video::SColor d = src->getPixel(sx, sy);
+ u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
ss += a;
sr += a * d.getRed();
sg += a * d.getGreen();
sb += a * d.getBlue();
}
- // If we found any neighbor RGB data, set pixel to average
- // weighted by alpha.
+ // Set pixel to average weighted by alpha
if (ss > 0) {
c.setRed(sr / ss);
c.setGreen(sg / ss);
c.setBlue(sb / ss);
src->setPixel(ctrx, ctry, c);
+ newmap.set(ctrx, ctry);
}
}
+
+ if (newmap.all())
+ return;
+
+ // Apply changes to bitmap for next run. This is done so we don't introduce
+ // a bias in color propagation in the direction pixels are processed.
+ newmap.copy(bitmap);
+
+ }
}
/* Scale a region of an image into another image, using nearest-neighbor with
diff --git a/src/client/imagefilters.h b/src/client/imagefilters.h
index 5676faf85..c9bdefbb6 100644
--- a/src/client/imagefilters.h
+++ b/src/client/imagefilters.h
@@ -23,13 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
/* Fill in RGB values for transparent pixels, to correct for odd colors
* appearing at borders when blending. This is because many PNG optimizers
* like to discard RGB values of transparent pixels, but when blending then
- * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ * with non-transparent neighbors, their RGB values will show up nonetheless.
*
* This function modifies the original image in-place.
*
* Parameter "threshold" is the alpha level below which pixels are considered
- * transparent. Should be 127 for 3d where alpha is threshold, but 0 for
- * 2d where alpha is blended.
+ * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
+ * 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold);
diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp
index dd810ee0a..97977c366 100644
--- a/src/client/minimap.cpp
+++ b/src/client/minimap.cpp
@@ -491,7 +491,8 @@ video::ITexture *Minimap::getMinimapTexture()
// Want to use texture source, to : 1 find texture, 2 cache it
video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
video::IImage* image = driver->createImageFromData(
- texture->getColorFormat(), texture->getSize(), texture->lock(), true, false);
+ texture->getColorFormat(), texture->getSize(),
+ texture->lock(video::ETLM_READ_ONLY), true, false);
texture->unlock();
auto dim = image->getDimension();
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index 7e3901247..d9f8c75a7 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -427,6 +427,7 @@ private:
std::unordered_map<std::string, Palette> m_palettes;
// Cached settings needed for making textures from meshes
+ bool m_setting_mipmap;
bool m_setting_trilinear_filter;
bool m_setting_bilinear_filter;
};
@@ -447,6 +448,7 @@ TextureSource::TextureSource()
// Cache some settings
// Note: Since this is only done once, the game must be restarted
// for these settings to take effect
+ m_setting_mipmap = g_settings->getBool("mip_map");
m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
}
@@ -667,7 +669,7 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
{
static thread_local bool filter_needed =
- g_settings->getBool("texture_clean_transparent") ||
+ g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
g_settings->getS32("texture_min_size") > 1);
// Avoid duplicating texture if it won't actually change
@@ -1636,8 +1638,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
return false;
}
- // Apply the "clean transparent" filter, if configured.
- if (g_settings->getBool("texture_clean_transparent"))
+ // Apply the "clean transparent" filter, if needed
+ if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
imageCleanTransparent(baseimg, 127);
/* Upscale textures to user's requested minimum size. This is a trick to make