Clean scaling pre-filter for formspec/HUD.
authorAaron Suen <warr1024@gmail.com>
Mon, 9 Mar 2015 13:32:11 +0000 (09:32 -0400)
committerkwolekr <kwolekr@minetest.net>
Wed, 1 Apr 2015 04:01:05 +0000 (00:01 -0400)
20 files changed:
build/android/jni/Android.mk
minetest.conf.example
src/CMakeLists.txt
src/client.cpp
src/client/tile.cpp
src/client/tile.h
src/defaultsettings.cpp
src/drawscene.cpp
src/guiEngine.cpp
src/guiFormSpecMenu.cpp
src/guiTable.cpp
src/guiscalingfilter.cpp [new file with mode: 0644]
src/guiscalingfilter.h [new file with mode: 0644]
src/hud.cpp
src/imagefilters.cpp [new file with mode: 0644]
src/imagefilters.h [new file with mode: 0644]
src/touchscreengui.cpp
src/touchscreengui.h
src/util/numeric.cpp
src/util/numeric.h

index 24367a2aac892a224076d1e30aff63184e6d6d04..af7980c5167dfef88a6e458e7338e79af73dd732 100644 (file)
@@ -143,9 +143,11 @@ LOCAL_SRC_FILES :=                                \
                jni/src/guiKeyChangeMenu.cpp              \
                jni/src/guiPasswordChange.cpp             \
                jni/src/guiTable.cpp                      \
+               jni/src/guiscalingfilter.cpp              \
                jni/src/guiVolumeChange.cpp               \
                jni/src/httpfetch.cpp                     \
                jni/src/hud.cpp                           \
+               jni/src/imagefilters.cpp                  \
                jni/src/inventory.cpp                     \
                jni/src/inventorymanager.cpp              \
                jni/src/itemdef.cpp                       \
index fd43d2797826ad3fbaee6337d1c235fc8d9bebf0..d9c3eb61ca490de05c620d7f62d155555e653c0d 100644 (file)
 #crosshair_alpha = 255
 #    Scale gui by a user specified value
 #gui_scaling = 1.0
+#    Use a nearest-neighbor-anti-alias filter to scale the GUI.
+#    This will smooth over some of the rough edges, and blend
+#    pixels when scaling down, at the cost of blurring some
+#    edge pixels when images are scaled by non-integer sizes.
+#gui_scaling_filter = false
+#    When gui_scaling_filter = true, all GUI images need to be
+#    filtered in software, but some images are generated directly
+#    to hardare (e.g. render-to-texture for nodes in inventory).
+#    When gui_scaling_filter_txr2img is true, copy those images
+#    from hardware to software for scaling.  When false, fall back
+#    to the old scaling method, for video drivers that don't
+#    propery support downloading textures back from hardware.
+#gui_scaling_filter_txr2img = true
 #    Sensitivity multiplier
 #mouse_sensitivity = 0.2
 #    Sound settings
index b33bea87b49f5f909b08a7eb51e75030f9221b47..a1c2d013c2f0ffa9de4e4980b7eda88ce4f3bc5b 100644 (file)
@@ -405,9 +405,11 @@ set(client_SRCS
        guiFormSpecMenu.cpp
        guiKeyChangeMenu.cpp
        guiPasswordChange.cpp
+       guiscalingfilter.cpp
        guiTable.cpp
        guiVolumeChange.cpp
        hud.cpp
+       imagefilters.cpp
        keycode.cpp
        localplayer.cpp
        main.cpp
index dc2b54e9b5c736fd40b71552f6e8dd3c060bd561..ba78cb51e5608106357e6b9791149e23767af340 100644 (file)
@@ -49,6 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "drawscene.h"
 #include "database-sqlite3.h"
 #include "serialization.h"
+#include "guiscalingfilter.h"
 
 extern gui::IGUIEnvironment* guienv;
 
@@ -1607,6 +1608,11 @@ void Client::afterContentReceived(IrrlichtDevice *device)
 
        const wchar_t* text = wgettext("Loading textures...");
 
+       // Clear cached pre-scaled 2D GUI images, as this cache
+       // might have images with the same name but different
+       // content from previous sessions.
+       guiScalingCacheClear(device->getVideoDriver());
+
        // Rebuild inherited images and recreate textures
        infostream<<"- Rebuilding images and textures"<<std::endl;
        draw_load_screen(text,device, guienv, 0, 70);
index b7f63502df91e50fcdc58a9b9053ce06b2f0c4c5..283b262a6343d1c284e2d7d34713cea08ea79ead 100644 (file)
@@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "gamedef.h"
 #include "strfnd.h"
 #include "util/string.h" // for parseColorString()
+#include "imagefilters.h"
+#include "guiscalingfilter.h"
 
 #ifdef __ANDROID__
 #include <GLES/gl.h>
@@ -223,58 +225,9 @@ 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);
-                               }
-                       }
-               }
+               // Apply the "clean transparent" filter, if configured.
+               if (g_settings->getBool("texture_clean_transparent"))
+                       imageCleanTransparent(toadd, 127);
 
                if (need_to_grab)
                        toadd->grab();
