Clean scaling pre-filter for formspec/HUD.

master
Aaron Suen 2015-03-09 09:32:11 -04:00 committed by kwolekr
parent b4247dff2e
commit 6d61375cc7
20 changed files with 524 additions and 102 deletions

View File

@ -143,9 +143,11 @@ LOCAL_SRC_FILES := \
jni/src/guiKeyChangeMenu.cpp \ jni/src/guiKeyChangeMenu.cpp \
jni/src/guiPasswordChange.cpp \ jni/src/guiPasswordChange.cpp \
jni/src/guiTable.cpp \ jni/src/guiTable.cpp \
jni/src/guiscalingfilter.cpp \
jni/src/guiVolumeChange.cpp \ jni/src/guiVolumeChange.cpp \
jni/src/httpfetch.cpp \ jni/src/httpfetch.cpp \
jni/src/hud.cpp \ jni/src/hud.cpp \
jni/src/imagefilters.cpp \
jni/src/inventory.cpp \ jni/src/inventory.cpp \
jni/src/inventorymanager.cpp \ jni/src/inventorymanager.cpp \
jni/src/itemdef.cpp \ jni/src/itemdef.cpp \

View File

@ -172,6 +172,19 @@
#crosshair_alpha = 255 #crosshair_alpha = 255
# Scale gui by a user specified value # Scale gui by a user specified value
#gui_scaling = 1.0 #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 # Sensitivity multiplier
#mouse_sensitivity = 0.2 #mouse_sensitivity = 0.2
# Sound settings # Sound settings

View File

