Skip to content

Commit 6d61375

Browse files
Warr1024kwolekr
authored andcommittedApr 1, 2015
Clean scaling pre-filter for formspec/HUD.
1 parent b4247df commit 6d61375

20 files changed

+524
-102
lines changed
 

Diff for: ‎build/android/jni/Android.mk

+2
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,11 @@ LOCAL_SRC_FILES := \
143143
jni/src/guiKeyChangeMenu.cpp \
144144
jni/src/guiPasswordChange.cpp \
145145
jni/src/guiTable.cpp \
146+
jni/src/guiscalingfilter.cpp \
146147
jni/src/guiVolumeChange.cpp \
147148
jni/src/httpfetch.cpp \
148149
jni/src/hud.cpp \
150+
jni/src/imagefilters.cpp \
149151
jni/src/inventory.cpp \
150152
jni/src/inventorymanager.cpp \
151153
jni/src/itemdef.cpp \

Diff for: ‎minetest.conf.example

+13
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,19 @@
172172
#crosshair_alpha = 255
173173
# Scale gui by a user specified value
174174
#gui_scaling = 1.0
175+
# Use a nearest-neighbor-anti-alias filter to scale the GUI.
176+
# This will smooth over some of the rough edges, and blend
177+
# pixels when scaling down, at the cost of blurring some
178+
# edge pixels when images are scaled by non-integer sizes.
179+
#gui_scaling_filter = false
180+
# When gui_scaling_filter = true, all GUI images need to be
181+
# filtered in software, but some images are generated directly
182+
# to hardare (e.g. render-to-texture for nodes in inventory).
183+
# When gui_scaling_filter_txr2img is true, copy those images
184+
# from hardware to software for scaling. When false, fall back
185+
# to the old scaling method, for video drivers that don't
186+
# propery support downloading textures back from hardware.
187+
#gui_scaling_filter_txr2img = true
175188
# Sensitivity multiplier
176189
#mouse_sensitivity = 0.2
177190
# Sound settings

Diff for: ‎src/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,11 @@ set(client_SRCS
405405
guiFormSpecMenu.cpp
406406
guiKeyChangeMenu.cpp
407407
guiPasswordChange.cpp
408+
guiscalingfilter.cpp
408409
guiTable.cpp
409410
guiVolumeChange.cpp
410411
hud.cpp
412+
imagefilters.cpp
411413
keycode.cpp
412414
localplayer.cpp
413415
main.cpp

Diff for: ‎src/client.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
4949
#include "drawscene.h"
5050
#include "database-sqlite3.h"
5151
#include "serialization.h"
52+
#include "guiscalingfilter.h"
5253

5354
extern gui::IGUIEnvironment* guienv;
5455

@@ -1607,6 +1608,11 @@ void Client::afterContentReceived(IrrlichtDevice *device)
16071608

16081609
const wchar_t* text = wgettext("Loading textures...");
16091610

1611+
// Clear cached pre-scaled 2D GUI images, as this cache
1612+
// might have images with the same name but different
1613+
// content from previous sessions.
1614+
guiScalingCacheClear(device->getVideoDriver());
1615+
16101616
// Rebuild inherited images and recreate textures
16111617
infostream<<"- Rebuilding images and textures"<<std::endl;
16121618
draw_load_screen(text,device, guienv, 0, 70);

Diff for: ‎src/client/tile.cpp

+9-52
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
3434
#include "gamedef.h"
3535
#include "strfnd.h"
3636
#include "util/string.h" // for parseColorString()
37+
#include "imagefilters.h"
38+
#include "guiscalingfilter.h"
3739

3840
#ifdef __ANDROID__
3941
#include <GLES/gl.h>
@@ -223,58 +225,9 @@ class SourceImageCache
223225
}
224226
}
225227

