summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/android/jni/Android.mk2
-rw-r--r--minetest.conf.example13
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/client.cpp6
-rw-r--r--src/client/tile.cpp61
-rw-r--r--src/client/tile.h16
-rw-r--r--src/defaultsettings.cpp2
-rw-r--r--src/drawscene.cpp17
-rw-r--r--src/guiEngine.cpp11
-rw-r--r--src/guiFormSpecMenu.cpp15
-rw-r--r--src/guiTable.cpp1
-rw-r--r--src/guiscalingfilter.cpp160
-rw-r--r--src/guiscalingfilter.h52
-rw-r--r--src/hud.cpp13
-rw-r--r--src/imagefilters.cpp172
-rw-r--r--src/imagefilters.h46
-rw-r--r--src/touchscreengui.cpp21
-rw-r--r--src/touchscreengui.h2
-rw-r--r--src/util/numeric.cpp1
-rw-r--r--src/util/numeric.h13
20 files changed, 524 insertions, 102 deletions
diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk
index 24367a2aa..af7980c51 100644
--- a/build/android/jni/Android.mk
+++ b/build/android/jni/Android.mk
@@ -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 \
diff --git a/minetest.conf.example b/minetest.conf.example
index fd43d2797..d9c3eb61c 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -172,6 +172,19 @@
#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
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b33bea87b..a1c2d013c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/client.cpp b/src/client.cpp
index dc2b54e9b..ba78cb51e 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -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);
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index b7f63502d..283b262a6 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -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();
diff --git a/src/client/tile.h b/src/client/tile.h
index eadfdc2a5..38f8bb623 100644
--- a/src/client/tile.h
+++ b/src/client/tile.h
@@ -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
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 0acb4a730..6fa420b69 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -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");
diff --git a/src/drawscene.cpp b/src/drawscene.cpp
index b089e71e6..c3c3b2ef2 100644
--- a/src/drawscene.cpp
+++ b/src/drawscene.cpp
@@ -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);
diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp
index c143e5110..07fdbb16e 100644
--- a/src/guiEngine.cpp
+++ b/src/guiEngine.cpp
@@ -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);
diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp
index 11360a0fc..56729846e 100644
--- a/src/guiFormSpecMenu.cpp
+++ b/src/guiFormSpecMenu.cpp
@@ -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);
diff --git a/src/guiTable.cpp b/src/guiTable.cpp
index a7a53f581..6dcd115b0 100644
--- a/src/guiTable.cpp
+++ b/src/guiTable.cpp
@@ -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
index 000000000..92dadeaec
--- /dev/null
+++ b/src/guiscalingfilter.cpp
@@ -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
index 000000000..768fe8d52
--- /dev/null
+++ b/src/guiscalingfilter.h
@@ -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
diff --git a/src/hud.cpp b/src/hud.cpp
index aabaa066c..0b34a7b5b 100644
--- a/src/hud.cpp
+++ b/src/hud.cpp
@@ -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
index 000000000..a995e98d6
--- /dev/null
+++ b/src/imagefilters.cpp
@@ -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
index 000000000..28787027f
--- /dev/null
+++ b/src/imagefilters.h
@@ -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
diff --git a/src/touchscreengui.cpp b/src/touchscreengui.cpp
index 2d489c1f8..f5868133f 100644
--- a/src/touchscreengui.cpp
+++ b/src/touchscreengui.cpp
@@ -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) {
diff --git a/src/touchscreengui.h b/src/touchscreengui.h
index 2ded26a05..bb3231793 100644
--- a/src/touchscreengui.h
+++ b/src/touchscreengui.h
@@ -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;
diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp
index 2306976ec..4ddfede92 100644
--- a/src/util/numeric.cpp
+++ b/src/util/numeric.cpp
@@ -245,4 +245,3 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir,
return true;
}
-
diff --git a/src/util/numeric.h b/src/util/numeric.h
index a158a2eae..b4b841918 100644
--- a/src/util/numeric.h
+++ b/src/util/numeric.h
@@ -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