Configurable automatic texture scaling and filtering at load time.
authorAaron Suen <warr1024@gmail.com>
Sun, 8 Mar 2015 00:52:59 +0000 (19:52 -0500)
committerkwolekr <kwolekr@minetest.net>
Sat, 21 Mar 2015 00:21:42 +0000 (20:21 -0400)
Signed off by: Zeno, kwolekr

minetest.conf.example
src/client/tile.cpp
src/defaultsettings.cpp

index 5e8637897f2273463ef300f124eb71ac3fff055a..2c6f5e861f3c6a031c943070d9200d2909c48fcd 100644 (file)
 #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.
index 541247fa8691faf863922b1353375ce2bbba845d..078e62741ab3036a053cfdfb2005817f3403958d 100644 (file)
@@ -223,6 +223,89 @@ public:
                        }
                }
 
+               /* 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;
index f49fbb008ba052664c71e012e7afa34778a91be6..ff2d148aa8feeaeef558443f8706fab5a56f3733 100644 (file)
@@ -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");