@@ -670,6 +623,7 @@ u32 TextureSource::generateTexture(const std::string &name)
 #endif
                // Create texture from resulting image
                tex = driver->addTexture(name.c_str(), img);
+               guiScalingCache(io::path(name.c_str()), driver, img);
                img->drop();
        }
 
@@ -776,6 +730,7 @@ void TextureSource::rebuildImagesAndTextures()
                video::ITexture *t = NULL;
                if (img) {
                        t = driver->addTexture(ti->name.c_str(), img);
+                       guiScalingCache(io::path(ti->name.c_str()), driver, img);
                        img->drop();
                }
                video::ITexture *t_old = ti->texture;
@@ -896,6 +851,8 @@ video::ITexture* TextureSource::generateTextureFromMesh(
                rawImage->copyToScaling(inventory_image);
                rawImage->drop();
 
+               guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
+
                video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
                inventory_image->drop();
 
index eadfdc2a5460a6a7fd8c27a245f3fec583a3b823..38f8bb6232ad1dd0933842793ce0477c646cabe5 100644 (file)
@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "threads.h"
 #include <string>
 #include <vector>
+#include "util/numeric.h"
 
 class IGameDef;
 
@@ -135,21 +136,6 @@ public:
 IWritableTextureSource* createTextureSource(IrrlichtDevice *device);
 
 #ifdef __ANDROID__
-/**
- * @param size get next npot2 value
- * @return npot2 value
- */
-inline unsigned int npot2(unsigned int size)
-{
-       if (size == 0) return 0;
-       unsigned int npot = 1;
-
-       while ((size >>= 1) > 0) {
-               npot <<= 1;
-       }
-       return npot;
-}
-
 video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver);
 #endif
 
index 0acb4a730773d30dc387baf675f3588ade19373d..6fa420b69c1fac436a1522f99d188d5049573bca 100644 (file)
@@ -137,6 +137,8 @@ void set_default_settings(Settings *settings)
        settings->setDefault("crosshair_alpha", "255");
        settings->setDefault("hud_scaling", "1.0");
        settings->setDefault("gui_scaling", "1.0");
+       settings->setDefault("gui_scaling_filter", "false");
+       settings->setDefault("gui_scaling_filter_txr2img", "true");
        settings->setDefault("mouse_sensitivity", "0.2");
        settings->setDefault("enable_sound", "true");
        settings->setDefault("sound_volume", "0.8");
index b089e71e697015e014e3286b0d34c29df4ce7cb0..c3c3b2ef207d2c2e3b739104a8585d77f756b7ee 100644 (file)
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "clientmap.h"
 #include "util/timetaker.h"
 #include "fontengine.h"
+#include "guiscalingfilter.h"
 
 typedef enum {
        LEFT = -1,
@@ -324,19 +325,19 @@ void draw_sidebyside_3d_mode(Camera& camera, bool show_hud,
        //makeColorKeyTexture mirrors texture so we do it twice to get it right again
        driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0));
 
