mirror of
https://github.com/minetest/minetest.git
synced 2024-11-27 01:53:45 +01:00
Clean scaling pre-filter for formspec/HUD.
This commit is contained in:
parent
b4247dff2e
commit
6d61375cc7
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
160
src/guiscalingfilter.cpp
Normal file
160
src/guiscalingfilter.cpp
Normal 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
52
src/guiscalingfilter.h
Normal 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
|
13
src/hud.cpp
13
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);
|
||||
|
172
src/imagefilters.cpp
Normal file
172
src/imagefilters.cpp
Normal 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
46
src/imagefilters.h
Normal 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
|
@ -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);
|
||||
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) {
|
||||
|
@ -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;
|
||||
|
@ -245,4 +245,3 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user