@ -405,9 +405,11 @@ set(client_SRCS
guiFormSpecMenu.cpp guiFormSpecMenu.cpp
guiKeyChangeMenu.cpp guiKeyChangeMenu.cpp
guiPasswordChange.cpp guiPasswordChange.cpp
guiscalingfilter.cpp
guiTable.cpp guiTable.cpp
guiVolumeChange.cpp guiVolumeChange.cpp
hud.cpp hud.cpp
imagefilters.cpp
keycode.cpp keycode.cpp
localplayer.cpp localplayer.cpp
main.cpp main.cpp

View File

@ -49,6 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "drawscene.h" #include "drawscene.h"
#include "database-sqlite3.h" #include "database-sqlite3.h"
#include "serialization.h" #include "serialization.h"
#include "guiscalingfilter.h"
extern gui::IGUIEnvironment* guienv; extern gui::IGUIEnvironment* guienv;
@ -1607,6 +1608,11 @@ void Client::afterContentReceived(IrrlichtDevice *device)
const wchar_t* text = wgettext("Loading textures..."); 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 // Rebuild inherited images and recreate textures
infostream<<"- Rebuilding images and textures"<<std::endl; infostream<<"- Rebuilding images and textures"<<std::endl;
draw_load_screen(text,device, guienv, 0, 70); draw_load_screen(text,device, guienv, 0, 70);

View File

@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gamedef.h" #include "gamedef.h"
#include "strfnd.h" #include "strfnd.h"
#include "util/string.h" // for parseColorString() #include "util/string.h" // for parseColorString()
#include "imagefilters.h"
#include "guiscalingfilter.h"
#ifdef __ANDROID__ #ifdef __ANDROID__
#include <GLES/gl.h> #include <GLES/gl.h>
@ -223,58 +225,9 @@ public:
} }
} }
/* Apply the "clean transparent" filter to textures, removing borders on transparent textures. // Apply the "clean transparent" filter, if configured.
* PNG optimizers discard RGB values of fully-transparent pixels, but filters may expose the if (g_settings->getBool("texture_clean_transparent"))
* replacement colors at borders by blending to them; this filter compensates for that by imageCleanTransparent(toadd, 127);
* 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);
}
}
}
if (need_to_grab) if (need_to_grab)
toadd->grab(); toadd->grab();
@ -670,6 +623,7 @@ u32 TextureSource::generateTexture(const std::string &name)
#endif #endif
// Create texture from resulting image // Create texture from resulting image
tex = driver->addTexture(name.c_str(), img); tex = driver->addTexture(name.c_str(), img);
guiScalingCache(io::path(name.c_str()), driver, img);
img->drop(); img->drop();
} }
@ -776,6 +730,7 @@ void TextureSource::rebuildImagesAndTextures()
video::ITexture *t = NULL; video::ITexture *t = NULL;
if (img) { if (img) {
t = driver->addTexture(ti->name.c_str(), img); t = driver->addTexture(ti->name.c_str(), img);
guiScalingCache(io::path(ti->name.c_str()), driver, img);
img->drop(); img->drop();
} }
video::ITexture *t_old = ti->texture; video::ITexture *t_old = ti->texture;
@ -896,6 +851,8 @@ video::ITexture* TextureSource::generateTextureFromMesh(
rawImage->copyToScaling(inventory_image); rawImage->copyToScaling(inventory_image);
rawImage->drop(); 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); video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
inventory_image->drop(); inventory_image->drop();

View File

@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "threads.h" #include "threads.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include "util/numeric.h"
class IGameDef; class IGameDef;
@ -135,21 +136,6 @@ public:
IWritableTextureSource* createTextureSource(IrrlichtDevice *device); IWritableTextureSource* createTextureSource(IrrlichtDevice *device);
#ifdef __ANDROID__ #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); video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver);
#endif #endif

View File

@ -137,6 +137,8 @@ void set_default_settings(Settings *settings)
settings->setDefault("crosshair_alpha", "255"); settings->setDefault("crosshair_alpha", "255");
settings->setDefault("hud_scaling", "1.0"); settings->setDefault("hud_scaling", "1.0");
settings->setDefault("gui_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("mouse_sensitivity", "0.2");
settings->setDefault("enable_sound", "true"); settings->setDefault("enable_sound", "true");
settings->setDefault("sound_volume", "0.8"); settings->setDefault("sound_volume", "0.8");

View File

@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "clientmap.h" #include "clientmap.h"
#include "util/timetaker.h" #include "util/timetaker.h"
#include "fontengine.h" #include "fontengine.h"
#include "guiscalingfilter.h"
typedef enum { typedef enum {
LEFT = -1, 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 //makeColorKeyTexture mirrors texture so we do it twice to get it right again
driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); 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/2, screensize.Y),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); 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/2, screensize.Y),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); 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>(screensize.X/2, 0, screensize.X, screensize.Y),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); 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>(screensize.X/2, 0, screensize.X, screensize.Y),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); 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 //makeColorKeyTexture mirrors texture so we do it twice to get it right again
driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); 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/2),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); 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/2),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); 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, screensize.Y/2, screensize.X, screensize.Y),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); 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, screensize.Y/2, screensize.X, screensize.Y),
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);

View File

@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "httpfetch.h" #include "httpfetch.h"
#include "log.h" #include "log.h"
#include "fontengine.h" #include "fontengine.h"
#include "guiscalingfilter.h"
#ifdef __ANDROID__ #ifdef __ANDROID__
#include "client/tile.h" #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 ) 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>(x, y, x+tilesize.X, y+tilesize.Y),
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
NULL, NULL, true); NULL, NULL, true);
@ -419,7 +420,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver)
} }
/* Draw background texture */ /* Draw background texture */
driver->draw2DImage(texture, draw2DImageFilterScaled(driver, texture,
core::rect<s32>(0, 0, screensize.X, screensize.Y), core::rect<s32>(0, 0, screensize.X, screensize.Y),
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
NULL, NULL, true); NULL, NULL, true);
@ -438,7 +439,7 @@ void GUIEngine::drawOverlay(video::IVideoDriver* driver)
/* Draw background texture */ /* Draw background texture */
v2u32 sourcesize = texture->getOriginalSize(); v2u32 sourcesize = texture->getOriginalSize();
driver->draw2DImage(texture, draw2DImageFilterScaled(driver, texture,
core::rect<s32>(0, 0, screensize.X, screensize.Y), core::rect<s32>(0, 0, screensize.X, screensize.Y),
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
NULL, NULL, true); NULL, NULL, true);
@ -471,7 +472,7 @@ void GUIEngine::drawHeader(video::IVideoDriver* driver)
video::SColor bgcolor(255,50,50,50); video::SColor bgcolor(255,50,50,50);
driver->draw2DImage(texture, splashrect, draw2DImageFilterScaled(driver, texture, splashrect,
core::rect<s32>(core::position2d<s32>(0,0), core::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(texture->getOriginalSize())), core::dimension2di(texture->getOriginalSize())),
NULL, NULL, true); NULL, NULL, true);
@ -503,7 +504,7 @@ void GUIEngine::drawFooter(video::IVideoDriver* driver)
rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y); rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
rect -= v2s32(footersize.X/2, 0); rect -= v2s32(footersize.X/2, 0);
driver->draw2DImage(texture, rect, draw2DImageFilterScaled(driver, texture, rect,
core::rect<s32>(core::position2d<s32>(0,0), core::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(texture->getOriginalSize())), core::dimension2di(texture->getOriginalSize())),
NULL, NULL, true); NULL, NULL, true);