226-
/* Apply the "clean transparent" filter to textures, removing borders on transparent textures.
227-
* PNG optimizers discard RGB values of fully-transparent pixels, but filters may expose the
228-
* replacement colors at borders by blending to them; this filter compensates for that by
229-
* filling in those RGB values from nearby pixels.
230-
*/
231-
if (g_settings->getBool("texture_clean_transparent")) {
232-
const core::dimension2d<u32> dim = toadd->getDimension();
233-
234-
// Walk each pixel looking for ones that will show as transparent.
235-
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++)
236-
for (u32 ctry = 0; ctry < dim.Height; ctry++) {
237-
irr::video::SColor c = toadd->getPixel(ctrx, ctry);
238-
if (c.getAlpha() > 127)
239-
continue;
240-
241-
// Sample size and total weighted r, g, b values.
242-
u32 ss = 0, sr = 0, sg = 0, sb = 0;
243-
244-
// Walk each neighbor pixel (clipped to image bounds).
245-
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
246-
sx <= (ctrx + 1) && sx < dim.Width; sx++)
247-
for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
248-
sy <= (ctry + 1) && sy < dim.Height; sy++) {
249-
250-
// Ignore the center pixel (its RGB is already
251-
// presumed meaningless).
252-
if ((sx == ctrx) && (sy == ctry))
253-
continue;
254-
255-
// Ignore other nearby pixels that would be
256-
// transparent upon display.
257-
irr::video::SColor d = toadd->getPixel(sx, sy);
258-
if(d.getAlpha() < 128)
259-
continue;
260-
261-
// Add one weighted sample.
262-
ss++;
263-
sr += d.getRed();
264-
sg += d.getGreen();
265-
sb += d.getBlue();
266-
}
267-
268-
// If we found any neighbor RGB data, set pixel to average
269-
// weighted by alpha.
270-
if (ss > 0) {
271-
c.setRed(sr / ss);
272-
c.setGreen(sg / ss);
273-
c.setBlue(sb / ss);
274-
toadd->setPixel(ctrx, ctry, c);
275-
}
276-
}
277-
}
228+
// Apply the "clean transparent" filter, if configured.
229+
if (g_settings->getBool("texture_clean_transparent"))
230+
imageCleanTransparent(toadd, 127);
278231

279232
if (need_to_grab)
280233
toadd->grab();
@@ -670,6 +623,7 @@ u32 TextureSource::generateTexture(const std::string &name)
670623
#endif
671624
// Create texture from resulting image
672625
tex = driver->addTexture(name.c_str(), img);
626+
guiScalingCache(io::path(name.c_str()), driver, img);
673627
img->drop();
674628
}
675629

@@ -776,6 +730,7 @@ void TextureSource::rebuildImagesAndTextures()
776730
video::ITexture *t = NULL;
777731
if (img) {
778732
t = driver->addTexture(ti->name.c_str(), img);
733+
guiScalingCache(io::path(ti->name.c_str()), driver, img);
779734
img->drop();
780735
}
781736
video::ITexture *t_old = ti->texture;
@@ -896,6 +851,8 @@ video::ITexture* TextureSource::generateTextureFromMesh(
896851
rawImage->copyToScaling(inventory_image);
897852
rawImage->drop();
898853

854+
guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
855+
899856
video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
900857
inventory_image->drop();
901858

Diff for: ‎src/client/tile.h

+1-15
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2828
#include "threads.h"
2929
#include <string>
3030
#include <vector>
31+
#include "util/numeric.h"
3132

3233
class IGameDef;
3334

@@ -135,21 +136,6 @@ class IWritableTextureSource : public ITextureSource
135136
IWritableTextureSource* createTextureSource(IrrlichtDevice *device);
136137

137138
#ifdef __ANDROID__
138-
/**
139-
* @param size get next npot2 value
140-
* @return npot2 value
141-
*/
142-
inline unsigned int npot2(unsigned int size)
143-
{
144-
if (size == 0) return 0;
145-
unsigned int npot = 1;
146-
147-
while ((size >>= 1) > 0) {
148-
npot <<= 1;
149-
}
150-
return npot;
151-
}
152-
153139
video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver);
154140
#endif
155141

Diff for: ‎src/defaultsettings.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ void set_default_settings(Settings *settings)
137137
settings->setDefault("crosshair_alpha", "255");
138138
settings->setDefault("hud_scaling", "1.0");
139139
settings->setDefault("gui_scaling", "1.0");
140+
settings->setDefault("gui_scaling_filter", "false");
141+
settings->setDefault("gui_scaling_filter_txr2img", "true");
140142
settings->setDefault("mouse_sensitivity", "0.2");
141143
settings->setDefault("enable_sound", "true");
142144
settings->setDefault("sound_volume", "0.8");

Diff for: ‎src/drawscene.cpp

+9-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2424
#include "clientmap.h"
2525
#include "util/timetaker.h"
2626
#include "fontengine.h"
27+
#include "guiscalingfilter.h"
2728

2829
typedef enum {
2930
LEFT = -1,
@@ -324,19 +325,19 @@ void draw_sidebyside_3d_mode(Camera& camera, bool show_hud,
324325
//makeColorKeyTexture mirrors texture so we do it twice to get it right again
325326
driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0));
326327

