mirror of
https://github.com/minetest/minetest.git
synced 2024-11-04 14:53:45 +01:00
Enable cleanTransparent filter for mipmapping and improve its' algorithm (#11145)
This commit is contained in:
parent
90a7bd6a0a
commit
1da73418cd
@ -504,18 +504,17 @@ bilinear_filter (Bilinear filtering) bool false
|
|||||||
trilinear_filter (Trilinear filtering) bool false
|
trilinear_filter (Trilinear filtering) bool false
|
||||||
|
|
||||||
# Filtered textures can blend RGB values with fully-transparent neighbors,
|
# Filtered textures can blend RGB values with fully-transparent neighbors,
|
||||||
# which PNG optimizers usually discard, sometimes resulting in a dark or
|
# which PNG optimizers usually discard, often resulting in dark or
|
||||||
# light edge to transparent textures. Apply this filter to clean that up
|
# light edges to transparent textures. Apply a filter to clean that up
|
||||||
# at texture load time.
|
# at texture load time. This is automatically enabled if mipmapping is enabled.
|
||||||
texture_clean_transparent (Clean transparent textures) bool false
|
texture_clean_transparent (Clean transparent textures) bool false
|
||||||
|
|
||||||
# When using bilinear/trilinear/anisotropic filters, low-resolution textures
|
# When using bilinear/trilinear/anisotropic filters, low-resolution textures
|
||||||
# can be blurred, so automatically upscale them with nearest-neighbor
|
# can be blurred, so automatically upscale them with nearest-neighbor
|
||||||
# interpolation to preserve crisp pixels. This sets the minimum texture size
|
# interpolation to preserve crisp pixels. This sets the minimum texture size
|
||||||
# for the upscaled textures; higher values look sharper, but require more
|
# for the upscaled textures; higher values look sharper, but require more
|
||||||
# memory. Powers of 2 are recommended. Setting this higher than 1 may not
|
# memory. Powers of 2 are recommended. This setting is ONLY applies if
|
||||||
# have a visible effect unless bilinear/trilinear/anisotropic filtering is
|
# bilinear/trilinear/anisotropic filtering is enabled.
|
||||||
# enabled.
|
|
||||||
# This is also used as the base node texture size for world-aligned
|
# This is also used as the base node texture size for world-aligned
|
||||||
# texture autoscaling.
|
# texture autoscaling.
|
||||||
texture_min_size (Minimum texture size) int 64
|
texture_min_size (Minimum texture size) int 64
|
||||||
|
@ -102,7 +102,7 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
|
|||||||
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
|
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
|
||||||
return src;
|
return src;
|
||||||
srcimg = driver->createImageFromData(src->getColorFormat(),
|
srcimg = driver->createImageFromData(src->getColorFormat(),
|
||||||
src->getSize(), src->lock(), false);
|
src->getSize(), src->lock(video::ETLM_READ_ONLY), false);
|
||||||
src->unlock();
|
src->unlock();
|
||||||
g_imgCache[origname] = srcimg;
|
g_imgCache[origname] = srcimg;
|
||||||
}
|
}
|
||||||
|
@ -19,63 +19,134 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "imagefilters.h"
|
#include "imagefilters.h"
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include <cmath>
|
#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
|
/* Fill in RGB values for transparent pixels, to correct for odd colors
|
||||||
* appearing at borders when blending. This is because many PNG optimizers
|
* appearing at borders when blending. This is because many PNG optimizers
|
||||||
* like to discard RGB values of transparent pixels, but when blending then
|
* 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.
|
* This function modifies the original image in-place.
|
||||||
*
|
*
|
||||||
* Parameter "threshold" is the alpha level below which pixels are considered
|
* Parameter "threshold" is the alpha level below which pixels are considered
|
||||||
* transparent. Should be 127 for 3d where alpha is threshold, but 0 for
|
* transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
|
||||||
* 2d where alpha is blended.
|
* 0 when alpha blending is used.
|
||||||
*/
|
*/
|
||||||
void imageCleanTransparent(video::IImage *src, u32 threshold)
|
void imageCleanTransparent(video::IImage *src, u32 threshold)
|
||||||
{
|
{
|
||||||
core::dimension2d<u32> dim = src->getDimension();
|
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.
|
// Note: loop y around x for better cache locality.
|
||||||
for (u32 ctry = 0; ctry < dim.Height; ctry++)
|
for (u32 ctry = 0; ctry < dim.Height; ctry++)
|
||||||
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
|
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
|
||||||
|
if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
|
||||||
|
bitmap.set(ctrx, ctry);
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore opaque pixels.
|
// Exit early if all pixels opaque
|
||||||
irr::video::SColor c = src->getPixel(ctrx, ctry);
|
if (bitmap.all())
|
||||||
if (c.getAlpha() > threshold)
|
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++) {
|
||||||
|
|
||||||
|
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;
|
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;
|
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);
|
for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
|
||||||
sy <= (ctry + 1) && sy < dim.Height; sy++)
|
sy <= (ctry + 1) && sy < dim.Height; sy++)
|
||||||
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
|
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
|
||||||
sx <= (ctrx + 1) && sx < dim.Width; sx++) {
|
sx <= (ctrx + 1) && sx < dim.Width; sx++) {
|
||||||
|
// Ignore pixels we haven't processed
|
||||||
// Ignore transparent pixels.
|
if (!bitmap.get(sx, sy))
|
||||||
irr::video::SColor d = src->getPixel(sx, sy);
|
|
||||||
if (d.getAlpha() <= threshold)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Add RGB values weighted by alpha.
|
// Add RGB values weighted by alpha IF the pixel is opaque, otherwise
|
||||||
u32 a = d.getAlpha();
|
// 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;
|
ss += a;
|
||||||
sr += a * d.getRed();
|
sr += a * d.getRed();
|
||||||
sg += a * d.getGreen();
|
sg += a * d.getGreen();
|
||||||
sb += a * d.getBlue();
|
sb += a * d.getBlue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found any neighbor RGB data, set pixel to average
|
// Set pixel to average weighted by alpha
|
||||||
// weighted by alpha.
|
|
||||||
if (ss > 0) {
|
if (ss > 0) {
|
||||||
c.setRed(sr / ss);
|
c.setRed(sr / ss);
|
||||||
c.setGreen(sg / ss);
|
c.setGreen(sg / ss);
|
||||||
c.setBlue(sb / ss);
|
c.setBlue(sb / ss);
|
||||||
src->setPixel(ctrx, ctry, c);
|
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
|
/* Scale a region of an image into another image, using nearest-neighbor with
|
||||||
|
@ -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
|
/* Fill in RGB values for transparent pixels, to correct for odd colors
|
||||||
* appearing at borders when blending. This is because many PNG optimizers
|
* appearing at borders when blending. This is because many PNG optimizers
|
||||||
* like to discard RGB values of transparent pixels, but when blending then
|
* 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.
|
* This function modifies the original image in-place.
|
||||||
*
|
*
|
||||||
* Parameter "threshold" is the alpha level below which pixels are considered
|
* Parameter "threshold" is the alpha level below which pixels are considered
|
||||||
* transparent. Should be 127 for 3d where alpha is threshold, but 0 for
|
* transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
|
||||||
* 2d where alpha is blended.
|
* 0 when alpha blending is used.
|
||||||
*/
|
*/
|
||||||
void imageCleanTransparent(video::IImage *src, u32 threshold);
|
void imageCleanTransparent(video::IImage *src, u32 threshold);
|
||||||
|
|
||||||
|
@ -491,7 +491,8 @@ video::ITexture *Minimap::getMinimapTexture()
|
|||||||
// Want to use texture source, to : 1 find texture, 2 cache it
|
// Want to use texture source, to : 1 find texture, 2 cache it
|
||||||
video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
|
video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
|
||||||
video::IImage* image = driver->createImageFromData(
|
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();
|
texture->unlock();
|
||||||
|
|
||||||
auto dim = image->getDimension();
|
auto dim = image->getDimension();
|
||||||
|
@ -427,6 +427,7 @@ private:
|
|||||||
std::unordered_map<std::string, Palette> m_palettes;
|
std::unordered_map<std::string, Palette> m_palettes;
|
||||||
|
|
||||||
// Cached settings needed for making textures from meshes
|
// Cached settings needed for making textures from meshes
|
||||||
|
bool m_setting_mipmap;
|
||||||
bool m_setting_trilinear_filter;
|
bool m_setting_trilinear_filter;
|
||||||
bool m_setting_bilinear_filter;
|
bool m_setting_bilinear_filter;
|
||||||
};
|
};
|
||||||
@ -447,6 +448,7 @@ TextureSource::TextureSource()
|
|||||||
// Cache some settings
|
// Cache some settings
|
||||||
// Note: Since this is only done once, the game must be restarted
|
// Note: Since this is only done once, the game must be restarted
|
||||||
// for these settings to take effect
|
// 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_trilinear_filter = g_settings->getBool("trilinear_filter");
|
||||||
m_setting_bilinear_filter = g_settings->getBool("bilinear_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)
|
video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
|
||||||
{
|
{
|
||||||
static thread_local bool filter_needed =
|
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) &&
|
((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
|
||||||
g_settings->getS32("texture_min_size") > 1);
|
g_settings->getS32("texture_min_size") > 1);
|
||||||
// Avoid duplicating texture if it won't actually change
|
// Avoid duplicating texture if it won't actually change
|
||||||
@ -1636,8 +1638,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the "clean transparent" filter, if configured.
|
// Apply the "clean transparent" filter, if needed
|
||||||
if (g_settings->getBool("texture_clean_transparent"))
|
if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
|
||||||
imageCleanTransparent(baseimg, 127);
|
imageCleanTransparent(baseimg, 127);
|
||||||
|
|
||||||
/* Upscale textures to user's requested minimum size. This is a trick to make
|
/* Upscale textures to user's requested minimum size. This is a trick to make
|
||||||
|
Loading…
Reference in New Issue
Block a user