View File

@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/hex.h" #include "util/hex.h"
#include "util/numeric.h" #include "util/numeric.h"
#include "util/string.h" // for parseColorString() #include "util/string.h" // for parseColorString()
#include "guiscalingfilter.h"
#define MY_CHECKPOS(a,b) \ #define MY_CHECKPOS(a,b) \
if (v_pos.size() != 2) { \ if (v_pos.size() != 2) { \
@ -1307,8 +1308,8 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,
} }
e->setUseAlphaChannel(true); e->setUseAlphaChannel(true);
e->setImage(texture); e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
e->setPressedImage(pressed_texture); e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
e->setScaleImage(true); e->setScaleImage(true);
e->setNotClipped(noclip); e->setNotClipped(noclip);
e->setDrawBorder(drawborder); e->setDrawBorder(drawborder);
@ -1452,8 +1453,8 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element)
} }
e->setUseAlphaChannel(true); e->setUseAlphaChannel(true);
e->setImage(texture); e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
e->setPressedImage(texture); e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
e->setScaleImage(true); e->setScaleImage(true);
spec.ftype = f_Button; spec.ftype = f_Button;
rect+=data->basepos-padding; rect+=data->basepos-padding;
@ -2283,7 +2284,7 @@ void GUIFormSpecMenu::drawMenu()
const video::SColor color(255,255,255,255); const video::SColor color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color}; 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::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(texture->getOriginalSize())), core::dimension2di(texture->getOriginalSize())),
NULL/*&AbsoluteClippingRect*/, colors, true); NULL/*&AbsoluteClippingRect*/, colors, true);
@ -2333,7 +2334,7 @@ void GUIFormSpecMenu::drawMenu()
core::rect<s32> rect = imgrect + spec.pos; core::rect<s32> rect = imgrect + spec.pos;
const video::SColor color(255,255,255,255); const video::SColor color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color}; 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), core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
NULL/*&AbsoluteClippingRect*/, colors, true); NULL/*&AbsoluteClippingRect*/, colors, true);
} }
@ -2362,7 +2363,7 @@ void GUIFormSpecMenu::drawMenu()
core::rect<s32> rect = imgrect + spec.pos; core::rect<s32> rect = imgrect + spec.pos;
const video::SColor color(255,255,255,255); const video::SColor color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color}; 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::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(texture->getOriginalSize())), core::dimension2di(texture->getOriginalSize())),
NULL/*&AbsoluteClippingRect*/, colors, true); NULL/*&AbsoluteClippingRect*/, colors, true);

View File