327-
driver->draw2DImage(left_image,
328+
draw2DImageFilterScaled(driver, left_image,
328329
irr::core::rect<s32>(0, 0, screensize.X/2, screensize.Y),
329330
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
330331

331-
driver->draw2DImage(hudtexture,
332+
draw2DImageFilterScaled(driver, hudtexture,
332333
irr::core::rect<s32>(0, 0, screensize.X/2, screensize.Y),
333334
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
334335

335-
driver->draw2DImage(right_image,
336+
draw2DImageFilterScaled(driver, right_image,
336337
irr::core::rect<s32>(screensize.X/2, 0, screensize.X, screensize.Y),
337338
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
338339

339-
driver->draw2DImage(hudtexture,
340+
draw2DImageFilterScaled(driver, hudtexture,
340341
irr::core::rect<s32>(screensize.X/2, 0, screensize.X, screensize.Y),
341342
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
342343

@@ -380,19 +381,19 @@ void draw_top_bottom_3d_mode(Camera& camera, bool show_hud,
380381
//makeColorKeyTexture mirrors texture so we do it twice to get it right again
381382
driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0));
382383

383-
driver->draw2DImage(left_image,
384+
draw2DImageFilterScaled(driver, left_image,
384385
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y/2),
385386
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
386387

387-
driver->draw2DImage(hudtexture,
388+
draw2DImageFilterScaled(driver, hudtexture,
388389
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y/2),
389390
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
390391

391-
driver->draw2DImage(right_image,
392+
draw2DImageFilterScaled(driver, right_image,
392393
irr::core::rect<s32>(0, screensize.Y/2, screensize.X, screensize.Y),
393394
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false);
394395

395-
driver->draw2DImage(hudtexture,
396+
draw2DImageFilterScaled(driver, hudtexture,
396397
irr::core::rect<s32>(0, screensize.Y/2, screensize.X, screensize.Y),
397398
irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true);
398399

Diff for: ‎src/guiEngine.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
3636
#include "httpfetch.h"
3737
#include "log.h"
3838
#include "fontengine.h"
39+
#include "guiscalingfilter.h"
3940

4041
#ifdef __ANDROID__
4142
#include "client/tile.h"
@@ -409,7 +410,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver)
409410
{
410411
for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y )
411412
{
412-
driver->draw2DImage(texture,
413+
draw2DImageFilterScaled(driver, texture,
413414
core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y),
414415
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
415416
NULL, NULL, true);
@@ -419,7 +420,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver)
419420
}
420421

421422
/* Draw background texture */
422-
driver->draw2DImage(texture,
423+
draw2DImageFilterScaled(driver, texture,
423424
core::rect<s32>(0, 0, screensize.X, screensize.Y),
424425
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
425426
NULL, NULL, true);
@@ -438,7 +439,7 @@ void GUIEngine::drawOverlay(video::IVideoDriver* driver)
438439

439440
/* Draw background texture */
440441
v2u32 sourcesize = texture->getOriginalSize();
441-
driver->draw2DImage(texture,
442+
draw2DImageFilterScaled(driver, texture,
442443
core::rect<s32>(0, 0, screensize.X, screensize.Y),
443444
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
444445
NULL, NULL, true);
@@ -471,7 +472,7 @@ void GUIEngine::drawHeader(video::IVideoDriver* driver)
471472

472473
video::SColor bgcolor(255,50,50,50);
473474

474-
driver->draw2DImage(texture, splashrect,
475+
draw2DImageFilterScaled(driver, texture, splashrect,
475476
core::rect<s32>(core::position2d<s32>(0,0),
476477
core::dimension2di(texture->getOriginalSize())),
477478
NULL, NULL, true);
@@ -503,7 +504,7 @@ void GUIEngine::drawFooter(video::IVideoDriver* driver)
503504
rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
504505
rect -= v2s32(footersize.X/2, 0);
505506

506-
driver->draw2DImage(texture, rect,
507+
draw2DImageFilterScaled(driver, texture, rect,
507508
core::rect<s32>(core::position2d<s32>(0,0),
508509
core::dimension2di(texture->getOriginalSize())),
509510
NULL, NULL, true);

Diff for: ‎src/guiFormSpecMenu.cpp

+8-7
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
5151
#include "util/hex.h"
5252
#include "util/numeric.h"
5353
#include "util/string.h" // for parseColorString()
54+
#include "guiscalingfilter.h"
5455

5556
#define MY_CHECKPOS(a,b) \
5657
if (v_pos.size() != 2) { \
@@ -1307,8 +1308,8 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,
13071308
}
13081309

13091310
e->setUseAlphaChannel(true);
1310-
e->setImage(texture);
1311-
e->setPressedImage(pressed_texture);
1311+
e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
1312+
e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
13121313
e->setScaleImage(true);
13131314
e->setNotClipped(noclip);
13141315
e->setDrawBorder(drawborder);
@@ -1452,8 +1453,8 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element)
14521453
}
14531454

14541455
e->setUseAlphaChannel(true);
1455-
e->setImage(texture);
1456-
e->setPressedImage(texture);
1456+
e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
1457+
e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y));
14571458
e->setScaleImage(true);
14581459
spec.ftype = f_Button;
14591460
rect+=data->basepos-padding;
@@ -2283,7 +2284,7 @@ void GUIFormSpecMenu::drawMenu()
22832284

22842285
const video::SColor color(255,255,255,255);
22852286
const video::SColor colors[] = {color,color,color,color};
2286-
driver->draw2DImage(texture, rect,
2287+
draw2DImageFilterScaled(driver, texture, rect,
22872288
core::rect<s32>(core::position2d<s32>(0,0),
22882289
core::dimension2di(texture->getOriginalSize())),
22892290
NULL/*&AbsoluteClippingRect*/, colors, true);
@@ -2333,7 +2334,7 @@ void GUIFormSpecMenu::drawMenu()
23332334
core::rect<s32> rect = imgrect + spec.pos;
23342335
const video::SColor color(255,255,255,255);
23352336
const video::SColor colors[] = {color,color,color,color};
2336-
driver->draw2DImage(texture, rect,
2337+
draw2DImageFilterScaled(driver, texture, rect,
23372338
core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
23382339
NULL/*&AbsoluteClippingRect*/, colors, true);
23392340
}
@@ -2362,7 +2363,7 @@ void GUIFormSpecMenu::drawMenu()
23622363
core::rect<s32> rect = imgrect + spec.pos;
23632364
const video::SColor color(255,255,255,255);
23642365
const video::SColor colors[] = {color,color,color,color};
2365-
driver->draw2DImage(texture, rect,
2366+
draw2DImageFilterScaled(driver, texture, rect,
23662367
core::rect<s32>(core::position2d<s32>(0,0),
23672368
core::dimension2di(texture->getOriginalSize())),
23682369
NULL/*&AbsoluteClippingRect*/, colors, true);

Diff for: ‎src/guiTable.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
3636
#include "main.h"
3737
#include "settings.h" // for settings
3838
#include "porting.h" // for dpi
39+
#include "guiscalingfilter.h"
3940

4041
/*
4142
GUITable

Diff for: ‎src/guiscalingfilter.cpp

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
3+
4+
This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation; either version 2.1 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License along
15+
with this program; if not, write to the Free Software Foundation, Inc.,
16+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
*/
18+
19+
#include "guiscalingfilter.h"
20+
#include "imagefilters.h"
21+
#include "settings.h"
22+
#include "main.h" // for g_settings
23+
#include "util/numeric.h"
24+
#include <stdio.h>
25+
26+
/* Maintain a static cache to store the images that correspond to textures
27+
* in a format that's manipulable by code. Some platforms exhibit issues
28+
* converting textures back into images repeatedly, and some don't even
29+
* allow it at all.
30+
*/
31+
std::map<io::path, video::IImage *> imgCache;
32+
33+
/* Maintain a static cache of all pre-scaled textures. These need to be
34+
* cleared as well when the cached images.
35+
*/
36+
std::map<io::path, video::ITexture *> txrCache;
37+
38+
/* Manually insert an image into the cache, useful to avoid texture-to-image
39+
* conversion whenever we can intercept it.
40+
*/
41+
void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value) {
42+
if (!g_settings->getBool("gui_scaling_filter"))
43+
return;
44+
video::IImage *copied = driver->createImage(value->getColorFormat(),
45+
value->getDimension());
46+
value->copyTo(copied);
47+
imgCache[key] = copied;
48+
}
49+
50+
// Manually clear the cache, e.g. when switching to different worlds.
51+
void guiScalingCacheClear(video::IVideoDriver *driver) {
52+
for (std::map<io::path, video::IImage *>::iterator it = imgCache.begin();
53+
it != imgCache.end(); it++) {
54+
if (it->second != NULL)
55+
it->second->drop();
56+
}
57+
imgCache.clear();
58+
for (std::map<io::path, video::ITexture *>::iterator it = txrCache.begin();
59+
it != txrCache.end(); it++) {
60+
if (it->second != NULL)
61+
driver->removeTexture(it->second);
62+
}
63+
txrCache.clear();
64+
}
65+
66+
/* Get a cached, high-quality pre-scaled texture for display purposes. If the
67+
* texture is not already cached, attempt to create it. Returns a pre-scaled texture,
68+
* or the original texture if unable to pre-scale it.
69+
*/
70+
video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
71+
const core::rect<s32> &srcrect, const core::rect<s32> &destrect) {
72+
73+
if (!g_settings->getBool("gui_scaling_filter"))
74+
return src;
75+
76+
// Calculate scaled texture name.
77+
char rectstr[200];
78+
sprintf(rectstr, "%d:%d:%d:%d:%d:%d",
79+
srcrect.UpperLeftCorner.X,
80+
srcrect.UpperLeftCorner.Y,
81+
srcrect.getWidth(),
82+
srcrect.getHeight(),
83+
destrect.getWidth(),
84+
destrect.getHeight());
85+
io::path origname = src->getName().getPath();
86+
io::path scalename = origname + "@guiScalingFilter:" + rectstr;
87+
88+
// Search for existing scaled texture.
89+
video::ITexture *scaled = txrCache[scalename];
90+
if (scaled)
91+
return scaled;
92+
93+
// Try to find the texture converted to an image in the cache.
94+
// If the image was not found, try to extract it from the texture.
95+
video::IImage* srcimg = imgCache[origname];
96+
if (srcimg == NULL) {
97+
if (!g_settings->getBool("gui_scaling_filter_txr2img"))
98+
return src;
99+
srcimg = driver->createImageFromData(src->getColorFormat(),
100+
src->getSize(), src->lock(), false);
101+
src->unlock();
102+
imgCache[origname] = srcimg;
103+
}
104+
105+
// Create a new destination image and scale the source into it.
106+
imageCleanTransparent(srcimg, 0);
107+
video::IImage *destimg = driver->createImage(src->getColorFormat(),
108+
core::dimension2d<u32>((u32)destrect.getWidth(),
109+
(u32)destrect.getHeight()));
110+
imageScaleNNAA(srcimg, srcrect, destimg);
111+
112+
#ifdef __ANDROID__
113+
// Android is very picky about textures being powers of 2, so expand
114+
// the image dimensions to the next power of 2, if necessary, for
115+
// that platform.
116+
video::IImage *po2img = driver->createImage(src->getColorFormat(),
117+
core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
118+
npot2((u32)destrect.getHeight())));
119+
po2img->fill(video::SColor(0, 0, 0, 0));
120+
destimg->copyTo(po2img);
121+
destimg->drop();
122+
destimg = po2img;
123+
#endif
124+
125+
// Convert the scaled image back into a texture.
126+
scaled = driver->addTexture(scalename, destimg, NULL);
127+
destimg->drop();
128+
txrCache[scalename] = scaled;
129+
130+
return scaled;
131+
}
132+
133+
/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
134+
* are available at GUI imagebutton creation time.
135+
*/
136+
video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
137+
s32 width, s32 height) {
138+
return guiScalingResizeCached(driver, src,
139+
core::rect<s32>(0, 0, src->getSize().Width, src->getSize().Height),
140+
core::rect<s32>(0, 0, width, height));
141+
}
142+
143+
/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
144+
* texture, if configured.
145+
*/
146+
void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
147+
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
148+
const core::rect<s32> *cliprect, const video::SColor *const colors,
149+
bool usealpha) {
150+
151+
// Attempt to pre-scale image in software in high quality.
152+
video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect);
153+
154+
// Correct source rect based on scaled image.
155+
const core::rect<s32> mysrcrect = (scaled != txr)
156+
? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
157+
: srcrect;
158+
159+
driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
160+
}

Diff for: ‎src/guiscalingfilter.h

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
3+
4+
This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation; either version 2.1 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License along
15+
with this program; if not, write to the Free Software Foundation, Inc.,
16+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
*/
18+
#ifndef _GUI_SCALING_FILTER_H_
19+
#define _GUI_SCALING_FILTER_H_
20+
21+
#include "irrlichttypes_extrabloated.h"
22+
23+
/* Manually insert an image into the cache, useful to avoid texture-to-image
24+
* conversion whenever we can intercept it.
25+
*/
26+
void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value);
27+
28+
// Manually clear the cache, e.g. when switching to different worlds.
29+
void guiScalingCacheClear(video::IVideoDriver *driver);
30+
31+
/* Get a cached, high-quality pre-scaled texture for display purposes. If the
32+
* texture is not already cached, attempt to create it. Returns a pre-scaled texture,
33+
* or the original texture if unable to pre-scale it.
34+
*/
35+
video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
36+
const core::rect<s32> &srcrect, const core::rect<s32> &destrect);
37+
38+
/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
39+
* are available at GUI imagebutton creation time.
40+
*/
41+
video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
42+
s32 width, s32 height);
43+
44+
/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
45+
* texture, if configured.
46+
*/
47+
void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
48+
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
49+
const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
50+
bool usealpha = false);
51+
52+
#endif

Diff for: ‎src/hud.cpp

+7-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
3232
#include "camera.h"
3333
#include "porting.h"
3434
#include "fontengine.h"
35+
#include "guiscalingfilter.h"
3536
#include <IGUIStaticText.h>
3637

3738
#ifdef HAVE_TOUCHSCREENGUI
@@ -94,7 +95,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect, bool sele
9495
imgrect2.LowerRightCorner.Y += (m_padding*2);
9596
video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
9697
core::dimension2di imgsize(texture->getOriginalSize());
97-
driver->draw2DImage(texture, imgrect2,
98+
draw2DImageFilterScaled(driver, texture, imgrect2,
9899
core::rect<s32>(core::position2d<s32>(0,0), imgsize),
99100
NULL, hbar_colors, true);
100101
} else {
@@ -200,7 +201,7 @@ void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset,
200201
core::rect<s32> rect2 = imgrect2 + pos;
201202
video::ITexture *texture = tsrc->getTexture(hotbar_image);
202203
core::dimension2di imgsize(texture->getOriginalSize());
203-
driver->draw2DImage(texture, rect2,
204+
draw2DImageFilterScaled(driver, texture, rect2,
204205
core::rect<s32>(core::position2d<s32>(0,0), imgsize),
205206
NULL, hbar_colors, true);
206207
}
@@ -266,7 +267,7 @@ void Hud::drawLuaElements(v3s16 camera_offset) {
266267
(e->align.Y - 1.0) * dstsize.Y / 2);
267268
core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
268269
rect += pos + offset + v2s32(e->offset.X, e->offset.Y);
269-
driver->draw2DImage(texture, rect,
270+
draw2DImageFilterScaled(driver, texture, rect,
270271
core::rect<s32>(core::position2d<s32>(0,0), imgsize),
271272
NULL, colors, true);
272273
break; }
@@ -378,7 +379,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
378379
core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height);
379380

380381
dstrect += p;
381-
driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true);
382+
draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
382383
p += steppos;
383384
}
384385