-       driver->draw2DImage(left_image,
+       draw2DImageFilterScaled(driver, left_image,
                        irr::core::rect<s32>(0, 0, screensize.X/2, screensize.Y),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
 
-       driver->draw2DImage(hudtexture,
+       draw2DImageFilterScaled(driver, hudtexture,
                        irr::core::rect<s32>(0, 0, screensize.X/2, screensize.Y),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
 
-       driver->draw2DImage(right_image,
+       draw2DImageFilterScaled(driver, right_image,
                        irr::core::rect<s32>(screensize.X/2, 0, screensize.X, screensize.Y),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
 
-       driver->draw2DImage(hudtexture,
+       draw2DImageFilterScaled(driver, hudtexture,
                        irr::core::rect<s32>(screensize.X/2, 0, screensize.X, screensize.Y),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
 
@@ -380,19 +381,19 @@ void draw_top_bottom_3d_mode(Camera& camera, bool show_hud,
        //makeColorKeyTexture mirrors texture so we do it twice to get it right again
        driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0));
 
-       driver->draw2DImage(left_image,
+       draw2DImageFilterScaled(driver, left_image,
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y/2),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
 
-       driver->draw2DImage(hudtexture,
+       draw2DImageFilterScaled(driver, hudtexture,
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y/2),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
 
-       driver->draw2DImage(right_image,
+       draw2DImageFilterScaled(driver, right_image,
                        irr::core::rect<s32>(0, screensize.Y/2, screensize.X, screensize.Y),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
 
-       driver->draw2DImage(hudtexture,
+       draw2DImageFilterScaled(driver, hudtexture,
                        irr::core::rect<s32>(0, screensize.Y/2, screensize.X, screensize.Y),
                        irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
 
index c143e51103bcfbe6facecc96a32ba18ed195ebbe..07fdbb16e01d950e2c27e781e9741acf15d6d49b 100644 (file)
@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "httpfetch.h"
 #include "log.h"
 #include "fontengine.h"
+#include "guiscalingfilter.h"
 
 #ifdef __ANDROID__
 #include "client/tile.h"
@@ -409,7 +410,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver)
                {
                        for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y )
                        {
-                               driver->draw2DImage(texture,
+                               draw2DImageFilterScaled(driver, texture,
                                        core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y),
                                        core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
                                        NULL, NULL, true);
@@ -419,7 +420,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver)
        }
 
        /* Draw background texture */
-       driver->draw2DImage(texture,
+       draw2DImageFilterScaled(driver, texture,
                core::rect<s32>(0, 0, screensize.X, screensize.Y),
                core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
                NULL, NULL, true);
@@ -438,7 +439,7 @@ void GUIEngine::drawOverlay(video::IVideoDriver* driver)
 
        /* Draw background texture */
        v2u32 sourcesize = texture->getOriginalSize();
-       driver->draw2DImage(texture,
+       draw2DImageFilterScaled(driver, texture,
                core::rect<s32>(0, 0, screensize.X, screensize.Y),
                core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
                NULL, NULL, true);
@@ -471,7 +472,7 @@ void GUIEngine::drawHeader(video::IVideoDriver* driver)
 
        video::SColor bgcolor(255,50,50,50);
 
-       driver->draw2DImage(texture, splashrect,
+       draw2DImageFilterScaled(driver, texture, splashrect,
                core::rect<s32>(core::position2d<s32>(0,0),
                core::dimension2di(texture->getOriginalSize())),
                NULL, NULL, true);
@@ -503,7 +504,7 @@ void GUIEngine::drawFooter(video::IVideoDriver* driver)
                rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
                rect -= v2s32(footersize.X/2, 0);
 
-               driver->draw2DImage(texture, rect,
+               draw2DImageFilterScaled(driver, texture, rect,
                        core::rect<s32>(core::position2d<s32>(0,0),
                        core::dimension2di(texture->getOriginalSize())),
                        NULL, NULL, true);
index 11360a0fc4f3a92b4b23b77e8ecd7d3d0e2cbe4f..56729846e82c3988210d5a00c7692f8b9750026d 100644 (file)
@@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/hex.h"
 #include "util/numeric.h"
 #include "util/string.h" // for parseColorString()
+#include "guiscalingfilter.h"
 
 #define MY_CHECKPOS(a,b)                                                                                                       \
        if (v_pos.size() != 2) {                                                                                                \
@@ -1307,8 +1308,8 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,
                }
 
                e->setUseAlphaChannel(true);
-               e->setImage(texture);
-               e->setPressedImage(pressed_texture);
+               e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
+               e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
                e->setScaleImage(true);
                e->setNotClipped(noclip);
                e->setDrawBorder(drawborder);
@@ -1452,8 +1453,8 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element)
                }
 
                e->setUseAlphaChannel(true);
-               e->setImage(texture);
-               e->setPressedImage(texture);
+               e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
+               e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
                e->setScaleImage(true);
                spec.ftype = f_Button;
                rect+=data->basepos-padding;
@@ -2283,7 +2284,7 @@ void GUIFormSpecMenu::drawMenu()
 
                        const video::SColor color(255,255,255,255);
                        const video::SColor colors[] = {color,color,color,color};
-                       driver->draw2DImage(texture, rect,
+                       draw2DImageFilterScaled(driver, texture, rect,
                                core::rect<s32>(core::position2d<s32>(0,0),
                                                core::dimension2di(texture->getOriginalSize())),
                                NULL/*&AbsoluteClippingRect*/, colors, true);
@@ -2333,7 +2334,7 @@ void GUIFormSpecMenu::drawMenu()
                        core::rect<s32> rect = imgrect + spec.pos;
                        const video::SColor color(255,255,255,255);
                        const video::SColor colors[] = {color,color,color,color};
-                       driver->draw2DImage(texture, rect,
+                       draw2DImageFilterScaled(driver, texture, rect,
                                core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
                                NULL/*&AbsoluteClippingRect*/, colors, true);
                }
@@ -2362,7 +2363,7 @@ void GUIFormSpecMenu::drawMenu()
                core::rect<s32> rect = imgrect + spec.pos;
                const video::SColor color(255,255,255,255);
                const video::SColor colors[] = {color,color,color,color};
-               driver->draw2DImage(texture, rect,
+               draw2DImageFilterScaled(driver, texture, rect,
                        core::rect<s32>(core::position2d<s32>(0,0),
                                        core::dimension2di(texture->getOriginalSize())),
                        NULL/*&AbsoluteClippingRect*/, colors, true);
index a7a53f581e2f61d9edb64ebc3eb8cebf10909948..6dcd115b0eeae24bd7f41aabb2266656b230bd9a 100644 (file)
@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "main.h"
 #include "settings.h" // for settings
 #include "porting.h" // for dpi
+#include "guiscalingfilter.h"
 
 /*
        GUITable
diff --git a/src/guiscalingfilter.cpp b/src/guiscalingfilter.cpp
new file mode 100644 (file)
index 0000000..92dadea
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiscalingfilter.h"
+#include "imagefilters.h"
+#include "settings.h"
+#include "main.h"              // for g_settings
+#include "util/numeric.h"
+#include <stdio.h>
+
+/* Maintain a static cache to store the images that correspond to textures
+ * in a format that's manipulable by code.  Some platforms exhibit issues
+ * converting textures back into images repeatedly, and some don't even
+ * allow it at all.
+ */
+std::map<io::path, video::IImage *> imgCache;
+
+/* Maintain a static cache of all pre-scaled textures.  These need to be
+ * cleared as well when the cached images.
+ */
+std::map<io::path, video::ITexture *> txrCache;
+
+/* Manually insert an image into the cache, useful to avoid texture-to-image
+ * conversion whenever we can intercept it.
+ */
+void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value) {
+       if (!g_settings->getBool("gui_scaling_filter"))
+               return;
+       video::IImage *copied = driver->createImage(value->getColorFormat(),
+                       value->getDimension());
+       value->copyTo(copied);
+       imgCache[key] = copied;
+}
+
+// Manually clear the cache, e.g. when switching to different worlds.
+void guiScalingCacheClear(video::IVideoDriver *driver) {
+       for (std::map<io::path, video::IImage *>::iterator it = imgCache.begin();
+                       it != imgCache.end(); it++) {
+               if (it->second != NULL)
+                       it->second->drop();
+       }
+       imgCache.clear();
+       for (std::map<io::path, video::ITexture *>::iterator it = txrCache.begin();
+                       it != txrCache.end(); it++) {
+               if (it->second != NULL)
+                       driver->removeTexture(it->second);
+       }
+       txrCache.clear();
+}
+
+/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
+ * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
+ * or the original texture if unable to pre-scale it.
+ */
+video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
+               const core::rect<s32> &srcrect, const core::rect<s32> &destrect) {
+
+       if (!g_settings->getBool("gui_scaling_filter"))
+               return src;
+
+       // Calculate scaled texture name.
+       char rectstr[200];
+       sprintf(rectstr, "%d:%d:%d:%d:%d:%d",
+               srcrect.UpperLeftCorner.X,
+               srcrect.UpperLeftCorner.Y,
+               srcrect.getWidth(),
+               srcrect.getHeight(),
+               destrect.getWidth(),
+               destrect.getHeight());
+       io::path origname = src->getName().getPath();
+       io::path scalename = origname + "@guiScalingFilter:" + rectstr;
+
+       // Search for existing scaled texture.
+       video::ITexture *scaled = txrCache[scalename];
+       if (scaled)
+               return scaled;
+
+       // Try to find the texture converted to an image in the cache.
+       // If the image was not found, try to extract it from the texture.
+       video::IImage* srcimg = imgCache[origname];
+       if (srcimg == NULL) {
+               if (!g_settings->getBool("gui_scaling_filter_txr2img"))
+                       return src;
+               srcimg = driver->createImageFromData(src->getColorFormat(),
+                       src->getSize(), src->lock(), false);
+               src->unlock();
+               imgCache[origname] = srcimg;
+       }
+
+       // Create a new destination image and scale the source into it.
+       imageCleanTransparent(srcimg, 0);
+       video::IImage *destimg = driver->createImage(src->getColorFormat(),
+                       core::dimension2d<u32>((u32)destrect.getWidth(),
+                       (u32)destrect.getHeight()));
+       imageScaleNNAA(srcimg, srcrect, destimg);
+
+#ifdef __ANDROID__
+       // Android is very picky about textures being powers of 2, so expand
+       // the image dimensions to the next power of 2, if necessary, for
+       // that platform.
+       video::IImage *po2img = driver->createImage(src->getColorFormat(),
+                       core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
+                       npot2((u32)destrect.getHeight())));
+       po2img->fill(video::SColor(0, 0, 0, 0));
+       destimg->copyTo(po2img);
+       destimg->drop();
+       destimg = po2img;
+#endif
+
+       // Convert the scaled image back into a texture.
+       scaled = driver->addTexture(scalename, destimg, NULL);
+       destimg->drop();
+       txrCache[scalename] = scaled;
+
+       return scaled;
+}
+
+/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
+ * are available at GUI imagebutton creation time.
+ */
+video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
+               s32 width, s32 height) {
+       return guiScalingResizeCached(driver, src,
+               core::rect<s32>(0, 0, src->getSize().Width, src->getSize().Height),
+               core::rect<s32>(0, 0, width, height));
+}
+
+/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
+ * texture, if configured.
+ */
+void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
+               const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
+               const core::rect<s32> *cliprect, const video::SColor *const colors,
+               bool usealpha) {
+
+       // Attempt to pre-scale image in software in high quality.
+       video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect);
+
+       // Correct source rect based on scaled image.
+       const core::rect<s32> mysrcrect = (scaled != txr)
+               ? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
+               : srcrect;
+
+       driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
+}
diff --git a/src/guiscalingfilter.h b/src/guiscalingfilter.h
new file mode 100644 (file)
index 0000000..768fe8d
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef _GUI_SCALING_FILTER_H_
+#define _GUI_SCALING_FILTER_H_
+
+#include "irrlichttypes_extrabloated.h"
+
+/* Manually insert an image into the cache, useful to avoid texture-to-image
+ * conversion whenever we can intercept it.
+ */
+void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value);
+
+// Manually clear the cache, e.g. when switching to different worlds.
+void guiScalingCacheClear(video::IVideoDriver *driver);
+
+/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
+ * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
+ * or the original texture if unable to pre-scale it.
+ */
+video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
+               const core::rect<s32> &srcrect, const core::rect<s32> &destrect);
+
+/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
+ * are available at GUI imagebutton creation time.
+ */
+video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
+               s32 width, s32 height);
+
+/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
+ * texture, if configured.
+ */
+void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
+               const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
+               const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
+               bool usealpha = false);
+
+#endif
index aabaa066c597e308c4d7693efa8158be8b93d24c..0b34a7b5bfb00d323841dac23b9f0b5ca7e7f678 100644 (file)
@@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "camera.h"
 #include "porting.h"
 #include "fontengine.h"
+#include "guiscalingfilter.h"
 #include <IGUIStaticText.h>
 
 #ifdef HAVE_TOUCHSCREENGUI
@@ -94,7 +95,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect, bool sele
                                imgrect2.LowerRightCorner.Y += (m_padding*2);
                                        video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
                                        core::dimension2di imgsize(texture->getOriginalSize());
-                               driver->draw2DImage(texture, imgrect2,
+                               draw2DImageFilterScaled(driver, texture, imgrect2,
                                                core::rect<s32>(core::position2d<s32>(0,0), imgsize),
                                                NULL, hbar_colors, true);
                        } else {
@@ -200,7 +201,7 @@ void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset,
                core::rect<s32> rect2 = imgrect2 + pos;
                video::ITexture *texture = tsrc->getTexture(hotbar_image);
                core::dimension2di imgsize(texture->getOriginalSize());
-               driver->draw2DImage(texture, rect2,
+               draw2DImageFilterScaled(driver, texture, rect2,
                        core::rect<s32>(core::position2d<s32>(0,0), imgsize),
                        NULL, hbar_colors, true);
        }
@@ -266,7 +267,7 @@ void Hud::drawLuaElements(v3s16 camera_offset) {
                                             (e->align.Y - 1.0) * dstsize.Y / 2);
                                core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
                                rect += pos + offset + v2s32(e->offset.X, e->offset.Y);
-                               driver->draw2DImage(texture, rect,
+                               draw2DImageFilterScaled(driver, texture, rect,
                                        core::rect<s32>(core::position2d<s32>(0,0), imgsize),
                                        NULL, colors, true);
                                break; }
@@ -378,7 +379,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
                core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height);
 
                dstrect += p;
-               driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true);
+               draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
                p += steppos;
        }
 
@@ -388,7 +389,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
                core::rect<s32> dstrect(0,0, dstd.Width / 2, dstd.Height);
 
                dstrect += p;
-               driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true);
+               draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
        }
 }
 
