From 1e4fb80d46da634f04d84e3f1065aaabc6909468 Mon Sep 17 00:00:00 2001
From: Aaron Suen <warr1024@gmail.com>
Date: Sat, 7 Mar 2015 19:52:59 -0500
Subject: [PATCH] Configurable automatic texture scaling and filtering at load
 time.

Signed off by: Zeno, kwolekr
---
 minetest.conf.example   | 13 +++++++
 src/client/tile.cpp     | 83 +++++++++++++++++++++++++++++++++++++++++
 src/defaultsettings.cpp |  2 +
 3 files changed, 98 insertions(+)

diff --git a/minetest.conf.example b/minetest.conf.example
index 5e8637897..2c6f5e861 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -193,6 +193,19 @@
 #anisotropic_filter = false
 #bilinear_filter = false
 #trilinear_filter = false
+#    Filtered textures can blend RGB values with fully-transparent neighbors,
+#    which PNG optimizers usually discard, sometimes resulting in a dark or
+#    light edge to transparent textures.  Apply this filter to clean that up
+#    at texture load time.
+#texture_clean_transparent = true
+#    When using bilinear/trilinear/anisotropic filters, low-resolution textures
+#    can be blurred, so automatically upscale them with nearest-neighbor
+#    interpolation to preserve crisp pixels.  This sets the minimum texture size
+#    for the upscaled textures; higher values look sharper, but require more
+#    memory.  Powers of 2 are recommended.  Setting this higher than 1 may not
+#    have a visible effect unless bilinear/trilinear/anisotropic filtering is
+#    enabled.
+#texture_min_size = 16
 #    Set to true to pre-generate all item visuals
 #preload_item_visuals = false
 #    Set to true to enable shaders. Disable them if video_driver = direct3d9/8.
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index 541247fa8..078e62741 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -223,6 +223,89 @@ class SourceImageCache
 			}
 		}
 
+		/* Apply the "clean transparent" filter to textures, removing borders on transparent textures.
+		 * PNG optimizers discard RGB values of fully-transparent pixels, but filters may expose the
+		 * replacement colors at borders by blending to them; this filter compensates for that by
+		 * filling in those RGB values from nearby pixels.
+		 */
+		if (g_settings->getBool("texture_clean_transparent")) {
+			const core::dimension2d<u32> dim = toadd->getDimension();
+
+			// Walk each pixel looking for ones that will show as transparent.
+			for (u32 ctrx = 0; ctrx < dim.Width; ctrx++)
+			for (u32 ctry = 0; ctry < dim.Height; ctry++) {
+				irr::video::SColor c = toadd->getPixel(ctrx, ctry);
+				if (c.getAlpha() > 127)
+					continue;
+
+				// 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).
+				for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
+						sx <= (ctrx + 1) && sx < dim.Width; sx++)
+				for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
+						sy <= (ctry + 1) && sy < dim.Height; sy++) {
+
+					// Ignore the center pixel (its RGB is already
+					// presumed meaningless).
+					if ((sx == ctrx) && (sy == ctry))
+						continue;
+
+					// Ignore other nearby pixels that would be
+					// transparent upon display.
+					irr::video::SColor d = toadd->getPixel(sx, sy);
+					if(d.getAlpha() < 128)
+						continue;
+
+					// Add one weighted sample.
+					ss++;
+					sr += d.getRed();
+					sg += d.getGreen();
+					sb += d.getBlue();
+				}
+
+				// If we found any neighbor RGB data, set pixel to average
+				// weighted by alpha.
+				if (ss > 0) {
+					c.setRed(sr / ss);
+					c.setGreen(sg / ss);
+					c.setBlue(sb / ss);
+					toadd->setPixel(ctrx, ctry, c);
+				}
+			}
+		}
+
+		/* Upscale textures to user's requested minimum size.  This is a trick to make
+		 * filters look as good on low-res textures as on high-res ones, by making
+		 * low-res textures BECOME high-res ones.  This is helpful for worlds that
+		 * mix high- and low-res textures, or for mods with least-common-denominator
+		 * textures that don't have the resources to offer high-res alternatives.
+		 */
+		s32 scaleto = g_settings->getS32("texture_min_size");
+		if (scaleto > 0) {
+
+			/* Calculate scaling needed to make the shortest texture dimension
+			 * equal to the target minimum.  If e.g. this is a vertical frames
+			 * animation, the short dimension will be the real size.
+			 */
+			const core::dimension2d<u32> dim = toadd->getDimension();
+			u32 xscale = scaleto / dim.Width;
+			u32 yscale = scaleto / dim.Height;
+			u32 scale = (xscale > yscale) ? xscale : yscale;
+
+			// Never downscale; only scale up by 2x or more.
+			if (scale > 1) {
+				u32 w = scale * dim.Width;
+				u32 h = scale * dim.Height;
+				const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
+				video::IImage *newimg = driver->createImage(
+						toadd->getColorFormat(), newdim);
+				toadd->copyToScaling(newimg);
+				toadd = newimg;
+			}
+		}
+
 		if (need_to_grab)
 			toadd->grab();
 		m_images[name] = toadd;
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index f49fbb008..ff2d148aa 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -149,6 +149,8 @@ void set_default_settings(Settings *settings)
 	settings->setDefault("anisotropic_filter", "false");
 	settings->setDefault("bilinear_filter", "false");
 	settings->setDefault("trilinear_filter", "false");
+	settings->setDefault("texture_clean_transparent", "true");
+	settings->setDefault("texture_min_size", "16");
 	settings->setDefault("preload_item_visuals", "false");
 	settings->setDefault("enable_bumpmapping", "false");
 	settings->setDefault("enable_parallax_occlusion", "false");
-- 
GitLab