@@ -388,7 +389,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
388389
core::rect<s32> dstrect(0,0, dstd.Width / 2, dstd.Height);
389390

390391
dstrect += p;
391-
driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true);
392+
draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
392393
}
393394
}
394395

@@ -502,7 +503,7 @@ void drawItemStack(video::IVideoDriver *driver,
502503
{
503504
const video::SColor color(255,255,255,255);
504505
const video::SColor colors[] = {color,color,color,color};
505-
driver->draw2DImage(texture, rect,
506+
draw2DImageFilterScaled(driver, texture, rect,
506507
core::rect<s32>(core::position2d<s32>(0,0),
507508
core::dimension2di(texture->getOriginalSize())),
508509
clip, colors, true);

Diff for: ‎src/imagefilters.cpp

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
3+
4+
This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation; either version 2.1 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License along
15+
with this program; if not, write to the Free Software Foundation, Inc.,
16+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
*/
18+
19+
#include "imagefilters.h"
20+
#include "util/numeric.h"
21+
#include <math.h>
22+
23+
/* Fill in RGB values for transparent pixels, to correct for odd colors
24+
* appearing at borders when blending. This is because many PNG optimizers
25+
* like to discard RGB values of transparent pixels, but when blending then
26+
* with non-transparent neighbors, their RGB values will shpw up nonetheless.
27+
*
28+
* This function modifies the original image in-place.
29+
*
30+
* Parameter "threshold" is the alpha level below which pixels are considered
31+
* transparent. Should be 127 for 3d where alpha is threshold, but 0 for
32+
* 2d where alpha is blended.
33+
*/
34+
void imageCleanTransparent(video::IImage *src, u32 threshold) {
35+
36+
core::dimension2d<u32> dim = src->getDimension();
37+
38+
// Walk each pixel looking for fully transparent ones.
39+
// Note: loop y around x for better cache locality.
40+
for (u32 ctry = 0; ctry < dim.Height; ctry++)
41+
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
42+
43+
// Ignore opaque pixels.
44+
irr::video::SColor c = src->getPixel(ctrx, ctry);
45+
if (c.getAlpha() > threshold)
46+
continue;
47+
48+
// Sample size and total weighted r, g, b values.
49+
u32 ss = 0, sr = 0, sg = 0, sb = 0;
50+
51+
// Walk each neighbor pixel (clipped to image bounds).
52+
for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
53+
sy <= (ctry + 1) && sy < dim.Height; sy++)
54+
for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
55+
sx <= (ctrx + 1) && sx < dim.Width; sx++) {
56+
57+
// Ignore transparent pixels.
58+
irr::video::SColor d = src->getPixel(sx, sy);
59+
if (d.getAlpha() <= threshold)
60+
continue;
61+
62+
// Add RGB values weighted by alpha.
63+
u32 a = d.getAlpha();
64+
ss += a;
65+
sr += a * d.getRed();
66+
sg += a * d.getGreen();
67+
sb += a * d.getBlue();
68+
}
69+
70+
// If we found any neighbor RGB data, set pixel to average
71+
// weighted by alpha.
72+
if (ss > 0) {
73+
c.setRed(sr / ss);
74+
c.setGreen(sg / ss);
75+
c.setBlue(sb / ss);
76+
src->setPixel(ctrx, ctry, c);
77+
}
78+
}
79+
}
80+
81+
/* Scale a region of an image into another image, using nearest-neighbor with
82+
* anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
83+
* to prevent non-integer scaling ratio artifacts. Note that this may cause
84+
* some blending at the edges where pixels don't line up perfectly, but this
85+
* filter is designed to produce the most accurate results for both upscaling
86+
* and downscaling.
87+
*/
88+
void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest) {
89+
90+
double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
91+
u32 dy, dx;
92+
video::SColor pxl;
93+
94+
// Cache rectsngle boundaries.
95+
double sox = srcrect.UpperLeftCorner.X * 1.0;
96+
double soy = srcrect.UpperLeftCorner.Y * 1.0;
97+
double sw = srcrect.getWidth() * 1.0;
98+
double sh = srcrect.getHeight() * 1.0;
99+
100+
// Walk each destination image pixel.
101+
// Note: loop y around x for better cache locality.
102+
core::dimension2d<u32> dim = dest->getDimension();
103+
for (dy = 0; dy < dim.Height; dy++)
104+
for (dx = 0; dx < dim.Width; dx++) {
105+
106+
// Calculate floating-point source rectangle bounds.
107+
// Do some basic clipping, and for mirrored/flipped rects,
108+
// make sure min/max are in the right order.
109+
minsx = sox + (dx * sw / dim.Width);
110+
minsx = rangelim(minsx, 0, sw);
111+
maxsx = minsx + sw / dim.Width;
112+
maxsx = rangelim(maxsx, 0, sw);
113+
if (minsx > maxsx)
114+
SWAP(double, minsx, maxsx);
115+
minsy = soy + (dy * sh / dim.Height);
116+
minsy = rangelim(minsy, 0, sh);
117+
maxsy = minsy + sh / dim.Height;
118+
maxsy = rangelim(maxsy, 0, sh);
119+
if (minsy > maxsy)
120+
SWAP(double, minsy, maxsy);
121+
122+
// Total area, and integral of r, g, b values over that area,
123+
// initialized to zero, to be summed up in next loops.
124+
area = 0;
125+
ra = 0;
126+
ga = 0;
127+
ba = 0;
128+
aa = 0;
129+
130+
// Loop over the integral pixel positions described by those bounds.
131+
for (sy = floor(minsy); sy < maxsy; sy++)
132+
for (sx = floor(minsx); sx < maxsx; sx++) {
133+
134+
// Calculate width, height, then area of dest pixel
135+
// that's covered by this source pixel.
136+
pw = 1;
137+
if (minsx > sx)
138+
pw += sx - minsx;
139+
if (maxsx < (sx + 1))
140+
pw += maxsx - sx - 1;
141+
ph = 1;
142+
if (minsy > sy)
143+
ph += sy - minsy;
144+
if (maxsy < (sy + 1))
145+
ph += maxsy - sy - 1;
146+
pa = pw * ph;
147+
148+
// Get source pixel and add it to totals, weighted
149+
// by covered area and alpha.
150+
pxl = src->getPixel((u32)sx, (u32)sy);
151+
area += pa;
152+
ra += pa * pxl.getRed();
153+
ga += pa * pxl.getGreen();
154+
ba += pa * pxl.getBlue();
155+
aa += pa * pxl.getAlpha();
156+
}
157+
158+
// Set the destination image pixel to the average color.
159+
if (area > 0) {
160+
pxl.setRed(ra / area + 0.5);
161+
pxl.setGreen(ga / area + 0.5);
162+
pxl.setBlue(ba / area + 0.5);
163+
pxl.setAlpha(aa / area + 0.5);
164+
} else {
165+
pxl.setRed(0);
166+
pxl.setGreen(0);
167+
pxl.setBlue(0);
168+
pxl.setAlpha(0);
169+
}
170+
dest->setPixel(dx, dy, pxl);
171+
}
172+
}