@@ -502,7 +503,7 @@ void drawItemStack(video::IVideoDriver *driver,
        {
                const video::SColor color(255,255,255,255);
                const video::SColor colors[] = {color,color,color,color};
-               driver->draw2DImage(texture, rect,
+               draw2DImageFilterScaled(driver, texture, rect,
                        core::rect<s32>(core::position2d<s32>(0,0),
                        core::dimension2di(texture->getOriginalSize())),
                        clip, colors, true);
diff --git a/src/imagefilters.cpp b/src/imagefilters.cpp
new file mode 100644 (file)
index 0000000..a995e98
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "imagefilters.h"
+#include "util/numeric.h"
+#include <math.h>
+
+/* 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.
+ *
+ * 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.
+ */
+void imageCleanTransparent(video::IImage *src, u32 threshold) {
+
+       core::dimension2d<u32> dim = src->getDimension();
+
+       // Walk each pixel looking for fully transparent ones.
+       // 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++) {
+
+               // Ignore opaque pixels.
+               irr::video::SColor c = src->getPixel(ctrx, ctry);
+               if (c.getAlpha() > threshold)
+                       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 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)
+                               continue;
+
+                       // Add RGB values weighted by alpha.
+                       u32 a = 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.
+               if (ss > 0) {
+                       c.setRed(sr / ss);
+                       c.setGreen(sg / ss);
+                       c.setBlue(sb / ss);
+                       src->setPixel(ctrx, ctry, c);
+               }
+       }
+}
+
+/* Scale a region of an image into another image, using nearest-neighbor with
+ * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
+ * to prevent non-integer scaling ratio artifacts.  Note that this may cause
+ * some blending at the edges where pixels don't line up perfectly, but this
+ * filter is designed to produce the most accurate results for both upscaling
+ * and downscaling.
+ */
+void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest) {
+
+       double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
+       u32 dy, dx;
+       video::SColor pxl;
+
+       // Cache rectsngle boundaries.
+       double sox = srcrect.UpperLeftCorner.X * 1.0;
+       double soy = srcrect.UpperLeftCorner.Y * 1.0;
+       double sw = srcrect.getWidth() * 1.0;
+       double sh = srcrect.getHeight() * 1.0;
+
+       // Walk each destination image pixel.
+       // Note: loop y around x for better cache locality.
+       core::dimension2d<u32> dim = dest->getDimension();
+       for (dy = 0; dy < dim.Height; dy++)
+       for (dx = 0; dx < dim.Width; dx++) {
+
+               // Calculate floating-point source rectangle bounds.
+               // Do some basic clipping, and for mirrored/flipped rects,
+               // make sure min/max are in the right order.
+               minsx = sox + (dx * sw / dim.Width);
+               minsx = rangelim(minsx, 0, sw);
+               maxsx = minsx + sw / dim.Width;
+               maxsx = rangelim(maxsx, 0, sw);
+               if (minsx > maxsx)
+                       SWAP(double, minsx, maxsx);
+               minsy = soy + (dy * sh / dim.Height);
+               minsy = rangelim(minsy, 0, sh);
+               maxsy = minsy + sh / dim.Height;
+               maxsy = rangelim(maxsy, 0, sh);
+               if (minsy > maxsy)
+                       SWAP(double, minsy, maxsy);
+
+               // Total area, and integral of r, g, b values over that area,
+               // initialized to zero, to be summed up in next loops.
+               area = 0;
+               ra = 0;
+               ga = 0;
+               ba = 0;
+               aa = 0;
+
+               // Loop over the integral pixel positions described by those bounds.
+               for (sy = floor(minsy); sy < maxsy; sy++)
+               for (sx = floor(minsx); sx < maxsx; sx++) {
+
+                       // Calculate width, height, then area of dest pixel
+                       // that's covered by this source pixel.
+                       pw = 1;
+                       if (minsx > sx)
+                               pw += sx - minsx;
+                       if (maxsx < (sx + 1))
+                               pw += maxsx - sx - 1;
+                       ph = 1;
+                       if (minsy > sy)
+                               ph += sy - minsy;
+                       if (maxsy < (sy + 1))
+                               ph += maxsy - sy - 1;
+                       pa = pw * ph;
+
+                       // Get source pixel and add it to totals, weighted
+                       // by covered area and alpha.
+                       pxl = src->getPixel((u32)sx, (u32)sy);
+                       area += pa;
+                       ra += pa * pxl.getRed();
+                       ga += pa * pxl.getGreen();
+                       ba += pa * pxl.getBlue();
+                       aa += pa * pxl.getAlpha();
+               }
+
+               // Set the destination image pixel to the average color.
+               if (area > 0) {
+                       pxl.setRed(ra / area + 0.5);
+                       pxl.setGreen(ga / area + 0.5);
+                       pxl.setBlue(ba / area + 0.5);
+                       pxl.setAlpha(aa / area + 0.5);
+               } else {
+                       pxl.setRed(0);
+                       pxl.setGreen(0);
+                       pxl.setBlue(0);
+                       pxl.setAlpha(0);
+               }
+               dest->setPixel(dx, dy, pxl);
+       }
+}
diff --git a/src/imagefilters.h b/src/imagefilters.h
new file mode 100644 (file)
index 0000000..2878702
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef _IMAGE_FILTERS_H_
+#define _IMAGE_FILTERS_H_
+
+#include "irrlichttypes_extrabloated.h"
+
+/* 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.
+ *
+ * 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.
+ */
+void imageCleanTransparent(video::IImage *src, u32 threshold);
+
+/* Scale a region of an image into another image, using nearest-neighbor with
+ * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
+ * to prevent non-integer scaling ratio artifacts.  Note that this may cause
+ * some blending at the edges where pixels don't line up perfectly, but this
+ * filter is designed to produce the most accurate results for both upscaling
+ * and downscaling.
+ */
+void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest);
+
+#endif
index 2d489c1f86ccfae4c2a0b003f2e57b3f79c63fb7..f5868133f9e3faf23bec8229dd8d9ab4fbab2923 100644 (file)
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "gettime.h"
 #include "util/numeric.h"
 #include "porting.h"