@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "main.h" #include "main.h"
#include "settings.h" // for settings #include "settings.h" // for settings
#include "porting.h" // for dpi #include "porting.h" // for dpi
#include "guiscalingfilter.h"
/* /*
GUITable GUITable

160
src/guiscalingfilter.cpp Normal file
View File

@ -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);
}

52
src/guiscalingfilter.h Normal file
View File

@ -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

View File

@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "camera.h" #include "camera.h"
#include "porting.h" #include "porting.h"
#include "fontengine.h" #include "fontengine.h"
#include "guiscalingfilter.h"
#include <IGUIStaticText.h> #include <IGUIStaticText.h>
#ifdef HAVE_TOUCHSCREENGUI #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); imgrect2.LowerRightCorner.Y += (m_padding*2);
video::ITexture *texture = tsrc->getTexture(hotbar_selected_image); video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
core::dimension2di imgsize(texture->getOriginalSize()); core::dimension2di imgsize(texture->getOriginalSize());
driver->draw2DImage(texture, imgrect2, draw2DImageFilterScaled(driver, texture, imgrect2,
core::rect<s32>(core::position2d<s32>(0,0), imgsize), core::rect<s32>(core::position2d<s32>(0,0), imgsize),
NULL, hbar_colors, true); NULL, hbar_colors, true);
} else { } else {
@ -200,7 +201,7 @@ void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset,
core::rect<s32> rect2 = imgrect2 + pos; core::rect<s32> rect2 = imgrect2 + pos;
video::ITexture *texture = tsrc->getTexture(hotbar_image); video::ITexture *texture = tsrc->getTexture(hotbar_image);
core::dimension2di imgsize(texture->getOriginalSize()); core::dimension2di imgsize(texture->getOriginalSize());
driver->draw2DImage(texture, rect2, draw2DImageFilterScaled(driver, texture, rect2,
core::rect<s32>(core::position2d<s32>(0,0), imgsize), core::rect<s32>(core::position2d<s32>(0,0), imgsize),
NULL, hbar_colors, true); NULL, hbar_colors, true);
} }
@ -266,7 +267,7 @@ void Hud::drawLuaElements(v3s16 camera_offset) {
(e->align.Y - 1.0) * dstsize.Y / 2); (e->align.Y - 1.0) * dstsize.Y / 2);
core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y); core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
rect += pos + offset + v2s32(e->offset.X, e->offset.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), core::rect<s32>(core::position2d<s32>(0,0), imgsize),
NULL, colors, true); NULL, colors, true);
break; } 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); core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height);
dstrect += p; dstrect += p;
driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true); draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
p += steppos; 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); core::rect<s32> dstrect(0,0, dstd.Width / 2, dstd.Height);
dstrect += p; 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 color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color}; 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::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(texture->getOriginalSize())), core::dimension2di(texture->getOriginalSize())),
clip, colors, true); clip, colors, true);

172
src/imagefilters.cpp Normal file
View File

@ -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);
}
}

46
src/imagefilters.h Normal file
View File

@ -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

View File

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gettime.h" #include "gettime.h"
#include "util/numeric.h" #include "util/numeric.h"
#include "porting.h" #include "porting.h"
#include "guiscalingfilter.h"
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
@ -130,15 +131,23 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver)
m_screensize = m_device->getVideoDriver()->getScreenSize(); 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; 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) { if (texture) {
btn->guibutton->setUseAlphaChannel(true); btn->guibutton->setUseAlphaChannel(true);
btn->guibutton->setImage(texture); if (g_settings->getBool("gui_scaling_filter")) {
btn->guibutton->setPressedImage(texture); rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
btn->guibutton->setScaleImage(true); 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->setDrawBorder(false);
btn->guibutton->setText(L""); 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->immediate_release = immediate_release;
btn->ids.clear(); btn->ids.clear();
loadButtonTexture(btn,touchgui_button_imagenames[id]); loadButtonTexture(btn,touchgui_button_imagenames[id], button_rect);
} }
static int getMaxControlPadSize(float density) { static int getMaxControlPadSize(float density) {

View File

@ -130,7 +130,7 @@ private:
float repeat_delay = BUTTON_REPEAT_DELAY); float repeat_delay = BUTTON_REPEAT_DELAY);
/* load texture */ /* 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{ struct id_status{
int id; int id;

View File

@ -245,4 +245,3 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir,
return true; return true;
} }

View File

@ -411,5 +411,16 @@ inline bool is_power_of_two(u32 n)
return n != 0 && (n & (n-1)) == 0; 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