Diff for: ‎src/imagefilters.h

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
3+
4+
This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation; either version 2.1 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License along
15+
with this program; if not, write to the Free Software Foundation, Inc.,
16+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17+
*/
18+
19+
#ifndef _IMAGE_FILTERS_H_
20+
#define _IMAGE_FILTERS_H_
21+
22+
#include "irrlichttypes_extrabloated.h"
23+
24+
/* Fill in RGB values for transparent pixels, to correct for odd colors
25+
* appearing at borders when blending. This is because many PNG optimizers
26+
* like to discard RGB values of transparent pixels, but when blending then
27+
* with non-transparent neighbors, their RGB values will shpw up nonetheless.
28+
*
29+
* This function modifies the original image in-place.
30+
*
31+
* Parameter "threshold" is the alpha level below which pixels are considered
32+
* transparent. Should be 127 for 3d where alpha is threshold, but 0 for
33+
* 2d where alpha is blended.
34+
*/
35+
void imageCleanTransparent(video::IImage *src, u32 threshold);
36+
37+
/* Scale a region of an image into another image, using nearest-neighbor with
38+
* anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
39+
* to prevent non-integer scaling ratio artifacts. Note that this may cause
40+
* some blending at the edges where pixels don't line up perfectly, but this
41+
* filter is designed to produce the most accurate results for both upscaling
42+
* and downscaling.
43+
*/
44+
void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest);
45+
46+
#endif