+#include "guiscalingfilter.h"
 
 #include <iostream>
 #include <algorithm>
@@ -130,15 +131,23 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver)
        m_screensize = m_device->getVideoDriver()->getScreenSize();
 }
 
-void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path)
+void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path, rect<s32> button_rect)
 {
        unsigned int tid;
-       video::ITexture *texture = m_texturesource->getTexture(path,&tid);
+       video::ITexture *texture = guiScalingImageButton(m_device->getVideoDriver(),
+               m_texturesource->getTexture(path, &tid), button_rect.getWidth(), button_rect.getHeight());
        if (texture) {
                btn->guibutton->setUseAlphaChannel(true);
-               btn->guibutton->setImage(texture);
-               btn->guibutton->setPressedImage(texture);
-               btn->guibutton->setScaleImage(true);
+               if (g_settings->getBool("gui_scaling_filter")) {
+                       rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
+                       btn->guibutton->setImage(texture, txr_rect);
+                       btn->guibutton->setPressedImage(texture, txr_rect);
+                       btn->guibutton->setScaleImage(false);
+               } else {
+                       btn->guibutton->setImage(texture);
+                       btn->guibutton->setPressedImage(texture);
+                       btn->guibutton->setScaleImage(true);
+               }
                btn->guibutton->setDrawBorder(false);
                btn->guibutton->setText(L"");
                }
@@ -157,7 +166,7 @@ void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
        btn->immediate_release = immediate_release;
        btn->ids.clear();
 
-       loadButtonTexture(btn,touchgui_button_imagenames[id]);
+       loadButtonTexture(btn,touchgui_button_imagenames[id], button_rect);
 }
 
 static int getMaxControlPadSize(float density) {
index 2ded26a0599b198b91a272574f5a74e4402d5e76..bb32317932acbd0c7efa3d0401f827f137b808e9 100644 (file)
@@ -130,7 +130,7 @@ private:
                        float repeat_delay = BUTTON_REPEAT_DELAY);
 
        /* load texture */
-       void loadButtonTexture(button_info* btn, const char* path);
+       void loadButtonTexture(button_info* btn, const char* path, rect<s32> button_rect);
 
        struct id_status{
                int id;
index 2306976ecd0803a03f7f12519cb0192698da438a..4ddfede92b045f189119f22c4e093b7aca7041a2 100644 (file)
@@ -245,4 +245,3 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir,
 
        return true;
 }
-
index a158a2eaeb34c83c185411d4e177e21cc7f0847b..b4b8419188526965996e28f693f37b5ed96d8639 100644 (file)
@@ -411,5 +411,16 @@ inline bool is_power_of_two(u32 n)
        return n != 0 && (n & (n-1)) == 0;
 }
 
-#endif
+// Compute next-higher power of 2 efficiently, e.g. for power-of-2 texture sizes.
+// Public Domain: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+inline u32 npot2(u32 orig) {
+       orig--;
+       orig |= orig >> 1;
+       orig |= orig >> 2;
+       orig |= orig >> 4;
+       orig |= orig >> 8;
+       orig |= orig >> 16;
+       return orig + 1;
+}
 
+#endif