Diff for: ‎src/touchscreengui.cpp

+15-6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2525
#include "gettime.h"
2626
#include "util/numeric.h"
2727
#include "porting.h"
28+
#include "guiscalingfilter.h"
2829

2930
#include <iostream>
3031
#include <algorithm>
@@ -130,15 +131,23 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver)
130131
m_screensize = m_device->getVideoDriver()->getScreenSize();
131132
}
132133

133-
void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path)
134+
void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path, rect<s32> button_rect)
134135
{
135136
unsigned int tid;
136-
video::ITexture *texture = m_texturesource->getTexture(path,&tid);
137+
video::ITexture *texture = guiScalingImageButton(m_device->getVideoDriver(),
138+
m_texturesource->getTexture(path, &tid), button_rect.getWidth(), button_rect.getHeight());
137139
if (texture) {
138140
btn->guibutton->setUseAlphaChannel(true);
139-
btn->guibutton->setImage(texture);
140-
btn->guibutton->setPressedImage(texture);
141-
btn->guibutton->setScaleImage(true);
141+
if (g_settings->getBool("gui_scaling_filter")) {
142+
rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
143+
btn->guibutton->setImage(texture, txr_rect);
144+
btn->guibutton->setPressedImage(texture, txr_rect);
145+
btn->guibutton->setScaleImage(false);
146+
} else {
147+
btn->guibutton->setImage(texture);
148+
btn->guibutton->setPressedImage(texture);
149+
btn->guibutton->setScaleImage(true);
150+
}
142151
btn->guibutton->setDrawBorder(false);
143152
btn->guibutton->setText(L"");
144153
}
@@ -157,7 +166,7 @@ void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
157166
btn->immediate_release = immediate_release;
158167
btn->ids.clear();
159168

160-
loadButtonTexture(btn,touchgui_button_imagenames[id]);
169+
loadButtonTexture(btn,touchgui_button_imagenames[id], button_rect);
161170
}
162171

163172
static int getMaxControlPadSize(float density) {

Diff for: ‎src/touchscreengui.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class TouchScreenGUI
130130
float repeat_delay = BUTTON_REPEAT_DELAY);
131131

132132
/* load texture */
133-
void loadButtonTexture(button_info* btn, const char* path);
133+
void loadButtonTexture(button_info* btn, const char* path, rect<s32> button_rect);
134134

135135
struct id_status{
136136
int id;

Diff for: ‎src/util/numeric.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,3 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir,
245245

246246
return true;
247247
}
248-

Diff for: ‎src/util/numeric.h

+12-1
Original file line numberDiff line numberDiff line change
@@ -411,5 +411,16 @@ inline bool is_power_of_two(u32 n)
411411
return n != 0 && (n & (n-1)) == 0;
412412
}
413413

414-
#endif
414+
// Compute next-higher power of 2 efficiently, e.g. for power-of-2 texture sizes.
415+
// Public Domain: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
416+
inline u32 npot2(u32 orig) {
417+
orig--;
418+
orig |= orig >> 1;
419+
orig |= orig >> 2;
420+
orig |= orig >> 4;
421+
orig |= orig >> 8;
422+
orig |= orig >> 16;
423+
return orig + 1;
424+
}
415425

426+
#endif

0 commit comments

Comments
 (0)
Please sign in to comment.